@principal-ai/subsystems-react 0.38.0 → 0.40.0

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.
Files changed (72) hide show
  1. package/dist/components/maintain-events/MaintainEventLog.d.ts +35 -0
  2. package/dist/components/maintain-events/MaintainEventLog.d.ts.map +1 -0
  3. package/dist/components/maintain-events/MaintainEventLog.js +66 -0
  4. package/dist/components/maintain-events/MaintainEventLog.js.map +1 -0
  5. package/dist/components/maintain-events/MaintainLivePanel.d.ts +15 -0
  6. package/dist/components/maintain-events/MaintainLivePanel.d.ts.map +1 -0
  7. package/dist/components/maintain-events/MaintainLivePanel.js +49 -0
  8. package/dist/components/maintain-events/MaintainLivePanel.js.map +1 -0
  9. package/dist/components/maintain-events/index.d.ts +8 -0
  10. package/dist/components/maintain-events/index.d.ts.map +1 -0
  11. package/dist/components/maintain-events/index.js +6 -0
  12. package/dist/components/maintain-events/index.js.map +1 -0
  13. package/dist/graphify/consolidated.d.ts +8 -0
  14. package/dist/graphify/consolidated.d.ts.map +1 -1
  15. package/dist/graphify/construct.d.ts +18 -0
  16. package/dist/graphify/construct.d.ts.map +1 -1
  17. package/dist/graphify/construct.js +23 -0
  18. package/dist/graphify/construct.js.map +1 -1
  19. package/dist/graphify/index.d.ts +2 -2
  20. package/dist/graphify/index.d.ts.map +1 -1
  21. package/dist/graphify/index.js +1 -1
  22. package/dist/graphify/index.js.map +1 -1
  23. package/dist/index.d.ts +3 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/subsystem/IssueList.d.ts +49 -3
  28. package/dist/subsystem/IssueList.d.ts.map +1 -1
  29. package/dist/subsystem/IssueList.js +180 -79
  30. package/dist/subsystem/IssueList.js.map +1 -1
  31. package/dist/subsystem/SubsystemComponentGraph.d.ts +15 -1
  32. package/dist/subsystem/SubsystemComponentGraph.d.ts.map +1 -1
  33. package/dist/subsystem/SubsystemComponentGraph.js +464 -14
  34. package/dist/subsystem/SubsystemComponentGraph.js.map +1 -1
  35. package/dist/subsystem/formatDeclaration.js +19 -5
  36. package/dist/subsystem/formatDeclaration.js.map +1 -1
  37. package/dist/subsystem/model.d.ts +38 -0
  38. package/dist/subsystem/model.d.ts.map +1 -1
  39. package/dist/subsystem/model.js.map +1 -1
  40. package/dist/subsystem/nodes.d.ts.map +1 -1
  41. package/dist/subsystem/nodes.js +124 -67
  42. package/dist/subsystem/nodes.js.map +1 -1
  43. package/dist/subsystem/symbolRefs.d.ts.map +1 -1
  44. package/dist/subsystem/symbolRefs.js +2 -0
  45. package/dist/subsystem/symbolRefs.js.map +1 -1
  46. package/package.json +3 -3
  47. package/src/components/maintain-events/MaintainEventLog.tsx +152 -0
  48. package/src/components/maintain-events/MaintainLivePanel.tsx +99 -0
  49. package/src/components/maintain-events/index.ts +10 -0
  50. package/src/graphify/consolidated.ts +8 -0
  51. package/src/graphify/construct.test.ts +61 -1
  52. package/src/graphify/construct.ts +33 -0
  53. package/src/graphify/index.ts +6 -1
  54. package/src/index.ts +9 -0
  55. package/src/stories/MaintainEvents/MaintainEventLog.stories.tsx +66 -0
  56. package/src/stories/MaintainEvents/MaintainLivePanel.stories.tsx +77 -0
  57. package/src/stories/Subsystem/ComponentGraph/IssueOverlay.stories.tsx +625 -0
  58. package/src/stories/Subsystem/ComponentGraph/Issues.stories.tsx +7 -1
  59. package/src/stories/Subsystem/ComponentGraph/NodeAnatomy.stories.tsx +40 -73
  60. package/src/stories/Subsystem/ComponentGraph/StoreValueType.stories.tsx +261 -0
  61. package/src/stories/Subsystem/StoreValueTypeDeclaration.stories.tsx +182 -0
  62. package/src/stories/data/maintain-live-events.json +554 -0
  63. package/src/subsystem/IssueList.focus.test.tsx +113 -0
  64. package/src/subsystem/IssueList.icon.test.tsx +95 -0
  65. package/src/subsystem/IssueList.tsx +267 -111
  66. package/src/subsystem/SubsystemComponentGraph.tsx +541 -13
  67. package/src/subsystem/formatDeclaration.test.ts +64 -0
  68. package/src/subsystem/formatDeclaration.ts +20 -5
  69. package/src/subsystem/model.ts +46 -0
  70. package/src/subsystem/nodes.group.test.tsx +52 -0
  71. package/src/subsystem/nodes.tsx +102 -2
  72. package/src/subsystem/symbolRefs.ts +2 -0
