Skip to content

CONTESTED: close/quit veto sequencing with two dirty windows #443

Description

@0monish

Generated by an AI agent (Claude Code, Fable 5.1) on behalf of @0monish during Wayfinder charting of the Electron compatibility program, 2026-10-06. Evidence-backed; every resolved decision was taken under explicit user delegation and cites its sources. Planning only — no implementation is authorized by this issue.

Parent map: #391 · Unit: perspective panel (8 isolated personas → cross-critique → 3 judges) · Wayfinder type: prototype

Tracker of record: this GitHub issue (decision ticket; claim by self-assignment). Linear reference: KEL-237 (In Progress) carries a link here.

Question

CONTESTED: close/quit veto sequencing with two dirty windows

Current position (contested — see both sides)

Quit does not route through windowShouldClose, so a host that models quit as per-window close CALLs may mis-order before-quit / window closes / will-quit relative to Electron (and quitAndInstall inverts before-quit). Settle with a small objc2 harness + Electron 44.4.5 transcript diff; fallback is serialized close requests with ▲.

Evidence

  • [fact] app.md v44.4.5: Cmd+Q/app.quit 'first try to close all the windows and then emit the will-quit event'; quitAndInstall: 'before-quit is emitted after emitting close event on all windows and closing them' (fetched 2026-10-06)
  • [fact] kel139:195 AC6 'Quit has one ordered owner'
  • [unknown] AppKit hook interleaving under NSTerminateLater with two windows

Context

Raised by the eight-persona perspective panel (isolated positions → cross-critique → three judges); judge extracts and persona positions are on the research branch (wayfinder/electron-compat/panel/).

Activity

  1. added theissue type on Oct 6, 2026
  2. 0monish commented on Oct 6, 2026

    @0monish
    MemberAuthor

    Generated by an AI agent (Claude Code, Fable 5.1) on behalf of @0monish, 2026-10-06, under the map's execution doctrine. Statements are labelled FACT / INFERENCE / UNKNOWN by their author; nothing here authorizes implementation.

    Decision packet

    Decision: How a quit request (Cmd+Q, app.quit, quitAndInstall) is sequenced against per-window close vetoes so that before-quit, per-window close, window-all-closed and will-quit match Electron 44.4.5 with two vetoing windows, and which AppKit termination reply the host uses to do it.

    Classification: needs-experiment · Milestone (YAGNI test against the first proof): next · Reversible: Yes before F01-T3 (#451) lands; the choice is host-internal. After conformance entries for quit ordering land, changing the mechanism means re-recording them. · Owner: Quit ordering owner KEL-139 AC6 (Linear: Done, completed 2026-09-29) with KEL-96 (Linear: In Progress, assignee GYLDLAB, fetched 2026-10-06) for the existing ordered Quit; map tickets F01-T3 (#451) (#451) and PANEL-P2 (#419) (#419) consume it.

    Facts

    Inferences

    • INFERENCE (from source, no fixture run): Cmd+Q with two dirty draw.io windows in Electron 44.4.5 yields before-quit, then close on the newer window, then close on the older window (both vetoed, which cancels the quit), then the unsaved-changes dialogs, then closed for each window as the app destroys it, then window-all-closed (because the quit was cancelled), then draw.io's own app.quit, a second before-quit, will-quit and quit.
    • INFERENCE: this whole state machine (quitting flag, cancellation on any close veto, reverse-order close, window-all-closed suppressed while quitting) can live in the facade, because it needs only a host 'quit requested' event, the per-window close protocol and the existing ordered Quit call.
    • INFERENCE: Electron does not use a delayed termination reply at all, so matching it does not require NSTerminateLater. A host that returns terminateLater sits in the modal-panel run-loop mode until it replies, which may starve host work scheduled only in the default mode. This is new primary evidence against the 'later reply' mechanism clause in PANEL-D5 (Close and quit vetoes are async AppKit hooks; deadline expiry keeps the window open #425).
    • INFERENCE: the first proof does not exercise quit, so it can ship without this decision.

    Unknowns

    • UNKNOWN: whether the Keld host keeps servicing Bun replies, web view callbacks and sheet presentation while AppKit holds it in the modal-panel run-loop mode after terminateLater.
    • UNKNOWN: behaviour on logout, restart and shutdown, where cancelling termination aborts the system action.
    • UNKNOWN: the recorded Electron transcript; the ordering above is source-derived only.

    Alternatives

    Option Cost New invariant created Existing invariant at risk
    A. Mirror Electron: the host cancels the native terminate request and emits one quit-requested event; the facade runs Electron's quit state machine; process exit happens only through the existing ordered Quit call. The facade must reproduce the quitting flag and its cancellation rule exactly; logout/shutdown handling needs its own decision. A native terminate request never terminates the host directly; only the ordered Quit call does. None identified; consistent with KEL-139 AC6 one quit owner.
    B. Host returns terminateLater and replies after the per-window close sequence (PANEL-D5 (#425)'s current mechanism). The host runs in the modal-panel run-loop mode for the whole unsaved-changes interaction; Electron does not do this, so interleaving has no oracle. The host may hold a pending AppKit termination across app-owned dialogs. KEL-142 rule that no UI thread blocks on Bun, if default-mode work stalls in that run-loop mode.
    C. Host serializes close requests per app and delivers them after any modal returns, recorded as a divergence (the panel's fallback). Event order differs from Electron when two windows veto; draw.io's second dialog timing changes. Close events are serialized across a blocking dialog. Electron ordering conformance for quit.

    Recommendation

    Prefer A, and settle the A-versus-B host mechanism with a reduced harness when F01-T3 (#451) is scheduled: (1) an Electron 44.4.5 fixture with two windows that veto close, driven through Cmd+Q, app.quit and quitAndInstall, recording the event transcript to confirm the source-derived order; (2) a small objc2 host harness with two arms, terminateCancel plus later programmatic terminate versus terminateLater plus reply, each recording whether a default-mode run-loop source, a web view script callback and a window sheet are serviced while termination is pending. Exit: arm chosen where all three are serviced and the facade transcript equals the Electron transcript; if neither host arm can match, fall back to C with a sequence test. Keep this out of the first proof.

    Falsifier: An Electron 44.4.5 transcript that differs from the source-derived order (for example, window-all-closed not emitted after a vetoed Cmd+Q), or a harness run showing terminateCancel cannot be re-driven to a clean ordered Quit.

    Missing evidence: The Electron transcript for the three quit entry points with two vetoing windows; host observations under terminateLater; the logout/shutdown path.

    Next action: Owner: map orchestrator. Post the source-derived Electron quit state machine and the two-arm harness on GitHub #443, add the terminateLater doc sentence to #425 as new evidence against its 'later reply' clause, and mark #443 as not blocking the first proof. First check: #443 lists both host arms and their three observables, and #451 references it instead of PANEL-P2 (#419) for quit ordering.

  3. added
    milestone:nextNeeded after the first proof (Zettlr or strict profile)
    on Oct 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    electron-compatElectron compatibility program areamilestone:nextNeeded after the first proof (Zettlr or strict profile)wayfinder:prototypeWayfinder prototype decision

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions