@timurproko/a1 0.1.8-dev.563 → 0.1.8-dev.572
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/README.md +13 -11
- package/dist/app/session-shell/session-shell-root.js +6 -3
- package/dist/app/session-shell/session-shell.js +7 -1
- package/dist/app/session-shell/session-viewport-controller.js +48 -30
- package/dist/cli/dispatch.d.ts +3 -1
- package/dist/cli/dispatch.js +56 -56
- package/dist/cli/package-messages.d.ts +4 -3
- package/dist/cli/package-messages.js +23 -11
- package/dist/integrations/pi/components/shell-footer-status.d.ts +4 -3
- package/dist/integrations/pi/components/shell-footer-status.js +53 -15
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -1
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.d.ts +2 -2
- package/dist/integrations/pi/components/upstream/adjacent/core/keybindings.js +3 -2
- package/dist/integrations/pi/engine/compaction-progress.d.ts +1 -1
- package/dist/integrations/pi/engine/compaction-progress.js +1 -0
- package/dist/integrations/pi/engine/repository-pr.d.ts +3 -3
- package/dist/integrations/pi/engine/repository-pr.js +6 -6
- package/dist/integrations/pi/engine/session-runtime.js +5 -4
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +5 -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 +3 -3
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/dist/ui/components/index.d.ts +2 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/progress-status.d.ts +9 -2
- package/dist/ui/components/progress-status.js +24 -3
- package/dist/ui/components/transcript-viewport.d.ts +9 -3
- package/dist/ui/components/transcript-viewport.js +90 -132
- package/docs/architecture/ui-reference-provenance.md +8 -5
- package/docs/openspec-archive-automation.md +2 -2
- package/docs/validation.md +1 -1
- package/package.json +1 -1
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { MAX_COPY_SOURCE_UNITS } from "./selection-copy.js";
|
|
2
2
|
import { isThumbRow, scrollbarGeometry, scrollbarPresentation, } from "./scrollbar.js";
|
|
3
3
|
import { backgroundSgrSpan, overlaySpan } from "./spans.js";
|
|
4
4
|
import { faint, truncateToWidth, displayWidth, stripAnsi } from "./text.js";
|
|
5
|
-
import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, } from "./text-selection.js";
|
|
5
|
+
import { extendTextSelection, orderedTextSelection, pressTextSelection, releaseTextSelection, textSelectionLineExtendColumn, textSelectionPointAt, textSelectionText, usefulTextLineContent, } from "./text-selection.js";
|
|
6
6
|
const CONTROL_STYLE_RESET = "\u001b]8;;\u001b\\\u001b[0m";
|
|
7
7
|
const IDENTITY_ROW = (row) => row;
|
|
8
8
|
const DEFAULT_CONFIG = {
|
|
@@ -35,7 +35,8 @@ export class TranscriptViewport {
|
|
|
35
35
|
#selectionClick;
|
|
36
36
|
#selectionRevision = 0;
|
|
37
37
|
#copyableSelection = false;
|
|
38
|
-
|
|
38
|
+
// Invariant: exact unselected rows cover the latest visible base frame and dock.
|
|
39
|
+
#selectionRows = [];
|
|
39
40
|
#paintedRowCache = new Map();
|
|
40
41
|
#baseRowCache = new Map();
|
|
41
42
|
#selectedRowCache = new Map();
|
|
@@ -78,19 +79,17 @@ export class TranscriptViewport {
|
|
|
78
79
|
setStickyHovered(hovered) { this.#stickyHovered = hovered; }
|
|
79
80
|
get selectionActive() { return this.#selection?.selecting === true; }
|
|
80
81
|
get hasSelection() { return orderedTextSelection(this.#selection) !== undefined; }
|
|
81
|
-
pressSelection(column,
|
|
82
|
-
const
|
|
83
|
-
if (
|
|
84
|
-
return false;
|
|
85
|
-
const line = clamp(this.#scrollTop + viewportRow - 1, 0, Math.max(0, this.#documentRows.length - 1));
|
|
86
|
-
if (line >= this.#selectableDocumentRowCount)
|
|
82
|
+
pressSelection(column, frameRow, now = Date.now()) {
|
|
83
|
+
const frameHeight = this.#frame?.rows.length ?? 0;
|
|
84
|
+
if (frameRow < 1 || frameRow > frameHeight || column < 1 || column > this.#selectionWidth)
|
|
87
85
|
return false;
|
|
86
|
+
const line = frameRow - 1;
|
|
88
87
|
const pressed = pressTextSelection({
|
|
89
88
|
line,
|
|
90
89
|
column,
|
|
91
|
-
contentWidth: this.#
|
|
92
|
-
lineText: this.#
|
|
93
|
-
lineContent: this.#
|
|
90
|
+
contentWidth: this.#selectionWidth,
|
|
91
|
+
lineText: this.#selectionRows[line] ?? "",
|
|
92
|
+
lineContent: usefulTextLineContent(this.#selectionRows[line] ?? ""),
|
|
94
93
|
...(this.#selectionClick === undefined ? {} : { previousClick: this.#selectionClick }),
|
|
95
94
|
now,
|
|
96
95
|
});
|
|
@@ -98,36 +97,35 @@ export class TranscriptViewport {
|
|
|
98
97
|
this.#selectionClick = pressed.click;
|
|
99
98
|
this.#selectionRevision += 1;
|
|
100
99
|
this.#updateCopyableSelection();
|
|
100
|
+
if (this.#copyableSelection)
|
|
101
|
+
this.#followingEnd = false;
|
|
101
102
|
return true;
|
|
102
103
|
}
|
|
103
|
-
extendSelection(column,
|
|
104
|
+
extendSelection(column, frameRow, now = Date.now(), autoScroll = true) {
|
|
104
105
|
const selection = this.#selection;
|
|
105
|
-
const
|
|
106
|
-
if (selection?.selecting !== true ||
|
|
106
|
+
const frameHeight = this.#frame?.rows.length ?? 0;
|
|
107
|
+
if (selection?.selecting !== true || frameHeight <= 0 || this.#selectionRows.length === 0)
|
|
107
108
|
return false;
|
|
108
109
|
// Performance: pointer motion updates only the endpoint; the shell's fixed-cadence timer
|
|
109
|
-
// owns edge scrolling.
|
|
110
|
-
|
|
111
|
-
if (autoScroll && viewportRow > viewportHeight)
|
|
110
|
+
// owns edge scrolling. Selection itself remains in visible-frame coordinates.
|
|
111
|
+
if (autoScroll && frameRow > frameHeight)
|
|
112
112
|
this.scrollBy(1, now);
|
|
113
|
-
else if (autoScroll &&
|
|
113
|
+
else if (autoScroll && frameRow <= 1 && this.#scrollTop > 0)
|
|
114
114
|
this.scrollBy(-1, now);
|
|
115
|
-
const
|
|
116
|
-
if (lastSelectableLine < 0)
|
|
117
|
-
return false;
|
|
118
|
-
const visibleRow = clamp(viewportRow, 1, viewportHeight);
|
|
119
|
-
const line = clamp(this.#scrollTop + visibleRow - 1, 0, lastSelectableLine);
|
|
115
|
+
const line = clamp(frameRow - 1, 0, this.#selectionRows.length - 1);
|
|
120
116
|
const targetColumn = selection.fullRow
|
|
121
117
|
? textSelectionLineExtendColumn(selection, line, this.#selectionWidth)
|
|
122
118
|
: clamp(column, 1, this.#selectionWidth);
|
|
123
119
|
const point = selection.fullRow
|
|
124
120
|
? { line, column: targetColumn }
|
|
125
|
-
: textSelectionPointAt(line, targetColumn, this.#
|
|
121
|
+
: textSelectionPointAt(line, targetColumn, this.#selectionRows[line] ?? "");
|
|
126
122
|
const next = extendTextSelection(selection, point);
|
|
127
123
|
if (next !== selection) {
|
|
128
124
|
this.#selection = next;
|
|
129
125
|
this.#selectionRevision += 1;
|
|
130
126
|
this.#updateCopyableSelection();
|
|
127
|
+
if (this.#copyableSelection)
|
|
128
|
+
this.#followingEnd = false;
|
|
131
129
|
}
|
|
132
130
|
return true;
|
|
133
131
|
}
|
|
@@ -136,6 +134,7 @@ export class TranscriptViewport {
|
|
|
136
134
|
return false;
|
|
137
135
|
this.#selection = releaseTextSelection(this.#selection);
|
|
138
136
|
this.#selectionRevision += 1;
|
|
137
|
+
this.#updateCopyableSelection();
|
|
139
138
|
return true;
|
|
140
139
|
}
|
|
141
140
|
clearSelection() {
|
|
@@ -143,7 +142,6 @@ export class TranscriptViewport {
|
|
|
143
142
|
return false;
|
|
144
143
|
this.#selection = undefined;
|
|
145
144
|
this.#copyableSelection = false;
|
|
146
|
-
this.#copyableRow = undefined;
|
|
147
145
|
this.#selectionRevision += 1;
|
|
148
146
|
return true;
|
|
149
147
|
}
|
|
@@ -151,39 +149,27 @@ export class TranscriptViewport {
|
|
|
151
149
|
const selection = orderedTextSelection(this.#selection);
|
|
152
150
|
if (selection === undefined)
|
|
153
151
|
return null;
|
|
154
|
-
const
|
|
155
|
-
return
|
|
152
|
+
const text = textSelectionText(selection, this.#selectionRows, line => usefulTextLineContent(this.#selectionRows[line] ?? ""));
|
|
153
|
+
return text.length === 0 ? null : text;
|
|
156
154
|
}
|
|
157
|
-
/** Captures selected
|
|
155
|
+
/** Captures only the selected visible frame text, never off-screen transcript history. */
|
|
158
156
|
captureSelectedText() {
|
|
159
157
|
if (!this.#copyableSelection)
|
|
160
158
|
return null;
|
|
161
159
|
const started = performance.now();
|
|
162
|
-
const
|
|
163
|
-
if (
|
|
164
|
-
return null;
|
|
165
|
-
const start = selection.start.line;
|
|
166
|
-
const end = Math.min(selection.end.line, this.#selectableDocumentRowCount - 1, this.#documentRows.length - 1);
|
|
167
|
-
if (end < start)
|
|
160
|
+
const text = this.selectedText();
|
|
161
|
+
if (text === null)
|
|
168
162
|
return null;
|
|
169
163
|
const normalized = {
|
|
170
|
-
start: { line: 0, column:
|
|
171
|
-
end: { line:
|
|
164
|
+
start: { line: 0, column: 0 },
|
|
165
|
+
end: { line: 0, column: Number.MAX_SAFE_INTEGER },
|
|
172
166
|
};
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const rows = [];
|
|
177
|
-
let sourceUnits = 0;
|
|
178
|
-
for (let line = start; line <= end; line++) {
|
|
179
|
-
const text = this.#documentRows[line] ?? "";
|
|
180
|
-
sourceUnits += text.length;
|
|
181
|
-
if (sourceUnits > MAX_COPY_SOURCE_UNITS)
|
|
182
|
-
return rejected();
|
|
183
|
-
const prompt = this.#promptKindAt(line);
|
|
184
|
-
rows.push({ text, ...(prompt === undefined ? {} : { prompt }) });
|
|
167
|
+
if (text.length > MAX_COPY_SOURCE_UNITS) {
|
|
168
|
+
return { literal: true, selection: normalized, revision: this.#selectionRevision, rows: [], sourceUnits: 0,
|
|
169
|
+
captureMs: performance.now() - started, rejected: "size" };
|
|
185
170
|
}
|
|
186
|
-
return { selection: normalized, revision: this.#selectionRevision, rows
|
|
171
|
+
return { literal: true, selection: normalized, revision: this.#selectionRevision, rows: [{ text }],
|
|
172
|
+
sourceUnits: text.length, captureMs: performance.now() - started };
|
|
187
173
|
}
|
|
188
174
|
scrollBy(lines, now = Date.now()) {
|
|
189
175
|
if (this.#maxScroll <= 0 || lines === 0)
|
|
@@ -255,7 +241,7 @@ export class TranscriptViewport {
|
|
|
255
241
|
this.#selectionRevision += 1;
|
|
256
242
|
this.#selection = undefined;
|
|
257
243
|
this.#copyableSelection = false;
|
|
258
|
-
this.#
|
|
244
|
+
this.#selectionRows = [];
|
|
259
245
|
this.#selectionClick = undefined;
|
|
260
246
|
this.#paintedRowCache.clear();
|
|
261
247
|
this.#baseRowCache.clear();
|
|
@@ -328,7 +314,6 @@ export class TranscriptViewport {
|
|
|
328
314
|
// Invariant: controls reserve the rail column, but source text beneath it
|
|
329
315
|
// remains reachable by a drag already owned by transcript selection.
|
|
330
316
|
this.#selectionWidth = width;
|
|
331
|
-
this.#updateCopyableSelection();
|
|
332
317
|
const paintDocumentRow = input.paintDocumentRow ?? IDENTITY_ROW;
|
|
333
318
|
const cacheLimit = Math.max(32, viewportHeight * 6);
|
|
334
319
|
trimCache(this.#paintedRowCache, cacheLimit);
|
|
@@ -337,16 +322,15 @@ export class TranscriptViewport {
|
|
|
337
322
|
trimCache(this.#finalRowCache, cacheLimit);
|
|
338
323
|
const paintId = this.#functionId(paintDocumentRow);
|
|
339
324
|
const paintRecomputedRows = new Set();
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
.
|
|
325
|
+
const visibleSource = documentRows.slice(this.#scrollTop, this.#scrollTop + viewportHeight);
|
|
326
|
+
while (visibleSource.length < viewportHeight)
|
|
327
|
+
visibleSource.push("");
|
|
328
|
+
const visible = visibleSource.map((row, index) => {
|
|
343
329
|
const painted = cachedString(this.#paintedRowCache, `${paintId}\u0000${row}`, cacheLimit, () => paintDocumentRow(row));
|
|
344
330
|
if (!painted.reused)
|
|
345
331
|
paintRecomputedRows.add(index);
|
|
346
332
|
return painted.value;
|
|
347
333
|
});
|
|
348
|
-
while (visible.length < viewportHeight)
|
|
349
|
-
visible.push("");
|
|
350
334
|
const governing = governingPrompt(input.promptAnchors, this.#scrollTop);
|
|
351
335
|
const stickyActive = governing !== null && governing.firstRow < this.#scrollTop;
|
|
352
336
|
if (stickyActive && visible.length > 0) {
|
|
@@ -362,7 +346,10 @@ export class TranscriptViewport {
|
|
|
362
346
|
paintRecomputedRows.add(0);
|
|
363
347
|
const sticky = theme.sticky(source.value, this.#stickyHovered);
|
|
364
348
|
visible[0] = quiet && !this.#stickyHovered ? theme.quietSticky(sticky) : sticky;
|
|
349
|
+
visibleSource[0] = sourceRow;
|
|
365
350
|
}
|
|
351
|
+
// Invariant: selection uses exact visible source rows; paint-only hyperlink guards stay rendered-only.
|
|
352
|
+
this.#selectionRows = [...visibleSource, ...dock].slice(0, height);
|
|
366
353
|
const selectionPainterId = this.#functionId(theme.selection);
|
|
367
354
|
// Concurrency: a painter may synchronously route input in tests or host integrations.
|
|
368
355
|
// Label this frame with the exact selection snapshot it began composing.
|
|
@@ -372,40 +359,8 @@ export class TranscriptViewport {
|
|
|
372
359
|
const reusedRows = [];
|
|
373
360
|
const selectionDamagedRows = [];
|
|
374
361
|
const visibleStates = [];
|
|
375
|
-
for (let row = 0; row < visible.length; row += 1) {
|
|
376
|
-
const documentLine = this.#scrollTop + row;
|
|
377
|
-
const painted = visible[row] ?? "";
|
|
378
|
-
const base = cachedString(this.#baseRowCache, `${width}\u0000${painted}`, cacheLimit, () => padRowPreservingBackground(painted, width));
|
|
379
|
-
let rowRecomputed = paintRecomputedRows.has(row) || !base.reused;
|
|
380
|
-
const range = stickyActive && row === 0
|
|
381
|
-
? null
|
|
382
|
-
: selectionRangeForLine(orderedSelection, documentLine, this.#selectableDocumentRowCount, width);
|
|
383
|
-
const rangeKey = range === null ? "-" : `${range.from}:${range.to}`;
|
|
384
|
-
const selected = range === null
|
|
385
|
-
? { value: base.value, reused: true }
|
|
386
|
-
: cachedString(this.#selectedRowCache, `${selectionPainterId}\u0000${rangeKey}\u0000${base.value}`, cacheLimit, () => theme.selection(base.value, range.from, range.to));
|
|
387
|
-
rowRecomputed ||= !selected.reused;
|
|
388
|
-
// Invariant: paint the rail after selection. Full-row selection reaches the terminal
|
|
389
|
-
// edge, while the foreground thumb/track remains visible above that
|
|
390
|
-
// background instead of disappearing into it.
|
|
391
|
-
let railCell = "";
|
|
392
|
-
if (presentation.visible && geometry !== null && row > 0) {
|
|
393
|
-
const trackRow = row - 1;
|
|
394
|
-
const thumb = isThumbRow(geometry, trackRow);
|
|
395
|
-
const glyph = thumb ? presentation.thumbGlyph : presentation.trackGlyph;
|
|
396
|
-
railCell = thumb ? theme.thumb(glyph, this.#railHovered || this.#railDragging) : theme.track(glyph);
|
|
397
|
-
}
|
|
398
|
-
const final = cachedString(this.#finalRowCache, `${width}\u0000${railCell}\u0000${selected.value}`, cacheLimit, () => railCell.length === 0 ? selected.value : overlaySpan(selected.value, width - 1, width, railCell, { inheritStartStyle: true }));
|
|
399
|
-
rowRecomputed ||= !final.reused;
|
|
400
|
-
visible[row] = final.value;
|
|
401
|
-
(rowRecomputed ? recomputedRows : reusedRows).push(row + 1);
|
|
402
|
-
const state = `${width}\u0000${documentLine}\u0000${painted}\u0000${rangeKey}\u0000${range === null ? "" : selectionPainterId}\u0000${railCell}`;
|
|
403
|
-
visibleStates.push(state);
|
|
404
|
-
if (this.#previousVisibleStates[row] !== state)
|
|
405
|
-
selectionDamagedRows.push(row + 1);
|
|
406
|
-
}
|
|
407
|
-
this.#previousVisibleStates = visibleStates;
|
|
408
362
|
const frameRows = [...visible, ...dock].slice(0, height);
|
|
363
|
+
const selectionRows = [...this.#selectionRows];
|
|
409
364
|
let bottomHit = null;
|
|
410
365
|
if (this.#maxScroll > 0 && !this.#followingEnd && frameRows.length > 0) {
|
|
411
366
|
const genericLabel = " Jump to bottom (Ctrl+End) ↓ ";
|
|
@@ -424,8 +379,49 @@ export class TranscriptViewport {
|
|
|
424
379
|
const bottomHovered = pointer !== undefined && pointer.row === bottomHit.row
|
|
425
380
|
&& pointer.column >= bottomHit.columnStart && pointer.column <= bottomHit.columnEnd;
|
|
426
381
|
frameRows[row] = overlaySpan(padRowPreservingBackground(frameRows[row] ?? "", width), left, left + labelWidth, `${CONTROL_STYLE_RESET}${theme.bottomControl(label, bottomHovered)}`);
|
|
382
|
+
selectionRows[row] = overlaySpan(padRowPreservingBackground(selectionRows[row] ?? "", width), left, left + labelWidth, label);
|
|
427
383
|
}
|
|
428
384
|
}
|
|
385
|
+
for (const covered of input.coveredCells ?? []) {
|
|
386
|
+
const row = covered.row - 1;
|
|
387
|
+
if (row < 0 || row >= selectionRows.length)
|
|
388
|
+
continue;
|
|
389
|
+
const from = clamp(covered.columnStart - 1, 0, width);
|
|
390
|
+
const to = clamp(covered.columnEnd, from, width);
|
|
391
|
+
selectionRows[row] = overlaySpan(padRowPreservingBackground(selectionRows[row] ?? "", width), from, to, " ".repeat(to - from));
|
|
392
|
+
}
|
|
393
|
+
this.#selectionRows = selectionRows;
|
|
394
|
+
this.#updateCopyableSelection();
|
|
395
|
+
for (let row = 0; row < frameRows.length; row += 1) {
|
|
396
|
+
const painted = frameRows[row] ?? "";
|
|
397
|
+
const range = selectionRangeForLine(orderedSelection, row, this.#selectionRows.length, width);
|
|
398
|
+
const padded = row < viewportHeight || range !== null;
|
|
399
|
+
const base = cachedString(this.#baseRowCache, `${width}\u0000${padded}\u0000${painted}`, cacheLimit, () => padded ? padRowPreservingBackground(painted, width) : painted);
|
|
400
|
+
let rowRecomputed = row < viewportHeight && paintRecomputedRows.has(row) || !base.reused;
|
|
401
|
+
const rangeKey = range === null ? "-" : `${range.from}:${range.to}`;
|
|
402
|
+
const selected = range === null
|
|
403
|
+
? { value: base.value, reused: true }
|
|
404
|
+
: cachedString(this.#selectedRowCache, `${selectionPainterId}\u0000${rangeKey}\u0000${base.value}`, cacheLimit, () => theme.selection(base.value, range.from, range.to));
|
|
405
|
+
rowRecomputed ||= !selected.reused;
|
|
406
|
+
// Invariant: paint the rail after selection. Full-row selection reaches the terminal
|
|
407
|
+
// edge, while the foreground thumb/track remains visible above that background.
|
|
408
|
+
let railCell = "";
|
|
409
|
+
if (row < viewportHeight && presentation.visible && geometry !== null && row > 0) {
|
|
410
|
+
const trackRow = row - 1;
|
|
411
|
+
const thumb = isThumbRow(geometry, trackRow);
|
|
412
|
+
const glyph = thumb ? presentation.thumbGlyph : presentation.trackGlyph;
|
|
413
|
+
railCell = thumb ? theme.thumb(glyph, this.#railHovered || this.#railDragging) : theme.track(glyph);
|
|
414
|
+
}
|
|
415
|
+
const final = cachedString(this.#finalRowCache, `${width}\u0000${railCell}\u0000${selected.value}`, cacheLimit, () => railCell.length === 0 ? selected.value : overlaySpan(selected.value, width - 1, width, railCell, { inheritStartStyle: true }));
|
|
416
|
+
rowRecomputed ||= !final.reused;
|
|
417
|
+
frameRows[row] = final.value;
|
|
418
|
+
(rowRecomputed ? recomputedRows : reusedRows).push(row + 1);
|
|
419
|
+
const state = `${width}\u0000${row}\u0000${painted}\u0000${rangeKey}\u0000${range === null ? "" : selectionPainterId}\u0000${railCell}`;
|
|
420
|
+
visibleStates.push(state);
|
|
421
|
+
if (this.#previousVisibleStates[row] !== state)
|
|
422
|
+
selectionDamagedRows.push(row + 1);
|
|
423
|
+
}
|
|
424
|
+
this.#previousVisibleStates = visibleStates;
|
|
429
425
|
const nextDocumentRange = {
|
|
430
426
|
start: this.#scrollTop,
|
|
431
427
|
end: Math.min(documentRows.length, this.#scrollTop + viewportHeight),
|
|
@@ -509,7 +505,7 @@ export class TranscriptViewport {
|
|
|
509
505
|
const boundedWidth = Math.max(1, width);
|
|
510
506
|
const boundedHeight = Math.max(0, height);
|
|
511
507
|
if (previous === null || previous.descriptor.width !== boundedWidth || previous.descriptor.height !== boundedHeight
|
|
512
|
-
|| previous.descriptor.selectionRevision !== this.#selectionRevision)
|
|
508
|
+
|| previous.descriptor.selectionRevision !== this.#selectionRevision || this.#selection !== undefined)
|
|
513
509
|
return null;
|
|
514
510
|
const dock = dockRows.length > boundedHeight ? dockRows.slice(-boundedHeight) : [...dockRows];
|
|
515
511
|
const viewportHeight = Math.max(0, boundedHeight - dock.length);
|
|
@@ -539,6 +535,7 @@ export class TranscriptViewport {
|
|
|
539
535
|
cause: "dock-input",
|
|
540
536
|
};
|
|
541
537
|
assertTranscriptViewportFrameDescriptor(descriptor);
|
|
538
|
+
this.#selectionRows = [...this.#selectionRows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight);
|
|
542
539
|
const frame = {
|
|
543
540
|
rows: [...previous.rows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight),
|
|
544
541
|
contentWidth: previous.contentWidth,
|
|
@@ -568,47 +565,8 @@ export class TranscriptViewport {
|
|
|
568
565
|
}
|
|
569
566
|
#updateCopyableSelection() {
|
|
570
567
|
const selection = orderedTextSelection(this.#selection);
|
|
571
|
-
|
|
572
|
-
this.#
|
|
573
|
-
this.#copyableRow = undefined;
|
|
574
|
-
return;
|
|
575
|
-
}
|
|
576
|
-
const end = Math.min(selection.end.line, this.#selectableDocumentRowCount - 1);
|
|
577
|
-
if (end !== selection.start.line) {
|
|
578
|
-
this.#copyableSelection = end > selection.start.line;
|
|
579
|
-
this.#copyableRow = undefined;
|
|
580
|
-
return;
|
|
581
|
-
}
|
|
582
|
-
const text = this.#documentRows[end] ?? "";
|
|
583
|
-
const prompt = this.#promptKindAt(end);
|
|
584
|
-
const from = selection.start.column, to = end === selection.end.line ? selection.end.column : Number.MAX_SAFE_INTEGER;
|
|
585
|
-
const cached = this.#copyableRow;
|
|
586
|
-
if (cached?.text === text && cached.prompt === prompt && cached.from === from && cached.to === to) {
|
|
587
|
-
this.#copyableSelection = cached.value;
|
|
588
|
-
return;
|
|
589
|
-
}
|
|
590
|
-
// Compatibility: learn whether an actual range is empty while presenting selection, not on Ctrl+C.
|
|
591
|
-
const value = selectionCopyRowText({ selection: { start: { line: 0, column: from }, end: { line: 0, column: to } } }, { text, ...(prompt === undefined ? {} : { prompt }) }, 0).length > 0;
|
|
592
|
-
this.#copyableRow = { text, prompt, from, to, value };
|
|
593
|
-
this.#copyableSelection = value;
|
|
594
|
-
}
|
|
595
|
-
#lineContentAt(line) {
|
|
596
|
-
return selectionCopyLineContent(this.#documentRows[line] ?? "", this.#promptKindAt(line));
|
|
597
|
-
}
|
|
598
|
-
#promptKindAt(line) {
|
|
599
|
-
// Performance: anchors are ordered, disjoint source ranges; do not scan off-screen prompts per copied row.
|
|
600
|
-
let low = 0, high = this.#promptAnchors.length - 1;
|
|
601
|
-
while (low <= high) {
|
|
602
|
-
const middle = (low + high) >>> 1;
|
|
603
|
-
const anchor = this.#promptAnchors[middle];
|
|
604
|
-
if (line < anchor.firstRow)
|
|
605
|
-
high = middle - 1;
|
|
606
|
-
else if (line > anchor.lastRow)
|
|
607
|
-
low = middle + 1;
|
|
608
|
-
else
|
|
609
|
-
return line === anchor.firstRow ? "first" : "continuation";
|
|
610
|
-
}
|
|
611
|
-
return undefined;
|
|
568
|
+
this.#copyableSelection = selection !== undefined
|
|
569
|
+
&& textSelectionText(selection, this.#selectionRows, line => usefulTextLineContent(this.#selectionRows[line] ?? "")).length > 0;
|
|
612
570
|
}
|
|
613
571
|
}
|
|
614
572
|
function selectionRangeForLine(selection, documentLine, selectableRowCount, width) {
|
|
@@ -81,13 +81,16 @@ its own `core` facade layer; A1 is a product, so the port adapts imports and kee
|
|
|
81
81
|
input behavior, but A1 uses its prior dark floating panel, lighter white-text active row,
|
|
82
82
|
and independent effective-value check mark because physical review rejected a menu that
|
|
83
83
|
blended into the settings rows.
|
|
84
|
-
- **Spinner-backed progress
|
|
84
|
+
- **Spinner-backed progress presentation.** Bare A1 resolves built-in and extension working
|
|
85
85
|
labels through `ui-components/progress-status.ts`, injected by
|
|
86
86
|
`session-shell/session-shell-root.ts` into the shared `pi-components/shell-footer-status.ts`
|
|
87
|
-
spinner factory. The factory canonicalizes the visible progress marker once, to
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
87
|
+
spinner factory. The factory canonicalizes the visible progress marker once, to one Unicode
|
|
88
|
+
ellipsis, and uses the existing spinner updates to move a two-grapheme accent band slowly
|
|
89
|
+
across otherwise muted label text with a muted pause between passes. The ellipsis remains
|
|
90
|
+
stationary, no additional timer exists, and theme roles cross the component-adapter boundary
|
|
91
|
+
as styling callbacks rather than imports or literal colours. Engine producers remain semantic;
|
|
92
|
+
pinned presentation keeps its three periods; and the source-synchronized Pi status indicators,
|
|
93
|
+
installed packages, `a1 pi`, and vanilla Pi remain untouched.
|
|
91
94
|
- **Keyboard presentation scheduling.** Pinned components, semantic key handling, and the
|
|
92
95
|
comparison profiles stay unchanged. Bare A1 adds one A1-owned event-loop-turn coordinator
|
|
93
96
|
before its fullscreen root and may omit only superseded visual states after every delivery
|
|
@@ -19,7 +19,7 @@ Version-1 and version-2 deliveries and their existing comments, acceptance PRs,
|
|
|
19
19
|
|
|
20
20
|
### Complete-regression evidence ordering
|
|
21
21
|
|
|
22
|
-
PR-attached Full regression is selected only for a repair that the trusted triage created from a failed Full regression: base policy verifies the immutable `openspec-ci` App author, generated candidate identity, and `regression-provenance.json`. Human-authored lookalikes, publishing or validation-authority changes, labels, Release-only repairs, and successful-run persistent-overrun candidates retain bounded ordinary validation.
|
|
22
|
+
PR-attached Full regression is selected only for a repair that the trusted triage created from a failed Full regression: base policy verifies the immutable `openspec-ci` App author, generated candidate identity, and `regression-provenance.json`. Human-authored lookalikes, publishing or validation-authority changes, labels, Release-only repairs, and successful-run persistent-overrun candidates retain bounded ordinary validation. Generated repair drafts run no Development test suites, including after implementation begins. Finish substantive implementation tasks, focused local evidence, pre-finalization observations, and known-gap disposition before marking ready. The ready active head defers testing while trusted finalization publishes the candidate; the finalized head then receives all four native lanes as new full evidence, required by `Development validation required` alongside the existing gates.
|
|
23
23
|
|
|
24
24
|
The final exact-head run is a handoff gate, not a task requiring a future run ID in committed `design.md`. Keep final head/run/selection identity in Actions outcomes and the maintainer handoff; do not invalidate successful evidence with an extra recording commit. Changed code, acceptance/body metadata, selection labels, or target baseline requires fresh evidence. A selected PR success removes the additional mandatory Full regression dispatch, but a standalone/manual run never substitutes for selected PR checks. Before deployment, existing separate-dispatch obligations remain. Neither PR validation nor manual regression proves numbered-package nightly publication recovery.
|
|
25
25
|
|
|
@@ -202,7 +202,7 @@ Unavailable, stale, automatic, unauthorized, conflicting, or contradictory prove
|
|
|
202
202
|
|
|
203
203
|
## CI and automation ownership
|
|
204
204
|
|
|
205
|
-
Normal Development CI remains complete for the implementation. An archive-shaped final diff does not select documentation-only validation because the authoritative version-3 association remains implementation-bound.
|
|
205
|
+
Normal Development CI remains complete for the implementation once its reviewable candidate exists. An archive-shaped final diff does not select documentation-only validation because the authoritative version-3 association remains implementation-bound. Base-controlled readiness defers a ready head that still holds the active change without running test suites or emitting `Development validation required`. The finalization workflow pushes the finalized head and updates its implementation fence; that update starts one complete selected validation, while PR-level concurrency cancels any superseded eligible run.
|
|
206
206
|
|
|
207
207
|
`pull_request` body edits rerun required CI. The ordinary finalized phase-free run exposes the stable protected aggregate directly; it does not wait for a lifecycle body edit. Documentation auto-merge's trusted owner also reevaluates lifecycle association and disables any armed merge. Every publication entry point explicitly refuses version 3.
|
|
208
208
|
|
package/docs/validation.md
CHANGED
|
@@ -94,7 +94,7 @@ Download these artifacts from the exact workflow run:
|
|
|
94
94
|
|
|
95
95
|
For selected implementation candidates, record focused and pre-finalization full observations before finalization. Keep final head/run/selection identity in Actions and handoff rather than adding a post-success commit that invalidates that evidence. A successful selected PR run replaces an additional mandatory dispatch; standalone runs remain diagnostic, and numbered-package nightly recovery remains independent.
|
|
96
96
|
|
|
97
|
-
A
|
|
97
|
+
Draft pull requests run no Development test suites. A ready version-3 PR whose body still identifies an active change defers testing and emits no protected aggregate while `OpenSpec finalization` publishes the candidate. After finalization records the archive and acceptance manifest, the ordinary exact-head workflow runs selected product/governance lanes and `Finalized delivery validation` in parallel, then emits the stable protected `Development validation required` aggregate only when both authorities succeed. Green CI enables maintainer review and manual merge but does not claim human acceptance or merge automatically. The finalization fence update is the validation trigger, so no pre-finalization test run or later lifecycle body edit is required. Any subsequent eligible implementation commit changes the head and reruns applicable validation; any body change reruns readiness and finalized-record validation and must continue to match the committed manifest. Legacy acceptance-record-only PRs retain their separate trusted `Acceptance record validation` route. Queue availability remains explicitly unavailable inside a runner and is calculated from the Actions API during final run analysis rather than guessed.
|
|
98
98
|
|
|
99
99
|
## Rollback
|
|
100
100
|
|
package/package.json
CHANGED