@try-works/dsh-recursive-mode 0.2.2 → 0.2.3

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.
@@ -9,8 +9,14 @@ export interface BoardProps {
9
9
  workspacePath?: string;
10
10
  onOpenInspector?: (selection: BoardSelection) => void;
11
11
  onClose?: () => void;
12
+ /**
13
+ * 'overlay' (default) renders the board as a full-viewport fixed overlay
14
+ * (shell.overlay / launcher). 'view' renders it inline, filling its parent
15
+ * (a conversation.view tab), with no close button and no fixed positioning.
16
+ */
17
+ variant?: 'overlay' | 'view';
12
18
  }
13
- export declare function Board({ snapshot, workspacePath, onOpenInspector, onClose }: BoardProps): import("react").DetailedReactHTMLElement<{
19
+ export declare function Board({ snapshot, workspacePath, onOpenInspector, onClose, variant }: BoardProps): import("react").DetailedReactHTMLElement<{
14
20
  className: string;
15
21
  'data-theme': import("./theme.ts").BoardTheme;
16
22
  }, HTMLElement> | null;
@@ -14,6 +14,7 @@
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 { RecursiveView } from './slots.ts';
17
18
  export { RecursiveSettings } from './settings.tsx';
18
19
  export { useLiveProjection } from './use-live.ts';
19
20
  export type { LiveProjectionSnapshot } from './use-live.ts';
@@ -41,6 +41,16 @@ export declare function useRecursiveSessions(useSessions: SnapshotSelectorHook<S
41
41
  export declare function RecursiveLauncherGate({ useSessions }: {
42
42
  useSessions: SnapshotSelectorHook<SessionListStateLike>;
43
43
  }): ReactNode;
44
+ /**
45
+ * RecursiveView: the conversation.view tab body. Renders the run board INLINE
46
+ * (fills the view area) keyed on the CURRENT workspace, and swaps to the
47
+ * inspector modal when a run is opened. Read-only (R9): uses the live host
48
+ * route. No preset gate — the tab is discoverable in any session.
49
+ */
50
+ export declare function RecursiveView({ useSessions, useWorkspaces }: {
51
+ useSessions: SnapshotSelectorHook<SessionListStateLike>;
52
+ useWorkspaces: SnapshotSelectorHook<WorkspaceListStateLike>;
53
+ }): ReactNode;
44
54
  export declare function registerSlots(ctx: ClientContext): () => void;
45
55
  /**
46
56
  * Board overlay: subscribes to the shared board store, gates on open + recursive
package/lib/client.js CHANGED
@@ -335,8 +335,8 @@ window.__ModuleLoader__.load({
335
335
  for (const worktreeRoot of Object.keys(projection)) for (const runId of Object.keys(projection[worktreeRoot])) runs.push(projection[worktreeRoot][runId]);
336
336
  return runs;
337
337
  }
338
- function closeButton$1(onClose) {
339
- if (onClose === void 0) return null;
338
+ function closeButton$1(onClose, variant) {
339
+ if (onClose === void 0 || variant === "view") return null;
340
340
  return (0, react.createElement)("button", {
341
341
  type: "button",
342
342
  className: "rec-close",
@@ -356,7 +356,7 @@ window.__ModuleLoader__.load({
356
356
  "data-pill": kind
357
357
  }, PILL_LABELS[kind]);
358
358
  }
359
- function Board({ snapshot, workspacePath, onOpenInspector, onClose }) {
359
+ function Board({ snapshot, workspacePath, onOpenInspector, onClose, variant = "overlay" }) {
360
360
  const { theme, toggle } = useBoardTheme();
361
361
  if (snapshot === null || snapshot.root === null) return null;
362
362
  const runs = workspacePath !== void 0 && workspacePath !== "" && !sameWorkspacePath(workspacePath, snapshot.root) ? [] : listRuns(snapshot.projection);
@@ -365,21 +365,22 @@ window.__ModuleLoader__.load({
365
365
  worktreeRoot: run.worktreeRoot,
366
366
  runId: run.runId
367
367
  });
368
+ const rootCls = variant === "view" ? "rec-board rec-board-view" : "rec-board";
368
369
  if (runs.length === 0) return (0, react.createElement)("div", {
369
- className: "rec-board",
370
+ className: rootCls,
370
371
  "data-theme": theme,
371
372
  "data-empty": true
372
373
  }, (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, {
373
374
  theme,
374
375
  toggle
375
- }), closeButton$1(onClose)), (0, react.createElement)("p", { className: "rec-board-empty" }, "No recursive runs in this workspace yet."));
376
+ }), closeButton$1(onClose, variant)), (0, react.createElement)("p", { className: "rec-board-empty" }, "No recursive runs in this workspace yet."));
376
377
  return (0, react.createElement)("div", {
377
- className: "rec-board",
378
+ className: rootCls,
378
379
  "data-theme": theme
379
380
  }, (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, {
380
381
  theme,
381
382
  toggle
382
- }), closeButton$1(onClose)), (0, react.createElement)("div", { className: "rec-columns" }, KANBAN_LANES.map((lane) => {
383
+ }), closeButton$1(onClose, variant)), (0, react.createElement)("div", { className: "rec-columns" }, KANBAN_LANES.map((lane) => {
383
384
  const laneRuns = runs.filter((r) => columnForRun(r) === lane.id);
384
385
  return (0, react.createElement)("section", {
385
386
  key: lane.id,
@@ -781,6 +782,17 @@ window.__ModuleLoader__.load({
781
782
  overflow: hidden;
782
783
  }
783
784
 
785
+ /* Inline board (conversation.view tab): fills its parent view area, not fixed. */
786
+ .rec-board-view {
787
+ position: relative;
788
+ inset: auto;
789
+ z-index: auto;
790
+ width: 100%;
791
+ height: 100%;
792
+ min-height: 0;
793
+ flex: 1;
794
+ }
795
+
784
796
  .rec-board-header {
785
797
  display: flex;
786
798
  align-items: center;
@@ -1257,9 +1269,51 @@ window.__ModuleLoader__.load({
1257
1269
  onClick: () => boardState.openBoard()
1258
1270
  }, "⧉");
1259
1271
  }
1272
+ /**
1273
+ * RecursiveView: the conversation.view tab body. Renders the run board INLINE
1274
+ * (fills the view area) keyed on the CURRENT workspace, and swaps to the
1275
+ * inspector modal when a run is opened. Read-only (R9): uses the live host
1276
+ * route. No preset gate — the tab is discoverable in any session.
1277
+ */
1278
+ function RecursiveView({ useSessions, useWorkspaces }) {
1279
+ const board = useBoardState();
1280
+ const sessions = useRecursiveSessions(useSessions);
1281
+ const wsPath = currentWorkspacePath(useWorkspaces((s) => s) ?? {
1282
+ items: [],
1283
+ recentWorkspaceId: void 0
1284
+ }, sessions);
1285
+ const snapshot = useLiveProjection({ cwd: wsPath });
1286
+ if (board.selection !== null) return (0, react.createElement)(Inspector, {
1287
+ runId: board.selection.runId,
1288
+ worktreeRoot: board.selection.worktreeRoot,
1289
+ snapshot,
1290
+ onBackToToolDetails: () => boardState.backToBoard(),
1291
+ onClose: () => boardState.close()
1292
+ });
1293
+ return (0, react.createElement)(Board, {
1294
+ snapshot,
1295
+ workspacePath: wsPath,
1296
+ variant: "view",
1297
+ onOpenInspector: (sel) => boardState.openInspector(sel)
1298
+ });
1299
+ }
1260
1300
  function registerSlots(ctx) {
1261
1301
  const disposers = [];
1262
1302
  disposers.push(injectBoardStyles());
1303
+ disposers.push(ctx.slots.inject("conversation.view", () => ctx.slots.register({
1304
+ name: "conversation.view",
1305
+ id: "recursive",
1306
+ order: 20,
1307
+ label: "Recursive"
1308
+ }, (props) => {
1309
+ const useSessions = props?.useSessions;
1310
+ const useWorkspaces = props?.useWorkspaces;
1311
+ if (useSessions === void 0 || useWorkspaces === void 0) return null;
1312
+ return (0, react.createElement)(RecursiveView, {
1313
+ useSessions,
1314
+ useWorkspaces
1315
+ });
1316
+ })));
1263
1317
  disposers.push(ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({
1264
1318
  name: "sidebar.footer.action",
1265
1319
  id: "recursive",
@@ -1355,6 +1409,7 @@ window.__ModuleLoader__.load({
1355
1409
  exports.Board = Board;
1356
1410
  exports.Inspector = Inspector;
1357
1411
  exports.RecursiveSettings = RecursiveSettings;
1412
+ exports.RecursiveView = RecursiveView;
1358
1413
  exports.apply = apply;
1359
1414
  exports.currentSessionCwd = currentSessionCwd;
1360
1415
  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.2.2",
4
+ "version": "0.2.3",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
7
7
  "types": "lib/index.d.ts",
@@ -32,10 +32,16 @@ export interface BoardProps {
32
32
  workspacePath?: string
33
33
  onOpenInspector?: (selection: BoardSelection) => void
34
34
  onClose?: () => void
35
+ /**
36
+ * 'overlay' (default) renders the board as a full-viewport fixed overlay
37
+ * (shell.overlay / launcher). 'view' renders it inline, filling its parent
38
+ * (a conversation.view tab), with no close button and no fixed positioning.
39
+ */
40
+ variant?: 'overlay' | 'view'
35
41
  }
36
42
 
37
- function closeButton(onClose: (() => void) | undefined) {
38
- if (onClose === undefined) return null
43
+ function closeButton(onClose: (() => void) | undefined, variant: 'overlay' | 'view') {
44
+ if (onClose === undefined || variant === 'view') return null
39
45
  return createElement('button', { type: 'button', className: 'rec-close', onClick: onClose, title: 'Close', 'aria-label': 'Close' }, '×')
40
46
  }
41
47
 
@@ -49,7 +55,7 @@ function solidPill(kind: ReturnType<typeof cardPill>) {
49
55
  return createElement('span', { className: 'rec-pill', 'data-pill': kind }, PILL_LABELS[kind])
50
56
  }
51
57
 
52
- export function Board({ snapshot, workspacePath, onOpenInspector, onClose }: BoardProps) {
58
+ export function Board({ snapshot, workspacePath, onOpenInspector, onClose, variant = 'overlay' }: BoardProps) {
53
59
  const { theme, toggle } = useBoardTheme()
54
60
  if (snapshot === null || snapshot.root === null) return null
55
61
  // Run 16: when the synchronous workspace path differs from the async snapshot
@@ -59,25 +65,26 @@ export function Board({ snapshot, workspacePath, onOpenInspector, onClose }: Boa
59
65
  const runs = stale ? [] : listRuns(snapshot.projection)
60
66
  const headerPath = workspacePath ?? snapshot.root
61
67
  const open = (run: RecursiveRunCard) => () => onOpenInspector?.({ worktreeRoot: run.worktreeRoot, runId: run.runId })
68
+ const rootCls = variant === 'view' ? 'rec-board rec-board-view' : 'rec-board'
62
69
  if (runs.length === 0) {
63
- return createElement('div', { className: 'rec-board', 'data-theme': theme, 'data-empty': true },
70
+ return createElement('div', { className: rootCls, 'data-theme': theme, 'data-empty': true },
64
71
  createElement('header', { className: 'rec-board-header' },
65
72
  createElement('h2', { className: 'rec-board-title' }, 'Recursive runs'),
66
73
  createElement('span', { className: 'rec-board-path' }, headerPath),
67
74
  createElement('span', { className: 'rec-board-count' }, '0 runs'),
68
75
  createElement(ThemeToggle, { theme, toggle }),
69
- closeButton(onClose),
76
+ closeButton(onClose, variant),
70
77
  ),
71
78
  createElement('p', { className: 'rec-board-empty' }, 'No recursive runs in this workspace yet.'),
72
79
  )
73
80
  }
74
- return createElement('div', { className: 'rec-board', 'data-theme': theme },
81
+ return createElement('div', { className: rootCls, 'data-theme': theme },
75
82
  createElement('header', { className: 'rec-board-header' },
76
83
  createElement('h2', { className: 'rec-board-title' }, 'Recursive runs'),
77
84
  createElement('span', { className: 'rec-board-path' }, headerPath),
78
85
  createElement('span', { className: 'rec-board-count' }, runs.length + ' runs'),
79
86
  createElement(ThemeToggle, { theme, toggle }),
80
- closeButton(onClose),
87
+ closeButton(onClose, variant),
81
88
  ),
82
89
  createElement('div', { className: 'rec-columns' },
83
90
  KANBAN_LANES.map((lane) => {
@@ -17,6 +17,7 @@ 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 { RecursiveView } from './slots.ts'
20
21
  export { RecursiveSettings } from './settings.tsx'
21
22
  export { useLiveProjection } from './use-live.ts'
22
23
  export type { LiveProjectionSnapshot } from './use-live.ts'
@@ -64,11 +64,54 @@ export function RecursiveLauncherGate({ useSessions }: { useSessions: SnapshotSe
64
64
  return createElement('button', { className: 'rec-launcher', title: 'Recursive runs', onClick: () => boardState.openBoard() }, '⧉')
65
65
  }
66
66
 
67
+ /**
68
+ * RecursiveView: the conversation.view tab body. Renders the run board INLINE
69
+ * (fills the view area) keyed on the CURRENT workspace, and swaps to the
70
+ * inspector modal when a run is opened. Read-only (R9): uses the live host
71
+ * route. No preset gate — the tab is discoverable in any session.
72
+ */
73
+ export function RecursiveView({ useSessions, useWorkspaces }: { useSessions: SnapshotSelectorHook<SessionListStateLike>; useWorkspaces: SnapshotSelectorHook<WorkspaceListStateLike> }): ReactNode {
74
+ const board = useBoardState()
75
+ const sessions = useRecursiveSessions(useSessions)
76
+ const workspaces: WorkspaceListStateLike = useWorkspaces((s) => s) ?? { items: [], recentWorkspaceId: undefined }
77
+ const wsPath = currentWorkspacePath(workspaces, sessions)
78
+ const scope = { cwd: wsPath }
79
+ const snapshot = useLiveProjection(scope)
80
+ // Inspector drill-down (modal over the inline board) when a run is selected.
81
+ if (board.selection !== null) {
82
+ return createElement(Inspector, {
83
+ runId: board.selection.runId,
84
+ worktreeRoot: board.selection.worktreeRoot,
85
+ snapshot,
86
+ onBackToToolDetails: () => boardState.backToBoard(),
87
+ onClose: () => boardState.close(),
88
+ })
89
+ }
90
+ return createElement(Board, { snapshot, workspacePath: wsPath, variant: 'view', onOpenInspector: (sel) => boardState.openInspector(sel) })
91
+ }
92
+
67
93
  export function registerSlots(ctx: ClientContext): () => void {
68
94
  const disposers: (() => void)[] = []
69
95
  // Run 15: inject the one-shot theme-token stylesheet once per document (idempotent).
70
96
  disposers.push(injectBoardStyles())
71
97
 
98
+ // Conversation view tab (Chat | Trajectory | Recursive): the recursive run
99
+ // board as a first-class tab in the conversation header, rendered INLINE in
100
+ // the view area (not a fixed overlay). Order 20 places it to the RIGHT of
101
+ // Trajectory (order 10). Always present — no recursive-preset gate, so the
102
+ // entry point is discoverable in any session.
103
+ disposers.push(ctx.slots.inject('conversation.view', () => ctx.slots.register({
104
+ name: 'conversation.view',
105
+ id: 'recursive',
106
+ order: 20,
107
+ label: 'Recursive',
108
+ }, (props: RootSlotProps) => {
109
+ const useSessions = props?.useSessions
110
+ const useWorkspaces = props?.useWorkspaces
111
+ if (useSessions === undefined || useWorkspaces === undefined) return null
112
+ return createElement(RecursiveView, { useSessions, useWorkspaces })
113
+ })))
114
+
72
115
  // Board launcher in the sidebar footer action list — OPENS the shared board store (run 08 R1).
73
116
  // Run 11 (UX gate): the seat is root-scoped (visible in every session), but the board it
74
117
  // opens is recursive-preset-gated. In a code/other session the icon was a dead click — a
@@ -136,6 +136,17 @@ const BOARD_CSS = `
136
136
  overflow: hidden;
137
137
  }
138
138
 
139
+ /* Inline board (conversation.view tab): fills its parent view area, not fixed. */
140
+ .rec-board-view {
141
+ position: relative;
142
+ inset: auto;
143
+ z-index: auto;
144
+ width: 100%;
145
+ height: 100%;
146
+ min-height: 0;
147
+ flex: 1;
148
+ }
149
+
139
150
  .rec-board-header {
140
151
  display: flex;
141
152
  align-items: center;