@timurproko/a1 0.1.8-dev.581 → 0.1.8-dev.588
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/dist/app/session-shell/session-viewport-controller.js +7 -3
- package/dist/integrations/pi/components/models-dialog.js +82 -3
- 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/components/scrollbar.d.ts +1 -1
- package/dist/ui/components/scrollbar.js +2 -2
- package/dist/ui/components/transcript-viewport.d.ts +2 -0
- package/dist/ui/components/transcript-viewport.js +36 -20
- package/docs/architecture/internal-naming.md +1 -0
- package/docs/validation.md +2 -0
- package/package.json +1 -1
|
@@ -398,7 +398,7 @@ export class SessionViewportController {
|
|
|
398
398
|
&& (event.column !== this.#pendingEditorClick.column || event.row !== this.#pendingEditorClick.row)) {
|
|
399
399
|
this.#pendingEditorClick = undefined;
|
|
400
400
|
}
|
|
401
|
-
this.#updateSelectionAutoScroll(event.column, event.row, hits.viewportHeight);
|
|
401
|
+
this.#updateSelectionAutoScroll(event.column, event.row, hits.viewportHeight, frame.rows.length);
|
|
402
402
|
activity = true;
|
|
403
403
|
return true;
|
|
404
404
|
}
|
|
@@ -555,8 +555,12 @@ export class SessionViewportController {
|
|
|
555
555
|
.find(candidate => column - 1 >= candidate.from && column - 1 < candidate.to);
|
|
556
556
|
return range === undefined ? undefined : JSON.stringify([row, range]);
|
|
557
557
|
}
|
|
558
|
-
#updateSelectionAutoScroll(column, row, frameHeight) {
|
|
559
|
-
|
|
558
|
+
#updateSelectionAutoScroll(column, row, viewportHeight, frameHeight) {
|
|
559
|
+
// Invariant: the last content row stays selectable; only the pointer below it scrolls,
|
|
560
|
+
// unless no row exists below. The top row scrolls because nothing lies above it. A
|
|
561
|
+
// dock-originated gesture never moves the transcript behind the fixed dock.
|
|
562
|
+
const beyondEdge = this.#viewport.selectionFromContent
|
|
563
|
+
&& (row <= 1 || row > viewportHeight || row >= frameHeight);
|
|
560
564
|
if (!beyondEdge) {
|
|
561
565
|
this.#stopSelectionAutoScroll();
|
|
562
566
|
return;
|
|
@@ -4,6 +4,8 @@ import { PiModalFrame } from "./modal-frame.js";
|
|
|
4
4
|
import { piTheme, renderPiModalShortcutHints } from "./theme.js";
|
|
5
5
|
const MAX_VISIBLE_ROWS = 10;
|
|
6
6
|
const MODELS_TITLE = "Models";
|
|
7
|
+
const REFRESHING_TITLE_MIN_DURATION_MS = 1_000;
|
|
8
|
+
const REFRESHED_TITLE_DURATION_MS = 2_000;
|
|
7
9
|
class ModalTitleLine {
|
|
8
10
|
#text = "";
|
|
9
11
|
setText(text) { this.#text = text; }
|
|
@@ -50,6 +52,11 @@ export class ModelsDialogComponent {
|
|
|
50
52
|
#selectedIndex = 0;
|
|
51
53
|
#preferredId;
|
|
52
54
|
#refreshStatus;
|
|
55
|
+
#refreshing = false;
|
|
56
|
+
#refreshStartedAt;
|
|
57
|
+
#refreshOutcomeTimer;
|
|
58
|
+
#refreshed = false;
|
|
59
|
+
#refreshDismissalTimer;
|
|
53
60
|
#disposed = false;
|
|
54
61
|
#focused = false;
|
|
55
62
|
constructor(config, callbacks) {
|
|
@@ -58,7 +65,10 @@ export class ModelsDialogComponent {
|
|
|
58
65
|
this.#scopeIds = [...config.scopeIds];
|
|
59
66
|
this.#savedScopeIds = [...config.savedScopeIds];
|
|
60
67
|
this.#filter = config.initialFilter ?? "all";
|
|
61
|
-
|
|
68
|
+
if (config.refreshStatus !== undefined) {
|
|
69
|
+
this.#refreshing = true;
|
|
70
|
+
this.#refreshStartedAt = Date.now();
|
|
71
|
+
}
|
|
62
72
|
if (config.initialQuery)
|
|
63
73
|
this.#input.setValue(config.initialQuery);
|
|
64
74
|
this.#replaceModels(config.models);
|
|
@@ -96,10 +106,44 @@ export class ModelsDialogComponent {
|
|
|
96
106
|
this.#restoreSelection(selectedId);
|
|
97
107
|
}
|
|
98
108
|
setRefreshStatus(message, kind) {
|
|
99
|
-
this.#
|
|
109
|
+
if (this.#disposed)
|
|
110
|
+
return;
|
|
111
|
+
if (kind === "muted") {
|
|
112
|
+
this.#clearRefreshOutcome();
|
|
113
|
+
this.#clearRefreshDismissal();
|
|
114
|
+
this.#refreshStatus = undefined;
|
|
115
|
+
this.#refreshing = true;
|
|
116
|
+
this.#refreshStartedAt = Date.now();
|
|
117
|
+
this.#refreshed = false;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const visibleMs = this.#refreshStartedAt === undefined ? REFRESHING_TITLE_MIN_DURATION_MS : Date.now() - this.#refreshStartedAt;
|
|
121
|
+
const remainingMs = Math.max(0, REFRESHING_TITLE_MIN_DURATION_MS - visibleMs);
|
|
122
|
+
if (this.#refreshing && remainingMs > 0) {
|
|
123
|
+
this.#clearRefreshOutcome();
|
|
124
|
+
const timer = setTimeout(() => {
|
|
125
|
+
if (this.#refreshOutcomeTimer !== timer)
|
|
126
|
+
return;
|
|
127
|
+
this.#refreshOutcomeTimer = undefined;
|
|
128
|
+
if (this.#disposed)
|
|
129
|
+
return;
|
|
130
|
+
this.#applyRefreshOutcome(message, kind);
|
|
131
|
+
this.#callbacks.requestRender();
|
|
132
|
+
}, remainingMs);
|
|
133
|
+
timer.unref?.();
|
|
134
|
+
this.#refreshOutcomeTimer = timer;
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
this.#clearRefreshOutcome();
|
|
138
|
+
this.#applyRefreshOutcome(message, kind);
|
|
100
139
|
}
|
|
101
140
|
dispose() {
|
|
102
141
|
this.#disposed = true;
|
|
142
|
+
this.#clearRefreshOutcome();
|
|
143
|
+
this.#clearRefreshDismissal();
|
|
144
|
+
this.#refreshing = false;
|
|
145
|
+
this.#refreshStartedAt = undefined;
|
|
146
|
+
this.#refreshed = false;
|
|
103
147
|
}
|
|
104
148
|
invalidate() {
|
|
105
149
|
this.#input.invalidate();
|
|
@@ -197,9 +241,44 @@ export class ModelsDialogComponent {
|
|
|
197
241
|
}
|
|
198
242
|
render(width) {
|
|
199
243
|
const theme = piTheme();
|
|
200
|
-
this.#title.setText(theme.fg("accent", theme.bold(MODELS_TITLE))
|
|
244
|
+
this.#title.setText(theme.fg("accent", theme.bold(MODELS_TITLE))
|
|
245
|
+
+ (this.dirty ? theme.fg("warning", " (unsaved)") : "")
|
|
246
|
+
+ (this.#refreshing ? theme.fg("muted", " (refreshing)") : "")
|
|
247
|
+
+ (this.#refreshed ? theme.fg("success", " (refreshed)") : ""));
|
|
201
248
|
return this.#frame.render(width);
|
|
202
249
|
}
|
|
250
|
+
#applyRefreshOutcome(message, kind) {
|
|
251
|
+
this.#clearRefreshDismissal();
|
|
252
|
+
this.#refreshing = false;
|
|
253
|
+
this.#refreshStartedAt = undefined;
|
|
254
|
+
this.#refreshed = kind === "success";
|
|
255
|
+
this.#refreshStatus = kind === "warning" ? { message, kind } : undefined;
|
|
256
|
+
if (kind !== "success")
|
|
257
|
+
return;
|
|
258
|
+
const timer = setTimeout(() => {
|
|
259
|
+
if (this.#refreshDismissalTimer !== timer)
|
|
260
|
+
return;
|
|
261
|
+
this.#refreshDismissalTimer = undefined;
|
|
262
|
+
if (this.#disposed)
|
|
263
|
+
return;
|
|
264
|
+
this.#refreshed = false;
|
|
265
|
+
this.#callbacks.requestRender();
|
|
266
|
+
}, REFRESHED_TITLE_DURATION_MS);
|
|
267
|
+
timer.unref?.();
|
|
268
|
+
this.#refreshDismissalTimer = timer;
|
|
269
|
+
}
|
|
270
|
+
#clearRefreshOutcome() {
|
|
271
|
+
if (this.#refreshOutcomeTimer === undefined)
|
|
272
|
+
return;
|
|
273
|
+
clearTimeout(this.#refreshOutcomeTimer);
|
|
274
|
+
this.#refreshOutcomeTimer = undefined;
|
|
275
|
+
}
|
|
276
|
+
#clearRefreshDismissal() {
|
|
277
|
+
if (this.#refreshDismissalTimer === undefined)
|
|
278
|
+
return;
|
|
279
|
+
clearTimeout(this.#refreshDismissalTimer);
|
|
280
|
+
this.#refreshDismissalTimer = undefined;
|
|
281
|
+
}
|
|
203
282
|
#renderBody(width) {
|
|
204
283
|
const theme = piTheme();
|
|
205
284
|
const lines = [];
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-25T08:17:55.130Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-25T08:17:49.253Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-09-
|
|
8
|
+
"builtAt": "2026-09-25T08:18:48.658Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "56eab3f4e22daaf23780b05cf751fb4a0eb62185877dcb18ebbea2123ece8595",
|
|
12
12
|
"size": 178176
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -24,7 +24,7 @@ export type ScrollbarStyle = "thin" | "thick";
|
|
|
24
24
|
export type ScrollbarSpeed = "normal" | "fast" | "high";
|
|
25
25
|
/** Wheel distance: normal (3), fast (6), high as normal + fast (9). */
|
|
26
26
|
export declare function scrollbarWheelRows(speed: ScrollbarSpeed): number;
|
|
27
|
-
/** Selection edge distance
|
|
27
|
+
/** Selection edge distance per auto-scroll tick: normal (1), fast (2), high (3). */
|
|
28
28
|
export declare function scrollbarSelectionRows(speed: ScrollbarSpeed): number;
|
|
29
29
|
export interface ScrollbarPresentationInput {
|
|
30
30
|
readonly geometry: ScrollbarGeometry | null;
|
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
export function scrollbarWheelRows(speed) {
|
|
8
8
|
return speed === "high" ? 9 : speed === "fast" ? 6 : 3;
|
|
9
9
|
}
|
|
10
|
-
/** Selection edge distance
|
|
10
|
+
/** Selection edge distance per auto-scroll tick: normal (1), fast (2), high (3). */
|
|
11
11
|
export function scrollbarSelectionRows(speed) {
|
|
12
|
-
return
|
|
12
|
+
return speed === "high" ? 3 : speed === "fast" ? 2 : 1;
|
|
13
13
|
}
|
|
14
14
|
/** Pure appearance decision; activity time is supplied so tests own the clock. */
|
|
15
15
|
export function scrollbarPresentation(input) {
|
|
@@ -162,6 +162,8 @@ export declare class TranscriptViewport {
|
|
|
162
162
|
setStickyHovered(hovered: boolean): void;
|
|
163
163
|
get selectionActive(): boolean;
|
|
164
164
|
get hasSelection(): boolean;
|
|
165
|
+
/** True when the active gesture began on transcript rows rather than the dock. */
|
|
166
|
+
get selectionFromContent(): boolean;
|
|
165
167
|
pressSelection(column: number, frameRow: number, now?: number): boolean;
|
|
166
168
|
extendSelection(column: number, frameRow: number, now?: number, autoScroll?: boolean): boolean;
|
|
167
169
|
releaseSelection(): boolean;
|
|
@@ -57,6 +57,7 @@ export class TranscriptViewport {
|
|
|
57
57
|
#contentWidth = 0;
|
|
58
58
|
#selectionWidth = 0;
|
|
59
59
|
#viewportHeight = 0;
|
|
60
|
+
#firstSelectableRow = 0;
|
|
60
61
|
#selectionRowAnchors = [];
|
|
61
62
|
#frameId = 0;
|
|
62
63
|
#frame = null;
|
|
@@ -88,12 +89,14 @@ export class TranscriptViewport {
|
|
|
88
89
|
setStickyHovered(hovered) { this.#stickyHovered = hovered; }
|
|
89
90
|
get selectionActive() { return this.#selection?.selecting === true; }
|
|
90
91
|
get hasSelection() { return orderedTextSelection(this.#selection) !== undefined; }
|
|
92
|
+
/** True when the active gesture began on transcript rows rather than the dock. */
|
|
93
|
+
get selectionFromContent() { return this.#selectionAnchors !== undefined && this.#selectionAnchors.anchor.kind !== "dock"; }
|
|
91
94
|
pressSelection(column, frameRow, now = Date.now()) {
|
|
92
95
|
const frameHeight = this.#frame?.rows.length ?? 0;
|
|
93
96
|
const selectionWidth = frameRow <= (this.#frame?.hits.viewportHeight ?? 0)
|
|
94
97
|
? this.#contentWidth
|
|
95
98
|
: this.#selectionWidth;
|
|
96
|
-
if (frameRow
|
|
99
|
+
if (frameRow <= this.#firstSelectableRow || frameRow > frameHeight || column < 1 || column > selectionWidth)
|
|
97
100
|
return false;
|
|
98
101
|
const line = frameRow - 1;
|
|
99
102
|
const pressed = pressTextSelection({
|
|
@@ -170,7 +173,7 @@ export class TranscriptViewport {
|
|
|
170
173
|
return true;
|
|
171
174
|
}
|
|
172
175
|
selectedText() {
|
|
173
|
-
const selection =
|
|
176
|
+
const selection = this.#visibleSelection();
|
|
174
177
|
if (selection === undefined)
|
|
175
178
|
return null;
|
|
176
179
|
const text = textSelectionText(selection, this.#selectionRows, line => usefulTextLineContent(this.#selectionRows[line] ?? ""));
|
|
@@ -388,9 +391,8 @@ export class TranscriptViewport {
|
|
|
388
391
|
? documentSelectionAnchor(documentRow, documentRows[documentRow] ?? "")
|
|
389
392
|
: { kind: "screen", row, source: this.#selectionRows[row] ?? "" };
|
|
390
393
|
});
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
+
// Invariant: a pinned prompt is chrome over its hidden source row, which clips like off-screen rows.
|
|
395
|
+
this.#firstSelectableRow = stickyActive && viewportHeight > 0 ? 1 : 0;
|
|
394
396
|
this.#selectionRowAnchors = [
|
|
395
397
|
...visibleAnchors,
|
|
396
398
|
...dock.map((_row, index) => ({ kind: "dock", fromBottom: dock.length - index - 1 })),
|
|
@@ -400,7 +402,7 @@ export class TranscriptViewport {
|
|
|
400
402
|
// Concurrency: a painter may synchronously route input in tests or host integrations.
|
|
401
403
|
// Label this frame with the exact selection snapshot it began composing.
|
|
402
404
|
const composingSelectionRevision = this.#selectionRevision;
|
|
403
|
-
const orderedSelection =
|
|
405
|
+
const orderedSelection = this.#visibleSelection();
|
|
404
406
|
const recomputedRows = [];
|
|
405
407
|
const reusedRows = [];
|
|
406
408
|
const selectionDamagedRows = [];
|
|
@@ -408,6 +410,7 @@ export class TranscriptViewport {
|
|
|
408
410
|
const frameRows = [...visible, ...dock].slice(0, height);
|
|
409
411
|
const selectionRows = [...this.#selectionRows];
|
|
410
412
|
let bottomHit = null;
|
|
413
|
+
let control;
|
|
411
414
|
if (this.#maxScroll > 0 && !this.#followingEnd && frameRows.length > 0) {
|
|
412
415
|
const genericLabel = " Jump to bottom (Ctrl+End) ↓ ";
|
|
413
416
|
const countedLabel = this.#newMessages > 0
|
|
@@ -424,8 +427,10 @@ export class TranscriptViewport {
|
|
|
424
427
|
const pointer = input.pointerPosition;
|
|
425
428
|
const bottomHovered = pointer !== undefined && pointer.row === bottomHit.row
|
|
426
429
|
&& pointer.column >= bottomHit.columnStart && pointer.column <= bottomHit.columnEnd;
|
|
427
|
-
|
|
428
|
-
|
|
430
|
+
control = { row, left, text: `${CONTROL_STYLE_RESET}${theme.bottomControl(label, bottomHovered)}` };
|
|
431
|
+
frameRows[row] = overlaySpan(padRowPreservingBackground(frameRows[row] ?? "", width), left, left + labelWidth, control.text);
|
|
432
|
+
// Invariant: the control floats above selection; its cells are never selected or copied.
|
|
433
|
+
selectionRows[row] = overlaySpan(padRowPreservingBackground(selectionRows[row] ?? "", width), left, left + labelWidth, " ".repeat(labelWidth));
|
|
429
434
|
}
|
|
430
435
|
}
|
|
431
436
|
for (const covered of input.coveredCells ?? []) {
|
|
@@ -442,15 +447,22 @@ export class TranscriptViewport {
|
|
|
442
447
|
const painted = frameRows[row] ?? "";
|
|
443
448
|
const rowSelectionWidth = row < viewportHeight ? contentWidth : width;
|
|
444
449
|
const range = selectionRangeForLine(orderedSelection, row, this.#selectionRows.length, rowSelectionWidth);
|
|
445
|
-
|
|
450
|
+
// Invariant: the reserved rail gutter is not a semantic selection or copy cell,
|
|
451
|
+
// but a range that reaches the content boundary paints through it so the rail
|
|
452
|
+
// floats above one continuous selection surface.
|
|
453
|
+
const paintRange = range !== null && row < viewportHeight
|
|
454
|
+
&& contentWidth < width && range.to === contentWidth
|
|
455
|
+
? { from: range.from, to: width }
|
|
456
|
+
: range;
|
|
457
|
+
const padded = row < viewportHeight || paintRange !== null;
|
|
446
458
|
const base = cachedString(this.#baseRowCache, `${width}\u0000${contentWidth}\u0000${row < viewportHeight}\u0000${padded}\u0000${painted}`, cacheLimit, () => row < viewportHeight
|
|
447
459
|
? padViewportRowWithGutter(painted, contentWidth, width)
|
|
448
460
|
: padded ? padRowPreservingBackground(painted, width) : painted);
|
|
449
461
|
let rowRecomputed = row < viewportHeight && paintRecomputedRows.has(row) || !base.reused;
|
|
450
|
-
const rangeKey =
|
|
451
|
-
const selected =
|
|
462
|
+
const rangeKey = paintRange === null ? "-" : `${paintRange.from}:${paintRange.to}`;
|
|
463
|
+
const selected = paintRange === null
|
|
452
464
|
? { value: base.value, reused: true }
|
|
453
|
-
: cachedString(this.#selectedRowCache, `${selectionPainterId}\u0000${rangeKey}\u0000${base.value}`, cacheLimit, () => theme.selection(base.value,
|
|
465
|
+
: cachedString(this.#selectedRowCache, `${selectionPainterId}\u0000${rangeKey}\u0000${base.value}`, cacheLimit, () => theme.selection(base.value, paintRange.from, paintRange.to));
|
|
454
466
|
rowRecomputed ||= !selected.reused;
|
|
455
467
|
// Invariant: paint the rail after selection over the gutter's continued row
|
|
456
468
|
// background. Source content and selection still end at the preceding cell.
|
|
@@ -471,7 +483,9 @@ export class TranscriptViewport {
|
|
|
471
483
|
return overlayWidth === 0 ? withRail : overlaySpan(padRowPreservingBackground(withRail, width), overlayLimit - overlayWidth, overlayLimit, `${CONTROL_STYLE_RESET}${overlayText}`);
|
|
472
484
|
});
|
|
473
485
|
rowRecomputed ||= !final.reused;
|
|
474
|
-
frameRows[row] =
|
|
486
|
+
frameRows[row] = control?.row === row && paintRange !== null && bottomHit !== null
|
|
487
|
+
? overlaySpan(final.value, control.left, bottomHit.columnEnd, control.text)
|
|
488
|
+
: final.value;
|
|
475
489
|
(rowRecomputed ? recomputedRows : reusedRows).push(row + 1);
|
|
476
490
|
const state = `${width}\u0000${row}\u0000${painted}\u0000${rangeKey}\u0000${range === null ? "" : selectionPainterId}\u0000${railCell}\u0000${overlayText}`;
|
|
477
491
|
visibleStates.push(state);
|
|
@@ -685,21 +699,23 @@ export class TranscriptViewport {
|
|
|
685
699
|
head: { ...selection.head, line: projectedHead.line },
|
|
686
700
|
};
|
|
687
701
|
}
|
|
702
|
+
#visibleSelection() {
|
|
703
|
+
const rows = this.#selectionAnchors?.anchor.kind === "document" ? this.#viewportHeight : this.#selectionRows.length;
|
|
704
|
+
return visibleTextSelection(orderedTextSelection(this.#selection), rows, this.#firstSelectableRow);
|
|
705
|
+
}
|
|
688
706
|
#updateCopyableSelection() {
|
|
689
|
-
|
|
690
|
-
this.#copyableSelection = selection !== undefined
|
|
691
|
-
&& textSelectionText(selection, this.#selectionRows, line => usefulTextLineContent(this.#selectionRows[line] ?? "")).length > 0;
|
|
707
|
+
this.#copyableSelection = this.selectedText() !== null;
|
|
692
708
|
}
|
|
693
709
|
}
|
|
694
|
-
function visibleTextSelection(selection, rowCount) {
|
|
695
|
-
if (selection === undefined || rowCount <=
|
|
710
|
+
function visibleTextSelection(selection, rowCount, firstRow) {
|
|
711
|
+
if (selection === undefined || rowCount <= firstRow || selection.end.line < firstRow || selection.start.line >= rowCount)
|
|
696
712
|
return undefined;
|
|
697
|
-
const startLine = Math.max(
|
|
713
|
+
const startLine = Math.max(firstRow, selection.start.line);
|
|
698
714
|
const endLine = Math.min(rowCount - 1, selection.end.line);
|
|
699
715
|
return {
|
|
700
716
|
start: {
|
|
701
717
|
line: startLine,
|
|
702
|
-
column: selection.start.line <
|
|
718
|
+
column: selection.start.line < firstRow ? 0 : selection.start.column,
|
|
703
719
|
},
|
|
704
720
|
end: {
|
|
705
721
|
line: endLine,
|
|
@@ -46,6 +46,7 @@ Pending review preserves a possible external interface, not an obsolete private
|
|
|
46
46
|
| `SOURCE_LEDGER_PATH`, `SOURCE_LEDGER_SCAN_ROOT`, `SOURCE_LEDGER_PORT_ROOT` | Source-ledger governance fixture overrides; produced by its tests and consumed by `check-pinned-pi-source-ledger.mjs`. |
|
|
47
47
|
| `RELEASE_RUNNER_LABEL` | Release/full-regression workflows supply certification-runner provenance to `run-release-gates.mjs`. |
|
|
48
48
|
| `TERMINAL_FIXTURE_INPUT`, `TERMINAL_FIXTURE_TOKEN` | Native terminal probe producer/consumer in `native/terminal-host/src/main.rs`; expected input and fixture token. |
|
|
49
|
+
| `TERMINAL_HOST_LOCAL_BUILD` | Developer shell sets exactly `1`; `native/terminal-host/build.rs` then permits a Windows build outside CI. |
|
|
49
50
|
| `DOCS_AUTO_MERGE_POLL_ATTEMPTS`, `DOCS_AUTO_MERGE_POLL_MS` | Documentation automation polling controls, produced by governance tests and consumed by `manage-documentation-auto-merge.mjs`. |
|
|
50
51
|
| `VALIDATION_JOB_STARTED_MS`, `RENDERING_JOB_STARTED_MS` | CI timing handoffs within `.github/workflows/ci.yml`; epoch milliseconds. |
|
|
51
52
|
| `RUN_PROCESS_CONTAINMENT_INTEGRATION` | CI enables the real containment fixture with exactly `1`; platform containment tests consume it. |
|
package/docs/validation.md
CHANGED
|
@@ -32,6 +32,8 @@ Conservative Development retains every pull-request owner but does not schedule
|
|
|
32
32
|
node scripts/release/generate-validation-ownership-ledger.mjs --output .artifacts/validation/ownership-ledger.json
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
The `terminal-host` owner is CI-only. A change under `native/terminal-host/` or to its run scripts selects the Windows x64 **Native terminal host (Windows)** job, which builds the crate with pinned Rust and Zig 0.15.2, runs `npm run test:terminal-host`, and uploads the debug executable. Local Windows builds fail in `build.rs` unless `TERMINAL_HOST_LOCAL_BUILD=1` is set, because Zig's package fetch unpacks test data that workstation antivirus quarantines along with the installed process guardian. See [`native/terminal-host/README.md`](../native/terminal-host/README.md).
|
|
36
|
+
|
|
35
37
|
## Failed-job reruns and attempt evidence
|
|
36
38
|
|
|
37
39
|
Every modular outcome, content-free job envelope, and uploaded artifact name is qualified by `github.run_attempt`. Authority remains bound to the workflow run ID, exact head, complete selection identity, logical job, and platform/runtime target.
|
package/package.json
CHANGED