Files
orca/mobile/scripts/terminal-document-module-order.mjs
T
Jinwoo-H efeede31e0 fix(mobile): move the document's state onto the scope (ruling 21)
Round 2's blocking finding, and ruling 20's second half: moving parse-time
effects out of the module bodies left the state behind. Nine module-level
bindings survived a mount, so the second terminal inherited a spent non-fatal
error budget (reporting nothing however it failed), the first terminal as its
committed surface (disposing it twice), and the first mount's momentum loop.

Every mutable binding now lives on the scope, and the scope carries one reset
the start sequence calls first: native once at parse, the page once per mount.
Moved, by module: query-reply 1, surface-swap 3, text-scaling 2, fit-scale 1,
host-notify 2, selection-state-and-eviction 1, mouse-click-drag 1,
tap-dispatch 1, surface-touch-gestures 1 — thirteen fields, two of them the
objects tap-dispatch and surface-touch-gestures used to own outright.

Because the reset is now the one initialiser, the start functions keep only
what it cannot do: element reads, listener installs and the reporter install.
Four start functions emptied and went; terminal-handle held nothing else and
is deleted from the order list. The scope type splits into state and host
seams, because a reset must restore the first and never the second.

Every stop function cancels what its module scheduled. Timers go back through
the handles the scope already held; frames go through the scope's own
scheduleDocumentFrame, so dispose can take back the ones no module tracks by
id. terminalGeneration and fitRetryToken carry forward across a reset, because
a stale callback tests itself against them and a reset to zero would make the
old number match again.

L2: the seams-before-scope case asserts the order in the emitted document, not
just non-membership. L3: the style docstring says what is true — one scope per
page, so mount refuses a second live document and gives the page back when a
mount fails.

Golden: 108134 -> 108047 bytes; payload 726168 -> 726081, sha256
6a5a3216aab7b99daeb26bcdcfe6e325c415e5ef60c16405eea329ca141405fe.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-09-20 14:46:27 -04:00

85 lines
2.7 KiB
JavaScript

/**
* The order the document's modules are spliced back into the script, which is the order the
* hand-written document had. It is data, not a dependency graph: the document is one function
* scope, so declarations must land where they landed before.
*
* Both the generator and the equivalence test read this, so neither can drift from the other.
*/
/**
* The host seams, emitted ahead of the scope object: the scope's defaults *are* these functions,
* and the factory that reads them runs as the script is parsed.
*/
export const TERMINAL_DOCUMENT_HOST_SEAMS_MODULE = 'document-host-seams'
/** The scope object, emitted ahead of everything else because everything else reads it. */
export const TERMINAL_DOCUMENT_SCOPE_MODULE = 'document-scope'
export const TERMINAL_DOCUMENT_MODULE_ORDER = [
'runtime-constants',
'query-reply',
'surface-swap',
'text-scaling',
'viewport-transform',
'terminal-theme',
'fit-scale',
'mouse-mode-decset-scan',
'write-queue',
'webgl-recovery',
'terminal-init',
'reflow',
'host-notify',
'host-message-router',
'selection-state-and-eviction',
'mode-mirroring',
'keyboard-avoidance-metrics',
'term-observers',
'viewport-cell',
'mouse-report-cell',
'mouse-input-encoding',
'normal-buffer-smooth-scroll',
'cell-geometry',
'path-tap',
'url-tap',
'osc-link-tap',
'surface-tap',
'selection-range',
'selection-overlay',
'tap-dispatch',
'wheel-scroll',
'mouse-click-drag',
'selection-menu-buttons',
'surface-touch-gestures',
'message-bridge'
]
/**
* The per-module start function's name, by convention rather than by a second list.
*
* Ruling 20: no module does work as it is parsed, so each one that had a top-level effect now
* exports one function holding it. The generator calls the ones that exist, in module order, at
* the foot of the document; the page calls the same names per mount. A convention rather than a
* list because a list is a second place to forget.
*/
export function terminalDocumentStartFunctionName(moduleName) {
return (
'start' +
moduleName
.split('-')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join('')
)
}
/** The per-module stop function's name, by the same convention (ruling 21). */
export function terminalDocumentStopFunctionName(moduleName) {
return terminalDocumentStartFunctionName(moduleName).replace(/^start/, 'stop')
}
/**
* The scope's reset, called ahead of every start (ruling 21).
*
* Module top level holds no mutable state, so a second mount's state comes from here and nowhere
* else. The WebView runs it once at parse, where it restores what the factory just built.
*/
export const TERMINAL_DOCUMENT_RESET_CALL = 'resetTerminalDocumentScope'