Files
orca/mobile/scripts/terminal-document-module-order.mjs
T
Jinwoo-H 89b6d5802d refactor(mobile): call the document instead of starting its modules
The page's half of ruling 23, and ruling 24. The mount plants the markup and calls
the factory; the handle is `send` and a `dispose` that stops it. The document is a
function, so the page holds an object per call and nothing else.

Deleted with the singleton it was written for: `page-document-modules.ts`, the
claim token and `liveDocument`, `release`, the owner-checked `dispose`, the
second-mount refusal, `resetTerminalDocumentScope`, the `adopt` callback, `ready`
and every pending-import path. All of it existed because two mounts shared one
module-level scope and because the handle had to come back before its import did.
A call is a document now, so a second mount cannot reach the first one's state and
a caller's cleanup cannot arrive before there is something to clean up. The
second-mount refusal is not replaced by a one-line guard: with a scope per call
there is no shared state left to refuse for, and a host element with two
documents planted in it is the caller's own doing, visible on the screen.

Ruling 24 splits `message-bridge` by what it is, which is what made the page able
to run this text at all. Two more seams, eight now: `installHostTransport`, whose
window default installs the `message` listeners on window and document and hands
back their removal, and `hasEngine`, whose default is the `window.Terminal` the
engine bundle installs. The page answers a transport that installs nothing,
because its transport is the handle, and an engine that is always there, because
the engine is the import above. So the page no longer takes the shell's frames or
reports a missing engine on every mount, and `stopMessageBridge` takes the
listeners off — the WebView never removed them, which ruling 21 asks for.

The refit the bridge happened to own moves to `fit-scale`, which is whose work it
is; both hosts start it, and the mount's hand-copied five calls are gone. The
engine's disposal moves into `stopTerminalInit` for the same reason: the mount
cannot reach the scope any more, and a stopped document's terminal is a WebGL
context nothing will read again.

The start sequence the generator emits is now inside the document's own undo: a
start that throws runs `stop` and rethrows, so neither host can be left holding a
listener from a build that failed. That replaces the deleted entry module's
unwind, and it covers every start rather than the four that had one.

Readiness arrives the same way on both hosts. The document posts `web-ready`
through `postToHost`, which the controller already handles, so the mount-side
`confirmWebReady` is gone. That flush is also the one caller that reaches `post`
before the effect has a handle, which is why the component's queue stays and now
says so.

The golden and the payload hash move, 102 diff lines: the two seam defaults and
their state fields, the reset gone, `startFitScale`, the disposal, the bridge over
its seams, and the start sequence inside its try.

Tests: the seam tests and the unwind test move to the factory and the derived
start sequence; the frame registry builds a fresh scope instead of resetting one;
the two mount test files and the page entry's order test go with their subjects.
The render check keeps every behavioural case and loses two whose subject the
static import removed — a Reload while the chunk is in flight, and a chunk that
will not load, which is now the route's chunk rather than the document's.

Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
2026-09-20 19:40:32 -04:00

77 lines
2.4 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')
}