@try-works/dsh-recursive-mode 0.1.9 → 0.1.11
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/contract.d.ts +29 -0
- package/lib/client/index.d.ts +2 -2
- package/lib/client/slots.d.ts +20 -1
- package/lib/client.js +39 -10
- package/package.json +6 -4
- package/src/client/contract.ts +48 -0
- package/src/client/index.ts +2 -2
- package/src/client/slots.ts +21 -12
package/lib/client/contract.d.ts
CHANGED
|
@@ -85,3 +85,32 @@ export interface LiveProjectionValue {
|
|
|
85
85
|
export declare function isRecursivePreset(state: SessionListStateLike): boolean;
|
|
86
86
|
/** The current session's cwd (the client passes it as a fallback hint; the host resolves the root). */
|
|
87
87
|
export declare function currentSessionCwd(state: SessionListStateLike): string;
|
|
88
|
+
/**
|
|
89
|
+
* A workspace row — the structural mirror of the REAL WorkspaceView the host
|
|
90
|
+
* injects. We consume only workspaceId/path/title/sessionIds.
|
|
91
|
+
*/
|
|
92
|
+
export interface WorkspaceViewLike {
|
|
93
|
+
workspaceId: string;
|
|
94
|
+
path: string;
|
|
95
|
+
title: string;
|
|
96
|
+
sessionIds: string[];
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The workspace-list snapshot — the structural subset of the REAL
|
|
100
|
+
* WorkspaceListState we consume (items + recentWorkspaceId). The real state
|
|
101
|
+
* also carries archivedSessionIds/state/phase/error/baselinesReady; those are
|
|
102
|
+
* irrelevant here and the real value satisfies this subset.
|
|
103
|
+
*/
|
|
104
|
+
export interface WorkspaceListStateLike {
|
|
105
|
+
items: readonly WorkspaceViewLike[];
|
|
106
|
+
recentWorkspaceId?: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Resolve the CURRENT workspace path for a workspace-scoped board (run 14):
|
|
110
|
+
* the WORKSPACE owns the .recursive/ run-layer, not the session. Resolution:
|
|
111
|
+
* 1) recentWorkspaceId's item path (the workspace the user is viewing), else
|
|
112
|
+
* 2) the workspace whose sessionIds contains sessionList.current, else
|
|
113
|
+
* 3) the workspace whose path === currentSessionCwd(sessionList), else
|
|
114
|
+
* 4) '' (empty -> the route returns null root -> board renders nothing).
|
|
115
|
+
*/
|
|
116
|
+
export declare function currentWorkspacePath(list: WorkspaceListStateLike, sessionList: SessionListStateLike): string;
|
package/lib/client/index.d.ts
CHANGED
|
@@ -20,8 +20,8 @@ export { useLiveProjection } from './use-live.ts';
|
|
|
20
20
|
export type { LiveProjectionSnapshot } from './use-live.ts';
|
|
21
21
|
export { fetchLiveState, subscribeLiveEvents } from './host-api.ts';
|
|
22
22
|
export type { LiveRecursiveState, LiveRecursiveFrame, LiveScope } from './host-api.ts';
|
|
23
|
-
export { isRecursivePreset, currentSessionCwd } from './contract.ts';
|
|
24
|
-
export type { SessionSummaryRow, SessionListStateLike, SnapshotSelectorHook, ClientContext } from './contract.ts';
|
|
23
|
+
export { isRecursivePreset, currentSessionCwd, currentWorkspacePath } from './contract.ts';
|
|
24
|
+
export type { SessionSummaryRow, SessionListStateLike, SnapshotSelectorHook, WorkspaceViewLike, WorkspaceListStateLike, ClientContext } from './contract.ts';
|
|
25
25
|
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
26
26
|
export declare const inject: string[];
|
|
27
27
|
/** Browser-half plugin entry (R4 + SP2 R1). */
|
package/lib/client/slots.d.ts
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* READ-ONLY (R9): the client only GETs the live host route; no mutation.
|
|
18
18
|
*/
|
|
19
19
|
import { type ReactNode } from 'react';
|
|
20
|
-
import type { ClientContext, SessionListStateLike, SnapshotSelectorHook } from './contract.ts';
|
|
20
|
+
import type { ClientContext, SessionListStateLike, SnapshotSelectorHook, WorkspaceListStateLike } from './contract.ts';
|
|
21
21
|
import { isRecursivePreset, currentSessionCwd } 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. */
|
|
@@ -42,5 +42,24 @@ export declare function RecursiveLauncherGate({ useSessions }: {
|
|
|
42
42
|
useSessions: SnapshotSelectorHook<SessionListStateLike>;
|
|
43
43
|
}): ReactNode;
|
|
44
44
|
export declare function registerSlots(ctx: ClientContext): () => void;
|
|
45
|
+
/**
|
|
46
|
+
* Board overlay: subscribes to the shared board store, gates on open + recursive
|
|
47
|
+
* preset, and swaps board <-> inspector (run 08 R1/R3).
|
|
48
|
+
*
|
|
49
|
+
* Run 10: consumes the full SessionListStateLike through the identity selector
|
|
50
|
+
* (useSessions((s) => s)); no fake {list:{getSnapshot}} wrapper.
|
|
51
|
+
*/
|
|
52
|
+
export declare function RecursiveBoardOverlay({ useSessions, useWorkspaces }: {
|
|
53
|
+
useSessions: SnapshotSelectorHook<SessionListStateLike>;
|
|
54
|
+
useWorkspaces?: SnapshotSelectorHook<WorkspaceListStateLike>;
|
|
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;
|
|
45
64
|
/** Export the gate helpers for tests. */
|
|
46
65
|
export { isRecursivePreset, currentSessionCwd };
|
package/lib/client.js
CHANGED
|
@@ -20,6 +20,31 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
function currentSessionCwd(state) {
|
|
21
21
|
return state.current === void 0 ? "" : state.byId[state.current]?.cwd ?? "";
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Resolve the CURRENT workspace path for a workspace-scoped board (run 14):
|
|
25
|
+
* the WORKSPACE owns the .recursive/ run-layer, not the session. Resolution:
|
|
26
|
+
* 1) recentWorkspaceId's item path (the workspace the user is viewing), else
|
|
27
|
+
* 2) the workspace whose sessionIds contains sessionList.current, else
|
|
28
|
+
* 3) the workspace whose path === currentSessionCwd(sessionList), else
|
|
29
|
+
* 4) '' (empty -> the route returns null root -> board renders nothing).
|
|
30
|
+
*/
|
|
31
|
+
function currentWorkspacePath(list, sessionList) {
|
|
32
|
+
if (list.recentWorkspaceId !== void 0) {
|
|
33
|
+
const recent = list.items.find((w) => w.workspaceId === list.recentWorkspaceId);
|
|
34
|
+
if (recent !== void 0) return recent.path;
|
|
35
|
+
}
|
|
36
|
+
const current = sessionList.current;
|
|
37
|
+
if (current !== void 0) {
|
|
38
|
+
const bySession = list.items.find((w) => w.sessionIds.includes(current));
|
|
39
|
+
if (bySession !== void 0) return bySession.path;
|
|
40
|
+
}
|
|
41
|
+
const cwd = currentSessionCwd(sessionList);
|
|
42
|
+
if (cwd !== "") {
|
|
43
|
+
const byCwd = list.items.find((w) => w.path === cwd);
|
|
44
|
+
if (byCwd !== void 0) return byCwd.path;
|
|
45
|
+
}
|
|
46
|
+
return "";
|
|
47
|
+
}
|
|
23
48
|
//#endregion
|
|
24
49
|
//#region src/client/derive.ts
|
|
25
50
|
const KANBAN_LANES = [
|
|
@@ -809,7 +834,10 @@ window.__ModuleLoader__.load({
|
|
|
809
834
|
}, (props) => {
|
|
810
835
|
const useSessions = props?.useSessions;
|
|
811
836
|
if (useSessions === void 0) return null;
|
|
812
|
-
return (0, react.createElement)(RecursiveBoardOverlay, {
|
|
837
|
+
return (0, react.createElement)(RecursiveBoardOverlay, {
|
|
838
|
+
useSessions,
|
|
839
|
+
useWorkspaces: props?.useWorkspaces
|
|
840
|
+
});
|
|
813
841
|
})));
|
|
814
842
|
disposers.push(ctx.slots.inject("conversation.input.dock", () => ctx.slots.register({
|
|
815
843
|
name: "conversation.input.dock",
|
|
@@ -840,16 +868,16 @@ window.__ModuleLoader__.load({
|
|
|
840
868
|
* Run 10: consumes the full SessionListStateLike through the identity selector
|
|
841
869
|
* (useSessions((s) => s)); no fake {list:{getSnapshot}} wrapper.
|
|
842
870
|
*/
|
|
843
|
-
function RecursiveBoardOverlay({ useSessions }) {
|
|
871
|
+
function RecursiveBoardOverlay({ useSessions, useWorkspaces }) {
|
|
844
872
|
const board = useBoardState();
|
|
845
|
-
const
|
|
846
|
-
const
|
|
873
|
+
const sessions = useRecursiveSessions(useSessions);
|
|
874
|
+
const workspaces = useWorkspaces !== void 0 ? useWorkspaces((s) => s) : {
|
|
875
|
+
items: [],
|
|
876
|
+
recentWorkspaceId: void 0
|
|
877
|
+
};
|
|
878
|
+
const content = overlayContent(board, sessions);
|
|
879
|
+
const snapshot = useLiveProjection({ cwd: currentWorkspacePath(workspaces, sessions) });
|
|
847
880
|
if (content === "hidden") return null;
|
|
848
|
-
const cwd = currentSessionCwd(list);
|
|
849
|
-
const snapshot = useLiveProjection({
|
|
850
|
-
sessionId: list.current,
|
|
851
|
-
cwd
|
|
852
|
-
});
|
|
853
881
|
if (content === "inspector" && board.selection !== null) return (0, react.createElement)(Inspector, {
|
|
854
882
|
runId: board.selection.runId,
|
|
855
883
|
worktreeRoot: board.selection.worktreeRoot,
|
|
@@ -869,11 +897,11 @@ window.__ModuleLoader__.load({
|
|
|
869
897
|
*/
|
|
870
898
|
function RecursiveStripGate({ useSessions, sessionId }) {
|
|
871
899
|
const list = useRecursiveSessions(useSessions);
|
|
872
|
-
if (!isRecursivePreset(list)) return null;
|
|
873
900
|
const snapshot = useLiveProjection({
|
|
874
901
|
sessionId,
|
|
875
902
|
cwd: currentSessionCwd(list)
|
|
876
903
|
});
|
|
904
|
+
if (!isRecursivePreset(list)) return null;
|
|
877
905
|
return (0, react.createElement)(StatusStrip, { snapshot });
|
|
878
906
|
}
|
|
879
907
|
//#endregion
|
|
@@ -910,6 +938,7 @@ window.__ModuleLoader__.load({
|
|
|
910
938
|
exports.StatusStrip = StatusStrip;
|
|
911
939
|
exports.apply = apply;
|
|
912
940
|
exports.currentSessionCwd = currentSessionCwd;
|
|
941
|
+
exports.currentWorkspacePath = currentWorkspacePath;
|
|
913
942
|
exports.fetchLiveState = fetchLiveState;
|
|
914
943
|
exports.inject = inject;
|
|
915
944
|
exports.isRecursivePreset = isRecursivePreset;
|
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.11",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "lib/index.d.ts",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
47
47
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
48
|
-
"@deepseek-ai/dsh-tools": "0.1.0-rc.5",
|
|
49
|
-
"@deepseek-ai/dsh-system-prompt": "0.1.0-rc.5",
|
|
50
|
-
"@deepseek-ai/dsh-session-projection": "0.1.0-rc.5",
|
|
51
48
|
"@deepseek-ai/dsh-session": "0.1.0-rc.5",
|
|
49
|
+
"@deepseek-ai/dsh-session-projection": "0.1.0-rc.5",
|
|
50
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.0-rc.5",
|
|
51
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.5",
|
|
52
52
|
"react": "^18.2.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
@@ -71,7 +71,9 @@
|
|
|
71
71
|
"@types/node": "^20.0.0",
|
|
72
72
|
"@types/react": "^18.2.0",
|
|
73
73
|
"@types/react-dom": "^18.2.0",
|
|
74
|
+
"@types/react-test-renderer": "18.3.0",
|
|
74
75
|
"react": "^18.2.0",
|
|
76
|
+
"react-test-renderer": "18.3.1",
|
|
75
77
|
"tsdown": "^0.22.2",
|
|
76
78
|
"tsx": "^4.19.0",
|
|
77
79
|
"typescript": "^5.6.0",
|
package/src/client/contract.ts
CHANGED
|
@@ -101,3 +101,51 @@ export function isRecursivePreset(state: SessionListStateLike): boolean {
|
|
|
101
101
|
export function currentSessionCwd(state: SessionListStateLike): string {
|
|
102
102
|
return state.current === undefined ? '' : (state.byId[state.current]?.cwd ?? '')
|
|
103
103
|
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* A workspace row — the structural mirror of the REAL WorkspaceView the host
|
|
107
|
+
* injects. We consume only workspaceId/path/title/sessionIds.
|
|
108
|
+
*/
|
|
109
|
+
export interface WorkspaceViewLike {
|
|
110
|
+
workspaceId: string
|
|
111
|
+
path: string
|
|
112
|
+
title: string
|
|
113
|
+
sessionIds: string[]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* The workspace-list snapshot — the structural subset of the REAL
|
|
118
|
+
* WorkspaceListState we consume (items + recentWorkspaceId). The real state
|
|
119
|
+
* also carries archivedSessionIds/state/phase/error/baselinesReady; those are
|
|
120
|
+
* irrelevant here and the real value satisfies this subset.
|
|
121
|
+
*/
|
|
122
|
+
export interface WorkspaceListStateLike {
|
|
123
|
+
items: readonly WorkspaceViewLike[]
|
|
124
|
+
recentWorkspaceId?: string
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Resolve the CURRENT workspace path for a workspace-scoped board (run 14):
|
|
129
|
+
* the WORKSPACE owns the .recursive/ run-layer, not the session. Resolution:
|
|
130
|
+
* 1) recentWorkspaceId's item path (the workspace the user is viewing), else
|
|
131
|
+
* 2) the workspace whose sessionIds contains sessionList.current, else
|
|
132
|
+
* 3) the workspace whose path === currentSessionCwd(sessionList), else
|
|
133
|
+
* 4) '' (empty -> the route returns null root -> board renders nothing).
|
|
134
|
+
*/
|
|
135
|
+
export function currentWorkspacePath(list: WorkspaceListStateLike, sessionList: SessionListStateLike): string {
|
|
136
|
+
if (list.recentWorkspaceId !== undefined) {
|
|
137
|
+
const recent = list.items.find((w) => w.workspaceId === list.recentWorkspaceId)
|
|
138
|
+
if (recent !== undefined) return recent.path
|
|
139
|
+
}
|
|
140
|
+
const current = sessionList.current
|
|
141
|
+
if (current !== undefined) {
|
|
142
|
+
const bySession = list.items.find((w) => w.sessionIds.includes(current))
|
|
143
|
+
if (bySession !== undefined) return bySession.path
|
|
144
|
+
}
|
|
145
|
+
const cwd = currentSessionCwd(sessionList)
|
|
146
|
+
if (cwd !== '') {
|
|
147
|
+
const byCwd = list.items.find((w) => w.path === cwd)
|
|
148
|
+
if (byCwd !== undefined) return byCwd.path
|
|
149
|
+
}
|
|
150
|
+
return ''
|
|
151
|
+
}
|
package/src/client/index.ts
CHANGED
|
@@ -23,8 +23,8 @@ export { useLiveProjection } from './use-live.ts'
|
|
|
23
23
|
export type { LiveProjectionSnapshot } from './use-live.ts'
|
|
24
24
|
export { fetchLiveState, subscribeLiveEvents } from './host-api.ts'
|
|
25
25
|
export type { LiveRecursiveState, LiveRecursiveFrame, LiveScope } from './host-api.ts'
|
|
26
|
-
export { isRecursivePreset, currentSessionCwd } from './contract.ts'
|
|
27
|
-
export type { SessionSummaryRow, SessionListStateLike, SnapshotSelectorHook, ClientContext } from './contract.ts'
|
|
26
|
+
export { isRecursivePreset, currentSessionCwd, currentWorkspacePath } from './contract.ts'
|
|
27
|
+
export type { SessionSummaryRow, SessionListStateLike, SnapshotSelectorHook, WorkspaceViewLike, WorkspaceListStateLike, ClientContext } from './contract.ts'
|
|
28
28
|
|
|
29
29
|
/** Required services (fiber inject waiting — the runtime must be up first). */
|
|
30
30
|
export const inject = ['slots', 'sessions', 'workspaces', 'connection']
|
package/src/client/slots.ts
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* READ-ONLY (R9): the client only GETs the live host route; no mutation.
|
|
18
18
|
*/
|
|
19
19
|
import { createElement, type ReactNode } from 'react'
|
|
20
|
-
import type { ClientContext, SessionListStateLike, SnapshotSelectorHook } from './contract.ts'
|
|
21
|
-
import { isRecursivePreset, currentSessionCwd } from './contract.ts'
|
|
20
|
+
import type { ClientContext, SessionListStateLike, SnapshotSelectorHook, WorkspaceListStateLike } from './contract.ts'
|
|
21
|
+
import { isRecursivePreset, currentSessionCwd, currentWorkspacePath } from './contract.ts'
|
|
22
22
|
import { Board } from './board.tsx'
|
|
23
23
|
import { Inspector } from './inspector.tsx'
|
|
24
24
|
import { StatusStrip } from './strip.tsx'
|
|
@@ -29,7 +29,7 @@ import { boardState, useBoardState } from './open-state.ts'
|
|
|
29
29
|
/** Structural standard-prop face for the root scope (useSessions/useWorkspaces). */
|
|
30
30
|
interface RootSlotProps {
|
|
31
31
|
useSessions?: SnapshotSelectorHook<SessionListStateLike>
|
|
32
|
-
useWorkspaces?: SnapshotSelectorHook<
|
|
32
|
+
useWorkspaces?: SnapshotSelectorHook<WorkspaceListStateLike>
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/** Structural standard-prop face for the session scope (sessionId + useSessions). */
|
|
@@ -97,7 +97,7 @@ export function registerSlots(ctx: ClientContext): () => void {
|
|
|
97
97
|
}, (props: RootSlotProps) => {
|
|
98
98
|
const useSessions = props?.useSessions
|
|
99
99
|
if (useSessions === undefined) return null
|
|
100
|
-
return createElement(RecursiveBoardOverlay, { useSessions })
|
|
100
|
+
return createElement(RecursiveBoardOverlay, { useSessions, useWorkspaces: props?.useWorkspaces })
|
|
101
101
|
})))
|
|
102
102
|
|
|
103
103
|
// Status strip in the composer input dock (session scope) — gated the same way.
|
|
@@ -129,14 +129,22 @@ export function registerSlots(ctx: ClientContext): () => void {
|
|
|
129
129
|
* Run 10: consumes the full SessionListStateLike through the identity selector
|
|
130
130
|
* (useSessions((s) => s)); no fake {list:{getSnapshot}} wrapper.
|
|
131
131
|
*/
|
|
132
|
-
function RecursiveBoardOverlay({ useSessions }: { useSessions: SnapshotSelectorHook<SessionListStateLike> }): ReactNode {
|
|
132
|
+
export function RecursiveBoardOverlay({ useSessions, useWorkspaces }: { useSessions: SnapshotSelectorHook<SessionListStateLike>; useWorkspaces?: SnapshotSelectorHook<WorkspaceListStateLike> }): ReactNode {
|
|
133
133
|
const board = useBoardState()
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
|
|
134
|
+
const sessions = useRecursiveSessions(useSessions)
|
|
135
|
+
// Run 14 (USER-DIRECTED): the board keys on the WORKSPACE, not the session —
|
|
136
|
+
// the workspace owns the .recursive/ run-layer history. Hoist ALL hooks first.
|
|
137
|
+
const workspaces: WorkspaceListStateLike = useWorkspaces !== undefined
|
|
138
|
+
? useWorkspaces((s) => s)
|
|
139
|
+
: { items: [], recentWorkspaceId: undefined }
|
|
140
|
+
const content = overlayContent(board, sessions)
|
|
141
|
+
const wsPath = currentWorkspacePath(workspaces, sessions)
|
|
142
|
+
const scope = { cwd: wsPath }
|
|
143
|
+
// Run 13 (LIVE BUG): ALWAYS call useLiveProjection — a conditional hook (after the
|
|
144
|
+
// hidden early return) changed the hook count across renders and threw
|
|
145
|
+
// 'Rendered more hooks than during the previous render'.
|
|
139
146
|
const snapshot = useLiveProjection(scope)
|
|
147
|
+
if (content === 'hidden') return null
|
|
140
148
|
if (content === 'inspector' && board.selection !== null) {
|
|
141
149
|
return createElement(Inspector, {
|
|
142
150
|
runId: board.selection.runId,
|
|
@@ -153,12 +161,13 @@ function RecursiveBoardOverlay({ useSessions }: { useSessions: SnapshotSelectorH
|
|
|
153
161
|
* Strip gate: same preset gate, session-scoped sessionId; the strip renders
|
|
154
162
|
* nothing for a non-recursive session. Run 10: useSessions((s) => s) selector.
|
|
155
163
|
*/
|
|
156
|
-
function RecursiveStripGate({ useSessions, sessionId }: { useSessions: SnapshotSelectorHook<SessionListStateLike>; sessionId?: string }): ReactNode {
|
|
164
|
+
export function RecursiveStripGate({ useSessions, sessionId }: { useSessions: SnapshotSelectorHook<SessionListStateLike>; sessionId?: string }): ReactNode {
|
|
157
165
|
const list = useRecursiveSessions(useSessions)
|
|
158
|
-
if (!isRecursivePreset(list)) return null
|
|
159
166
|
const cwd = currentSessionCwd(list)
|
|
160
167
|
const scope = { sessionId, cwd }
|
|
168
|
+
// Run 13: ALWAYS call useLiveProjection BEFORE the preset early return.
|
|
161
169
|
const snapshot = useLiveProjection(scope)
|
|
170
|
+
if (!isRecursivePreset(list)) return null
|
|
162
171
|
return createElement(StatusStrip, { snapshot })
|
|
163
172
|
}
|
|
164
173
|
|