@principal-ai/subsystems-react 0.43.2 → 0.44.1

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.
@@ -0,0 +1,143 @@
1
+ import type { Meta, StoryObj } from '@storybook/react';
2
+ import { useEffect, useMemo, useState } from 'react';
3
+ import { ThemeProvider, defaultEditorTheme } from '@principal-ade/industry-theme';
4
+ import { SubsystemComponentGraph } from '../../../subsystem/SubsystemComponentGraph';
5
+ import {
6
+ SubsystemModelTransition,
7
+ type SubsystemTransitionStep,
8
+ } from '../../../subsystem/SubsystemModelTransition';
9
+ import type { SubsystemComponent, SubsystemWalkthrough } from '../../../subsystem/model';
10
+
11
+ /**
12
+ * Harness for `SubsystemModelTransition` — the same progression cases
13
+ * (constructs → static → dynamic → walkthrough) animated between, with a nav.
14
+ */
15
+
16
+ const meta = {
17
+ title: 'Subsystem/ComponentGraph/Animated progression',
18
+ component: SubsystemComponentGraph,
19
+ parameters: { layout: 'fullscreen' },
20
+ } satisfies Meta<typeof SubsystemComponentGraph>;
21
+
22
+ export default meta;
23
+ type Story = StoryObj<typeof meta>;
24
+
25
+ const PURL = 'pkg:github/you/scheduling-app';
26
+
27
+ const components: SubsystemComponent[] = [
28
+ { alias: 'booking-page', name: 'BookingPage', construct: 'function', symbol: 'BookingPage', role: 'entry', framework: 'react', stereotype: 'component', purl: PURL, file: 'app/book/page.tsx', module: 'app/book/page.tsx', process: 'booking-web/client' },
29
+ { alias: 'list-open-slots', name: 'listOpenSlots', construct: 'function', symbol: 'listOpenSlots', role: 'entry', framework: 'next', stereotype: 'server-action', purl: PURL, file: 'app/book/actions.ts', module: 'app/book/actions.ts', process: 'booking-web/server' },
30
+ { alias: 'book-slot', name: 'bookSlot', construct: 'function', symbol: 'bookSlot', role: 'entry', framework: 'next', stereotype: 'server-action', purl: PURL, file: 'app/book/actions.ts', module: 'app/book/actions.ts', process: 'booking-web/server' },
31
+ { alias: 'list-slots', name: 'listSlots', construct: 'function', symbol: 'listSlots', purl: PURL, file: 'lib/listSlots.ts', module: 'lib/listSlots.ts', process: 'booking-web/server' },
32
+ { alias: 'create-booking', name: 'createBooking', construct: 'function', symbol: 'createBooking', purl: PURL, file: 'lib/createBooking.ts', module: 'lib/createBooking.ts', process: 'booking-web/server' },
33
+ { alias: 'capture-event', name: 'captureEvent', construct: 'function', symbol: 'captureEvent', purl: PURL, file: 'lib/captureEvent.ts', module: 'lib/captureEvent.ts', process: 'booking-web/client' },
34
+ { alias: 'Database', name: 'Database', construct: 'external', role: 'service', purl: 'external', file: '' },
35
+ ];
36
+
37
+ const walkthroughs: SubsystemWalkthrough[] = [
38
+ {
39
+ id: 'wt-book',
40
+ title: 'Guest books',
41
+ steps: [
42
+ { from: 'booking-page', to: 'book-slot', mechanism: 'calls', file: 'app/book/page.tsx', line: 24, purl: PURL, symbol: 'bookSlot' },
43
+ { from: 'book-slot', to: 'create-booking', mechanism: 'calls', file: 'app/book/actions.ts', line: 22, purl: PURL, symbol: 'createBooking' },
44
+ { from: 'create-booking', to: 'Database', mechanism: 'writes', file: 'lib/createBooking.ts', line: 13, purl: PURL, symbol: 'insert' },
45
+ { from: 'booking-page', to: 'capture-event', mechanism: 'calls', file: 'app/book/page.tsx', line: 26, purl: PURL, symbol: 'captureEvent' },
46
+ ],
47
+ },
48
+ {
49
+ id: 'wt-pick',
50
+ title: 'Guest picks a slot',
51
+ steps: [
52
+ { from: 'booking-page', to: 'list-open-slots', mechanism: 'calls', file: 'app/book/page.tsx', line: 17, purl: PURL, symbol: 'listOpenSlots' },
53
+ { from: 'list-open-slots', to: 'list-slots', mechanism: 'calls', file: 'app/book/actions.ts', line: 14, purl: PURL, symbol: 'listSlots' },
54
+ { from: 'list-slots', to: 'Database', mechanism: 'reads', file: 'lib/listSlots.ts', line: 9, purl: PURL, symbol: 'findOpen' },
55
+ ],
56
+ },
57
+ ];
58
+
59
+ const LABELS = ['Constructs', 'Static topology', 'Dynamic topology', 'Walkthrough'];
60
+
61
+ const STEPS: SubsystemTransitionStep[] = [
62
+ {
63
+ model: { title: 'Booking', components: components.map(({ module: _m, process: _p, ...rest }) => rest) },
64
+ },
65
+ {
66
+ model: { title: 'Booking', components: components.map(({ process: _p, ...rest }) => rest) },
67
+ moduleNesting: 'path',
68
+ },
69
+ {
70
+ // Dynamic topology = process framing only (no runtime edges).
71
+ model: { title: 'Booking', components },
72
+ moduleNesting: 'path',
73
+ },
74
+ {
75
+ model: { title: 'Booking', components, walkthroughs },
76
+ moduleNesting: 'path',
77
+ autoPlayWalkthroughs: true,
78
+ showWalkthroughTitle: true,
79
+ },
80
+ ];
81
+
82
+ const STEP_MS = 4000;
83
+
84
+ /** Dwell long enough for an autoplaying step to cycle all its hops. */
85
+ function dwellFor(index: number): number {
86
+ const step = STEPS[index];
87
+ if (step?.autoPlayWalkthroughs) {
88
+ const hops = step.model.walkthroughs?.reduce((n, w) => n + w.steps.length, 0) ?? 0;
89
+ // Whole-flow phase + one tick per hop.
90
+ if (hops > 0) return (hops + 1) * STEP_MS;
91
+ }
92
+ return 3200;
93
+ }
94
+
95
+ function Harness() {
96
+ const [active, setActive] = useState(0);
97
+ const steps = useMemo(() => STEPS, []);
98
+
99
+ useEffect(() => {
100
+ const t = window.setTimeout(
101
+ () => setActive((i) => (i + 1) % STEPS.length),
102
+ dwellFor(active),
103
+ );
104
+ return () => window.clearTimeout(t);
105
+ }, [active]);
106
+
107
+ return (
108
+ <div style={{ position: 'relative', width: '100%', height: '100vh', background: defaultEditorTheme.colors.background }}>
109
+ <SubsystemModelTransition
110
+ steps={steps}
111
+ activeIndex={active}
112
+ walkthroughAutoPlayIntervalMs={STEP_MS}
113
+ />
114
+ <div style={{ position: 'absolute', top: 12, left: 12, display: 'flex', gap: 8, zIndex: 10 }}>
115
+ {LABELS.map((label, i) => (
116
+ <button
117
+ key={label}
118
+ onClick={() => setActive(i)}
119
+ style={{
120
+ padding: '6px 12px',
121
+ borderRadius: 6,
122
+ border: `1px solid ${i === active ? defaultEditorTheme.colors.primary : '#555'}`,
123
+ background: i === active ? defaultEditorTheme.colors.primary : 'transparent',
124
+ color: i === active ? '#fff' : defaultEditorTheme.colors.text,
125
+ cursor: 'pointer',
126
+ font: '600 12px system-ui',
127
+ }}
128
+ >
129
+ {label}
130
+ </button>
131
+ ))}
132
+ </div>
133
+ </div>
134
+ );
135
+ }
136
+
137
+ export const Cases: Story = {
138
+ render: () => (
139
+ <ThemeProvider theme={defaultEditorTheme}>
140
+ <Harness />
141
+ </ThemeProvider>
142
+ ),
143
+ };