@try-works/dsh-recursive-mode 0.1.15 → 0.1.17
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/lib/client/board.d.ts +3 -1
- package/lib/client/index.d.ts +0 -1
- package/lib/client/slots.d.ts +2 -10
- package/lib/client.js +18 -48
- package/package.json +1 -1
- package/src/client/board.tsx +17 -5
- package/src/client/index.ts +0 -1
- package/src/client/slots.ts +3 -36
- package/src/client/use-live.ts +5 -1
- package/lib/client/strip.d.ts +0 -8
- package/src/client/strip.tsx +0 -28
package/lib/client/board.d.ts
CHANGED
|
@@ -5,10 +5,12 @@ import type { BoardSelection } from './open-state.ts';
|
|
|
5
5
|
export declare function listRuns(projection: RecursiveProjection | undefined): RecursiveRunCard[];
|
|
6
6
|
export interface BoardProps {
|
|
7
7
|
snapshot: LiveProjectionValue | null;
|
|
8
|
+
/** The authoritative current-workspace path (synchronous). snapshot.root is async/stale. */
|
|
9
|
+
workspacePath?: string;
|
|
8
10
|
onOpenInspector?: (selection: BoardSelection) => void;
|
|
9
11
|
onClose?: () => void;
|
|
10
12
|
}
|
|
11
|
-
export declare function Board({ snapshot, onOpenInspector, onClose }: BoardProps): import("react").DetailedReactHTMLElement<{
|
|
13
|
+
export declare function Board({ snapshot, workspacePath, onOpenInspector, onClose }: BoardProps): import("react").DetailedReactHTMLElement<{
|
|
12
14
|
className: string;
|
|
13
15
|
'data-theme': import("./theme.ts").BoardTheme;
|
|
14
16
|
}, HTMLElement> | null;
|
package/lib/client/index.d.ts
CHANGED
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
import type { ClientContext } from './contract.ts';
|
|
15
15
|
export { Board, listRuns } from './board.tsx';
|
|
16
16
|
export { Inspector } from './inspector.tsx';
|
|
17
|
-
export { StatusStrip } from './strip.tsx';
|
|
18
17
|
export { RecursiveSettings } from './settings.tsx';
|
|
19
18
|
export { useLiveProjection } from './use-live.ts';
|
|
20
19
|
export type { LiveProjectionSnapshot } from './use-live.ts';
|
package/lib/client/slots.d.ts
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { type ReactNode } from 'react';
|
|
20
20
|
import type { ClientContext, SessionListStateLike, SnapshotSelectorHook, WorkspaceListStateLike } from './contract.ts';
|
|
21
|
-
import { isRecursivePreset
|
|
21
|
+
import { isRecursivePreset } from './contract.ts';
|
|
22
22
|
export type OverlayContent = 'hidden' | 'board' | 'inspector';
|
|
23
23
|
/** Gate: hidden unless open AND recursive preset; inspector when a run is selected. */
|
|
24
24
|
export declare function overlayContent(state: {
|
|
@@ -53,13 +53,5 @@ export declare function RecursiveBoardOverlay({ useSessions, useWorkspaces }: {
|
|
|
53
53
|
useSessions: SnapshotSelectorHook<SessionListStateLike>;
|
|
54
54
|
useWorkspaces?: SnapshotSelectorHook<WorkspaceListStateLike>;
|
|
55
55
|
}): ReactNode;
|
|
56
|
-
/**
|
|
57
|
-
* Strip gate: same preset gate, session-scoped sessionId; the strip renders
|
|
58
|
-
* nothing for a non-recursive session. Run 10: useSessions((s) => s) selector.
|
|
59
|
-
*/
|
|
60
|
-
export declare function RecursiveStripGate({ useSessions, sessionId }: {
|
|
61
|
-
useSessions: SnapshotSelectorHook<SessionListStateLike>;
|
|
62
|
-
sessionId?: string;
|
|
63
|
-
}): ReactNode;
|
|
64
56
|
/** Export the gate helpers for tests. */
|
|
65
|
-
export { isRecursivePreset
|
|
57
|
+
export { isRecursivePreset };
|
package/lib/client.js
CHANGED
|
@@ -345,17 +345,22 @@ window.__ModuleLoader__.load({
|
|
|
345
345
|
"aria-label": "Close"
|
|
346
346
|
}, "×");
|
|
347
347
|
}
|
|
348
|
+
/** Case/trailing-separator tolerant workspace-path equality (Windows host). */
|
|
349
|
+
function sameWorkspacePath(a, b) {
|
|
350
|
+
const norm = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
|
|
351
|
+
return norm(a) === norm(b);
|
|
352
|
+
}
|
|
348
353
|
function solidPill$1(kind) {
|
|
349
354
|
return (0, react.createElement)("span", {
|
|
350
355
|
className: "rec-pill",
|
|
351
356
|
"data-pill": kind
|
|
352
357
|
}, PILL_LABELS[kind]);
|
|
353
358
|
}
|
|
354
|
-
function Board({ snapshot, onOpenInspector, onClose }) {
|
|
359
|
+
function Board({ snapshot, workspacePath, onOpenInspector, onClose }) {
|
|
355
360
|
const { theme, toggle } = useBoardTheme();
|
|
356
361
|
if (snapshot === null || snapshot.root === null) return null;
|
|
357
|
-
const runs = listRuns(snapshot.projection);
|
|
358
|
-
const
|
|
362
|
+
const runs = workspacePath !== void 0 && workspacePath !== "" && !sameWorkspacePath(workspacePath, snapshot.root) ? [] : listRuns(snapshot.projection);
|
|
363
|
+
const headerPath = workspacePath ?? snapshot.root;
|
|
359
364
|
const open = (run) => () => onOpenInspector?.({
|
|
360
365
|
worktreeRoot: run.worktreeRoot,
|
|
361
366
|
runId: run.runId
|
|
@@ -364,14 +369,14 @@ window.__ModuleLoader__.load({
|
|
|
364
369
|
className: "rec-board",
|
|
365
370
|
"data-theme": theme,
|
|
366
371
|
"data-empty": true
|
|
367
|
-
}, (0, react.createElement)("header", { className: "rec-board-header" }, (0, react.createElement)("h2", { className: "rec-board-title" }, "Recursive runs"), (0, react.createElement)("span", { className: "rec-board-path" },
|
|
372
|
+
}, (0, react.createElement)("header", { className: "rec-board-header" }, (0, react.createElement)("h2", { className: "rec-board-title" }, "Recursive runs"), (0, react.createElement)("span", { className: "rec-board-path" }, headerPath), (0, react.createElement)("span", { className: "rec-board-count" }, "0 runs"), (0, react.createElement)(ThemeToggle, {
|
|
368
373
|
theme,
|
|
369
374
|
toggle
|
|
370
375
|
}), closeButton$1(onClose)), (0, react.createElement)("p", { className: "rec-board-empty" }, "No recursive runs in this workspace yet."));
|
|
371
376
|
return (0, react.createElement)("div", {
|
|
372
377
|
className: "rec-board",
|
|
373
378
|
"data-theme": theme
|
|
374
|
-
}, (0, react.createElement)("header", { className: "rec-board-header" }, (0, react.createElement)("h2", { className: "rec-board-title" }, "Recursive runs"), (0, react.createElement)("span", { className: "rec-board-path" },
|
|
379
|
+
}, (0, react.createElement)("header", { className: "rec-board-header" }, (0, react.createElement)("h2", { className: "rec-board-title" }, "Recursive runs"), (0, react.createElement)("span", { className: "rec-board-path" }, headerPath), (0, react.createElement)("span", { className: "rec-board-count" }, runs.length + " runs"), (0, react.createElement)(ThemeToggle, {
|
|
375
380
|
theme,
|
|
376
381
|
toggle
|
|
377
382
|
}), closeButton$1(onClose)), (0, react.createElement)("div", { className: "rec-columns" }, KANBAN_LANES.map((lane) => {
|
|
@@ -447,21 +452,6 @@ window.__ModuleLoader__.load({
|
|
|
447
452
|
}, (0, react.createElement)("span", { className: "rec-phase-id" }, row.phase), (0, react.createElement)("span", { className: "rec-phase-name" }, row.fileName ?? "—"), solidPill(phaseStatusPill(row.status)), row.lockHash !== void 0 && (0, react.createElement)("code", { className: "rec-lockhash" }, "#" + row.lockHash.slice(0, 8)))), facts.gateBlocked && (0, react.createElement)("div", { className: "rec-detail-section" }, (0, react.createElement)("h3", {}, "Gate"), (0, react.createElement)("p", { className: "rec-detail-text" }, "Blocked (" + (facts.gateKind ?? "") + ")"))), theme, toggle);
|
|
448
453
|
}
|
|
449
454
|
//#endregion
|
|
450
|
-
//#region src/client/strip.tsx
|
|
451
|
-
/**
|
|
452
|
-
* Status strip (SP2 R1): a thin glance at the active run's phase, gate state,
|
|
453
|
-
* and tamper badge, docked at conversation.input.dock. Renders NOTHING when no
|
|
454
|
-
* run is current or the snapshot is null (the preset gate lives in slots.ts).
|
|
455
|
-
*/
|
|
456
|
-
function StatusStrip({ snapshot }) {
|
|
457
|
-
if (snapshot === null || snapshot.root === null) return null;
|
|
458
|
-
const runs = listRuns(snapshot.projection);
|
|
459
|
-
if (runs.length === 0) return null;
|
|
460
|
-
const active = runs[runs.length - 1];
|
|
461
|
-
const facts = cardFacts(active);
|
|
462
|
-
return (0, react.createElement)("div", { className: "rec-strip" }, (0, react.createElement)("span", { className: "rec-strip-run" }, active.runId), (0, react.createElement)("span", { className: "rec-badge" }, facts.currentPhase ?? "no phases"), facts.gateBlocked && (0, react.createElement)("span", { className: "rec-badge rec-badge-gate" }, "gate blocked"), facts.tampered && (0, react.createElement)("span", { className: "rec-badge rec-badge-tamper" }, "tampered"));
|
|
463
|
-
}
|
|
464
|
-
//#endregion
|
|
465
455
|
//#region src/client/settings.tsx
|
|
466
456
|
/**
|
|
467
457
|
* Recursive settings page (Phase D R8, §11.8): a settings.section list entry
|
|
@@ -572,7 +562,11 @@ window.__ModuleLoader__.load({
|
|
|
572
562
|
function useLiveProjection(scope) {
|
|
573
563
|
const [state, setState] = (0, react.useState)(null);
|
|
574
564
|
(0, react.useEffect)(() => {
|
|
575
|
-
if (isEmpty(scope))
|
|
565
|
+
if (isEmpty(scope)) {
|
|
566
|
+
setState(null);
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
setState(null);
|
|
576
570
|
let disposed = false;
|
|
577
571
|
const apply = (frame) => {
|
|
578
572
|
if (!disposed) setState(frame);
|
|
@@ -1289,18 +1283,6 @@ window.__ModuleLoader__.load({
|
|
|
1289
1283
|
useWorkspaces: props?.useWorkspaces
|
|
1290
1284
|
});
|
|
1291
1285
|
})));
|
|
1292
|
-
disposers.push(ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
1293
|
-
name: "conversation.input.dock",
|
|
1294
|
-
id: "recursive-strip",
|
|
1295
|
-
order: 20
|
|
1296
|
-
}, (props) => {
|
|
1297
|
-
const useSessions = props?.useSessions;
|
|
1298
|
-
if (useSessions === void 0) return null;
|
|
1299
|
-
return (0, react.createElement)(RecursiveStripGate, {
|
|
1300
|
-
useSessions,
|
|
1301
|
-
sessionId: props?.sessionId
|
|
1302
|
-
});
|
|
1303
|
-
})));
|
|
1304
1286
|
disposers.push(ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
1305
1287
|
name: "settings.section",
|
|
1306
1288
|
id: "recursive",
|
|
@@ -1326,7 +1308,8 @@ window.__ModuleLoader__.load({
|
|
|
1326
1308
|
recentWorkspaceId: void 0
|
|
1327
1309
|
};
|
|
1328
1310
|
const content = overlayContent(board, sessions);
|
|
1329
|
-
const
|
|
1311
|
+
const wsPath = currentWorkspacePath(workspaces, sessions);
|
|
1312
|
+
const snapshot = useLiveProjection({ cwd: wsPath });
|
|
1330
1313
|
if (content === "hidden") return null;
|
|
1331
1314
|
if (content === "inspector" && board.selection !== null) return (0, react.createElement)(Inspector, {
|
|
1332
1315
|
runId: board.selection.runId,
|
|
@@ -1337,23 +1320,11 @@ window.__ModuleLoader__.load({
|
|
|
1337
1320
|
});
|
|
1338
1321
|
return (0, react.createElement)(Board, {
|
|
1339
1322
|
snapshot,
|
|
1323
|
+
workspacePath: wsPath,
|
|
1340
1324
|
onOpenInspector: (sel) => boardState.openInspector(sel),
|
|
1341
1325
|
onClose: () => boardState.close()
|
|
1342
1326
|
});
|
|
1343
1327
|
}
|
|
1344
|
-
/**
|
|
1345
|
-
* Strip gate: same preset gate, session-scoped sessionId; the strip renders
|
|
1346
|
-
* nothing for a non-recursive session. Run 10: useSessions((s) => s) selector.
|
|
1347
|
-
*/
|
|
1348
|
-
function RecursiveStripGate({ useSessions, sessionId }) {
|
|
1349
|
-
const list = useRecursiveSessions(useSessions);
|
|
1350
|
-
const snapshot = useLiveProjection({
|
|
1351
|
-
sessionId,
|
|
1352
|
-
cwd: currentSessionCwd(list)
|
|
1353
|
-
});
|
|
1354
|
-
if (!isRecursivePreset(list)) return null;
|
|
1355
|
-
return (0, react.createElement)(StatusStrip, { snapshot });
|
|
1356
|
-
}
|
|
1357
1328
|
//#endregion
|
|
1358
1329
|
//#region src/client/apply-guard.ts
|
|
1359
1330
|
/** Claims the client apply slot. Returns true when this call won the slot. */
|
|
@@ -1385,7 +1356,6 @@ window.__ModuleLoader__.load({
|
|
|
1385
1356
|
exports.Board = Board;
|
|
1386
1357
|
exports.Inspector = Inspector;
|
|
1387
1358
|
exports.RecursiveSettings = RecursiveSettings;
|
|
1388
|
-
exports.StatusStrip = StatusStrip;
|
|
1389
1359
|
exports.apply = apply;
|
|
1390
1360
|
exports.currentSessionCwd = currentSessionCwd;
|
|
1391
1361
|
exports.currentWorkspacePath = currentWorkspacePath;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@try-works/dsh-recursive-mode",
|
|
3
3
|
"description": "recursive-mode workflow as a DeepSeek Harness bundle: RecursiveRuntime service + recursive_status tool",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.17",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
package/src/client/board.tsx
CHANGED
|
@@ -28,6 +28,8 @@ export function listRuns(projection: RecursiveProjection | undefined): Recursive
|
|
|
28
28
|
|
|
29
29
|
export interface BoardProps {
|
|
30
30
|
snapshot: LiveProjectionValue | null
|
|
31
|
+
/** The authoritative current-workspace path (synchronous). snapshot.root is async/stale. */
|
|
32
|
+
workspacePath?: string
|
|
31
33
|
onOpenInspector?: (selection: BoardSelection) => void
|
|
32
34
|
onClose?: () => void
|
|
33
35
|
}
|
|
@@ -37,21 +39,31 @@ function closeButton(onClose: (() => void) | undefined) {
|
|
|
37
39
|
return createElement('button', { type: 'button', className: 'rec-close', onClick: onClose, title: 'Close', 'aria-label': 'Close' }, '×')
|
|
38
40
|
}
|
|
39
41
|
|
|
42
|
+
/** Case/trailing-separator tolerant workspace-path equality (Windows host). */
|
|
43
|
+
function sameWorkspacePath(a: string, b: string): boolean {
|
|
44
|
+
const norm = (p: string) => p.replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase()
|
|
45
|
+
return norm(a) === norm(b)
|
|
46
|
+
}
|
|
47
|
+
|
|
40
48
|
function solidPill(kind: ReturnType<typeof cardPill>) {
|
|
41
49
|
return createElement('span', { className: 'rec-pill', 'data-pill': kind }, PILL_LABELS[kind])
|
|
42
50
|
}
|
|
43
51
|
|
|
44
|
-
export function Board({ snapshot, onOpenInspector, onClose }: BoardProps) {
|
|
52
|
+
export function Board({ snapshot, workspacePath, onOpenInspector, onClose }: BoardProps) {
|
|
45
53
|
const { theme, toggle } = useBoardTheme()
|
|
46
54
|
if (snapshot === null || snapshot.root === null) return null
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
// Run 16: when the synchronous workspace path differs from the async snapshot
|
|
56
|
+
// root, the projection is stale (from the previously-viewed workspace) — suppress
|
|
57
|
+
// its run cards so a workspace switch can never flash another workspace's runs.
|
|
58
|
+
const stale = workspacePath !== undefined && workspacePath !== '' && !sameWorkspacePath(workspacePath, snapshot.root)
|
|
59
|
+
const runs = stale ? [] : listRuns(snapshot.projection)
|
|
60
|
+
const headerPath = workspacePath ?? snapshot.root
|
|
49
61
|
const open = (run: RecursiveRunCard) => () => onOpenInspector?.({ worktreeRoot: run.worktreeRoot, runId: run.runId })
|
|
50
62
|
if (runs.length === 0) {
|
|
51
63
|
return createElement('div', { className: 'rec-board', 'data-theme': theme, 'data-empty': true },
|
|
52
64
|
createElement('header', { className: 'rec-board-header' },
|
|
53
65
|
createElement('h2', { className: 'rec-board-title' }, 'Recursive runs'),
|
|
54
|
-
createElement('span', { className: 'rec-board-path' },
|
|
66
|
+
createElement('span', { className: 'rec-board-path' }, headerPath),
|
|
55
67
|
createElement('span', { className: 'rec-board-count' }, '0 runs'),
|
|
56
68
|
createElement(ThemeToggle, { theme, toggle }),
|
|
57
69
|
closeButton(onClose),
|
|
@@ -62,7 +74,7 @@ export function Board({ snapshot, onOpenInspector, onClose }: BoardProps) {
|
|
|
62
74
|
return createElement('div', { className: 'rec-board', 'data-theme': theme },
|
|
63
75
|
createElement('header', { className: 'rec-board-header' },
|
|
64
76
|
createElement('h2', { className: 'rec-board-title' }, 'Recursive runs'),
|
|
65
|
-
createElement('span', { className: 'rec-board-path' },
|
|
77
|
+
createElement('span', { className: 'rec-board-path' }, headerPath),
|
|
66
78
|
createElement('span', { className: 'rec-board-count' }, runs.length + ' runs'),
|
|
67
79
|
createElement(ThemeToggle, { theme, toggle }),
|
|
68
80
|
closeButton(onClose),
|
package/src/client/index.ts
CHANGED
|
@@ -17,7 +17,6 @@ import { claimClientApply, releaseClientApply } from './apply-guard.ts'
|
|
|
17
17
|
|
|
18
18
|
export { Board, listRuns } from './board.tsx'
|
|
19
19
|
export { Inspector } from './inspector.tsx'
|
|
20
|
-
export { StatusStrip } from './strip.tsx'
|
|
21
20
|
export { RecursiveSettings } from './settings.tsx'
|
|
22
21
|
export { useLiveProjection } from './use-live.ts'
|
|
23
22
|
export type { LiveProjectionSnapshot } from './use-live.ts'
|
package/src/client/slots.ts
CHANGED
|
@@ -18,10 +18,9 @@
|
|
|
18
18
|
*/
|
|
19
19
|
import { createElement, type ReactNode } from 'react'
|
|
20
20
|
import type { ClientContext, SessionListStateLike, SnapshotSelectorHook, WorkspaceListStateLike } from './contract.ts'
|
|
21
|
-
import { isRecursivePreset,
|
|
21
|
+
import { isRecursivePreset, currentWorkspacePath } from './contract.ts'
|
|
22
22
|
import { Board } from './board.tsx'
|
|
23
23
|
import { Inspector } from './inspector.tsx'
|
|
24
|
-
import { StatusStrip } from './strip.tsx'
|
|
25
24
|
import { RecursiveSettings } from './settings.tsx'
|
|
26
25
|
import { useLiveProjection } from './use-live.ts'
|
|
27
26
|
import { boardState, useBoardState } from './open-state.ts'
|
|
@@ -33,13 +32,6 @@ interface RootSlotProps {
|
|
|
33
32
|
useWorkspaces?: SnapshotSelectorHook<WorkspaceListStateLike>
|
|
34
33
|
}
|
|
35
34
|
|
|
36
|
-
/** Structural standard-prop face for the session scope (sessionId + useSessions). */
|
|
37
|
-
interface SessionSlotProps {
|
|
38
|
-
sessionId?: string
|
|
39
|
-
useSessions?: SnapshotSelectorHook<SessionListStateLike>
|
|
40
|
-
useProjection?: unknown
|
|
41
|
-
}
|
|
42
|
-
|
|
43
35
|
export type OverlayContent = 'hidden' | 'board' | 'inspector'
|
|
44
36
|
|
|
45
37
|
/** Gate: hidden unless open AND recursive preset; inspector when a run is selected. */
|
|
@@ -103,17 +95,6 @@ export function registerSlots(ctx: ClientContext): () => void {
|
|
|
103
95
|
return createElement(RecursiveBoardOverlay, { useSessions, useWorkspaces: props?.useWorkspaces })
|
|
104
96
|
})))
|
|
105
97
|
|
|
106
|
-
// Status strip in the composer input dock (session scope) — gated the same way.
|
|
107
|
-
disposers.push(ctx.slots.inject('conversation.input.dock', () => ctx.slots.register({
|
|
108
|
-
name: 'conversation.input.dock',
|
|
109
|
-
id: 'recursive-strip',
|
|
110
|
-
order: 20,
|
|
111
|
-
}, (props: SessionSlotProps) => {
|
|
112
|
-
const useSessions = props?.useSessions
|
|
113
|
-
if (useSessions === undefined) return null
|
|
114
|
-
return createElement(RecursiveStripGate, { useSessions, sessionId: props?.sessionId })
|
|
115
|
-
})))
|
|
116
|
-
|
|
117
98
|
// Settings section (root scope, always present; no gate — configuration is always available).
|
|
118
99
|
disposers.push(ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
119
100
|
name: 'settings.section',
|
|
@@ -157,22 +138,8 @@ export function RecursiveBoardOverlay({ useSessions, useWorkspaces }: { useSessi
|
|
|
157
138
|
onClose: () => boardState.close(),
|
|
158
139
|
})
|
|
159
140
|
}
|
|
160
|
-
return createElement(Board, { snapshot, onOpenInspector: (sel) => boardState.openInspector(sel), onClose: () => boardState.close() })
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Strip gate: same preset gate, session-scoped sessionId; the strip renders
|
|
165
|
-
* nothing for a non-recursive session. Run 10: useSessions((s) => s) selector.
|
|
166
|
-
*/
|
|
167
|
-
export function RecursiveStripGate({ useSessions, sessionId }: { useSessions: SnapshotSelectorHook<SessionListStateLike>; sessionId?: string }): ReactNode {
|
|
168
|
-
const list = useRecursiveSessions(useSessions)
|
|
169
|
-
const cwd = currentSessionCwd(list)
|
|
170
|
-
const scope = { sessionId, cwd }
|
|
171
|
-
// Run 13: ALWAYS call useLiveProjection BEFORE the preset early return.
|
|
172
|
-
const snapshot = useLiveProjection(scope)
|
|
173
|
-
if (!isRecursivePreset(list)) return null
|
|
174
|
-
return createElement(StatusStrip, { snapshot })
|
|
141
|
+
return createElement(Board, { snapshot, workspacePath: wsPath, onOpenInspector: (sel) => boardState.openInspector(sel), onClose: () => boardState.close() })
|
|
175
142
|
}
|
|
176
143
|
|
|
177
144
|
/** Export the gate helpers for tests. */
|
|
178
|
-
export { isRecursivePreset
|
|
145
|
+
export { isRecursivePreset }
|
package/src/client/use-live.ts
CHANGED
|
@@ -33,7 +33,11 @@ export function useLiveProjection(scope: LiveScope): LiveProjectionSnapshot {
|
|
|
33
33
|
|
|
34
34
|
useEffect(() => {
|
|
35
35
|
// Empty scope = no resolvable session row yet — render nothing, never fetch.
|
|
36
|
-
if (isEmpty(scope)) return
|
|
36
|
+
if (isEmpty(scope)) { setState(null); return }
|
|
37
|
+
// 0.1.16 (LIVE BUG 3): RESET the snapshot synchronously on scope change so
|
|
38
|
+
// the board never renders the PREVIOUS workspace's {root, projection} during
|
|
39
|
+
// a switch. The fresh fetch below re-populates state only when it resolves.
|
|
40
|
+
setState(null)
|
|
37
41
|
let disposed = false
|
|
38
42
|
const apply = (frame: LiveRecursiveFrame) => { if (!disposed) setState(frame) }
|
|
39
43
|
|
package/lib/client/strip.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { LiveProjectionValue } from './contract.ts';
|
|
2
|
-
export interface StripProps {
|
|
3
|
-
/** The live host-route snapshot (null = not loaded / no recursive root). */
|
|
4
|
-
snapshot: LiveProjectionValue | null;
|
|
5
|
-
}
|
|
6
|
-
export declare function StatusStrip({ snapshot }: StripProps): import("react").DetailedReactHTMLElement<{
|
|
7
|
-
className: string;
|
|
8
|
-
}, HTMLElement> | null;
|
package/src/client/strip.tsx
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Status strip (SP2 R1): a thin glance at the active run's phase, gate state,
|
|
3
|
-
* and tamper badge, docked at conversation.input.dock. Renders NOTHING when no
|
|
4
|
-
* run is current or the snapshot is null (the preset gate lives in slots.ts).
|
|
5
|
-
*/
|
|
6
|
-
import { createElement } from 'react'
|
|
7
|
-
import { cardFacts } from './derive.ts'
|
|
8
|
-
import { listRuns } from './board.tsx'
|
|
9
|
-
import type { LiveProjectionValue } from './contract.ts'
|
|
10
|
-
|
|
11
|
-
export interface StripProps {
|
|
12
|
-
/** The live host-route snapshot (null = not loaded / no recursive root). */
|
|
13
|
-
snapshot: LiveProjectionValue | null
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export function StatusStrip({ snapshot }: StripProps) {
|
|
17
|
-
if (snapshot === null || snapshot.root === null) return null
|
|
18
|
-
const runs = listRuns(snapshot.projection)
|
|
19
|
-
if (runs.length === 0) return null
|
|
20
|
-
const active = runs[runs.length - 1]
|
|
21
|
-
const facts = cardFacts(active)
|
|
22
|
-
return createElement('div', { className: 'rec-strip' },
|
|
23
|
-
createElement('span', { className: 'rec-strip-run' }, active.runId),
|
|
24
|
-
createElement('span', { className: 'rec-badge' }, facts.currentPhase ?? 'no phases'),
|
|
25
|
-
facts.gateBlocked && createElement('span', { className: 'rec-badge rec-badge-gate' }, 'gate blocked'),
|
|
26
|
-
facts.tampered && createElement('span', { className: 'rec-badge rec-badge-tamper' }, 'tampered'),
|
|
27
|
-
)
|
|
28
|
-
}
|