mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
refactor(mobile): make the query-reply gate a module the page can import
The second of the twelve groups, and the one that corrects the scope table's
membership rule.
`terminalDataRepliesEnabled` is written from four places, so the whole-script
census counted it among the 57 variables that cannot stay free across modules.
All four writes are in this group. Once the script is modules, a variable
written only inside the module that declares it is that module's own state, not
the document's, and it stays a `let` there. So the scope object holds what
crosses a module boundary, and the 57 is an upper bound rather than the answer;
the qualifier count the flip commit pins will be lower than the 641 measured
over the single scope, and by how much is a function of where the boundaries
fall.
Two references do cross here and are qualified: the write-queue generation this
group compares against, and the observer-disposal list it pushes onto.
Counts pinned: two qualified references, one `var` to `let`, two one-statement
`if` bodies braced, both `catch (e) {}` clauses unbound, no declaration moved.
The document is untouched, so the byte pin is still green.
Claude-Session: https://claude.ai/code/session_01JNnE9qzUZMMnqpZWCqM3nb
This commit is contained in:
@@ -17,3 +17,9 @@ export declare function getCellHeight(): number
|
||||
|
||||
/** `terminal-fit-scale`: the fit scale times the user's pinch scale. */
|
||||
export declare function getTotalScale(): number
|
||||
|
||||
/** `runtime-state-and-text-scaling`: posts one message to the native host. */
|
||||
export declare function notify(message: Record<string, unknown>): void
|
||||
|
||||
/** `write-queue`: runs a callback when the replay queue reaches the point it was enqueued at. */
|
||||
export declare function enqueueWriteBoundary(callback: () => void): void
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
* import — assigning an imported binding is a syntax error. So the written ones become fields here,
|
||||
* and the group that owns each is named beside it.
|
||||
*
|
||||
* Only the written ones move. A `var` the script never assigns again is an ordinary local and stays
|
||||
* one, which is what keeps the qualifier off most of the program.
|
||||
* Two things keep a variable out of this table. One the script never assigns again is an ordinary
|
||||
* local. One assigned only inside the group that declares it is that module's own state, however
|
||||
* often it is written — `terminalDataRepliesEnabled` is written from four places and all four are
|
||||
* in `query-reply`, so it stays a `let` there. Only what crosses a module boundary is shared
|
||||
* state, which is what keeps the qualifier off most of the program.
|
||||
*
|
||||
* The table grows one group at a time as C7.1 extracts them; a field arrives with its group.
|
||||
*/
|
||||
@@ -25,8 +28,15 @@ export type TerminalDocumentScope = {
|
||||
/** `smooth-scroll-and-cell-geometry`: the surface's pan offset, in viewport pixels. */
|
||||
panX: number
|
||||
panY: number
|
||||
/** `terminal-init-and-write`: bumped on every re-init, so a late callback can tell it is stale. */
|
||||
terminalGeneration: number
|
||||
/** `term-observers`: xterm listener handles to dispose when the terminal is replaced. */
|
||||
termObserverDisposables: TerminalDocumentDisposable[]
|
||||
}
|
||||
|
||||
/** An xterm listener handle, as the document disposes of one. */
|
||||
export type TerminalDocumentDisposable = { dispose?: () => void }
|
||||
|
||||
/**
|
||||
* The initial values, which are the ones the document's own declarations carried.
|
||||
*
|
||||
@@ -34,7 +44,13 @@ export type TerminalDocumentScope = {
|
||||
* starts from its own state instead of inheriting what the last one left.
|
||||
*/
|
||||
export function createTerminalDocumentScope(): TerminalDocumentScope {
|
||||
return { term: null, panX: 0, panY: 0 }
|
||||
return {
|
||||
term: null,
|
||||
panX: 0,
|
||||
panY: 0,
|
||||
terminalGeneration: 0,
|
||||
termObserverDisposables: []
|
||||
}
|
||||
}
|
||||
|
||||
/** The document's own scope. The generator emits this declaration at the top of the script. */
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { emitTerminalDocumentModule } from '../../../scripts/build-terminal-document-script.mjs'
|
||||
import { TERMINAL_QUERY_REPLY_JS } from '../terminal-webview-query-reply-injected'
|
||||
import { compareTerminalDocumentScripts } from './terminal-document-equivalence.test-support'
|
||||
|
||||
const modulePath = fileURLToPath(new URL('./query-reply.ts', import.meta.url))
|
||||
|
||||
describe('the query-reply module', () => {
|
||||
it('emits the script the document carries, modulo the four normalisations', async () => {
|
||||
const emitted = await emitTerminalDocumentModule(modulePath)
|
||||
expect(compareTerminalDocumentScripts(TERMINAL_QUERY_REPLY_JS, emitted, 'scope')).toEqual({
|
||||
equivalent: true,
|
||||
normalisations: {
|
||||
// `terminalGeneration` and `termObserverDisposables` once each. The reply flag is not
|
||||
// among them: every one of its four writes is in this module, so it stays a local.
|
||||
qualifiedReferences: 2,
|
||||
scopeFieldDeclarations: 0,
|
||||
// That flag's own declaration, `var` to `let`.
|
||||
rebindings: 1,
|
||||
// The two one-statement `if` bodies.
|
||||
bracedBodies: 2,
|
||||
// Both `catch (e) {}` clauses, whose binding was never read.
|
||||
unboundCatches: 2
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,69 @@
|
||||
import { enqueueWriteBoundary, notify } from './document-externals'
|
||||
import { scope, type TerminalDocumentDisposable } from './document-scope'
|
||||
|
||||
/**
|
||||
* The gate deciding when xterm's parser replies may reach the native host.
|
||||
*
|
||||
* One unit so the tests exercise the same replay and generation gate the document runs rather than
|
||||
* a re-implementation of it — which was already the reason this was one injected string.
|
||||
*/
|
||||
export type QueryReplyTerminal = {
|
||||
attachCustomKeyEventHandler: (handler: () => boolean) => void
|
||||
textarea?: {
|
||||
readOnly: boolean
|
||||
tabIndex: number
|
||||
setAttribute: (name: string, value: string) => void
|
||||
}
|
||||
onData: (listener: (data: string) => void) => TerminalDocumentDisposable
|
||||
}
|
||||
|
||||
// Written from four places, all of them here, so it is this module's state rather than the
|
||||
// document's and stays a local.
|
||||
let terminalDataRepliesEnabled = false
|
||||
|
||||
export function resetTerminalDataReplyAuthority() {
|
||||
terminalDataRepliesEnabled = false
|
||||
}
|
||||
|
||||
export function resumeTerminalDataReplyAuthority() {
|
||||
terminalDataRepliesEnabled = true
|
||||
}
|
||||
|
||||
export function forwardTerminalDataReply(data: string) {
|
||||
if (terminalDataRepliesEnabled) {
|
||||
notify({ type: 'terminal-data', bytes: data })
|
||||
}
|
||||
}
|
||||
|
||||
export function enqueueTerminalDataReplyBoundary(gen: number) {
|
||||
enqueueWriteBoundary(function () {
|
||||
if (gen === scope.terminalGeneration) {
|
||||
terminalDataRepliesEnabled = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export function attachTerminalQueryReplyBridge(term: QueryReplyTerminal, gen: number) {
|
||||
// Why: parser replies require stdin enabled, but mobile input is owned by
|
||||
// native controls. Keep xterm's textarea inert for touch/hardware keys.
|
||||
try {
|
||||
term.attachCustomKeyEventHandler(function () {
|
||||
return false
|
||||
})
|
||||
if (term.textarea) {
|
||||
term.textarea.readOnly = true
|
||||
term.textarea.tabIndex = -1
|
||||
term.textarea.setAttribute('inputmode', 'none')
|
||||
}
|
||||
} catch {}
|
||||
try {
|
||||
scope.termObserverDisposables.push(
|
||||
term.onData(function (data) {
|
||||
forwardTerminalDataReply(data)
|
||||
})
|
||||
)
|
||||
} catch {}
|
||||
// Why: live output can queue before initial replay finishes. Enable replies
|
||||
// at the replay boundary so those live queries are answered, never replayed ones.
|
||||
enqueueTerminalDataReplyBoundary(gen)
|
||||
}
|
||||
Reference in New Issue
Block a user