@timurproko/a1 0.1.8-dev.182 → 0.1.8-dev.214
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cli.js +2 -2
- package/bin/sync-pi-tui-proxy.js +1 -1
- package/bin/ui.js +1 -1
- package/dist/cli/dispatch.js +3 -3
- package/dist/cli/packages.js +1 -1
- package/dist/cli/version-stats.js +3 -3
- package/dist/composition/owned-ui.js +1 -1
- package/dist/composition/settings-route-host.js +6 -4
- package/dist/features/launch/development-launch.js +1 -1
- package/dist/features/owned-ui/customization.d.ts +2 -0
- package/dist/features/owned-ui/customization.js +2 -0
- package/dist/features/owned-ui/diagnostics.d.ts +1 -0
- package/dist/features/owned-ui/diagnostics.js +1 -0
- package/dist/features/owned-ui/project-trust-prompt.d.ts +11 -6
- package/dist/features/owned-ui/project-trust-prompt.js +134 -17
- package/dist/features/owned-ui/settings-app.js +47 -50
- package/dist/features/workspace/reducer.d.ts +1 -0
- package/dist/features/workspace/reducer.js +1 -0
- package/dist/features/workspace/router.d.ts +1 -0
- package/dist/features/workspace/router.js +1 -0
- package/dist/features/workspace/store.d.ts +1 -0
- package/dist/features/workspace/store.js +1 -0
- package/dist/foundation/launch-guardian/main.js +1 -1
- package/dist/foundation/native-host-protocol/codec.d.ts +2 -0
- package/dist/foundation/native-host-protocol/codec.js +2 -0
- package/dist/foundation/process-containment/linux-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/linux-process-inspector.js +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.d.ts +1 -0
- package/dist/foundation/process-containment/native-guardian-containment.js +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.d.ts +1 -0
- package/dist/foundation/process-containment/windows-process-inspector.js +1 -0
- package/dist/foundation/protocol/client.d.ts +1 -0
- package/dist/foundation/protocol/client.js +1 -0
- package/dist/foundation/protocol/messages.d.ts +1 -0
- package/dist/foundation/protocol/messages.js +1 -0
- package/dist/foundation/release/bootstrap.js +6 -6
- package/dist/foundation/release/cohort-state.d.ts +1 -0
- package/dist/foundation/release/cohort-state.js +1 -0
- package/dist/foundation/release/release-gc.js +1 -1
- package/dist/foundation/release/release.js +1 -1
- package/dist/foundation/release/update-transaction.d.ts +1 -0
- package/dist/foundation/release/update-transaction.js +1 -0
- package/dist/foundation/release/update.js +12 -12
- package/dist/foundation/storage/control-store.d.ts +1 -0
- package/dist/foundation/storage/control-store.js +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/backpressure.js +1 -0
- package/dist/foundation/structured-agent-runtime/commands.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/commands.js +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/reconnection.js +1 -0
- package/dist/foundation/structured-agent-runtime/state.d.ts +1 -0
- package/dist/foundation/structured-agent-runtime/state.js +1 -0
- package/dist/foundation/supervision/main.js +1 -1
- package/dist/foundation/supervision/server.d.ts +1 -0
- package/dist/foundation/supervision/server.js +4 -6
- package/dist/integrations/pi/components/conformance.d.ts +1 -0
- package/dist/integrations/pi/components/conformance.js +1 -0
- package/dist/integrations/pi/components/shell-footer-status.d.ts +1 -1
- package/dist/integrations/pi/components/shell-footer-status.js +8 -7
- package/dist/integrations/pi/components/shell-shared-facade.js +1 -1
- package/dist/integrations/pi/engine/adapter.d.ts +7 -0
- package/dist/integrations/pi/engine/adapter.js +37 -35
- package/dist/integrations/pi/engine/conformance.d.ts +2 -0
- package/dist/integrations/pi/engine/conformance.js +2 -0
- package/dist/integrations/pi/engine/http-dispatcher.js +1 -1
- package/dist/integrations/pi/engine/model-auth-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/model-auth-integration.js +1 -0
- package/dist/integrations/pi/engine/package-integration.js +1 -1
- package/dist/integrations/pi/engine/project-trust-preflight.js +1 -1
- package/dist/integrations/pi/engine/resource-extension-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/resource-extension-integration.js +1 -0
- package/dist/integrations/pi/engine/session-integration.d.ts +1 -0
- package/dist/integrations/pi/engine/session-integration.js +2 -1
- package/dist/integrations/pi/engine/settings-effects.d.ts +13 -2
- package/dist/integrations/pi/engine/settings-effects.js +62 -42
- package/dist/integrations/pi/engine/settings-integration.js +4 -4
- package/dist/integrations/pi/engine/workflow-controllers.d.ts +1 -0
- package/dist/integrations/pi/engine/workflow-controllers.js +1 -0
- package/dist/integrations/pi/session-ui/index.d.ts +1 -0
- package/dist/integrations/pi/session-ui/index.js +1 -0
- package/dist/integrations/pi/session-ui/prompt-chips.js +1 -1
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +30 -2
- package/dist/integrations/pi/session-ui/session-shell-root.js +133 -72
- package/dist/integrations/pi/session-ui/session-shell.d.ts +9 -2
- package/dist/integrations/pi/session-ui/session-shell.js +112 -37
- package/dist/integrations/pi/session-ui/session-viewport-controller.d.ts +5 -0
- package/dist/integrations/pi/session-ui/session-viewport-controller.js +44 -11
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.d.ts +29 -0
- package/dist/integrations/pi/session-ui/stream-presentation-coalescer.js +79 -0
- package/dist/integrations/pi/session-ui/system-clipboard.js +6 -6
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +138 -8
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +25 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.d.ts +63 -0
- package/dist/integrations/pi/tui-runtime/damage-aware-terminal.js +240 -0
- package/dist/integrations/pi/tui-runtime/index.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/index.js +2 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.d.ts +39 -0
- package/dist/integrations/pi/tui-runtime/input-presentation-coordinator.js +139 -0
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/apps/host.js +5 -9
- package/dist/ui/apps/registry.d.ts +1 -0
- package/dist/ui/apps/registry.js +1 -0
- package/dist/ui/components/dialog-panel.js +5 -3
- package/dist/ui/components/frame.d.ts +1 -0
- package/dist/ui/components/frame.js +1 -0
- package/dist/ui/components/index.d.ts +1 -0
- package/dist/ui/components/index.js +1 -0
- package/dist/ui/components/line-input.d.ts +1 -6
- package/dist/ui/components/line-input.js +4 -9
- package/dist/ui/components/list-view.d.ts +0 -6
- package/dist/ui/components/list-view.js +14 -7
- package/dist/ui/components/progress-status.d.ts +2 -0
- package/dist/ui/components/progress-status.js +4 -0
- package/dist/ui/components/scrollbar.js +1 -1
- package/dist/ui/components/shortcuts.d.ts +1 -0
- package/dist/ui/components/shortcuts.js +1 -0
- package/dist/ui/components/spans.d.ts +0 -5
- package/dist/ui/components/spans.js +3 -8
- package/dist/ui/components/surface.d.ts +0 -5
- package/dist/ui/components/surface.js +1 -5
- package/dist/ui/components/text-selection.d.ts +10 -1
- package/dist/ui/components/text-selection.js +96 -29
- package/dist/ui/components/text.js +3 -3
- package/dist/ui/components/transcript-viewport.d.ts +42 -0
- package/dist/ui/components/transcript-viewport.js +287 -39
- package/dist/ui/settings/sections.d.ts +0 -2
- package/dist/ui/settings/sections.js +10 -16
- package/dist/ui/settings/session.js +5 -4
- package/dist/ui/settings/store.d.ts +1 -0
- package/dist/ui/settings/store.js +2 -1
- package/docs/architecture/code-documentation.md +77 -0
- package/docs/architecture/project-structure.md +1 -1
- package/docs/architecture/ui-reference-provenance.md +23 -3
- package/docs/ci-release-runbook.md +30 -4
- package/docs/manual-owned-ui-checkpoint.md +28 -1
- package/docs/manual-terminal-colour-check.md +4 -3
- package/package.json +7 -2
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { isThumbRow, scrollbarGeometry, scrollbarPresentation, } from "./scrollbar.js";
|
|
2
2
|
import { backgroundSgrSpan, overlaySpan } from "./spans.js";
|
|
3
3
|
import { faint, truncateToWidth, displayWidth, stripAnsi } from "./text.js";
|
|
4
|
-
import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
|
|
4
|
+
import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
|
|
5
5
|
const CONTROL_STYLE_RESET = "\u001b]8;;\u001b\\\u001b[0m";
|
|
6
|
+
const IDENTITY_ROW = (row) => row;
|
|
6
7
|
const DEFAULT_CONFIG = {
|
|
7
8
|
scrollbarAppearance: "auto",
|
|
8
9
|
scrollbarStyle: "thin",
|
|
@@ -32,16 +33,26 @@ export class TranscriptViewport {
|
|
|
32
33
|
#bottomHovered = false;
|
|
33
34
|
#selection;
|
|
34
35
|
#selectionClick;
|
|
36
|
+
#selectionRevision = 0;
|
|
37
|
+
#paintedRowCache = new Map();
|
|
38
|
+
#baseRowCache = new Map();
|
|
39
|
+
#selectedRowCache = new Map();
|
|
40
|
+
#finalRowCache = new Map();
|
|
41
|
+
#functionIds = new WeakMap();
|
|
42
|
+
#nextFunctionId = 1;
|
|
43
|
+
#previousVisibleStates = [];
|
|
35
44
|
#documentRows = [];
|
|
36
45
|
#selectableDocumentRowCount = 0;
|
|
37
46
|
#promptAnchors = [];
|
|
38
47
|
#contentWidth = 0;
|
|
48
|
+
#frameId = 0;
|
|
39
49
|
#frame = null;
|
|
40
50
|
get scrollTop() { return this.#scrollTop; }
|
|
41
51
|
get maxScroll() { return this.#maxScroll; }
|
|
42
52
|
get followingEnd() { return this.#followingEnd; }
|
|
43
53
|
get newMessages() { return this.#newMessages; }
|
|
44
54
|
get frame() { return this.#frame; }
|
|
55
|
+
get selectionRevision() { return this.#selectionRevision; }
|
|
45
56
|
/** Counts one completed assistant reply while detached; tool rows never call this. */
|
|
46
57
|
noteNewMessage() {
|
|
47
58
|
if (this.#followingEnd)
|
|
@@ -83,6 +94,7 @@ export class TranscriptViewport {
|
|
|
83
94
|
});
|
|
84
95
|
this.#selection = pressed.selection;
|
|
85
96
|
this.#selectionClick = pressed.click;
|
|
97
|
+
this.#selectionRevision += 1;
|
|
86
98
|
return true;
|
|
87
99
|
}
|
|
88
100
|
extendSelection(column, viewportRow, now = Date.now(), autoScroll = true) {
|
|
@@ -90,7 +102,7 @@ export class TranscriptViewport {
|
|
|
90
102
|
const viewportHeight = this.#frame?.hits.viewportHeight ?? 0;
|
|
91
103
|
if (selection?.selecting !== true || viewportHeight <= 0 || this.#documentRows.length === 0)
|
|
92
104
|
return false;
|
|
93
|
-
//
|
|
105
|
+
// Performance: pointer motion updates only the endpoint; the shell's fixed-cadence timer
|
|
94
106
|
// owns edge scrolling. This option prevents high-rate motion reports from
|
|
95
107
|
// adding irregular extra rows between timer ticks.
|
|
96
108
|
if (autoScroll && viewportRow > viewportHeight)
|
|
@@ -105,19 +117,28 @@ export class TranscriptViewport {
|
|
|
105
117
|
const targetColumn = selection.fullRow
|
|
106
118
|
? textSelectionLineExtendColumn(selection, line, this.#contentWidth)
|
|
107
119
|
: clamp(column, 1, this.#contentWidth);
|
|
108
|
-
|
|
120
|
+
const point = selection.fullRow
|
|
121
|
+
? { line, column: targetColumn }
|
|
122
|
+
: textSelectionPointAt(line, targetColumn, this.#documentRows[line] ?? "");
|
|
123
|
+
const next = extendTextSelection(selection, point);
|
|
124
|
+
if (next !== selection) {
|
|
125
|
+
this.#selection = next;
|
|
126
|
+
this.#selectionRevision += 1;
|
|
127
|
+
}
|
|
109
128
|
return true;
|
|
110
129
|
}
|
|
111
130
|
releaseSelection() {
|
|
112
131
|
if (this.#selection?.selecting !== true)
|
|
113
132
|
return false;
|
|
114
133
|
this.#selection = releaseTextSelection(this.#selection);
|
|
134
|
+
this.#selectionRevision += 1;
|
|
115
135
|
return true;
|
|
116
136
|
}
|
|
117
137
|
clearSelection() {
|
|
118
138
|
if (this.#selection === undefined)
|
|
119
139
|
return false;
|
|
120
140
|
this.#selection = undefined;
|
|
141
|
+
this.#selectionRevision += 1;
|
|
121
142
|
return true;
|
|
122
143
|
}
|
|
123
144
|
selectedText() {
|
|
@@ -142,7 +163,7 @@ export class TranscriptViewport {
|
|
|
142
163
|
const next = clamp(position, 0, this.#maxScroll);
|
|
143
164
|
const changed = next !== this.#scrollTop || this.#followingEnd;
|
|
144
165
|
this.#scrollTop = next;
|
|
145
|
-
//
|
|
166
|
+
// Invariant: every scrolling path that reaches the final legal top row resumes follow.
|
|
146
167
|
this.#followingEnd = next >= this.#maxScroll;
|
|
147
168
|
if (this.#followingEnd)
|
|
148
169
|
this.#newMessages = 0;
|
|
@@ -166,7 +187,7 @@ export class TranscriptViewport {
|
|
|
166
187
|
if (anchor.firstRow < this.#scrollTop && anchor.firstRow > target)
|
|
167
188
|
target = anchor.firstRow;
|
|
168
189
|
}
|
|
169
|
-
//
|
|
190
|
+
// Invariant: the first prompt owns the document's opening breathing row. At that final
|
|
170
191
|
// navigation stop, reveal the spacer too rather than pinning the prompt to
|
|
171
192
|
// terminal row one as later prompt jumps do.
|
|
172
193
|
const destination = target === earliest ? 0 : target;
|
|
@@ -177,7 +198,7 @@ export class TranscriptViewport {
|
|
|
177
198
|
if (this.#promptAnchors.length === 0)
|
|
178
199
|
return false;
|
|
179
200
|
const earliest = Math.min(...this.#promptAnchors.map(anchor => anchor.firstRow));
|
|
180
|
-
//
|
|
201
|
+
// Invariant: scroll position zero is the first-prompt stop because it includes that
|
|
181
202
|
// prompt's opening spacer. Do not spend an extra keypress moving one row.
|
|
182
203
|
const after = this.#scrollTop === 0 ? earliest : this.#scrollTop;
|
|
183
204
|
let target = Number.POSITIVE_INFINITY;
|
|
@@ -192,8 +213,15 @@ export class TranscriptViewport {
|
|
|
192
213
|
this.#maxScroll = 0;
|
|
193
214
|
this.#followingEnd = true;
|
|
194
215
|
this.#newMessages = 0;
|
|
216
|
+
if (this.#selection !== undefined)
|
|
217
|
+
this.#selectionRevision += 1;
|
|
195
218
|
this.#selection = undefined;
|
|
196
219
|
this.#selectionClick = undefined;
|
|
220
|
+
this.#paintedRowCache.clear();
|
|
221
|
+
this.#baseRowCache.clear();
|
|
222
|
+
this.#selectedRowCache.clear();
|
|
223
|
+
this.#finalRowCache.clear();
|
|
224
|
+
this.#previousVisibleStates = [];
|
|
197
225
|
this.#documentRows = [];
|
|
198
226
|
this.#selectableDocumentRowCount = 0;
|
|
199
227
|
this.#promptAnchors = [];
|
|
@@ -212,6 +240,7 @@ export class TranscriptViewport {
|
|
|
212
240
|
const width = Math.max(1, input.width);
|
|
213
241
|
const height = Math.max(0, input.height);
|
|
214
242
|
const theme = input.theme ?? PLAIN_THEME;
|
|
243
|
+
const previousDescriptor = this.#frame?.descriptor ?? null;
|
|
215
244
|
const dock = input.dockRows.length > height ? input.dockRows.slice(-height) : [...input.dockRows];
|
|
216
245
|
const viewportHeight = Math.max(0, height - dock.length);
|
|
217
246
|
this.#maxScroll = Math.max(0, input.documentRows.length - viewportHeight);
|
|
@@ -227,7 +256,7 @@ export class TranscriptViewport {
|
|
|
227
256
|
contentLength: input.documentRows.length,
|
|
228
257
|
viewportHeight,
|
|
229
258
|
scroll: this.#scrollTop,
|
|
230
|
-
//
|
|
259
|
+
// Compatibility: the session rail deliberately starts one line below the viewport top.
|
|
231
260
|
trackHeight: Math.max(0, viewportHeight - 1),
|
|
232
261
|
});
|
|
233
262
|
const presentation = scrollbarPresentation({
|
|
@@ -244,62 +273,82 @@ export class TranscriptViewport {
|
|
|
244
273
|
this.#selectableDocumentRowCount = clamp(input.selectableDocumentRowCount ?? input.documentRows.length, 0, input.documentRows.length);
|
|
245
274
|
this.#promptAnchors = input.promptAnchors;
|
|
246
275
|
this.#contentWidth = contentWidth;
|
|
247
|
-
const paintDocumentRow = input.paintDocumentRow ??
|
|
276
|
+
const paintDocumentRow = input.paintDocumentRow ?? IDENTITY_ROW;
|
|
277
|
+
const cacheLimit = Math.max(32, viewportHeight * 6);
|
|
278
|
+
trimCache(this.#paintedRowCache, cacheLimit);
|
|
279
|
+
trimCache(this.#baseRowCache, cacheLimit);
|
|
280
|
+
trimCache(this.#selectedRowCache, cacheLimit);
|
|
281
|
+
trimCache(this.#finalRowCache, cacheLimit);
|
|
282
|
+
const paintId = this.#functionId(paintDocumentRow);
|
|
283
|
+
const paintRecomputedRows = new Set();
|
|
248
284
|
const visible = input.documentRows
|
|
249
285
|
.slice(this.#scrollTop, this.#scrollTop + viewportHeight)
|
|
250
|
-
.map(
|
|
286
|
+
.map((row, index) => {
|
|
287
|
+
const painted = cachedString(this.#paintedRowCache, `${paintId}\u0000${row}`, cacheLimit, () => paintDocumentRow(row));
|
|
288
|
+
if (!painted.reused)
|
|
289
|
+
paintRecomputedRows.add(index);
|
|
290
|
+
return painted.value;
|
|
291
|
+
});
|
|
251
292
|
while (visible.length < viewportHeight)
|
|
252
293
|
visible.push("");
|
|
253
294
|
const governing = governingPrompt(input.promptAnchors, this.#scrollTop);
|
|
254
295
|
const stickyActive = governing !== null && governing.firstRow < this.#scrollTop;
|
|
255
296
|
if (stickyActive && visible.length > 0) {
|
|
256
297
|
const quiet = this.#scrollTop > governing.lastRow;
|
|
257
|
-
//
|
|
298
|
+
// Compatibility: sticky prompts use the same normal/hover surface roles as the bottom
|
|
258
299
|
// control. Hover always starts from the full source row, so a quiet prompt
|
|
259
300
|
// becomes prominent again with its timestamp intact.
|
|
260
|
-
const
|
|
301
|
+
const source = cachedString(this.#paintedRowCache, `${paintId}\u0000${governing.sourceRow}`, cacheLimit, () => paintDocumentRow(governing.sourceRow));
|
|
302
|
+
if (!source.reused)
|
|
303
|
+
paintRecomputedRows.add(0);
|
|
304
|
+
const sticky = theme.sticky(source.value, this.#stickyHovered);
|
|
261
305
|
visible[0] = quiet && !this.#stickyHovered ? theme.quietSticky(sticky) : sticky;
|
|
262
306
|
}
|
|
307
|
+
const selectionPainterId = this.#functionId(theme.selection);
|
|
308
|
+
// Concurrency: a painter may synchronously route input in tests or host integrations.
|
|
309
|
+
// Label this frame with the exact selection snapshot it began composing.
|
|
310
|
+
const composingSelectionRevision = this.#selectionRevision;
|
|
263
311
|
const orderedSelection = orderedTextSelection(this.#selection);
|
|
312
|
+
const recomputedRows = [];
|
|
313
|
+
const reusedRows = [];
|
|
314
|
+
const selectionDamagedRows = [];
|
|
315
|
+
const visibleStates = [];
|
|
264
316
|
for (let row = 0; row < visible.length; row += 1) {
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
const throughFinalColumn = documentLine === orderedSelection.end.line
|
|
279
|
-
&& orderedSelection.throughFinalColumn === true;
|
|
280
|
-
const to = fullVisualRow || documentLine !== orderedSelection.end.line || throughFinalColumn
|
|
281
|
-
? width
|
|
282
|
-
: orderedSelection.end.column;
|
|
283
|
-
if (to > from)
|
|
284
|
-
line = theme.selection(line, from, Math.min(width, to));
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
// Paint the rail after selection. Full-row selection reaches the terminal
|
|
317
|
+
const documentLine = this.#scrollTop + row;
|
|
318
|
+
const painted = visible[row] ?? "";
|
|
319
|
+
const base = cachedString(this.#baseRowCache, `${width}\u0000${painted}`, cacheLimit, () => padRowPreservingBackground(painted, width));
|
|
320
|
+
let rowRecomputed = paintRecomputedRows.has(row) || !base.reused;
|
|
321
|
+
const range = stickyActive && row === 0
|
|
322
|
+
? null
|
|
323
|
+
: selectionRangeForLine(orderedSelection, documentLine, this.#selectableDocumentRowCount, width);
|
|
324
|
+
const rangeKey = range === null ? "-" : `${range.from}:${range.to}`;
|
|
325
|
+
const selected = range === null
|
|
326
|
+
? { value: base.value, reused: true }
|
|
327
|
+
: cachedString(this.#selectedRowCache, `${selectionPainterId}\u0000${rangeKey}\u0000${base.value}`, cacheLimit, () => theme.selection(base.value, range.from, range.to));
|
|
328
|
+
rowRecomputed ||= !selected.reused;
|
|
329
|
+
// Invariant: paint the rail after selection. Full-row selection reaches the terminal
|
|
288
330
|
// edge, while the foreground thumb/track remains visible above that
|
|
289
331
|
// background instead of disappearing into it.
|
|
290
|
-
|
|
332
|
+
let railCell = "";
|
|
291
333
|
if (presentation.visible && geometry !== null && row > 0) {
|
|
292
334
|
const trackRow = row - 1;
|
|
293
335
|
const thumb = isThumbRow(geometry, trackRow);
|
|
294
336
|
const glyph = thumb ? presentation.thumbGlyph : presentation.trackGlyph;
|
|
295
|
-
|
|
296
|
-
line = overlaySpan(line, width - 1, width, cell);
|
|
337
|
+
railCell = thumb ? theme.thumb(glyph, this.#railHovered || this.#railDragging) : theme.track(glyph);
|
|
297
338
|
}
|
|
298
|
-
|
|
339
|
+
const final = cachedString(this.#finalRowCache, `${width}\u0000${railCell}\u0000${selected.value}`, cacheLimit, () => railCell.length === 0 ? selected.value : overlaySpan(selected.value, width - 1, width, railCell));
|
|
340
|
+
rowRecomputed ||= !final.reused;
|
|
341
|
+
visible[row] = final.value;
|
|
342
|
+
(rowRecomputed ? recomputedRows : reusedRows).push(row + 1);
|
|
343
|
+
const state = `${width}\u0000${documentLine}\u0000${painted}\u0000${rangeKey}\u0000${range === null ? "" : selectionPainterId}\u0000${railCell}`;
|
|
344
|
+
visibleStates.push(state);
|
|
345
|
+
if (this.#previousVisibleStates[row] !== state)
|
|
346
|
+
selectionDamagedRows.push(row + 1);
|
|
299
347
|
}
|
|
348
|
+
this.#previousVisibleStates = visibleStates;
|
|
300
349
|
const frameRows = [...visible, ...dock].slice(0, height);
|
|
301
350
|
let bottomHit = null;
|
|
302
|
-
if (
|
|
351
|
+
if (this.#maxScroll > 0 && !this.#followingEnd && frameRows.length > 0) {
|
|
303
352
|
const genericLabel = " Jump to bottom (End) ";
|
|
304
353
|
const countedLabel = this.#newMessages > 0
|
|
305
354
|
? ` ${this.#newMessages} new message${this.#newMessages === 1 ? "" : "s"} (End) `
|
|
@@ -314,6 +363,48 @@ export class TranscriptViewport {
|
|
|
314
363
|
bottomHit = { row: row + 1, columnStart: left + 1, columnEnd: left + labelWidth };
|
|
315
364
|
}
|
|
316
365
|
}
|
|
366
|
+
const nextDocumentRange = {
|
|
367
|
+
start: this.#scrollTop,
|
|
368
|
+
end: Math.min(input.documentRows.length, this.#scrollTop + viewportHeight),
|
|
369
|
+
};
|
|
370
|
+
const previousDocumentRange = previousDescriptor?.nextDocumentRange ?? null;
|
|
371
|
+
const sameGeometry = previousDescriptor !== null
|
|
372
|
+
&& previousDescriptor.width === width
|
|
373
|
+
&& previousDescriptor.height === height
|
|
374
|
+
&& sameRectangle(previousDescriptor.transcript, viewportHeight === 0 ? null : { rowStart: 1, rowEnd: viewportHeight })
|
|
375
|
+
&& sameRectangle(previousDescriptor.dock, dock.length === 0 ? null : { rowStart: viewportHeight + 1, rowEnd: height });
|
|
376
|
+
const verticalShiftRows = previousDocumentRange === null ? 0 : nextDocumentRange.start - previousDocumentRange.start;
|
|
377
|
+
const safeVerticalShift = sameGeometry
|
|
378
|
+
&& previousDescriptor?.followingEnd === true
|
|
379
|
+
&& this.#followingEnd
|
|
380
|
+
&& this.#selection === undefined
|
|
381
|
+
&& verticalShiftRows > 0
|
|
382
|
+
&& verticalShiftRows < viewportHeight;
|
|
383
|
+
const descriptor = {
|
|
384
|
+
frameId: ++this.#frameId,
|
|
385
|
+
width,
|
|
386
|
+
height,
|
|
387
|
+
transcript: viewportHeight === 0 ? null : { rowStart: 1, rowEnd: viewportHeight },
|
|
388
|
+
dock: dock.length === 0 ? null : { rowStart: viewportHeight + 1, rowEnd: height },
|
|
389
|
+
previousDocumentRange,
|
|
390
|
+
nextDocumentRange,
|
|
391
|
+
previousFollowingEnd: previousDescriptor?.followingEnd ?? null,
|
|
392
|
+
followingEnd: this.#followingEnd,
|
|
393
|
+
verticalShiftRows,
|
|
394
|
+
safeVerticalShift,
|
|
395
|
+
selectionRevision: composingSelectionRevision,
|
|
396
|
+
selectionDamagedRows,
|
|
397
|
+
cause: previousDescriptor === null
|
|
398
|
+
? "initial"
|
|
399
|
+
: !sameGeometry
|
|
400
|
+
? "geometry-change"
|
|
401
|
+
: !this.#followingEnd
|
|
402
|
+
? "detached"
|
|
403
|
+
: safeVerticalShift
|
|
404
|
+
? "follow-shift"
|
|
405
|
+
: "steady",
|
|
406
|
+
};
|
|
407
|
+
assertTranscriptViewportFrameDescriptor(descriptor);
|
|
317
408
|
const hits = {
|
|
318
409
|
viewportHeight,
|
|
319
410
|
rail: geometry === null || this.#config.scrollbarAppearance === "hidden" ? null : {
|
|
@@ -332,10 +423,77 @@ export class TranscriptViewport {
|
|
|
332
423
|
maxScroll: this.#maxScroll,
|
|
333
424
|
followingEnd: this.#followingEnd,
|
|
334
425
|
hits,
|
|
426
|
+
descriptor,
|
|
427
|
+
selectionDamage: {
|
|
428
|
+
revision: composingSelectionRevision,
|
|
429
|
+
recomputedRows,
|
|
430
|
+
reusedRows,
|
|
431
|
+
cacheEntries: this.#paintedRowCache.size + this.#baseRowCache.size
|
|
432
|
+
+ this.#selectedRowCache.size + this.#finalRowCache.size,
|
|
433
|
+
},
|
|
335
434
|
};
|
|
336
435
|
this.#frame = frame;
|
|
337
436
|
return frame;
|
|
338
437
|
}
|
|
438
|
+
/** Reuses an established transcript frame when only same-height dock rows changed. */
|
|
439
|
+
composeDockOnly(dockRows, width, height) {
|
|
440
|
+
const previous = this.#frame;
|
|
441
|
+
const boundedWidth = Math.max(1, width);
|
|
442
|
+
const boundedHeight = Math.max(0, height);
|
|
443
|
+
if (previous === null || previous.descriptor.width !== boundedWidth || previous.descriptor.height !== boundedHeight
|
|
444
|
+
|| previous.descriptor.selectionRevision !== this.#selectionRevision)
|
|
445
|
+
return null;
|
|
446
|
+
const dock = dockRows.length > boundedHeight ? dockRows.slice(-boundedHeight) : [...dockRows];
|
|
447
|
+
const viewportHeight = Math.max(0, boundedHeight - dock.length);
|
|
448
|
+
const expectedDock = dock.length === 0 ? null : { rowStart: viewportHeight + 1, rowEnd: boundedHeight };
|
|
449
|
+
const expectedTranscript = viewportHeight === 0 ? null : { rowStart: 1, rowEnd: viewportHeight };
|
|
450
|
+
if (!sameRectangle(previous.descriptor.dock, expectedDock)
|
|
451
|
+
|| !sameRectangle(previous.descriptor.transcript, expectedTranscript))
|
|
452
|
+
return null;
|
|
453
|
+
const descriptor = {
|
|
454
|
+
frameId: ++this.#frameId,
|
|
455
|
+
width: boundedWidth,
|
|
456
|
+
height: boundedHeight,
|
|
457
|
+
transcript: expectedTranscript,
|
|
458
|
+
dock: expectedDock,
|
|
459
|
+
previousDocumentRange: previous.descriptor.nextDocumentRange,
|
|
460
|
+
nextDocumentRange: previous.descriptor.nextDocumentRange,
|
|
461
|
+
previousFollowingEnd: previous.followingEnd,
|
|
462
|
+
followingEnd: previous.followingEnd,
|
|
463
|
+
verticalShiftRows: 0,
|
|
464
|
+
safeVerticalShift: false,
|
|
465
|
+
selectionRevision: this.#selectionRevision,
|
|
466
|
+
selectionDamagedRows: [],
|
|
467
|
+
cause: "dock-input",
|
|
468
|
+
};
|
|
469
|
+
assertTranscriptViewportFrameDescriptor(descriptor);
|
|
470
|
+
const frame = {
|
|
471
|
+
rows: [...previous.rows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight),
|
|
472
|
+
contentWidth: previous.contentWidth,
|
|
473
|
+
scrollTop: previous.scrollTop,
|
|
474
|
+
maxScroll: previous.maxScroll,
|
|
475
|
+
followingEnd: previous.followingEnd,
|
|
476
|
+
hits: previous.hits,
|
|
477
|
+
descriptor,
|
|
478
|
+
selectionDamage: {
|
|
479
|
+
revision: this.#selectionRevision,
|
|
480
|
+
recomputedRows: [],
|
|
481
|
+
reusedRows: Array.from({ length: viewportHeight }, (_, index) => index + 1),
|
|
482
|
+
cacheEntries: previous.selectionDamage.cacheEntries,
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
this.#frame = frame;
|
|
486
|
+
return frame;
|
|
487
|
+
}
|
|
488
|
+
#functionId(value) {
|
|
489
|
+
const existing = this.#functionIds.get(value);
|
|
490
|
+
if (existing !== undefined)
|
|
491
|
+
return existing;
|
|
492
|
+
const id = this.#nextFunctionId;
|
|
493
|
+
this.#nextFunctionId += 1;
|
|
494
|
+
this.#functionIds.set(value, id);
|
|
495
|
+
return id;
|
|
496
|
+
}
|
|
339
497
|
#lineContentAt(line) {
|
|
340
498
|
const plain = stripAnsi(this.#documentRows[line] ?? "");
|
|
341
499
|
const prompt = this.#promptAnchors.find(anchor => line >= anchor.firstRow && line <= anchor.lastRow);
|
|
@@ -354,6 +512,42 @@ export class TranscriptViewport {
|
|
|
354
512
|
return content;
|
|
355
513
|
}
|
|
356
514
|
}
|
|
515
|
+
function selectionRangeForLine(selection, documentLine, selectableRowCount, width) {
|
|
516
|
+
if (selection === undefined || documentLine >= selectableRowCount
|
|
517
|
+
|| documentLine < selection.start.line || documentLine > selection.end.line)
|
|
518
|
+
return null;
|
|
519
|
+
const fullVisualRow = selection.fullRow === true
|
|
520
|
+
|| (selection.start.line !== selection.end.line
|
|
521
|
+
&& documentLine !== selection.start.line
|
|
522
|
+
&& documentLine !== selection.end.line);
|
|
523
|
+
const from = fullVisualRow || documentLine !== selection.start.line
|
|
524
|
+
? 0
|
|
525
|
+
: selection.start.column;
|
|
526
|
+
const throughFinalColumn = documentLine === selection.end.line && selection.throughFinalColumn === true;
|
|
527
|
+
const to = fullVisualRow || documentLine !== selection.end.line || throughFinalColumn
|
|
528
|
+
? width
|
|
529
|
+
: selection.end.column;
|
|
530
|
+
const boundedFrom = clamp(from, 0, width);
|
|
531
|
+
const boundedTo = clamp(to, boundedFrom, width);
|
|
532
|
+
return boundedTo > boundedFrom ? { from: boundedFrom, to: boundedTo } : null;
|
|
533
|
+
}
|
|
534
|
+
function cachedString(cache, key, limit, create) {
|
|
535
|
+
const existing = cache.get(key);
|
|
536
|
+
if (existing !== undefined) {
|
|
537
|
+
cache.delete(key);
|
|
538
|
+
cache.set(key, existing);
|
|
539
|
+
return { value: existing, reused: true };
|
|
540
|
+
}
|
|
541
|
+
const value = create();
|
|
542
|
+
cache.set(key, value);
|
|
543
|
+
while (cache.size > limit)
|
|
544
|
+
cache.delete(cache.keys().next().value);
|
|
545
|
+
return { value, reused: false };
|
|
546
|
+
}
|
|
547
|
+
function trimCache(cache, limit) {
|
|
548
|
+
while (cache.size > limit)
|
|
549
|
+
cache.delete(cache.keys().next().value);
|
|
550
|
+
}
|
|
357
551
|
function governingPrompt(anchors, scrollTop) {
|
|
358
552
|
let result = null;
|
|
359
553
|
for (const anchor of anchors) {
|
|
@@ -376,6 +570,60 @@ function padRowPreservingBackground(line, width) {
|
|
|
376
570
|
return `${shown}${reverse}${" ".repeat(padding)}\u001b[27m`;
|
|
377
571
|
return shown + " ".repeat(padding);
|
|
378
572
|
}
|
|
573
|
+
export function assertTranscriptViewportFrameDescriptor(descriptor) {
|
|
574
|
+
if (!Number.isSafeInteger(descriptor.frameId) || descriptor.frameId < 1)
|
|
575
|
+
throw new TypeError("viewport frame descriptor identity is invalid");
|
|
576
|
+
if (!Number.isSafeInteger(descriptor.width) || descriptor.width < 1)
|
|
577
|
+
throw new TypeError("viewport frame descriptor width is invalid");
|
|
578
|
+
if (!Number.isSafeInteger(descriptor.height) || descriptor.height < 0)
|
|
579
|
+
throw new TypeError("viewport frame descriptor height is invalid");
|
|
580
|
+
assertRectangle(descriptor.transcript, descriptor.height, "transcript");
|
|
581
|
+
assertRectangle(descriptor.dock, descriptor.height, "dock");
|
|
582
|
+
if (descriptor.transcript !== null && descriptor.dock !== null && descriptor.transcript.rowEnd >= descriptor.dock.rowStart) {
|
|
583
|
+
throw new TypeError("viewport frame descriptor regions overlap");
|
|
584
|
+
}
|
|
585
|
+
assertDocumentRange(descriptor.nextDocumentRange, "next");
|
|
586
|
+
if (descriptor.previousDocumentRange !== null)
|
|
587
|
+
assertDocumentRange(descriptor.previousDocumentRange, "previous");
|
|
588
|
+
if (!Number.isSafeInteger(descriptor.verticalShiftRows))
|
|
589
|
+
throw new TypeError("viewport frame descriptor shift is invalid");
|
|
590
|
+
if (descriptor.previousDocumentRange === null && descriptor.verticalShiftRows !== 0) {
|
|
591
|
+
throw new TypeError("viewport frame descriptor initial shift is invalid");
|
|
592
|
+
}
|
|
593
|
+
if (descriptor.previousDocumentRange !== null
|
|
594
|
+
&& descriptor.verticalShiftRows !== descriptor.nextDocumentRange.start - descriptor.previousDocumentRange.start) {
|
|
595
|
+
throw new TypeError("viewport frame descriptor shift disagrees with document ranges");
|
|
596
|
+
}
|
|
597
|
+
if (descriptor.safeVerticalShift && (descriptor.verticalShiftRows <= 0
|
|
598
|
+
|| descriptor.previousFollowingEnd !== true
|
|
599
|
+
|| !descriptor.followingEnd
|
|
600
|
+
|| descriptor.cause !== "follow-shift")) {
|
|
601
|
+
throw new TypeError("viewport frame descriptor marks an unsafe shift");
|
|
602
|
+
}
|
|
603
|
+
if (!Number.isSafeInteger(descriptor.selectionRevision) || descriptor.selectionRevision < 0) {
|
|
604
|
+
throw new TypeError("viewport frame descriptor selection revision is invalid");
|
|
605
|
+
}
|
|
606
|
+
if (descriptor.selectionDamagedRows.some((row, index) => !Number.isSafeInteger(row)
|
|
607
|
+
|| row < 1 || row > descriptor.height || (index > 0 && row <= descriptor.selectionDamagedRows[index - 1]))) {
|
|
608
|
+
throw new TypeError("viewport frame descriptor selection damage is invalid");
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
function assertRectangle(rectangle, height, label) {
|
|
612
|
+
if (rectangle === null)
|
|
613
|
+
return;
|
|
614
|
+
if (!Number.isSafeInteger(rectangle.rowStart) || !Number.isSafeInteger(rectangle.rowEnd)
|
|
615
|
+
|| rectangle.rowStart < 1 || rectangle.rowEnd < rectangle.rowStart || rectangle.rowEnd > height) {
|
|
616
|
+
throw new TypeError(`viewport frame descriptor ${label} region is invalid`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
function assertDocumentRange(range, label) {
|
|
620
|
+
if (!Number.isSafeInteger(range.start) || !Number.isSafeInteger(range.end) || range.start < 0 || range.end < range.start) {
|
|
621
|
+
throw new TypeError(`viewport frame descriptor ${label} document range is invalid`);
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
function sameRectangle(left, right) {
|
|
625
|
+
return left === null ? right === null : right !== null && left.rowStart === right.rowStart && left.rowEnd === right.rowEnd;
|
|
626
|
+
}
|
|
379
627
|
function clamp(value, minimum, maximum) {
|
|
380
628
|
return Math.min(Math.max(value, minimum), maximum);
|
|
381
629
|
}
|
|
@@ -15,8 +15,6 @@ export interface OwnedUiSettingsEntry {
|
|
|
15
15
|
readonly storedValue: unknown;
|
|
16
16
|
readonly effectiveValue: unknown;
|
|
17
17
|
readonly editable: boolean;
|
|
18
|
-
readonly available: boolean;
|
|
19
|
-
readonly limitationReason: string | null;
|
|
20
18
|
readonly choices: readonly OwnedUiSettingValue[] | null;
|
|
21
19
|
/** True when the value is a structured object edited through its own surface. */
|
|
22
20
|
readonly structured: boolean;
|
|
@@ -15,8 +15,6 @@ export function buildOwnedUiSettingsSections(input) {
|
|
|
15
15
|
storedValue: setting.value,
|
|
16
16
|
effectiveValue: setting.value,
|
|
17
17
|
editable: true,
|
|
18
|
-
available: true,
|
|
19
|
-
limitationReason: null,
|
|
20
18
|
structured: false,
|
|
21
19
|
minimum: null,
|
|
22
20
|
maximum: null,
|
|
@@ -52,9 +50,12 @@ function agentSection(snapshot) {
|
|
|
52
50
|
return frozenSection(`Agent settings are unavailable: ${snapshot.failure}`, null, []);
|
|
53
51
|
}
|
|
54
52
|
if (snapshot.descriptors.length === 0) {
|
|
55
|
-
return
|
|
53
|
+
return { id: AGENT_SECTION_ID, title: "Agent", entries: Object.freeze([]), unavailableReason: null, readOnlyReason: null };
|
|
56
54
|
}
|
|
57
|
-
const
|
|
55
|
+
const presented = snapshot.writeAdvertised
|
|
56
|
+
? snapshot.descriptors.filter(descriptor => descriptor.writable && descriptor.available)
|
|
57
|
+
: [];
|
|
58
|
+
const entries = presented.map(descriptor => {
|
|
58
59
|
const raw = descriptor.storedValue;
|
|
59
60
|
return {
|
|
60
61
|
id: descriptor.key,
|
|
@@ -65,33 +66,26 @@ function agentSection(snapshot) {
|
|
|
65
66
|
rawValue: raw,
|
|
66
67
|
storedValue: descriptor.storedValue,
|
|
67
68
|
effectiveValue: descriptor.effectiveValue,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
// value menu, so it stays reachable instead of being reported as fixed.
|
|
72
|
-
editable: snapshot.writeAdvertised && descriptor.writable && descriptor.available,
|
|
69
|
+
// Invariant: unavailable and non-writable descriptors are filtered before projection.
|
|
70
|
+
// Every presented structured value remains editable through its own surface.
|
|
71
|
+
editable: true,
|
|
73
72
|
structured: descriptor.valueType === "json",
|
|
74
73
|
minimum: descriptor.minimum ?? null,
|
|
75
74
|
maximum: descriptor.maximum ?? null,
|
|
76
75
|
flags: descriptor.flags ?? [],
|
|
77
|
-
//
|
|
76
|
+
// Protocol: a boolean is a two-value choice even when the engine names no choices,
|
|
78
77
|
// so it opens the same menu as any other enumerated setting.
|
|
79
78
|
choices: descriptor.valueType === "boolean" ? BOOLEAN_CHOICES : choicesOf(descriptor.choices),
|
|
80
79
|
origin: "engine",
|
|
81
80
|
application: descriptor.application,
|
|
82
81
|
};
|
|
83
82
|
});
|
|
84
|
-
const readOnlyReason = snapshot.writeAdvertised
|
|
85
|
-
? (entries.some(entry => entry.editable)
|
|
86
|
-
? null
|
|
87
|
-
: "The agent engine reports no writable settings")
|
|
88
|
-
: "The agent engine does not support changing settings from this surface";
|
|
89
83
|
return {
|
|
90
84
|
id: AGENT_SECTION_ID,
|
|
91
85
|
title: "Agent",
|
|
92
86
|
entries: Object.freeze(entries),
|
|
93
87
|
unavailableReason: null,
|
|
94
|
-
readOnlyReason,
|
|
88
|
+
readOnlyReason: null,
|
|
95
89
|
};
|
|
96
90
|
}
|
|
97
91
|
function frozenSection(unavailableReason, readOnlyReason, entries) {
|
|
@@ -49,7 +49,7 @@ export class OwnedUiSettingsSession {
|
|
|
49
49
|
if (entry === null)
|
|
50
50
|
return failed(`unknown ${backend} setting: ${id}`);
|
|
51
51
|
if (!entry.editable)
|
|
52
|
-
return failed(
|
|
52
|
+
return failed(`${id} is not editable from this surface`);
|
|
53
53
|
return backend === "a1" ? this.#changeOwned(id, value) : await this.#changeAgentValue(id, value);
|
|
54
54
|
}
|
|
55
55
|
#changeOwned(id, value) {
|
|
@@ -78,7 +78,7 @@ export class OwnedUiSettingsSession {
|
|
|
78
78
|
}
|
|
79
79
|
let result;
|
|
80
80
|
try {
|
|
81
|
-
//
|
|
81
|
+
// Invariant: the coordinator behind the port owns effect installation, persistence,
|
|
82
82
|
// flush, and rollback. A second surface-level flush would split authority.
|
|
83
83
|
result = await agent.writeSetting(id, value);
|
|
84
84
|
}
|
|
@@ -102,10 +102,11 @@ async function snapshotOf(agent, hidden) {
|
|
|
102
102
|
const listed = await agent.listSettings();
|
|
103
103
|
for (const descriptor of listed)
|
|
104
104
|
assertAgentSettingDescriptor(descriptor);
|
|
105
|
-
const
|
|
105
|
+
const writeAdvertised = agent.capabilities.write && typeof agent.writeSetting === "function";
|
|
106
|
+
const descriptors = listed.filter(descriptor => !hidden.has(descriptor.key) && writeAdvertised && descriptor.writable && descriptor.available);
|
|
106
107
|
return {
|
|
107
108
|
descriptors,
|
|
108
|
-
writeAdvertised
|
|
109
|
+
writeAdvertised,
|
|
109
110
|
failure: null,
|
|
110
111
|
};
|
|
111
112
|
}
|
|
@@ -13,6 +13,7 @@ export interface OwnedUiSettingsWriteOutcome {
|
|
|
13
13
|
/** Present when the write failed; the caller must not report the change as saved. */
|
|
14
14
|
readonly failure: string | null;
|
|
15
15
|
}
|
|
16
|
+
/** Resolves and atomically persists one profile's owned settings without discarding unknown keys. */
|
|
16
17
|
export declare class OwnedUiSettingsStore {
|
|
17
18
|
#private;
|
|
18
19
|
constructor(options: OwnedUiSettingsStoreOptions);
|
|
@@ -5,6 +5,7 @@ import { OWNED_UI_SETTINGS_MIGRATIONS } from "./migrations.js";
|
|
|
5
5
|
import { documentFrom, parseOwnedUiSettingsDocument, resolveOwnedUiSettings, } from "./resolution.js";
|
|
6
6
|
const PROFILE_ID_PATTERN = /^[a-z][a-z0-9-]{0,63}$/;
|
|
7
7
|
const MAX_DOCUMENT_BYTES = 256 * 1024;
|
|
8
|
+
/** Resolves and atomically persists one profile's owned settings without discarding unknown keys. */
|
|
8
9
|
export class OwnedUiSettingsStore {
|
|
9
10
|
#file;
|
|
10
11
|
#declarations;
|
|
@@ -76,7 +77,7 @@ export class OwnedUiSettingsStore {
|
|
|
76
77
|
rmSync(temporary, { force: true });
|
|
77
78
|
}
|
|
78
79
|
catch {
|
|
79
|
-
//
|
|
80
|
+
// Security: a leftover temporary file is inert: read() only ever opens the target path.
|
|
80
81
|
}
|
|
81
82
|
return { stored: false, failure: `${this.#file} could not be written: ${describe(error)}` };
|
|
82
83
|
}
|