@@ -0,0 +1,152 @@
1
+ /**
2
+ * MaintainEventLog — presentational live event log for an OpenCode agent run.
3
+ *
4
+ * Renders one row per event as `time type — summary`, with a header carrying
5
+ * title / agent / session id / status / counts. Subsystems Studio's
6
+ * `MaintainEventsView` owns the RPC + subscription wiring and feeds the
7
+ * resulting state in as props; keeping the wiring out of this component lets it
8
+ * render in Storybook and embed in the subsystem graph sidebar.
9
+ */
10
+
11
+ import { useEffect, useRef, type CSSProperties } from 'react';
12
+ import { useTheme } from '@principal-ade/industry-theme';
13
+
14
+ export interface MaintainEventLogEvent {
15
+ at: number;
16
+ type: string;
17
+ sessionId?: string;
18
+ summary: string;
19
+ }
20
+
21
+ export interface MaintainEventLogProps {
22
+ title?: string;
23
+ agent?: string;
24
+ sessionId?: string;
25
+ status: string;
26
+ events: MaintainEventLogEvent[];
27
+ total: number;
28
+ error?: string | null;
29
+ /** Shown before a session id is known. @default 'Waiting for OpenCode session…' */
30
+ waitingLabel?: string;
31
+ /** Shown once a session exists but no events have arrived. @default 'Waiting for SSE events…' */
32
+ emptyLabel?: string;
33
+ /** Keep the log pinned to the newest event. @default true */
34
+ autoScroll?: boolean;
35
+ /** Render the title/status header above the log. @default true */
36
+ showHeader?: boolean;
37
+ }
38
+
39
+ function formatTime(at: number): string {
40
+ return new Date(at).toLocaleTimeString(undefined, {
41
+ hour: '2-digit',
42
+ minute: '2-digit',
43
+ second: '2-digit',
44
+ });
45
+ }
46
+
47
+ export function MaintainEventLog({
48
+ title,
49
+ agent,
50
+ sessionId,
51
+ status,
52
+ events,
53
+ total,
54
+ error,
55
+ waitingLabel = 'Waiting for OpenCode session…',
56
+ emptyLabel = 'Waiting for SSE events…',
57
+ autoScroll = true,
58
+ showHeader = true,
59
+ }: MaintainEventLogProps) {
60
+ const { theme } = useTheme();
61
+ const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
62
+ const logRef = useRef<HTMLDivElement | null>(null);
63
+
64
+ useEffect(() => {
65
+ if (!autoScroll) return;
66
+ const el = logRef.current;
67
+ if (!el) return;
68
+ el.scrollTop = el.scrollHeight;
69
+ }, [autoScroll, events.length]);
70
+
71
+ if (!sessionId) {
72
+ return (
73
+ <div
74
+ style={{
75
+ width: '100%',
76
+ height: '100%',
77
+ display: 'flex',
78
+ alignItems: 'center',
79
+ justifyContent: 'center',
80
+ color: muted,
81
+ fontFamily: theme.fonts.body,
82
+ fontSize: theme.fontSizes[1],
83
+ }}
84
+ >
85
+ {waitingLabel}
86
+ </div>
87
+ );
88
+ }
89
+
90
+ const wrap: CSSProperties = {
91
+ height: '100%',
92
+ display: 'flex',
93
+ flexDirection: 'column',
94
+ padding: 20,
95
+ color: theme.colors.text,
96
+ fontFamily: theme.fonts.body,
97
+ boxSizing: 'border-box',
98
+ };
99
+
100
+ return (
101
+ <div style={wrap}>
102
+ {showHeader ? (
103
+ <div style={{ marginBottom: 12 }}>
104
+ <div style={{ fontSize: theme.fontSizes[3], fontWeight: 650 }}>
105
+ {title ?? 'Maintain events'}
106
+ </div>
107
+ <div style={{ marginTop: 4, fontSize: theme.fontSizes[0], color: muted }}>
108
+ {agent ? `${agent} · ` : null}
109
+ {sessionId}
110
+ {' · '}
111
+ <span style={{ fontWeight: 600 }}>{status}</span>
112
+ {' · '}
113
+ {total} events
114
+ {events.length < total ? ` (showing last ${events.length})` : null}
115
+ </div>
116
+ {error ? (
117
+ <div style={{ marginTop: 8, color: theme.colors.error, fontSize: theme.fontSizes[0] }}>
118
+ {error}
119
+ </div>
120
+ ) : null}
121
+ </div>
122
+ ) : null}
123
+ <div
124
+ ref={logRef}
125
+ style={{
126
+ flex: 1,
127
+ minHeight: 0,
128
+ overflow: 'auto',
129
+ borderRadius: 8,
130
+ border: `1px solid ${theme.colors.border ?? 'rgba(255,255,255,0.12)'}`,
131
+ background: 'rgba(0,0,0,0.25)',
132
+ padding: '10px 12px',
133
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',
134
+ fontSize: theme.fontSizes[0],
135
+ lineHeight: 1.45,
136
+ }}
137
+ >
138
+ {events.length === 0 ? (
139
+ <div style={{ color: muted }}>{emptyLabel}</div>
140
+ ) : (
141
+ events.map((ev, i) => (
142
+ <div key={`${ev.at}-${ev.type}-${i}`} style={{ marginBottom: 6 }}>
143
+ <span style={{ color: muted }}>{formatTime(ev.at)}</span>{' '}
144
+ <span style={{ color: theme.colors.primary ?? '#8fd' }}>{ev.type}</span>
145
+ {ev.summary && ev.summary !== ev.type ? <span> — {ev.summary}</span> : null}
146
+ </div>
147
+ ))
148
+ )}
149
+ </div>
150
+ </div>
151
+ );
152
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * MaintainLivePanel — a collapsible, half-height panel that overlays the graph
3
+ * canvas and streams a live agent run's events. Subsystems Studio supplies the
4
+ * state (status/events/total/error) and renders it through
5
+ * `SubsystemComponentGraph`'s `liveEvents` prop; collapsing leaves just the
6
+ * header bar so the graph stays readable.
7
+ */
8
+
9
+ import { useState } from 'react';
10
+ import { useTheme } from '@principal-ade/industry-theme';
11
+ import { ChevronDown, ChevronUp } from 'lucide-react';
12
+ import { MaintainEventLog, type MaintainEventLogProps } from './MaintainEventLog';
13
+
14
+ export interface MaintainLivePanelProps extends MaintainEventLogProps {
15
+ /** Start collapsed (header only). @default false */
16
+ defaultCollapsed?: boolean;
17
+ }
18
+
19
+ const HEADER_HEIGHT = 36;
20
+
21
+ export function MaintainLivePanel({
22
+ title,
23
+ agent,
24
+ sessionId,
25
+ status,
26
+ events,
27
+ total,
28
+ error,
29
+ defaultCollapsed = false,
30
+ }: MaintainLivePanelProps) {
31
+ const { theme } = useTheme();
32
+ const muted = theme.colors.textMuted ?? theme.colors.textSecondary;
33
+ const border = theme.colors.border ?? 'rgba(255,255,255,0.12)';
34
+ const [collapsed, setCollapsed] = useState(defaultCollapsed);
35
+
36
+ return (
37
+ <div
38
+ style={{
39
+ position: 'absolute',
40
+ left: 0,
41
+ right: 0,
42
+ bottom: 0,
43
+ height: collapsed ? HEADER_HEIGHT : '50%',
44
+ zIndex: 5,
45
+ display: 'flex',
46
+ flexDirection: 'column',
47
+ background: theme.colors.backgroundSecondary ?? theme.colors.background,
48
+ borderTop: `1px solid ${border}`,
49
+ boxShadow: '0 -8px 24px rgba(0,0,0,0.35)',
50
+ overflow: 'hidden',
51
+ }}
52
+ >
53
+ <button
54
+ type="button"
55
+ onClick={() => setCollapsed((c) => !c)}
56
+ style={{
57
+ display: 'flex',
58
+ alignItems: 'center',
59
+ gap: 8,
60
+ height: HEADER_HEIGHT,
61
+ flexShrink: 0,
62
+ padding: '0 12px',
63
+ border: 'none',
64
+ borderBottom: collapsed ? 'none' : `1px solid ${border}`,
65
+ background: 'transparent',
66
+ color: theme.colors.text,
67
+ fontFamily: theme.fonts.body,
68
+ fontSize: theme.fontSizes[0],
69
+ cursor: 'pointer',
70
+ textAlign: 'left',
71
+ }}
72
+ >
73
+ {collapsed ? <ChevronUp size={14} /> : <ChevronDown size={14} />}
74
+ <span style={{ fontWeight: 600 }}>Live events</span>
75
+ {agent ? <span style={{ color: muted }}>{agent}</span> : null}
76
+ {status ? <span style={{ color: muted }}>· {status}</span> : null}
77
+ {total > 0 ? <span style={{ color: muted }}>· {total} events</span> : null}
78
+ <span style={{ flex: 1 }} />
79
+ {sessionId ? (
80
+ <span style={{ color: muted, fontFamily: theme.fonts.monospace }}>{sessionId}</span>
81
+ ) : null}
82
+ </button>
83
+ {!collapsed ? (
84
+ <div style={{ flex: 1, minHeight: 0 }}>
85
+ <MaintainEventLog
86
+ title={title}
87
+ agent={agent}
88
+ sessionId={sessionId}
89
+ status={status}
90
+ events={events}
91
+ total={total}
92
+ error={error}
93
+ showHeader={false}
94
+ />
95
+ </div>
96
+ ) : null}
97
+ </div>
98
+ );
99
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Maintain event log — presentational live OpenCode agent-run event feed.
3
+ */
4
+ export { MaintainEventLog } from './MaintainEventLog';
5
+ export type {
6
+ MaintainEventLogEvent,
7
+ MaintainEventLogProps,
8
+ } from './MaintainEventLog';
9
+ export { MaintainLivePanel } from './MaintainLivePanel';
10
+ export type { MaintainLivePanelProps } from './MaintainLivePanel';
@@ -240,6 +240,14 @@ export interface GraphifyStoreDetail {
240
240
  * system (db/service; carries no `process`).
241
241
  */
242
242
  storage?: 'memory' | 'disk' | 'external';
243
+ /**
244
+ * The type of the retained state itself — a store declares its type the way
245
+ * every other declaration does (`Map<string, FeedState>`, `Set<Listener>`,
246
+ * a table's row type). Orthogonal to `storage`.
247
+ */
248
+ valueType?: string;
249
+ /** Backing edge target for `valueType`, when the graph has one. */
250
+ valueTypeRef?: GraphifyReferenceInfo;
243
251
  }
244
252
 
245
253
  /** A standalone method selected from its owning class. */
@@ -1,6 +1,10 @@
1
1
  import { describe, expect, test } from 'bun:test';
2
2
  import type { GraphifyEdge, GraphifyNode } from './types';
3
- import { inferConstructFromGraphify, constructsMatch } from './construct';
3
+ import {
4
+ inferConstructFromGraphify,
5
+ constructsMatch,
6
+ resolveConstructMatch,
7
+ } from './construct';
4
8
 
5
9
  function node(
6
10
  id: string,
@@ -106,3 +110,59 @@ describe('constructsMatch', () => {
106
110
  expect(constructsMatch('function', 'class')).toBe(false);
107
111
  });
108
112
  });
113
+
114
+ describe('resolveConstructMatch', () => {
115
+ test('structural match is enough (no augmentation)', () => {
116
+ expect(resolveConstructMatch('function', 'function')).toEqual({
117
+ match: true,
118
+ });
119
+ });
120
+
121
+ test('plain mismatch without augmentation stays a hard failure', () => {
122
+ expect(resolveConstructMatch('function', 'class')).toEqual({
123
+ match: false,
124
+ });
125
+ });
126
+
127
+ test('unknown without augmentation is unconfirmed, not a mismatch', () => {
128
+ expect(resolveConstructMatch('store', 'unknown')).toEqual({ match: null });
129
+ });
130
+
131
+ test('accepted augmentation confirms a claim Graphify infers differently', () => {
132
+ expect(resolveConstructMatch('store', 'function', 'store')).toEqual({
133
+ match: true,
134
+ augmentedBy: 'store',
135
+ });
136
+ });
137
+
138
+ test('accepted augmentation confirms a claim Graphify leaves unknown', () => {
139
+ expect(resolveConstructMatch('interface', 'unknown', 'interface')).toEqual({
140
+ match: true,
141
+ augmentedBy: 'interface',
142
+ });
143
+ });
144
+
145
+ test('augmentation only confirms its own claim, not a different one', () => {
146
+ expect(resolveConstructMatch('store', 'function', 'module')).toEqual({
147
+ match: false,
148
+ });
149
+ expect(resolveConstructMatch('store', 'unknown', 'module')).toEqual({
150
+ match: null,
151
+ });
152
+ });
153
+
154
+ test('blank augmentation is ignored', () => {
155
+ expect(resolveConstructMatch('store', 'function', ' ')).toEqual({
156
+ match: false,
157
+ });
158
+ });
159
+
160
+ test('external / missing claimed still short-circuits to match', () => {
161
+ expect(resolveConstructMatch('external', 'function')).toEqual({
162
+ match: true,
163
+ });
164
+ expect(resolveConstructMatch(undefined, 'unknown')).toEqual({
165
+ match: true,
166
+ });
167
+ });
168
+ });
@@ -118,3 +118,36 @@ export function constructsMatch(
118
118
  if (TYPE_FAMILY.has(claimed)) return inferred === 'type';
119
119
  return claimed === inferred;
120
120
  }
121
+
122
+ export interface ConstructCheckOutcome {
123
+ /** true = confirmed, false = known mismatch, null = unconfirmed (needs agent). */
124
+ match: boolean | null;
125
+ /** Confirming construct from an accepted augmentation, when that is what confirmed it. */
126
+ augmentedBy?: string;
127
+ }
128
+
129
+ /**
130
+ * Resolve a claimed construct against Graphify's inferred construct, allowing an
131
+ * accepted, agent-confirmed augmentation to stand in for Graphify when it is
132
+ * silent (`unknown`) or when it disagrees.
133
+ *
134
+ * Graphify's inferred construct is a *structural hint*, never ground truth: its
135
+ * vocabulary (`class | function | method | type | module | unknown`) has no
136
+ * `store` or `custom_entity`, and a call-style accessor label biases it to
137
+ * `function`. When source-reading confirms the model's claim, that confirmation
138
+ * (recorded as an accepted augmentation) wins over the hint.
139
+ */
140
+ export function resolveConstructMatch(
141
+ claimed: string | undefined,
142
+ inferred: InferredGraphifyConstruct,
143
+ augmentedConstruct?: string | null,
144
+ ): ConstructCheckOutcome {
145
+ const aug = augmentedConstruct?.trim();
146
+ if (!claimed || claimed === 'external') return { match: true };
147
+ const augConfirms = !!aug && aug === claimed;
148
+ if (inferred === 'unknown') {
149
+ return augConfirms ? { match: true, augmentedBy: aug } : { match: null };
150
+ }
151
+ if (constructsMatch(claimed, inferred)) return { match: true };
152
+ return augConfirms ? { match: true, augmentedBy: aug } : { match: false };
153
+ }
@@ -61,8 +61,13 @@ export {
61
61
  export type {
62
62
  InferredGraphifyConstruct,
63
63
  InferConstructFromGraphifyResult,
64
+ ConstructCheckOutcome,
65
+ } from './construct';
66
+ export {
67
+ inferConstructFromGraphify,
68
+ constructsMatch,
69
+ resolveConstructMatch,
64
70
  } from './construct';
65
- export { inferConstructFromGraphify, constructsMatch } from './construct';
66
71
  export type {
67
72
  GraphifyInferredSignature,
68
73
  ClaimedSignature,
package/src/index.ts CHANGED
@@ -16,6 +16,14 @@ export type {
16
16
  SessionEventFeedRow,
17
17
  } from './components/session-events';
18
18
 
19
+ // Maintain event log — presentational live OpenCode agent-run event feed
20
+ export { MaintainEventLog, MaintainLivePanel } from './components/maintain-events';
21
+ export type {
22
+ MaintainEventLogEvent,
23
+ MaintainEventLogProps,
24
+ MaintainLivePanelProps,
25
+ } from './components/maintain-events';
26
+
19
27
  // Graphify integration types (graph.json data model)
20
28
  export type {
21
29
  JsonValue,
@@ -62,6 +70,7 @@ export {
62
70
  symbolLabelVariants,
63
71
  inferConstructFromGraphify,
64
72
  constructsMatch,
73
+ resolveConstructMatch,
65
74
  extractNamedTypes,
66
75
  extractGraphifySignature,
67
76
  compareSignatures,
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Maintain Event Log — presentational live agent-run event feed.
3
+ *
4
+ * The `Live` story replays a fixture derived from a real OpenCode V2 agent
5
+ * session (`packages/subsystems-core/src/opencode/__fixtures__/session-events.json`),
6
+ * mapped to the live-feed vocabulary (`session.next.*` / `session.status`) and
7
+ * summarized the same way the host's `/api/event` subscription is
8
+ * (`summarizeEvent` in `subsystems-studio/src/bun/opencode-v2-live.ts`).
9
+ */
10
+ import React from 'react';
11
+ import type { Meta, StoryObj } from '@storybook/react';
12
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
13
+ import {
14
+ MaintainEventLog,
15
+ type MaintainEventLogEvent,
16
+ } from '../../components/maintain-events';
17
+ import liveEvents from '../data/maintain-live-events.json';
18
+
19
+ const events = liveEvents as MaintainEventLogEvent[];
20
+
21
+ const meta = {
22
+ title: 'Agent Sessions/Maintain Event Log',
23
+ component: MaintainEventLog,
24
+ parameters: { layout: 'fullscreen' },
25
+ tags: ['autodocs'],
26
+ decorators: [
27
+ (Story) => (
28
+ <ThemeProvider theme={defaultEditorTheme}>
29
+ <div
30
+ style={{
31
+ height: '100vh',
32
+ background: defaultEditorTheme.colors.background,
33
+ color: defaultEditorTheme.colors.text,
34
+ }}
35
+ >
36
+ <Story />
37
+ </div>
38
+ </ThemeProvider>
39
+ ),
40
+ ],
41
+ args: {
42
+ title: 'Maintain events',
43
+ agent: 'construct-verifier',
44
+ sessionId: 'ses_056b1f037ffeu1d00DzvJBrHq9',
45
+ status: 'running',
46
+ events,
47
+ total: events.length,
48
+ },
49
+ } satisfies Meta<typeof MaintainEventLog>;
50
+
51
+ export default meta;
52
+ type Story = StoryObj<typeof meta>;
53
+
54
+ export const Live: Story = {};
55
+
56
+ export const WaitingForSession: Story = {
57
+ args: { sessionId: undefined, events: [], total: 0, status: 'starting' },
58
+ };
59
+
60
+ export const WaitingForEvents: Story = {
61
+ args: { events: [], total: 0 },
62
+ };
63
+
64
+ export const WithError: Story = {
65
+ args: { error: 'live event stream dropped — reconnecting and reconciling with server state' },
66
+ };
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Maintain Live Panel — the collapsible half-height live-agent-run panel that
3
+ * `SubsystemComponentGraph` renders over its canvas via the `liveEvents` prop.
4
+ *
5
+ * The fixture replays a real OpenCode V2 agent session
6
+ * (`packages/subsystems-core/src/opencode/__fixtures__/session-events.json`),
7
+ * mapped to the live-feed vocabulary and summarized like the host's
8
+ * `/api/event` subscription.
9
+ */
10
+ import React from 'react';
11
+ import type { Meta, StoryObj } from '@storybook/react';
12
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
13
+ import {
14
+ MaintainLivePanel,
15
+ type MaintainEventLogEvent,
16
+ } from '../../components/maintain-events';
17
+ import liveEvents from '../data/maintain-live-events.json';
18
+
19
+ const events = liveEvents as MaintainEventLogEvent[];
20
+
21
+ const meta = {
22
+ title: 'Agent Sessions/Maintain Live Panel',
23
+ component: MaintainLivePanel,
24
+ parameters: { layout: 'fullscreen' },
25
+ tags: ['autodocs'],
26
+ decorators: [
27
+ (Story) => (
28
+ <ThemeProvider theme={defaultEditorTheme}>
29
+ <div
30
+ style={{
31
+ position: 'relative',
32
+ height: '100vh',
33
+ overflow: 'hidden',
34
+ background: defaultEditorTheme.colors.background,
35
+ color: defaultEditorTheme.colors.text,
36
+ }}
37
+ >
38
+ <div
39
+ style={{
40
+ position: 'absolute',
41
+ inset: 0,
42
+ display: 'grid',
43
+ placeItems: 'center',
44
+ color: defaultEditorTheme.colors.textMuted,
45
+ fontFamily: defaultEditorTheme.fonts.monospace,
46
+ fontSize: 13,
47
+ }}
48
+ >
49
+ graph canvas
50
+ </div>
51
+ <Story />
52
+ </div>
53
+ </ThemeProvider>
54
+ ),
55
+ ],
56
+ args: {
57
+ title: 'Maintain events',
58
+ agent: 'construct-verifier',
59
+ sessionId: 'ses_056b1f037ffeu1d00DzvJBrHq9',
60
+ status: 'running',
61
+ events,
62
+ total: events.length,
63
+ },
64
+ } satisfies Meta<typeof MaintainLivePanel>;
65
+
66
+ export default meta;
67
+ type Story = StoryObj<typeof meta>;
68
+
69
+ export const Expanded: Story = {};
70
+
71
+ export const Collapsed: Story = {
72
+ args: { defaultCollapsed: true },
73
+ };
74
+
75
+ export const WaitingForEvents: Story = {
76
+ args: { events: [], total: 0 },
77
+ };