@timurproko/a1 0.1.8-dev.563 → 0.1.8-dev.573
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/prompt-chips.js +20 -28
- package/dist/app/session-shell/session-shell-root.d.ts +2 -0
- package/dist/app/session-shell/session-shell-root.js +60 -17
- package/dist/app/session-shell/session-shell.js +35 -1
- package/dist/app/session-shell/session-viewport-controller.d.ts +1 -0
- package/dist/app/session-shell/session-viewport-controller.js +52 -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/contracts/owned-ui/index.d.ts +2 -0
- package/dist/contracts/owned-ui/index.js +1 -0
- package/dist/contracts/owned-ui/prompt-chips.d.ts +15 -0
- package/dist/contracts/owned-ui/prompt-chips.js +59 -0
- package/dist/contracts/owned-ui/validation.js +1 -1
- package/dist/integrations/pi/components/index.d.ts +1 -0
- package/dist/integrations/pi/components/index.js +1 -0
- package/dist/integrations/pi/components/modal-frame.d.ts +41 -0
- package/dist/integrations/pi/components/modal-frame.js +110 -0
- package/dist/integrations/pi/components/models-dialog.js +19 -5
- 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-selectors-dialogs.js +38 -2
- package/dist/integrations/pi/components/shell-shared-facade.d.ts +9 -1
- package/dist/integrations/pi/components/skills-dialog.js +3 -3
- package/dist/integrations/pi/components/submitted-prompt-adapter.d.ts +1 -1
- package/dist/integrations/pi/components/submitted-prompt-adapter.js +5 -2
- 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/components/upstream/components/extension-editor.d.ts +2 -1
- package/dist/integrations/pi/components/upstream/components/extension-editor.js +7 -6
- package/dist/integrations/pi/components/upstream/components/extension-input.js +9 -8
- package/dist/integrations/pi/components/upstream/components/extension-selector.js +10 -9
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/scoped-models-selector.js +3 -3
- package/dist/integrations/pi/components/upstream/components/session-selector.js +13 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.d.ts +7 -6
- package/dist/integrations/pi/components/upstream/components/thinking-selector.js +15 -12
- package/dist/integrations/pi/components/upstream/components/tree-selector.d.ts +1 -1
- package/dist/integrations/pi/components/upstream/components/tree-selector.js +13 -6
- package/dist/integrations/pi/components/upstream/components/trust-selector.js +9 -9
- package/dist/integrations/pi/engine/compaction-progress.d.ts +3 -7
- package/dist/integrations/pi/engine/compaction-progress.js +11 -10
- 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/engine/settings-bridge.js +1 -1
- package/dist/integrations/pi/engine/settings-port.js +1 -1
- package/dist/integrations/pi/tui-runtime/adapter.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/adapter.js +11 -2
- package/dist/integrations/pi/tui-runtime/contracts.d.ts +2 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.d.ts +6 -0
- package/dist/integrations/pi/tui-runtime/mouse-report-input.js +34 -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/dialog-panel.js +10 -6
- package/dist/ui/components/index.d.ts +2 -1
- package/dist/ui/components/index.js +1 -1
- package/dist/ui/components/mouse.d.ts +2 -2
- package/dist/ui/components/mouse.js +2 -2
- 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 +116 -136
- 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
|
@@ -15,7 +15,8 @@ export { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput, routeMouseInput
|
|
|
15
15
|
export type { ParsedMouseInput, RoutedMouseInput } from "./mouse.js";
|
|
16
16
|
export { isInsidePane, toPaneLocalMouse } from "./pane.js";
|
|
17
17
|
export type { Pane, PaneInputResult, PaneMouseEvent } from "./pane.js";
|
|
18
|
-
export { progressStatusText } from "./progress-status.js";
|
|
18
|
+
export { progressStatusFrame, progressStatusText } from "./progress-status.js";
|
|
19
|
+
export type { ProgressStatusFrameStyles, ProgressStatusMarker } from "./progress-status.js";
|
|
19
20
|
export { FrameCache, RENDER_REVISION_KINDS, RenderRevisionTracker, ZERO_REVISIONS, normalizeRevisions, revisionsEqual } from "./revision.js";
|
|
20
21
|
export type { RenderCacheContract, RenderRevisionKind, RenderRevisions } from "./revision.js";
|
|
21
22
|
export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
|
|
@@ -7,7 +7,7 @@ export { blockJumpTarget, blockRowSpan, clampScroll, indexOfKey, layoutList, max
|
|
|
7
7
|
export { STEPPER_RESERVE, placementFor, regionAt, renderGroupHeader, renderListRow, renderNote, valueColumnFor } from "./list-view.js";
|
|
8
8
|
export { MOUSE_TRACKING_OFF, MOUSE_TRACKING_ON, parseMouseInput, routeMouseInput } from "./mouse.js";
|
|
9
9
|
export { isInsidePane, toPaneLocalMouse } from "./pane.js";
|
|
10
|
-
export { progressStatusText } from "./progress-status.js";
|
|
10
|
+
export { progressStatusFrame, progressStatusText } from "./progress-status.js";
|
|
11
11
|
export { FrameCache, RENDER_REVISION_KINDS, RenderRevisionTracker, ZERO_REVISIONS, normalizeRevisions, revisionsEqual } from "./revision.js";
|
|
12
12
|
export { MAX_COPY_ROWS, MAX_COPY_SOURCE_UNITS, selectionCopyLineContent, selectionCopyRowText } from "./selection-copy.js";
|
|
13
13
|
export { ScrollbarRails, isThumbRow, scrollForThumbRow, scrollForTrackPage, scrollbarGeometry, scrollbarPresentation, scrollbarReservesSpace, scrollbarSelectionRows, scrollbarWheelRows, } from "./scrollbar.js";
|
|
@@ -4,8 +4,8 @@ import type { PaneMouseEvent } from "./pane.js";
|
|
|
4
4
|
* terminal to send reports; nothing else in A1 turns this on, and the sequences
|
|
5
5
|
* are paired so the terminal is always left as it was found.
|
|
6
6
|
*/
|
|
7
|
-
export declare const MOUSE_TRACKING_ON = "\u001B[?1006h\u001B[?1000h\u001B[?1003h";
|
|
8
|
-
export declare const MOUSE_TRACKING_OFF = "\u001B[?1003l\u001B[?1000l\u001B[?1006l";
|
|
7
|
+
export declare const MOUSE_TRACKING_ON = "\u001B[?1006h\u001B[?1000h\u001B[?1003h\u001B[?1004h";
|
|
8
|
+
export declare const MOUSE_TRACKING_OFF = "\u001B[?1004l\u001B[?1003l\u001B[?1000l\u001B[?1006l";
|
|
9
9
|
export interface ParsedMouseInput {
|
|
10
10
|
readonly events: readonly PaneMouseEvent[];
|
|
11
11
|
/** Input with every mouse report removed, for ordinary key handling. */
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
* terminal to send reports; nothing else in A1 turns this on, and the sequences
|
|
4
4
|
* are paired so the terminal is always left as it was found.
|
|
5
5
|
*/
|
|
6
|
-
export const MOUSE_TRACKING_ON = "\u001b[?1006h\u001b[?1000h\u001b[?1003h";
|
|
7
|
-
export const MOUSE_TRACKING_OFF = "\u001b[?1003l\u001b[?1000l\u001b[?1006l";
|
|
6
|
+
export const MOUSE_TRACKING_ON = "\u001b[?1006h\u001b[?1000h\u001b[?1003h\u001b[?1004h";
|
|
7
|
+
export const MOUSE_TRACKING_OFF = "\u001b[?1004l\u001b[?1003l\u001b[?1000l\u001b[?1006l";
|
|
8
8
|
const SGR_PATTERN = /\u001b\[<(\d+);(\d+);(\d+)([Mm])/g;
|
|
9
9
|
/** Extracts SGR mouse reports, leaving any keyboard input untouched. */
|
|
10
10
|
export function parseMouseInput(data) {
|
|
@@ -1,2 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
export
|
|
1
|
+
export type ProgressStatusMarker = "…" | "...";
|
|
2
|
+
export interface ProgressStatusFrameStyles {
|
|
3
|
+
readonly muted: (text: string) => string;
|
|
4
|
+
readonly accent: (text: string) => string;
|
|
5
|
+
}
|
|
6
|
+
/** Returns spinner-backed status text with the selected terminal progress marker. */
|
|
7
|
+
export declare function progressStatusText(message: string, marker?: ProgressStatusMarker): string;
|
|
8
|
+
/** Paints one width-stable, grapheme-safe frame of the quiet progress-label sweep. */
|
|
9
|
+
export declare function progressStatusFrame(message: string, phase: number, styles: ProgressStatusFrameStyles): string;
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
const GRAPHEMES = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
2
|
+
const HIGHLIGHT_WIDTH = 2;
|
|
3
|
+
const UPDATES_PER_STEP = 3;
|
|
4
|
+
/** Returns spinner-backed status text with the selected terminal progress marker. */
|
|
5
|
+
export function progressStatusText(message, marker = "…") {
|
|
6
|
+
return `${message.replace(/(?:…|\.+)$/u, "")}${marker}`;
|
|
7
|
+
}
|
|
8
|
+
/** Paints one width-stable, grapheme-safe frame of the quiet progress-label sweep. */
|
|
9
|
+
export function progressStatusFrame(message, phase, styles) {
|
|
10
|
+
const marker = message.endsWith("…") ? "…" : "";
|
|
11
|
+
const label = marker === "" ? message : message.slice(0, -marker.length);
|
|
12
|
+
const graphemes = [...GRAPHEMES.segment(label)].map(segment => segment.segment);
|
|
13
|
+
if (graphemes.length === 0)
|
|
14
|
+
return paint(marker, styles.muted);
|
|
15
|
+
const step = Math.floor(Math.max(0, phase) / UPDATES_PER_STEP) % (graphemes.length * 2);
|
|
16
|
+
if (step >= graphemes.length)
|
|
17
|
+
return paint(`${label}${marker}`, styles.muted);
|
|
18
|
+
const before = graphemes.slice(0, step).join("");
|
|
19
|
+
const highlight = graphemes.slice(step, step + HIGHLIGHT_WIDTH).join("");
|
|
20
|
+
const after = `${graphemes.slice(step + HIGHLIGHT_WIDTH).join("")}${marker}`;
|
|
21
|
+
return `${paint(before, styles.muted)}${paint(highlight, styles.accent)}${paint(after, styles.muted)}`;
|
|
22
|
+
}
|
|
23
|
+
function paint(text, style) {
|
|
24
|
+
return text === "" ? "" : style(text);
|
|
4
25
|
}
|
|
@@ -50,6 +50,12 @@ export interface TranscriptViewportFrameInput {
|
|
|
50
50
|
readonly column: number;
|
|
51
51
|
readonly row: number;
|
|
52
52
|
};
|
|
53
|
+
/** Base-frame cells hidden by modal/replacement surfaces and therefore excluded from visual copy. */
|
|
54
|
+
readonly coveredCells?: readonly {
|
|
55
|
+
readonly row: number;
|
|
56
|
+
readonly columnStart: number;
|
|
57
|
+
readonly columnEnd: number;
|
|
58
|
+
}[];
|
|
53
59
|
readonly now?: number;
|
|
54
60
|
readonly theme?: TranscriptViewportTheme;
|
|
55
61
|
}
|
|
@@ -150,12 +156,12 @@ export declare class TranscriptViewport {
|
|
|
150
156
|
setStickyHovered(hovered: boolean): void;
|
|
151
157
|
get selectionActive(): boolean;
|
|
152
158
|
get hasSelection(): boolean;
|
|
153
|
-
pressSelection(column: number,
|
|
154
|
-
extendSelection(column: number,
|
|
159
|
+
pressSelection(column: number, frameRow: number, now?: number): boolean;
|
|
160
|
+
extendSelection(column: number, frameRow: number, now?: number, autoScroll?: boolean): boolean;
|
|
155
161
|
releaseSelection(): boolean;
|
|
156
162
|
clearSelection(): boolean;
|
|
157
163
|
selectedText(): string | null;
|
|
158
|
-
/** Captures selected
|
|
164
|
+
/** Captures only the selected visible frame text, never off-screen transcript history. */
|
|
159
165
|
captureSelectedText(): SelectionCopySnapshot | null;
|
|
160
166
|
scrollBy(lines: number, now?: number): boolean;
|
|
161
167
|
scrollTo(position: number, now?: number): boolean;
|
|
@@ -1,9 +1,12 @@
|
|
|
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
|
+
// Invariant: the scrollbar gutter is presentation chrome: no source link, emphasis,
|
|
8
|
+
// selection, or background may cross from the content cell beside it.
|
|
9
|
+
const GUTTER_STYLE_RESET = CONTROL_STYLE_RESET;
|
|
7
10
|
const IDENTITY_ROW = (row) => row;
|
|
8
11
|
const DEFAULT_CONFIG = {
|
|
9
12
|
scrollbarAppearance: "auto",
|
|
@@ -35,7 +38,8 @@ export class TranscriptViewport {
|
|
|
35
38
|
#selectionClick;
|
|
36
39
|
#selectionRevision = 0;
|
|
37
40
|
#copyableSelection = false;
|
|
38
|
-
|
|
41
|
+
// Invariant: exact unselected rows cover the latest visible base frame and dock.
|
|
42
|
+
#selectionRows = [];
|
|
39
43
|
#paintedRowCache = new Map();
|
|
40
44
|
#baseRowCache = new Map();
|
|
41
45
|
#selectedRowCache = new Map();
|
|
@@ -78,19 +82,20 @@ export class TranscriptViewport {
|
|
|
78
82
|
setStickyHovered(hovered) { this.#stickyHovered = hovered; }
|
|
79
83
|
get selectionActive() { return this.#selection?.selecting === true; }
|
|
80
84
|
get hasSelection() { return orderedTextSelection(this.#selection) !== undefined; }
|
|
81
|
-
pressSelection(column,
|
|
82
|
-
const
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if (
|
|
85
|
+
pressSelection(column, frameRow, now = Date.now()) {
|
|
86
|
+
const frameHeight = this.#frame?.rows.length ?? 0;
|
|
87
|
+
const selectionWidth = frameRow <= (this.#frame?.hits.viewportHeight ?? 0)
|
|
88
|
+
? this.#contentWidth
|
|
89
|
+
: this.#selectionWidth;
|
|
90
|
+
if (frameRow < 1 || frameRow > frameHeight || column < 1 || column > selectionWidth)
|
|
87
91
|
return false;
|
|
92
|
+
const line = frameRow - 1;
|
|
88
93
|
const pressed = pressTextSelection({
|
|
89
94
|
line,
|
|
90
95
|
column,
|
|
91
|
-
contentWidth:
|
|
92
|
-
lineText: this.#
|
|
93
|
-
lineContent: this.#
|
|
96
|
+
contentWidth: selectionWidth,
|
|
97
|
+
lineText: this.#selectionRows[line] ?? "",
|
|
98
|
+
lineContent: usefulTextLineContent(this.#selectionRows[line] ?? ""),
|
|
94
99
|
...(this.#selectionClick === undefined ? {} : { previousClick: this.#selectionClick }),
|
|
95
100
|
now,
|
|
96
101
|
});
|
|
@@ -98,36 +103,38 @@ export class TranscriptViewport {
|
|
|
98
103
|
this.#selectionClick = pressed.click;
|
|
99
104
|
this.#selectionRevision += 1;
|
|
100
105
|
this.#updateCopyableSelection();
|
|
106
|
+
if (this.#copyableSelection)
|
|
107
|
+
this.#followingEnd = false;
|
|
101
108
|
return true;
|
|
102
109
|
}
|
|
103
|
-
extendSelection(column,
|
|
110
|
+
extendSelection(column, frameRow, now = Date.now(), autoScroll = true) {
|
|
104
111
|
const selection = this.#selection;
|
|
105
|
-
const
|
|
106
|
-
if (selection?.selecting !== true ||
|
|
112
|
+
const frameHeight = this.#frame?.rows.length ?? 0;
|
|
113
|
+
if (selection?.selecting !== true || frameHeight <= 0 || this.#selectionRows.length === 0)
|
|
107
114
|
return false;
|
|
108
115
|
// Performance: pointer motion updates only the endpoint; the shell's fixed-cadence timer
|
|
109
|
-
// owns edge scrolling.
|
|
110
|
-
|
|
111
|
-
if (autoScroll && viewportRow > viewportHeight)
|
|
116
|
+
// owns edge scrolling. Selection itself remains in visible-frame coordinates.
|
|
117
|
+
if (autoScroll && frameRow > frameHeight)
|
|
112
118
|
this.scrollBy(1, now);
|
|
113
|
-
else if (autoScroll &&
|
|
119
|
+
else if (autoScroll && frameRow <= 1 && this.#scrollTop > 0)
|
|
114
120
|
this.scrollBy(-1, now);
|
|
115
|
-
const
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
const line = clamp(this.#scrollTop + visibleRow - 1, 0, lastSelectableLine);
|
|
121
|
+
const line = clamp(frameRow - 1, 0, this.#selectionRows.length - 1);
|
|
122
|
+
const selectionWidth = line < (this.#frame?.hits.viewportHeight ?? 0)
|
|
123
|
+
? this.#contentWidth
|
|
124
|
+
: this.#selectionWidth;
|
|
120
125
|
const targetColumn = selection.fullRow
|
|
121
|
-
? textSelectionLineExtendColumn(selection, line,
|
|
122
|
-
: clamp(column, 1,
|
|
126
|
+
? textSelectionLineExtendColumn(selection, line, selectionWidth)
|
|
127
|
+
: clamp(column, 1, selectionWidth);
|
|
123
128
|
const point = selection.fullRow
|
|
124
129
|
? { line, column: targetColumn }
|
|
125
|
-
: textSelectionPointAt(line, targetColumn, this.#
|
|
130
|
+
: textSelectionPointAt(line, targetColumn, this.#selectionRows[line] ?? "");
|
|
126
131
|
const next = extendTextSelection(selection, point);
|
|
127
132
|
if (next !== selection) {
|
|
128
133
|
this.#selection = next;
|
|
129
134
|
this.#selectionRevision += 1;
|
|
130
135
|
this.#updateCopyableSelection();
|
|
136
|
+
if (this.#copyableSelection)
|
|
137
|
+
this.#followingEnd = false;
|
|
131
138
|
}
|
|
132
139
|
return true;
|
|
133
140
|
}
|
|
@@ -136,6 +143,7 @@ export class TranscriptViewport {
|
|
|
136
143
|
return false;
|
|
137
144
|
this.#selection = releaseTextSelection(this.#selection);
|
|
138
145
|
this.#selectionRevision += 1;
|
|
146
|
+
this.#updateCopyableSelection();
|
|
139
147
|
return true;
|
|
140
148
|
}
|
|
141
149
|
clearSelection() {
|
|
@@ -143,7 +151,6 @@ export class TranscriptViewport {
|
|
|
143
151
|
return false;
|
|
144
152
|
this.#selection = undefined;
|
|
145
153
|
this.#copyableSelection = false;
|
|
146
|
-
this.#copyableRow = undefined;
|
|
147
154
|
this.#selectionRevision += 1;
|
|
148
155
|
return true;
|
|
149
156
|
}
|
|
@@ -151,39 +158,27 @@ export class TranscriptViewport {
|
|
|
151
158
|
const selection = orderedTextSelection(this.#selection);
|
|
152
159
|
if (selection === undefined)
|
|
153
160
|
return null;
|
|
154
|
-
const
|
|
155
|
-
return
|
|
161
|
+
const text = textSelectionText(selection, this.#selectionRows, line => usefulTextLineContent(this.#selectionRows[line] ?? ""));
|
|
162
|
+
return text.length === 0 ? null : text;
|
|
156
163
|
}
|
|
157
|
-
/** Captures selected
|
|
164
|
+
/** Captures only the selected visible frame text, never off-screen transcript history. */
|
|
158
165
|
captureSelectedText() {
|
|
159
166
|
if (!this.#copyableSelection)
|
|
160
167
|
return null;
|
|
161
168
|
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)
|
|
169
|
+
const text = this.selectedText();
|
|
170
|
+
if (text === null)
|
|
168
171
|
return null;
|
|
169
172
|
const normalized = {
|
|
170
|
-
start: { line: 0, column:
|
|
171
|
-
end: { line:
|
|
173
|
+
start: { line: 0, column: 0 },
|
|
174
|
+
end: { line: 0, column: Number.MAX_SAFE_INTEGER },
|
|
172
175
|
};
|
|
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 }) });
|
|
176
|
+
if (text.length > MAX_COPY_SOURCE_UNITS) {
|
|
177
|
+
return { literal: true, selection: normalized, revision: this.#selectionRevision, rows: [], sourceUnits: 0,
|
|
178
|
+
captureMs: performance.now() - started, rejected: "size" };
|
|
185
179
|
}
|
|
186
|
-
return { selection: normalized, revision: this.#selectionRevision, rows
|
|
180
|
+
return { literal: true, selection: normalized, revision: this.#selectionRevision, rows: [{ text }],
|
|
181
|
+
sourceUnits: text.length, captureMs: performance.now() - started };
|
|
187
182
|
}
|
|
188
183
|
scrollBy(lines, now = Date.now()) {
|
|
189
184
|
if (this.#maxScroll <= 0 || lines === 0)
|
|
@@ -255,7 +250,7 @@ export class TranscriptViewport {
|
|
|
255
250
|
this.#selectionRevision += 1;
|
|
256
251
|
this.#selection = undefined;
|
|
257
252
|
this.#copyableSelection = false;
|
|
258
|
-
this.#
|
|
253
|
+
this.#selectionRows = [];
|
|
259
254
|
this.#selectionClick = undefined;
|
|
260
255
|
this.#paintedRowCache.clear();
|
|
261
256
|
this.#baseRowCache.clear();
|
|
@@ -325,10 +320,9 @@ export class TranscriptViewport {
|
|
|
325
320
|
this.#selectableDocumentRowCount = clamp(input.selectableDocumentRowCount ?? input.documentRows.length, 0, input.documentRows.length);
|
|
326
321
|
this.#promptAnchors = input.promptAnchors;
|
|
327
322
|
this.#contentWidth = contentWidth;
|
|
328
|
-
// Invariant:
|
|
329
|
-
//
|
|
323
|
+
// Invariant: dock rows remain selectable at full width; scrollable source rows
|
|
324
|
+
// use contentWidth and leave the gutter outside semantic selection.
|
|
330
325
|
this.#selectionWidth = width;
|
|
331
|
-
this.#updateCopyableSelection();
|
|
332
326
|
const paintDocumentRow = input.paintDocumentRow ?? IDENTITY_ROW;
|
|
333
327
|
const cacheLimit = Math.max(32, viewportHeight * 6);
|
|
334
328
|
trimCache(this.#paintedRowCache, cacheLimit);
|
|
@@ -337,16 +331,15 @@ export class TranscriptViewport {
|
|
|
337
331
|
trimCache(this.#finalRowCache, cacheLimit);
|
|
338
332
|
const paintId = this.#functionId(paintDocumentRow);
|
|
339
333
|
const paintRecomputedRows = new Set();
|
|
340
|
-
const
|
|
341
|
-
|
|
342
|
-
.
|
|
334
|
+
const visibleSource = documentRows.slice(this.#scrollTop, this.#scrollTop + viewportHeight);
|
|
335
|
+
while (visibleSource.length < viewportHeight)
|
|
336
|
+
visibleSource.push("");
|
|
337
|
+
const visible = visibleSource.map((row, index) => {
|
|
343
338
|
const painted = cachedString(this.#paintedRowCache, `${paintId}\u0000${row}`, cacheLimit, () => paintDocumentRow(row));
|
|
344
339
|
if (!painted.reused)
|
|
345
340
|
paintRecomputedRows.add(index);
|
|
346
341
|
return painted.value;
|
|
347
342
|
});
|
|
348
|
-
while (visible.length < viewportHeight)
|
|
349
|
-
visible.push("");
|
|
350
343
|
const governing = governingPrompt(input.promptAnchors, this.#scrollTop);
|
|
351
344
|
const stickyActive = governing !== null && governing.firstRow < this.#scrollTop;
|
|
352
345
|
if (stickyActive && visible.length > 0) {
|
|
@@ -362,7 +355,14 @@ export class TranscriptViewport {
|
|
|
362
355
|
paintRecomputedRows.add(0);
|
|
363
356
|
const sticky = theme.sticky(source.value, this.#stickyHovered);
|
|
364
357
|
visible[0] = quiet && !this.#stickyHovered ? theme.quietSticky(sticky) : sticky;
|
|
358
|
+
visibleSource[0] = sourceRow;
|
|
365
359
|
}
|
|
360
|
+
// Invariant: selection uses exact visible source rows within the rendered
|
|
361
|
+
// content boundary; paint-only guards and the rail gutter stay presentation-only.
|
|
362
|
+
this.#selectionRows = [
|
|
363
|
+
...visibleSource.map(row => truncateToWidth(row, contentWidth)),
|
|
364
|
+
...dock,
|
|
365
|
+
].slice(0, height);
|
|
366
366
|
const selectionPainterId = this.#functionId(theme.selection);
|
|
367
367
|
// Concurrency: a painter may synchronously route input in tests or host integrations.
|
|
368
368
|
// Label this frame with the exact selection snapshot it began composing.
|
|
@@ -372,40 +372,8 @@ export class TranscriptViewport {
|
|
|
372
372
|
const reusedRows = [];
|
|
373
373
|
const selectionDamagedRows = [];
|
|
374
374
|
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
375
|
const frameRows = [...visible, ...dock].slice(0, height);
|
|
376
|
+
const selectionRows = [...this.#selectionRows];
|
|
409
377
|
let bottomHit = null;
|
|
410
378
|
if (this.#maxScroll > 0 && !this.#followingEnd && frameRows.length > 0) {
|
|
411
379
|
const genericLabel = " Jump to bottom (Ctrl+End) ↓ ";
|
|
@@ -424,8 +392,52 @@ export class TranscriptViewport {
|
|
|
424
392
|
const bottomHovered = pointer !== undefined && pointer.row === bottomHit.row
|
|
425
393
|
&& pointer.column >= bottomHit.columnStart && pointer.column <= bottomHit.columnEnd;
|
|
426
394
|
frameRows[row] = overlaySpan(padRowPreservingBackground(frameRows[row] ?? "", width), left, left + labelWidth, `${CONTROL_STYLE_RESET}${theme.bottomControl(label, bottomHovered)}`);
|
|
395
|
+
selectionRows[row] = overlaySpan(padRowPreservingBackground(selectionRows[row] ?? "", width), left, left + labelWidth, label);
|
|
427
396
|
}
|
|
428
397
|
}
|
|
398
|
+
for (const covered of input.coveredCells ?? []) {
|
|
399
|
+
const row = covered.row - 1;
|
|
400
|
+
if (row < 0 || row >= selectionRows.length)
|
|
401
|
+
continue;
|
|
402
|
+
const from = clamp(covered.columnStart - 1, 0, width);
|
|
403
|
+
const to = clamp(covered.columnEnd, from, width);
|
|
404
|
+
selectionRows[row] = overlaySpan(padRowPreservingBackground(selectionRows[row] ?? "", width), from, to, " ".repeat(to - from));
|
|
405
|
+
}
|
|
406
|
+
this.#selectionRows = selectionRows;
|
|
407
|
+
this.#updateCopyableSelection();
|
|
408
|
+
for (let row = 0; row < frameRows.length; row += 1) {
|
|
409
|
+
const painted = frameRows[row] ?? "";
|
|
410
|
+
const rowSelectionWidth = row < viewportHeight ? contentWidth : width;
|
|
411
|
+
const range = selectionRangeForLine(orderedSelection, row, this.#selectionRows.length, rowSelectionWidth);
|
|
412
|
+
const padded = row < viewportHeight || range !== null;
|
|
413
|
+
const base = cachedString(this.#baseRowCache, `${width}\u0000${contentWidth}\u0000${row < viewportHeight}\u0000${padded}\u0000${painted}`, cacheLimit, () => row < viewportHeight
|
|
414
|
+
? padViewportRowWithGutter(painted, contentWidth, width)
|
|
415
|
+
: padded ? padRowPreservingBackground(painted, width) : painted);
|
|
416
|
+
let rowRecomputed = row < viewportHeight && paintRecomputedRows.has(row) || !base.reused;
|
|
417
|
+
const rangeKey = range === null ? "-" : `${range.from}:${range.to}`;
|
|
418
|
+
const selected = range === null
|
|
419
|
+
? { value: base.value, reused: true }
|
|
420
|
+
: cachedString(this.#selectedRowCache, `${selectionPainterId}\u0000${rangeKey}\u0000${base.value}`, cacheLimit, () => theme.selection(base.value, range.from, range.to));
|
|
421
|
+
rowRecomputed ||= !selected.reused;
|
|
422
|
+
// Invariant: paint the rail after selection, inside its neutral gutter.
|
|
423
|
+
// Source and full-row selection end at the preceding content cell.
|
|
424
|
+
let railCell = "";
|
|
425
|
+
if (row < viewportHeight && presentation.visible && geometry !== null && row > 0) {
|
|
426
|
+
const trackRow = row - 1;
|
|
427
|
+
const thumb = isThumbRow(geometry, trackRow);
|
|
428
|
+
const glyph = thumb ? presentation.thumbGlyph : presentation.trackGlyph;
|
|
429
|
+
railCell = thumb ? theme.thumb(glyph, this.#railHovered || this.#railDragging) : theme.track(glyph);
|
|
430
|
+
}
|
|
431
|
+
const final = cachedString(this.#finalRowCache, `${width}\u0000${railCell}\u0000${selected.value}`, cacheLimit, () => railCell.length === 0 ? selected.value : overlaySpan(selected.value, width - 1, width, `${GUTTER_STYLE_RESET}${railCell}`));
|
|
432
|
+
rowRecomputed ||= !final.reused;
|
|
433
|
+
frameRows[row] = final.value;
|
|
434
|
+
(rowRecomputed ? recomputedRows : reusedRows).push(row + 1);
|
|
435
|
+
const state = `${width}\u0000${row}\u0000${painted}\u0000${rangeKey}\u0000${range === null ? "" : selectionPainterId}\u0000${railCell}`;
|
|
436
|
+
visibleStates.push(state);
|
|
437
|
+
if (this.#previousVisibleStates[row] !== state)
|
|
438
|
+
selectionDamagedRows.push(row + 1);
|
|
439
|
+
}
|
|
440
|
+
this.#previousVisibleStates = visibleStates;
|
|
429
441
|
const nextDocumentRange = {
|
|
430
442
|
start: this.#scrollTop,
|
|
431
443
|
end: Math.min(documentRows.length, this.#scrollTop + viewportHeight),
|
|
@@ -509,7 +521,7 @@ export class TranscriptViewport {
|
|
|
509
521
|
const boundedWidth = Math.max(1, width);
|
|
510
522
|
const boundedHeight = Math.max(0, height);
|
|
511
523
|
if (previous === null || previous.descriptor.width !== boundedWidth || previous.descriptor.height !== boundedHeight
|
|
512
|
-
|| previous.descriptor.selectionRevision !== this.#selectionRevision)
|
|
524
|
+
|| previous.descriptor.selectionRevision !== this.#selectionRevision || this.#selection !== undefined)
|
|
513
525
|
return null;
|
|
514
526
|
const dock = dockRows.length > boundedHeight ? dockRows.slice(-boundedHeight) : [...dockRows];
|
|
515
527
|
const viewportHeight = Math.max(0, boundedHeight - dock.length);
|
|
@@ -539,6 +551,7 @@ export class TranscriptViewport {
|
|
|
539
551
|
cause: "dock-input",
|
|
540
552
|
};
|
|
541
553
|
assertTranscriptViewportFrameDescriptor(descriptor);
|
|
554
|
+
this.#selectionRows = [...this.#selectionRows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight);
|
|
542
555
|
const frame = {
|
|
543
556
|
rows: [...previous.rows.slice(0, viewportHeight), ...dock].slice(0, boundedHeight),
|
|
544
557
|
contentWidth: previous.contentWidth,
|
|
@@ -568,47 +581,8 @@ export class TranscriptViewport {
|
|
|
568
581
|
}
|
|
569
582
|
#updateCopyableSelection() {
|
|
570
583
|
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;
|
|
584
|
+
this.#copyableSelection = selection !== undefined
|
|
585
|
+
&& textSelectionText(selection, this.#selectionRows, line => usefulTextLineContent(this.#selectionRows[line] ?? "")).length > 0;
|
|
612
586
|
}
|
|
613
587
|
}
|
|
614
588
|
function selectionRangeForLine(selection, documentLine, selectableRowCount, width) {
|
|
@@ -668,6 +642,12 @@ function governingPrompt(anchors, scrollTop) {
|
|
|
668
642
|
}
|
|
669
643
|
return result;
|
|
670
644
|
}
|
|
645
|
+
function padViewportRowWithGutter(line, contentWidth, frameWidth) {
|
|
646
|
+
const boundedContentWidth = Math.min(Math.max(1, contentWidth), frameWidth);
|
|
647
|
+
const content = padRowPreservingBackground(line, boundedContentWidth);
|
|
648
|
+
const gutterWidth = frameWidth - boundedContentWidth;
|
|
649
|
+
return gutterWidth === 0 ? content : `${content}${GUTTER_STYLE_RESET}${" ".repeat(gutterWidth)}`;
|
|
650
|
+
}
|
|
671
651
|
function padRowPreservingBackground(line, width) {
|
|
672
652
|
const shown = displayWidth(line) > width ? truncateToWidth(line, width) : line;
|
|
673
653
|
const padding = Math.max(0, width - displayWidth(shown));
|
|
@@ -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