Files
orca/mobile/src/terminal/terminal-webview-html/write-queue.ts
T
Neil c8937936eb refactor(mobile): pin the terminal WebView payload and split its widest slice
The payload is one concatenated string, so slice boundaries follow document
order rather than responsibility -- but join is associative, so cutting a slice
into consecutive slices is byte-identical by construction. Splits the widest
slice, which carried fit-scale, a DECSET scanner and the write queue together
with no room left under the line cap.

Adds a hash guard. The behavioral tests each execute one region of the payload
in a vm, so an edit to an uncovered region shipped silently; the composed output
is now pinned by sha256 and length.

Derives the source-file list from the composer's own imports instead of a second
hardcoded list a new slice had to be added to by hand -- the same silent
subject-loss shape already found twice elsewhere in this repo.
2026-09-01 16:38:22 -07:00

111 lines
3.9 KiB
TypeScript

// Also carries disposeTermObservers() and extractMouseModeScanTail(): both belong to
// other concerns, but emitted-document order pins them inside this queue.
export const TERMINAL_HTML_WRITE_QUEUE = ` function resetWriteQueue() {
writeQueue = [];
writeQueueHead = 0;
}
function isStatusDotPresentationSelector(value) {
return value === TEXT_PRESENTATION_SELECTOR || value === EMOJI_PRESENTATION_SELECTOR;
}
function endsWithStatusDotPresentationSequence(data) {
var i = data.length - 1;
while (i >= 0 && isStatusDotPresentationSelector(data.charAt(i))) i--;
return i >= 0 && data.charAt(i) === CLAUDE_STATUS_DOT;
}
// Why: iOS WebKit promotes Claude's record/status dot to a colorful emoji glyph.
function normalizeStatusDotPresentation(data) {
if (typeof data !== 'string' || data.length === 0) return data;
if (statusDotPendingSelector) {
statusDotPendingSelector = false;
var strippedPendingSelectors = false;
while (data.length > 0 && isStatusDotPresentationSelector(data.charAt(0))) data = data.slice(1);
strippedPendingSelectors = data.length === 0;
if (strippedPendingSelectors) {
statusDotPendingSelector = true;
return '';
}
}
var normalized = data.replace(CLAUDE_STATUS_DOT_PATTERN, CLAUDE_STATUS_DOT + TEXT_PRESENTATION_SELECTOR);
statusDotPendingSelector = endsWithStatusDotPresentationSequence(data);
return normalized;
}
function enqueueWrite(data) {
writeQueue.push(normalizeStatusDotPresentation(data));
}
function enqueueWriteBoundary(callback) {
writeQueue.push(callback);
}
function nextQueuedWrite() {
if (writeQueueHead >= writeQueue.length) {
resetWriteQueue();
return undefined;
}
var next = writeQueue[writeQueueHead];
writeQueueHead++;
// Why: high-throughput terminals can enqueue faster than xterm parses;
// compact consumed slots so drain work stays O(1) without retaining old chunks.
if (writeQueueHead > 128 && writeQueueHead * 2 > writeQueue.length) {
writeQueue = writeQueue.slice(writeQueueHead);
writeQueueHead = 0;
}
return next;
}
function disposeTermObservers() {
var disposables = termObserverDisposables;
termObserverDisposables = [];
for (var i = 0; i < disposables.length; i++) {
try { disposables[i] && disposables[i].dispose && disposables[i].dispose(); } catch (e) {}
}
}
function extractMouseModeScanTail(input) {
var start = Math.max(input.lastIndexOf(ESC), input.lastIndexOf(C1_CSI));
if (start === -1) return '';
var tail = input.slice(start);
// Why: PTY/SSH chunks can split a long combined DECSET before the final h/l.
// Keep parser state far beyond normal mode lists while still bounding memory.
if (tail.length > PRIVATE_MODE_SCAN_TAIL_LIMIT) return '';
if (tail === ESC || tail === ESC + '[' || tail === C1_CSI) return tail;
if (tail.indexOf(ESC + '[?') === 0) {
return /^[0-9;]*$/.test(tail.slice(3)) ? tail : '';
}
if (tail.indexOf(C1_CSI + '?') === 0) {
return /^[0-9;]*$/.test(tail.slice(2)) ? tail : '';
}
return '';
}
function pumpWrites(gen) {
if (!ready || !term || writesDraining || gen !== terminalGeneration) return;
var next = nextQueuedWrite();
if (typeof next !== 'string') {
if (typeof next === 'function') return next(), pumpWrites(gen);
var callbacks = afterDrainCallbacks;
afterDrainCallbacks = [];
for (var i = 0; i < callbacks.length; i++) callbacks[i]();
return;
}
writesDraining = true;
// Why: xterm.write() parses asynchronously. Row adjustment/resizing must
// wait until replayed SGR attributes have landed in the buffer.
term.write(next, function() {
if (gen !== terminalGeneration) return;
writesDraining = false;
pumpWrites(gen);
});
}
function afterWritesDrained(callback) {
afterDrainCallbacks.push(callback);
pumpWrites(terminalGeneration);
}
`