@timurproko/a1 0.1.8-dev.260 → 0.1.8-dev.269
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/update-recovery.js +272 -0
- package/dist/foundation/release/index.d.ts +1 -0
- package/dist/foundation/release/index.js +1 -0
- package/dist/foundation/release/release-gc.js +2 -0
- package/dist/foundation/release/update-recovery.d.ts +74 -0
- package/dist/foundation/release/update-recovery.js +352 -0
- package/dist/foundation/release/update-transaction.d.ts +10 -0
- package/dist/foundation/release/update-transaction.js +12 -1
- package/dist/foundation/release/update.d.ts +19 -1
- package/dist/foundation/release/update.js +67 -10
- package/dist/integrations/pi/engine/conformance.d.ts +1 -1
- package/dist/integrations/pi/engine/conformance.js +9 -1
- package/dist/integrations/pi/engine/runtime-integration.js +10 -1
- package/dist/integrations/pi/engine/windows-filesystem-hygiene.d.ts +21 -0
- package/dist/integrations/pi/engine/windows-filesystem-hygiene.js +55 -0
- package/dist/integrations/pi/session-ui/session-shell-root.d.ts +1 -1
- package/dist/integrations/pi/session-ui/session-shell-root.js +29 -20
- 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/runtime-payload-inventory.json +1 -0
- package/dist/ui/components/transcript-viewport.d.ts +8 -0
- package/dist/ui/components/transcript-viewport.js +30 -6
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type InlineExtension } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
interface FileMetadata {
|
|
3
|
+
isFile(): boolean;
|
|
4
|
+
isSymbolicLink(): boolean;
|
|
5
|
+
}
|
|
6
|
+
interface CleanupFileSystem {
|
|
7
|
+
lstat(path: string): Promise<FileMetadata>;
|
|
8
|
+
unlink(path: string): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
interface CleanupPaths {
|
|
11
|
+
dirname(path: string): string;
|
|
12
|
+
join(...paths: string[]): string;
|
|
13
|
+
resolve(...paths: string[]): string;
|
|
14
|
+
}
|
|
15
|
+
export interface WindowsNulCleanupOptions {
|
|
16
|
+
readonly platform?: NodeJS.Platform;
|
|
17
|
+
readonly fileSystem?: CleanupFileSystem;
|
|
18
|
+
readonly paths?: CleanupPaths;
|
|
19
|
+
}
|
|
20
|
+
export declare function createWindowsNulCleanupExtension(options?: WindowsNulCleanupOptions): InlineExtension | null;
|
|
21
|
+
export {};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { isBashToolResult, isEditToolResult, isWriteToolResult, } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { lstat, unlink } from "node:fs/promises";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
const DEFAULT_FILE_SYSTEM = { lstat, unlink };
|
|
5
|
+
const DEFAULT_PATHS = { dirname, join, resolve };
|
|
6
|
+
const SIMPLE_CD = /(?:^|[;&|\n])\s*cd\s+(?:"([^"\r\n]+)"|'([^'\r\n]+)'|([^\s;&|]+))/;
|
|
7
|
+
export function createWindowsNulCleanupExtension(options = {}) {
|
|
8
|
+
if ((options.platform ?? process.platform) !== "win32")
|
|
9
|
+
return null;
|
|
10
|
+
const fileSystem = options.fileSystem ?? DEFAULT_FILE_SYSTEM;
|
|
11
|
+
const paths = options.paths ?? DEFAULT_PATHS;
|
|
12
|
+
return {
|
|
13
|
+
name: "windows-nul-file-cleanup",
|
|
14
|
+
factory: pi => registerNulCleanup(pi, fileSystem, paths),
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
function registerNulCleanup(pi, fileSystem, paths) {
|
|
18
|
+
const cleanDirectory = async (directory) => {
|
|
19
|
+
const candidate = paths.join(directory, "nul");
|
|
20
|
+
try {
|
|
21
|
+
const metadata = await fileSystem.lstat(candidate);
|
|
22
|
+
if (metadata.isFile() && !metadata.isSymbolicLink())
|
|
23
|
+
await fileSystem.unlink(candidate);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
30
|
+
await cleanDirectory(ctx.cwd);
|
|
31
|
+
});
|
|
32
|
+
pi.on("tool_result", async (event, ctx) => {
|
|
33
|
+
const directories = new Set([ctx.cwd]);
|
|
34
|
+
if (isBashToolResult(event)) {
|
|
35
|
+
const command = stringInput(event.input, "command");
|
|
36
|
+
const target = command === null ? null : simpleCdTarget(command);
|
|
37
|
+
if (target !== null)
|
|
38
|
+
directories.add(paths.resolve(ctx.cwd, target));
|
|
39
|
+
}
|
|
40
|
+
if (isWriteToolResult(event) || isEditToolResult(event)) {
|
|
41
|
+
const target = stringInput(event.input, "path");
|
|
42
|
+
if (target !== null)
|
|
43
|
+
directories.add(paths.dirname(paths.resolve(ctx.cwd, target)));
|
|
44
|
+
}
|
|
45
|
+
await Promise.all(Array.from(directories, cleanDirectory));
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
function simpleCdTarget(command) {
|
|
49
|
+
const match = SIMPLE_CD.exec(command);
|
|
50
|
+
return match?.[1] ?? match?.[2] ?? match?.[3] ?? null;
|
|
51
|
+
}
|
|
52
|
+
function stringInput(input, key) {
|
|
53
|
+
const value = input[key];
|
|
54
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
55
|
+
}
|
|
@@ -92,7 +92,7 @@ export declare class OwnedUiSessionShellRoot implements PiTuiComponentPort {
|
|
|
92
92
|
applyTranscriptBlock(block: OwnedUiSessionViewModel["transcript"][number]): void;
|
|
93
93
|
render(width: number): readonly string[];
|
|
94
94
|
viewportFrameDescriptor(): TranscriptViewportFrameDescriptor | null;
|
|
95
|
-
/** Visible
|
|
95
|
+
/** Visible non-selectable Steering, alignment, and Working rows, for rendering evidence. */
|
|
96
96
|
viewportTransientTailRowCount(): number;
|
|
97
97
|
hasActiveSelection(): boolean;
|
|
98
98
|
setViewportConfig(config: OwnedUiViewportSettings): void;
|
|
@@ -45,7 +45,6 @@ export class OwnedUiSessionShellRoot {
|
|
|
45
45
|
#dockInputReuseEnabled;
|
|
46
46
|
#dockInputCandidate = false;
|
|
47
47
|
#dockInputSnapshot;
|
|
48
|
-
#dockInputStatusSignature;
|
|
49
48
|
#visibleViewportSnapshot;
|
|
50
49
|
#fullViewportCompositions = 0;
|
|
51
50
|
#dockOnlyViewportCompositions = 0;
|
|
@@ -232,14 +231,18 @@ export class OwnedUiSessionShellRoot {
|
|
|
232
231
|
// intentional final gutter after their right-aligned timestamp.
|
|
233
232
|
const documentWidth = width;
|
|
234
233
|
const document = this.#renderDocumentLayout(documentWidth);
|
|
234
|
+
const steeringRows = this.#renderQueued(width);
|
|
235
235
|
const statusRows = this.#renderStatus(width);
|
|
236
|
-
const
|
|
236
|
+
const transientSignature = transientRowsSignature(steeringRows, statusRows);
|
|
237
237
|
const snapshot = this.#visibleViewportSnapshot;
|
|
238
238
|
const dockInputCandidate = this.#dockInputCandidate;
|
|
239
239
|
const dockInputSnapshot = dockInputCandidate ? this.#dockInputSnapshot : undefined;
|
|
240
|
-
const
|
|
241
|
-
|
|
242
|
-
|
|
240
|
+
const dockInputTransientSignature = dockInputCandidate
|
|
241
|
+
? dockInputSnapshot?.transientSignature ?? transientSignature
|
|
242
|
+
: transientSignature;
|
|
243
|
+
// Invariant: pending Steering and live Working rows share one non-selectable
|
|
244
|
+
// viewport tail; only non-working status, widgets, input, and footer stay docked.
|
|
245
|
+
const scrollRows = [...document.rows, ...steeringRows, ...statusRows];
|
|
243
246
|
const dockRows = dock.rows;
|
|
244
247
|
const selectableDocumentRowCount = document.rows.length;
|
|
245
248
|
const dockStartRow = height - dockRows.length + 1;
|
|
@@ -256,7 +259,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
256
259
|
&& snapshot.width === width
|
|
257
260
|
&& snapshot.height === height
|
|
258
261
|
&& snapshot.documentRows === (dockInputSnapshot?.documentRows ?? scrollRows)
|
|
259
|
-
&& snapshot.
|
|
262
|
+
&& snapshot.transientSignature === dockInputTransientSignature
|
|
260
263
|
&& snapshot.promptAnchors === (dockInputSnapshot?.promptAnchors ?? document.promptAnchors)
|
|
261
264
|
&& snapshot.dockLength === dockRows.length
|
|
262
265
|
&& snapshot.inputSurface === (dockInputSnapshot?.inputSurface ?? this.#inputSurface)
|
|
@@ -264,9 +267,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
264
267
|
&& snapshot.selectionRevision === (dockInputSnapshot?.selectionRevision ?? this.#viewportController.selectionRevision)
|
|
265
268
|
? this.#viewportController.composeDockOnly(dockRows, width, height)
|
|
266
269
|
: null;
|
|
267
|
-
// Performance: a
|
|
268
|
-
//
|
|
269
|
-
if (frame !== null &&
|
|
270
|
+
// Performance: a spinner tick or queue update can race the input-triggered
|
|
271
|
+
// render. Recompute the complete transient signature before reusing rows.
|
|
272
|
+
if (frame !== null && dockInputTransientSignature !== transientSignature)
|
|
270
273
|
frame = null;
|
|
271
274
|
if (frame === null) {
|
|
272
275
|
frame = this.#viewportController.compose({
|
|
@@ -274,15 +277,16 @@ export class OwnedUiSessionShellRoot {
|
|
|
274
277
|
...(this.#viewportController.transcriptPointerSelecting
|
|
275
278
|
? { paintDocumentRow: heldNativeHyperlinkStyle }
|
|
276
279
|
: {}),
|
|
277
|
-
// Invariant: selection and copying stop at the real document tail;
|
|
278
|
-
//
|
|
280
|
+
// Invariant: selection and copying stop at the real document tail; Steering,
|
|
281
|
+
// fitting alignment, and live Working remain transient presentation chrome.
|
|
279
282
|
selectableDocumentRowCount,
|
|
283
|
+
bottomAlignedTailRowCount: statusRows.length,
|
|
280
284
|
dockRows,
|
|
281
285
|
promptAnchors: document.promptAnchors,
|
|
282
286
|
width,
|
|
283
287
|
height,
|
|
284
|
-
// Invariant: the control belongs immediately above the complete dock
|
|
285
|
-
//
|
|
288
|
+
// Invariant: the control belongs immediately above the complete dock and
|
|
289
|
+
// floats over transient viewport content rather than consuming a dock row.
|
|
286
290
|
bottomControlRow: Math.max(0, height - Math.min(height, dockRows.length) - 1),
|
|
287
291
|
theme: this.#viewportTheme,
|
|
288
292
|
});
|
|
@@ -294,8 +298,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
294
298
|
width,
|
|
295
299
|
height,
|
|
296
300
|
documentRows: scrollRows,
|
|
297
|
-
|
|
298
|
-
dockInputStatusSignature,
|
|
301
|
+
transientSignature,
|
|
299
302
|
promptAnchors: document.promptAnchors,
|
|
300
303
|
dockLength: dockRows.length,
|
|
301
304
|
inputSurface: this.#inputSurface,
|
|
@@ -308,7 +311,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
308
311
|
viewportFrameDescriptor() {
|
|
309
312
|
return this.#viewportController.frame?.descriptor ?? null;
|
|
310
313
|
}
|
|
311
|
-
/** Visible
|
|
314
|
+
/** Visible non-selectable Steering, alignment, and Working rows, for rendering evidence. */
|
|
312
315
|
viewportTransientTailRowCount() {
|
|
313
316
|
return this.#viewportController.frame?.hits.transientTail.length ?? 0;
|
|
314
317
|
}
|
|
@@ -338,7 +341,7 @@ export class OwnedUiSessionShellRoot {
|
|
|
338
341
|
return this.#renderDockLayout(width).rows;
|
|
339
342
|
}
|
|
340
343
|
#renderDockLayout(width) {
|
|
341
|
-
const queued = this.#
|
|
344
|
+
const queued = this.#customViewport ? [] : this.#renderQueued(width);
|
|
342
345
|
const statusRows = this.#customViewport ? this.#status.renderDock(width) : this.#renderStatus(width);
|
|
343
346
|
const transientRows = [...queued, ...statusRows];
|
|
344
347
|
const aboveWidgets = this.#renderWidgets("aboveEditor", width);
|
|
@@ -352,6 +355,9 @@ export class OwnedUiSessionShellRoot {
|
|
|
352
355
|
inputRows: input.length,
|
|
353
356
|
};
|
|
354
357
|
}
|
|
358
|
+
#renderQueued(width) {
|
|
359
|
+
return this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width);
|
|
360
|
+
}
|
|
355
361
|
layoutRoot() {
|
|
356
362
|
const document = layoutPort(width => this.#renderDocument(width), () => this.invalidate());
|
|
357
363
|
const queued = layoutPort(width => this.#view.editor.queuedSubmissions.length === 0 ? [] : this.#queued.render(width), () => this.#queued.invalidate());
|
|
@@ -781,14 +787,15 @@ export class OwnedUiSessionShellRoot {
|
|
|
781
787
|
}
|
|
782
788
|
#captureDockInputSnapshot() {
|
|
783
789
|
const width = Math.max(1, this.#componentRuntime.getColumns());
|
|
790
|
+
const steeringRows = this.#renderQueued(width);
|
|
784
791
|
const statusRows = this.#renderStatus(width);
|
|
785
|
-
|
|
792
|
+
const transientSignature = transientRowsSignature(steeringRows, statusRows);
|
|
786
793
|
const snapshot = this.#visibleViewportSnapshot;
|
|
787
794
|
this.#dockInputSnapshot = snapshot === undefined
|
|
788
795
|
? undefined
|
|
789
796
|
: {
|
|
790
797
|
documentRows: snapshot.documentRows,
|
|
791
|
-
|
|
798
|
+
transientSignature,
|
|
792
799
|
promptAnchors: snapshot.promptAnchors,
|
|
793
800
|
inputSurface: this.#inputSurface,
|
|
794
801
|
viewportRevision: this.#viewportController.presentationRevision,
|
|
@@ -824,7 +831,6 @@ export class OwnedUiSessionShellRoot {
|
|
|
824
831
|
this.#visibleViewportSnapshot = undefined;
|
|
825
832
|
this.#dockInputCandidate = false;
|
|
826
833
|
this.#dockInputSnapshot = undefined;
|
|
827
|
-
this.#dockInputStatusSignature = undefined;
|
|
828
834
|
if (this.#inputSurface !== this.editor)
|
|
829
835
|
this.#inputSurface.dispose?.();
|
|
830
836
|
this.#extensionHeader?.dispose?.();
|
|
@@ -1156,3 +1162,6 @@ function isPackageExtensionSource(sourceInfo) {
|
|
|
1156
1162
|
const source = sourceInfo?.source ?? "";
|
|
1157
1163
|
return source.startsWith("npm:") || source.startsWith("git:");
|
|
1158
1164
|
}
|
|
1165
|
+
function transientRowsSignature(steeringRows, statusRows) {
|
|
1166
|
+
return `${steeringRows.length}\u0000${steeringRows.join("\u0000")}\u0001${statusRows.length}\u0000${statusRows.join("\u0000")}`;
|
|
1167
|
+
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-06T14:17:35.764Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "dc03605e5780e2aeebb4ecafd62868dc673e22ddd38b024a369aff704ff136dc",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-06T14:17:46.307Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "ee8a00eaaf79c707459bbbfb52518e9739314967049fbe5fa625f36ce33db9ee",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-06T14:18:04.630Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "d78243dd7f7852c64abc40b2b544039eaa9eba0d1f0bd42317002a97e2e84c7b",
|
|
12
12
|
"size": 177664
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -24,6 +24,8 @@ export interface TranscriptViewportFrameInput {
|
|
|
24
24
|
readonly paintDocumentRow?: (row: string) => string;
|
|
25
25
|
/** Leading document rows that participate in pointer selection and copying. */
|
|
26
26
|
readonly selectableDocumentRowCount?: number;
|
|
27
|
+
/** Final transient rows to bottom-align with unused viewport space while content fits. */
|
|
28
|
+
readonly bottomAlignedTailRowCount?: number;
|
|
27
29
|
readonly dockRows: readonly string[];
|
|
28
30
|
readonly promptAnchors: readonly TranscriptPromptAnchor[];
|
|
29
31
|
readonly width: number;
|
|
@@ -86,6 +88,12 @@ export interface TranscriptViewportFrameDescriptor {
|
|
|
86
88
|
};
|
|
87
89
|
readonly previousFollowingEnd: boolean | null;
|
|
88
90
|
readonly followingEnd: boolean;
|
|
91
|
+
/** Complete non-selectable suffix, including pending steering, alignment, and status rows. */
|
|
92
|
+
readonly transientRowCount: number;
|
|
93
|
+
/** Flexible rows inserted before the bottom-aligned live status while content fits. */
|
|
94
|
+
readonly transientAlignmentGapRows: number;
|
|
95
|
+
/** Live status rows at the end of the transient suffix. */
|
|
96
|
+
readonly bottomAlignedTailRowCount: number;
|
|
89
97
|
readonly verticalShiftRows: number;
|
|
90
98
|
readonly safeVerticalShift: boolean;
|
|
91
99
|
/** Monotonic interaction revision used to reject stale selection evidence. */
|
|
@@ -240,7 +240,19 @@ export class TranscriptViewport {
|
|
|
240
240
|
const previousDescriptor = this.#frame?.descriptor ?? null;
|
|
241
241
|
const dock = input.dockRows.length > height ? input.dockRows.slice(-height) : [...input.dockRows];
|
|
242
242
|
const viewportHeight = Math.max(0, height - dock.length);
|
|
243
|
-
|
|
243
|
+
const bottomAlignedTailRowCount = clamp(input.bottomAlignedTailRowCount ?? 0, 0, input.documentRows.length);
|
|
244
|
+
const transientAlignmentGapRows = bottomAlignedTailRowCount > 0
|
|
245
|
+
? Math.max(0, viewportHeight - input.documentRows.length)
|
|
246
|
+
: 0;
|
|
247
|
+
const tailStart = input.documentRows.length - bottomAlignedTailRowCount;
|
|
248
|
+
const documentRows = transientAlignmentGapRows === 0
|
|
249
|
+
? input.documentRows
|
|
250
|
+
: [
|
|
251
|
+
...input.documentRows.slice(0, tailStart),
|
|
252
|
+
...Array.from({ length: transientAlignmentGapRows }, () => ""),
|
|
253
|
+
...input.documentRows.slice(tailStart),
|
|
254
|
+
];
|
|
255
|
+
this.#maxScroll = Math.max(0, documentRows.length - viewportHeight);
|
|
244
256
|
if (this.#followingEnd)
|
|
245
257
|
this.#scrollTop = this.#maxScroll;
|
|
246
258
|
else
|
|
@@ -250,7 +262,7 @@ export class TranscriptViewport {
|
|
|
250
262
|
if (this.#followingEnd)
|
|
251
263
|
this.#newMessages = 0;
|
|
252
264
|
const geometry = scrollbarGeometry({
|
|
253
|
-
contentLength:
|
|
265
|
+
contentLength: documentRows.length,
|
|
254
266
|
viewportHeight,
|
|
255
267
|
scroll: this.#scrollTop,
|
|
256
268
|
// Compatibility: the session rail deliberately starts one line below the viewport top.
|
|
@@ -266,7 +278,7 @@ export class TranscriptViewport {
|
|
|
266
278
|
now: input.now ?? Date.now(),
|
|
267
279
|
});
|
|
268
280
|
const contentWidth = presentation.reservesSpace ? Math.max(1, width - 1) : width;
|
|
269
|
-
this.#documentRows =
|
|
281
|
+
this.#documentRows = documentRows;
|
|
270
282
|
this.#selectableDocumentRowCount = clamp(input.selectableDocumentRowCount ?? input.documentRows.length, 0, input.documentRows.length);
|
|
271
283
|
this.#promptAnchors = input.promptAnchors;
|
|
272
284
|
this.#contentWidth = contentWidth;
|
|
@@ -278,7 +290,7 @@ export class TranscriptViewport {
|
|
|
278
290
|
trimCache(this.#finalRowCache, cacheLimit);
|
|
279
291
|
const paintId = this.#functionId(paintDocumentRow);
|
|
280
292
|
const paintRecomputedRows = new Set();
|
|
281
|
-
const visible =
|
|
293
|
+
const visible = documentRows
|
|
282
294
|
.slice(this.#scrollTop, this.#scrollTop + viewportHeight)
|
|
283
295
|
.map((row, index) => {
|
|
284
296
|
const painted = cachedString(this.#paintedRowCache, `${paintId}\u0000${row}`, cacheLimit, () => paintDocumentRow(row));
|
|
@@ -366,7 +378,7 @@ export class TranscriptViewport {
|
|
|
366
378
|
}
|
|
367
379
|
const nextDocumentRange = {
|
|
368
380
|
start: this.#scrollTop,
|
|
369
|
-
end: Math.min(
|
|
381
|
+
end: Math.min(documentRows.length, this.#scrollTop + viewportHeight),
|
|
370
382
|
};
|
|
371
383
|
const previousDocumentRange = previousDescriptor?.nextDocumentRange ?? null;
|
|
372
384
|
const sameGeometry = previousDescriptor !== null
|
|
@@ -391,6 +403,9 @@ export class TranscriptViewport {
|
|
|
391
403
|
nextDocumentRange,
|
|
392
404
|
previousFollowingEnd: previousDescriptor?.followingEnd ?? null,
|
|
393
405
|
followingEnd: this.#followingEnd,
|
|
406
|
+
transientRowCount: Math.max(0, documentRows.length - this.#selectableDocumentRowCount),
|
|
407
|
+
transientAlignmentGapRows,
|
|
408
|
+
bottomAlignedTailRowCount,
|
|
394
409
|
verticalShiftRows,
|
|
395
410
|
safeVerticalShift,
|
|
396
411
|
selectionRevision: composingSelectionRevision,
|
|
@@ -416,7 +431,7 @@ export class TranscriptViewport {
|
|
|
416
431
|
},
|
|
417
432
|
sticky: stickyActive ? { row: 1, target: governing.firstRow, width: contentWidth } : null,
|
|
418
433
|
bottom: bottomHit,
|
|
419
|
-
transientTail: Array.from({ length: Math.max(0, Math.min(
|
|
434
|
+
transientTail: Array.from({ length: Math.max(0, Math.min(documentRows.length, this.#scrollTop + viewportHeight) - Math.max(this.#scrollTop, this.#selectableDocumentRowCount)) }, (_row, index) => Math.max(this.#scrollTop, this.#selectableDocumentRowCount) - this.#scrollTop + index + 1),
|
|
420
435
|
};
|
|
421
436
|
const frame = {
|
|
422
437
|
rows: frameRows,
|
|
@@ -462,6 +477,9 @@ export class TranscriptViewport {
|
|
|
462
477
|
nextDocumentRange: previous.descriptor.nextDocumentRange,
|
|
463
478
|
previousFollowingEnd: previous.followingEnd,
|
|
464
479
|
followingEnd: previous.followingEnd,
|
|
480
|
+
transientRowCount: previous.descriptor.transientRowCount,
|
|
481
|
+
transientAlignmentGapRows: previous.descriptor.transientAlignmentGapRows,
|
|
482
|
+
bottomAlignedTailRowCount: previous.descriptor.bottomAlignedTailRowCount,
|
|
465
483
|
verticalShiftRows: 0,
|
|
466
484
|
safeVerticalShift: false,
|
|
467
485
|
selectionRevision: this.#selectionRevision,
|
|
@@ -587,6 +605,12 @@ export function assertTranscriptViewportFrameDescriptor(descriptor) {
|
|
|
587
605
|
assertDocumentRange(descriptor.nextDocumentRange, "next");
|
|
588
606
|
if (descriptor.previousDocumentRange !== null)
|
|
589
607
|
assertDocumentRange(descriptor.previousDocumentRange, "previous");
|
|
608
|
+
if (!Number.isSafeInteger(descriptor.transientRowCount) || descriptor.transientRowCount < 0
|
|
609
|
+
|| !Number.isSafeInteger(descriptor.transientAlignmentGapRows) || descriptor.transientAlignmentGapRows < 0
|
|
610
|
+
|| !Number.isSafeInteger(descriptor.bottomAlignedTailRowCount) || descriptor.bottomAlignedTailRowCount < 0
|
|
611
|
+
|| descriptor.transientAlignmentGapRows + descriptor.bottomAlignedTailRowCount > descriptor.transientRowCount) {
|
|
612
|
+
throw new TypeError("viewport frame descriptor transient geometry is invalid");
|
|
613
|
+
}
|
|
590
614
|
if (!Number.isSafeInteger(descriptor.verticalShiftRows))
|
|
591
615
|
throw new TypeError("viewport frame descriptor shift is invalid");
|
|
592
616
|
if (descriptor.previousDocumentRange === null && descriptor.verticalShiftRows !== 0) {
|