@oneie/claude 0.6.0 → 0.7.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 (111) hide show
  1. package/agents/w1-recon.md +9 -4
  2. package/agents/w2-decide.md +51 -28
  3. package/agents/w3-edit.md +70 -78
  4. package/agents/w4-verify.md +164 -69
  5. package/commands/browser.md +98 -35
  6. package/commands/cc-connect.md +6 -0
  7. package/commands/chat.md +13 -1
  8. package/commands/close.md +45 -19
  9. package/commands/create.md +20 -10
  10. package/commands/db-sync.md +5 -1
  11. package/commands/deploy.md +174 -87
  12. package/commands/do-autonomous.md +31 -20
  13. package/commands/do-improve.md +4 -4
  14. package/commands/do-show.md +4 -3
  15. package/commands/do.md +53 -13
  16. package/commands/go.md +75 -0
  17. package/commands/improve.md +4 -2
  18. package/commands/kill.md +6 -8
  19. package/commands/notify.md +15 -2
  20. package/commands/oo-push.md +24 -9
  21. package/commands/rag.md +6 -0
  22. package/commands/release.md +46 -25
  23. package/commands/restart.md +5 -4
  24. package/commands/see.md +45 -35
  25. package/commands/skill-create.md +38 -5
  26. package/commands/sync.md +21 -11
  27. package/hooks/scripts/auto-continue.sh +70 -0
  28. package/hooks/scripts/branch-pin.sh +85 -0
  29. package/hooks/scripts/git-add-guard.sh +16 -5
  30. package/hooks/scripts/post-edit-check.sh +16 -0
  31. package/hooks/scripts/session-start.sh +13 -8
  32. package/hooks/scripts/sync-priority-todo.sh +5 -0
  33. package/package.json +1 -1
  34. package/rules/api.md +9 -0
  35. package/rules/design.md +13 -3
  36. package/rules/documentation.md +30 -71
  37. package/rules/engine.md +8 -1
  38. package/rules/ui.md +5 -1
  39. package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
  40. package/scripts/browser-check.mjs +44 -103
  41. package/scripts/cc-events-proof.sh +13 -12
  42. package/scripts/chrome.mjs +381 -0
  43. package/scripts/do-analyze.sh +109 -4
  44. package/scripts/do-auto.sh +350 -43
  45. package/scripts/do-consumer-sweep.sh +72 -0
  46. package/scripts/do-derives-check.sh +88 -0
  47. package/scripts/do-fleet.sh +50 -1
  48. package/scripts/do-killswitch-audit.py +33 -14
  49. package/scripts/do-promise-lint.sh +150 -4
  50. package/scripts/do-promise-settle.sh +131 -9
  51. package/scripts/do-prove.sh +197 -17
  52. package/scripts/do-rank.py +606 -20
  53. package/scripts/do-rubric.py +21 -2
  54. package/scripts/do-signal.sh +157 -17
  55. package/scripts/do-smoke.sh +9 -0
  56. package/scripts/do-substrate-check.sh +49 -0
  57. package/scripts/do-tasks-wire-check.sh +81 -0
  58. package/scripts/do-tier.sh +90 -5
  59. package/scripts/do-ui-gate.sh +332 -0
  60. package/scripts/do-untracked-gate.sh +231 -0
  61. package/scripts/do-walk.sh +344 -0
  62. package/scripts/factory-check.sh +747 -0
  63. package/scripts/factory-repo.sh +1361 -0
  64. package/scripts/factory-tasks-check.sh +85 -0
  65. package/scripts/outcome-pull.ts +279 -0
  66. package/scripts/promise-manifest.mjs +167 -0
  67. package/scripts/reconcile-allow.txt +5 -0
  68. package/scripts/rubric-weights.json +64 -0
  69. package/scripts/spine-canary.sh +20 -0
  70. package/scripts/typedb-cluster-status.sh +128 -0
  71. package/scripts/typedb-env.sh +182 -0
  72. package/scripts/typedb-flap-recorder.sh +85 -0
  73. package/scripts/typedb-probes/containment-probe.py +88 -0
  74. package/scripts/typedb-probes/panic-probe-version.py +92 -0
  75. package/scripts/typedb-probes/panic-probe.py +77 -0
  76. package/scripts/typedb-scratch.sh +73 -0
  77. package/scripts/urls-lint.sh +58 -0
  78. package/scripts/w4-rubric.ts +96 -12
  79. package/scripts/wf-check.mjs +75 -0
  80. package/skills/ai-ui/SKILL.md +1 -1
  81. package/skills/astro/SKILL.md +177 -132
  82. package/skills/astro/add-content-collection.md +29 -13
  83. package/skills/astro/check-build.md +19 -13
  84. package/skills/astro/create-component.md +36 -9
  85. package/skills/astro/create-page.md +39 -10
  86. package/skills/astro/optimize-performance.md +36 -8
  87. package/skills/cli/SKILL.md +254 -220
  88. package/skills/dev/SKILL.md +1 -1
  89. package/skills/directory-autofill/SKILL.md +68 -0
  90. package/skills/docs/SKILL.md +4 -4
  91. package/skills/mcp/SKILL.md +186 -156
  92. package/skills/promise-make/SKILL.md +2 -2
  93. package/skills/puck/SKILL.md +465 -250
  94. package/skills/react19/SKILL.md +196 -296
  95. package/skills/reactflow/SKILL.md +387 -412
  96. package/skills/sdk/SKILL.md +167 -106
  97. package/skills/shadcn/SKILL.md +245 -201
  98. package/skills/signal/SKILL.md +5 -0
  99. package/skills/sui/SKILL.md +6 -1
  100. package/skills/tutorial/SKILL.md +15 -0
  101. package/skills/typedb/SKILL.md +427 -217
  102. package/skills/typedb/examples/python-patterns.py +11 -4
  103. package/skills/typedb/examples/query-patterns.tql +8 -3
  104. package/skills/typedb/examples/schema-patterns.tql +4 -2
  105. package/skills/typedb/reference/migration-2x-3x.md +19 -1
  106. package/skills/typedb/reference/python-driver.md +17 -1
  107. package/skills/voice/SKILL.md +117 -0
  108. package/skills/writer/SKILL.md +29 -12
  109. package/templates/template-feature.md +26 -1
  110. package/templates/template-tests.md +4 -0
  111. package/templates/template-todo.md +65 -10
@@ -1,177 +1,198 @@
1
1
  ---
2
2
  name: react19
3
- description: React 19 patterns including Actions, use() hook, transitions, ref as prop, and optimistic updates for the Envelope System
3
+ description: React 19 patterns as they are actually used in `one.ie/web` — Actions/useActionState, use(), useTransition, useOptimistic, ref-as-prop, document metadata — plus the repo's real async surfaces (props from the Astro page, SSE via useSurfaceRefresh, fetch-in-effect). Use when writing or reviewing a .tsx island in one.ie/web, choosing between a transition and an Action for a form, or deciding how a client component gets its data.
4
4
  user-invocable: true
5
5
  allowed-tools: Read, Write, Edit, Glob, Grep
6
6
  ---
7
7
 
8
8
  # React 19 Development
9
9
 
10
- Modern React 19 patterns for building interactive components in the Envelope System.
10
+ React 19 patterns for the interactive islands in `one.ie/web/src/components/`.
11
+
12
+ Installed: **React 19.2.6** (declared `^19.1.0`), rendered by **Astro 6.3.7** on
13
+ Cloudflare Workers. `template/site` is a separate tree on Astro 7 — use the
14
+ `template:react19` skill there.
15
+
16
+ For prop typing, file structure, `cn()` styling, `useState`/`useReducer` choice,
17
+ and named-vs-default exports, read `.claude/rules/react.md`. It auto-loads on
18
+ every `*.tsx` and is already in context when you edit — this skill does not
19
+ repeat it.
11
20
 
12
21
  ## Works With
13
22
 
14
23
  | Skill | Load when |
15
24
  |--------------|----------------------------------------------------------------------------------|
16
- | `/signal` | Any `onClick` every click emits `ui:<surface>:<action>` via `emitClick()`. See `.claude/rules/ui.md`. |
17
- | `/astro` | The component lives in an Astro page — hydration directives (`client:load`, `client:only`) decide worker bundle size. |
18
- | `/shadcn` | Using Cards, Tabs, Badges, Dialog shadcn components are React components with ref-as-prop. |
19
- | `/ai-ui` | Agent reasoning UIs, tool-call visualization, generative components composed from shadcn. |
20
- | `/reactflow` | Graph visualizations — ColonyGraph, PheromoneGraph read substrate highways. |
25
+ | `/shadcn` | Using Card, Tabs, Badge, Dialog primitives under the 6-token system. |
26
+ | `/astro` | The component lives in an Astro page — hydration directives (`client:load`, `client:idle`, `client:only`) decide worker bundle size. |
27
+ | `/puck` | Block `render` fns are React components; the editor is a `client:only` island. |
28
+ | `/reactflow` | Graph islands — WorkflowFlow, OrgChartView, LifecycleFlow, PathGraph. |
29
+ | `/ai-ui` | Agent reasoning UIs, tool-call visualization, generative components. |
30
+
31
+ Every semantic `onClick` emits `emitClick('ui:<surface>:<action>')` from
32
+ `@/lib/ui-signal`. That contract lives in `.claude/rules/ui.md`, which auto-loads
33
+ on `one.ie/web/src/components/**/*.tsx`. There is no `/signal` skill.
21
34
 
22
- Auto-loads on `*.tsx`: `rules/react.md` + `rules/ui.md` (per `.claude/settings.json`).
35
+ Rules auto-load via each rule file's own `paths:` frontmatter (not
36
+ `settings.json`, which wires hooks only): `rules/react.md` on `**/*.tsx`,
37
+ `rules/ui.md` on `one.ie/web/src/components/**/*.tsx`, `rules/design.md` on
38
+ `one.ie/web/**/*.{tsx,astro,css}`.
23
39
 
24
40
  ## When to Use This Skill
25
41
 
26
- - Build React components in `src/components/`
42
+ - Build a React island in `one.ie/web/src/components/`
27
43
  - Implement form handling with Actions
28
- - Use the new `use()` hook for promises/context
44
+ - Use `use()` for promises/context
29
45
  - Apply transitions for non-blocking updates
30
- - Implement optimistic UI patterns
46
+ - Decide how a client component gets its data (SSE, fetch-in-effect, or props)
31
47
 
32
48
  ## Key React 19 Features
33
49
 
50
+ Adoption in `one.ie/web` is deliberate, not uniform. Before reaching for a
51
+ feature, know where it already lives:
52
+
53
+ | Feature | Files using it in `one.ie/web/src` |
54
+ |---|---|
55
+ | `useTransition` | 7 — the auth forms, `SubscribeForm`, `CampaignCardRenderer` |
56
+ | `useActionState` | 1 — `components/peer/VideoRoomManager.tsx` |
57
+ | `useDeferredValue` | 2 |
58
+ | `useSyncExternalStore` | 2 |
59
+ | `useReducer` | 1 |
60
+ | `useOptimistic` | **0** — no in-repo precedent |
61
+ | `forwardRef` | **0** — fully migrated to ref-as-prop |
62
+
34
63
  ### 1. Actions (Form Handling)
35
64
 
36
- Actions replace manual form handling. They work with `<form action={...}>`:
65
+ `useActionState` returns `[state, dispatch, isPending]`. The real usage in
66
+ `VideoRoomManager.tsx` posts through `ask()` from `@/lib/in/ask`:
37
67
 
38
68
  ```tsx
39
- // src/components/envelopes/EnvelopeSubmitter.tsx
40
69
  import { useActionState } from 'react';
70
+ import { ask } from '@/lib/in/ask';
41
71
 
42
- interface EnvelopeFormState {
43
- success: boolean;
72
+ interface CreateRoomState {
73
+ ok: boolean;
44
74
  error?: string;
45
- envelopeId?: string;
75
+ slug?: string;
46
76
  }
47
77
 
48
- async function submitEnvelope(
49
- prevState: EnvelopeFormState,
78
+ async function createRoom(
79
+ _prev: CreateRoomState | null,
50
80
  formData: FormData
51
- ): Promise<EnvelopeFormState> {
52
- const action = formData.get('action') as string;
53
- const target = formData.get('target') as string;
54
-
81
+ ): Promise<CreateRoomState> {
82
+ const name = formData.get('name') as string;
83
+ const slug = formData.get('slug') as string;
55
84
  try {
56
- // Process envelope
57
- const envelope = createEnvelope({ action, target });
58
- return { success: true, envelopeId: envelope.id };
85
+ await ask('room:create', { name, slug });
86
+ return { ok: true, slug };
59
87
  } catch (e) {
60
- return { success: false, error: (e as Error).message };
88
+ return { ok: false, error: (e as Error).message };
61
89
  }
62
90
  }
63
91
 
64
- export function EnvelopeSubmitter() {
65
- const [state, formAction, isPending] = useActionState(
66
- submitEnvelope,
67
- { success: false }
68
- );
92
+ export function CreateRoomForm() {
93
+ const [state, dispatch, isPending] = useActionState(createRoom, null);
69
94
 
70
95
  return (
71
- <form action={formAction}>
72
- <input name="action" placeholder="Action name" disabled={isPending} />
73
- <input name="target" placeholder="Target agent" disabled={isPending} />
96
+ <form action={dispatch} className="space-y-3">
97
+ <input name="name" placeholder="Room name" disabled={isPending} />
98
+ <input name="slug" placeholder="room-slug" disabled={isPending} />
74
99
  <button type="submit" disabled={isPending}>
75
- {isPending ? 'Sending...' : 'Send Envelope'}
100
+ {isPending ? 'Creating…' : 'Create room'}
76
101
  </button>
77
- {state.error && <p className="text-red-500">{state.error}</p>}
78
- {state.success && <p className="text-green-500">Sent: {state.envelopeId}</p>}
102
+ {state?.error && <p className="text-destructive text-sm">{state.error}</p>}
103
+ {state?.ok && <p className="text-success text-sm">Created {state.slug}</p>}
79
104
  </form>
80
105
  );
81
106
  }
82
107
  ```
83
108
 
109
+ The initial state is `null` in the real call, so every read is `state?.field`.
110
+ If you initialise with an object instead, type it and drop the optional chain —
111
+ but be consistent within one component.
112
+
84
113
  ### 2. use() Hook
85
114
 
86
- Read promises and context directly with `use()`:
115
+ `use()` reads a promise or a context. **No component in `one.ie/web` uses it
116
+ today** — islands hydrate with props from the Astro page, or fetch in an effect.
117
+ Introduce it only when you also introduce the `<Suspense>` boundary and a stable
118
+ promise; a promise recreated each render suspends forever.
87
119
 
88
120
  ```tsx
89
- // Reading a promise
90
121
  import { use, Suspense } from 'react';
91
122
 
92
- interface RuntimeState {
93
- agents: Agent[];
94
- promises: AgentPromise[];
95
- }
123
+ interface Stats { actors: number; signals: number }
96
124
 
97
- function RuntimeDisplay({ runtimePromise }: { runtimePromise: Promise<RuntimeState> }) {
98
- const runtime = use(runtimePromise); // Suspends until resolved
125
+ function StatsDisplay({ statsPromise }: { statsPromise: Promise<Stats> }) {
126
+ const stats = use(statsPromise); // suspends until resolved
99
127
 
100
128
  return (
101
129
  <div>
102
- <h2>Agents: {runtime.agents.length}</h2>
103
- <h2>Promises: {runtime.promises.length}</h2>
130
+ <h2>Actors: {stats.actors}</h2>
131
+ <h2>Signals: {stats.signals}</h2>
104
132
  </div>
105
133
  );
106
134
  }
107
135
 
108
- // Wrap with Suspense
109
- export function RuntimeContainer() {
110
- const runtimePromise = fetchRuntimeState();
111
-
136
+ // The promise must be created OUTSIDE the suspending component —
137
+ // created in render, it is a new promise every attempt.
138
+ export function StatsContainer({ statsPromise }: { statsPromise: Promise<Stats> }) {
112
139
  return (
113
- <Suspense fallback={<div>Loading runtime...</div>}>
114
- <RuntimeDisplay runtimePromise={runtimePromise} />
140
+ <Suspense fallback={<div>Loading…</div>}>
141
+ <StatsDisplay statsPromise={statsPromise} />
115
142
  </Suspense>
116
143
  );
117
144
  }
118
145
  ```
119
146
 
120
- ```tsx
121
- // Reading context conditionally
122
- import { use, createContext } from 'react';
123
-
124
- const RuntimeContext = createContext<Runtime | null>(null);
147
+ Reading context with `use()` is the safe half — it may be called conditionally,
148
+ unlike `useContext`:
125
149
 
126
- function AgentCard({ agentId }: { agentId: string }) {
127
- const runtime = use(RuntimeContext);
128
- if (!runtime) return null;
150
+ ```tsx
151
+ import { use } from 'react';
152
+ import { EditorSlugContext } from '@/components/puck/editor-slug-context';
129
153
 
130
- const agent = runtime.getAgent(agentId);
131
- return <div>{agent.name}</div>;
154
+ function BlockInspector({ enabled }: { enabled: boolean }) {
155
+ if (!enabled) return null;
156
+ const slug = use(EditorSlugContext); // legal after an early return
157
+ return <span>{slug}</span>;
132
158
  }
133
159
  ```
134
160
 
135
161
  ### 3. Transitions
136
162
 
137
- Non-blocking state updates with `useTransition`:
163
+ The repo's most-used React 19 primitive. `SubscribeForm` and the auth pages wrap
164
+ their submit in `startTransition` so the button stays responsive:
138
165
 
139
166
  ```tsx
140
167
  import { useState, useTransition } from 'react';
168
+ import { emitClick } from '@/lib/ui-signal';
141
169
 
142
- interface Agent {
143
- id: string;
144
- name: string;
145
- envelopes: Envelope[];
146
- }
170
+ interface Actor { id: string; name: string }
147
171
 
148
- export function AgentTabs({ agents }: { agents: Agent[] }) {
149
- const [selectedAgent, setSelectedAgent] = useState(agents[0]?.id);
172
+ export function ActorTabs({ actors }: { actors: Actor[] }) {
173
+ const [selected, setSelected] = useState(actors[0]?.id);
150
174
  const [isPending, startTransition] = useTransition();
151
175
 
152
- const handleTabChange = (agentId: string) => {
153
- startTransition(() => {
154
- setSelectedAgent(agentId);
155
- });
156
- };
157
-
158
- const agent = agents.find(a => a.id === selectedAgent);
176
+ const actor = actors.find(a => a.id === selected);
159
177
 
160
178
  return (
161
179
  <div>
162
- <div className="tabs">
163
- {agents.map(a => (
180
+ <div className="flex gap-2">
181
+ {actors.map(a => (
164
182
  <button
165
183
  key={a.id}
166
- onClick={() => handleTabChange(a.id)}
167
- className={a.id === selectedAgent ? 'active' : ''}
184
+ onClick={() => {
185
+ emitClick('ui:actors:select', { id: a.id });
186
+ startTransition(() => setSelected(a.id));
187
+ }}
188
+ className={a.id === selected ? 'bg-primary text-on-primary' : ''}
168
189
  >
169
190
  {a.name}
170
191
  </button>
171
192
  ))}
172
193
  </div>
173
194
  <div className={isPending ? 'opacity-50' : ''}>
174
- {agent && <AgentContent agent={agent} />}
195
+ {actor && <ActorContent actor={actor} />}
175
196
  </div>
176
197
  </div>
177
198
  );
@@ -180,45 +201,40 @@ export function AgentTabs({ agents }: { agents: Agent[] }) {
180
201
 
181
202
  ### 4. Optimistic Updates
182
203
 
183
- Show expected state before server confirms:
204
+ `useOptimistic` has **no precedent in this repo** — treat an introduction as a
205
+ new pattern and keep it inside one component. It only reverts correctly when the
206
+ update runs inside an Action or a transition; calling it from a bare event
207
+ handler throws.
184
208
 
185
209
  ```tsx
186
210
  import { useOptimistic } from 'react';
211
+ import { ask } from '@/lib/in/ask';
187
212
 
188
- interface Envelope {
189
- id: string;
190
- status: 'pending' | 'resolved' | 'rejected';
191
- }
213
+ interface Task { id: string; title: string; status: string }
192
214
 
193
- export function EnvelopeList({ envelopes: initialEnvelopes }: { envelopes: Envelope[] }) {
194
- const [optimisticEnvelopes, addOptimisticEnvelope] = useOptimistic(
195
- initialEnvelopes,
196
- (state, newEnvelope: Envelope) => [...state, newEnvelope]
215
+ export function TaskList({ tasks }: { tasks: Task[] }) {
216
+ const [optimisticTasks, addOptimisticTask] = useOptimistic(
217
+ tasks,
218
+ (state: Task[], next: Task) => [...state, next]
197
219
  );
198
220
 
199
- async function sendEnvelope(formData: FormData) {
200
- const tempEnvelope: Envelope = {
201
- id: `temp-${Date.now()}`,
202
- status: 'pending',
203
- };
204
-
205
- // Show optimistically
206
- addOptimisticEnvelope(tempEnvelope);
207
-
208
- // Actually send
209
- await submitEnvelope(formData);
221
+ // Must be a form action (or wrapped in startTransition) — not a plain onClick.
222
+ async function createTask(formData: FormData) {
223
+ const title = formData.get('title') as string;
224
+ addOptimisticTask({ id: `temp-${Date.now()}`, title, status: 'pending' });
225
+ await ask('tasks:create', { title });
210
226
  }
211
227
 
212
228
  return (
213
229
  <div>
214
- <form action={sendEnvelope}>
215
- <input name="action" />
216
- <button type="submit">Send</button>
230
+ <form action={createTask}>
231
+ <input name="title" />
232
+ <button type="submit">Add</button>
217
233
  </form>
218
234
  <ul>
219
- {optimisticEnvelopes.map(env => (
220
- <li key={env.id} className={env.id.startsWith('temp-') ? 'opacity-50' : ''}>
221
- {env.id}: {env.status}
235
+ {optimisticTasks.map(t => (
236
+ <li key={t.id} className={t.id.startsWith('temp-') ? 'opacity-50' : ''}>
237
+ {t.title}: {t.status}
222
238
  </li>
223
239
  ))}
224
240
  </ul>
@@ -227,17 +243,15 @@ export function EnvelopeList({ envelopes: initialEnvelopes }: { envelopes: Envel
227
243
  }
228
244
  ```
229
245
 
246
+ The optimistic entry disappears on its own when the Action settles and the real
247
+ `tasks` prop updates — do not remove it by hand.
248
+
230
249
  ### 5. ref as Prop
231
250
 
232
- No more `forwardRef` needed in React 19:
251
+ `forwardRef` is fully retired here — zero occurrences in
252
+ `one.ie/web/src/components/`. Declare `ref` as an ordinary prop:
233
253
 
234
254
  ```tsx
235
- // Before (React 18)
236
- const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => (
237
- <input ref={ref} {...props} />
238
- ));
239
-
240
- // After (React 19)
241
255
  interface InputProps {
242
256
  ref?: React.Ref<HTMLInputElement>;
243
257
  placeholder?: string;
@@ -247,24 +261,27 @@ function Input({ ref, ...props }: InputProps) {
247
261
  return <input ref={ref} {...props} />;
248
262
  }
249
263
 
250
- // Usage
251
264
  function Form() {
252
265
  const inputRef = useRef<HTMLInputElement>(null);
253
- return <Input ref={inputRef} placeholder="Enter action" />;
266
+ return <Input ref={inputRef} placeholder="Receiver" />;
254
267
  }
255
268
  ```
256
269
 
257
270
  ### 6. Document Metadata
258
271
 
259
- Render `<title>` and `<meta>` anywhere:
272
+ React 19 hoists `<title>` and `<meta>` from anywhere in the tree. In this repo
273
+ that is almost always the wrong layer — the Astro page owns the head, and an
274
+ island rendered `client:only` will not produce metadata for the SSR response or
275
+ for a crawler. Use it only for a genuinely client-routed sub-view.
260
276
 
261
277
  ```tsx
262
- function AgentPage({ agent }: { agent: Agent }) {
278
+ interface Room { id: string; name: string }
279
+
280
+ function RoomView({ room }: { room: Room }) {
263
281
  return (
264
282
  <div>
265
- <title>{agent.name} | Envelope System</title>
266
- <meta name="description" content={`Agent ${agent.name} details`} />
267
- <h1>{agent.name}</h1>
283
+ <title>{room.name}</title>
284
+ <h1>{room.name}</h1>
268
285
  </div>
269
286
  );
270
287
  }
@@ -272,129 +289,74 @@ function AgentPage({ agent }: { agent: Agent }) {
272
289
 
273
290
  ## Project-Specific Patterns
274
291
 
275
- ### Runtime Context Provider
292
+ ### How an island gets its data
276
293
 
277
- ```tsx
278
- // src/components/providers/RuntimeProvider.tsx
279
- import { createContext, useState, useCallback, ReactNode } from 'react';
280
- import { Runtime } from '@/engine/Runtime';
281
-
282
- interface RuntimeContextValue {
283
- runtime: Runtime;
284
- refresh: () => void;
285
- uiSchema: object;
286
- }
287
-
288
- export const RuntimeContext = createContext<RuntimeContextValue | null>(null);
294
+ Three real paths, in order of preference:
289
295
 
290
- export function RuntimeProvider({ children }: { children: ReactNode }) {
291
- const [runtime] = useState(() => new Runtime());
292
- const [uiSchema, setUiSchema] = useState(() => runtime.toUISchema());
296
+ **1. Props from the Astro page (default).** The page runs SSR, queries, and
297
+ passes plain data across the island boundary. No client fetch, no loading state.
293
298
 
294
- const refresh = useCallback(() => {
295
- setUiSchema(runtime.toUISchema());
296
- }, [runtime]);
297
-
298
- return (
299
- <RuntimeContext.Provider value={{ runtime, refresh, uiSchema }}>
300
- {children}
301
- </RuntimeContext.Provider>
302
- );
303
- }
299
+ ```astro
300
+ ---
301
+ export const prerender = false;
302
+ import { OrgChartView } from '@/components/org/OrgChartView';
303
+ ---
304
+ <OrgChartView chart="marketing" client:only="react" />
304
305
  ```
305
306
 
306
- ### Envelope Form with Actions
307
+ **2. Live refresh over SSE — `useSurfaceRefresh`.** A resolver mutation from
308
+ anywhere (UI, chat, MCP, CLI) broadcasts one frame; every mounted surface on that
309
+ dimension re-pulls. This is the repo's answer to "keep the view fresh", and it
310
+ replaces polling:
307
311
 
308
312
  ```tsx
309
- // src/components/envelopes/CreateEnvelopeForm.tsx
310
- import { useActionState } from 'react';
311
- import { use } from 'react';
312
- import { RuntimeContext } from '@/components/providers/RuntimeProvider';
313
- import { createEnvelope } from '@/engine/Envelope';
313
+ import { useState, useCallback } from 'react';
314
+ import { useSurfaceRefresh } from '@/lib/use-surface-refresh';
314
315
 
315
- export function CreateEnvelopeForm() {
316
- const { runtime, refresh } = use(RuntimeContext)!;
316
+ interface Workflow { id: string; name: string }
317
317
 
318
- async function handleSubmit(prevState: any, formData: FormData) {
319
- const envelope = createEnvelope({
320
- action: formData.get('action') as string,
321
- inputs: JSON.parse(formData.get('inputs') as string || '{}'),
322
- sender: 'user',
323
- receiver: formData.get('receiver') as string,
324
- });
318
+ export function WorkflowList({ slug }: { slug: string }) {
319
+ const [rows, setRows] = useState<Workflow[]>([]);
325
320
 
326
- await runtime.send(envelope);
327
- refresh();
321
+ const reload = useCallback(() => {
322
+ fetch(`/api/workflows?slug=${slug}`)
323
+ .then(r => r.json() as Promise<{ data?: Workflow[] }>)
324
+ .then(d => setRows(d.data ?? []))
325
+ .catch(() => {});
326
+ }, [slug]);
328
327
 
329
- return { success: true, envelopeId: envelope.id };
330
- }
331
-
332
- const [state, formAction, isPending] = useActionState(handleSubmit, {});
328
+ useSurfaceRefresh(slug, 'workflows', reload);
333
329
 
334
- return (
335
- <form action={formAction} className="space-y-4">
336
- <input name="action" placeholder="Action" className="input" />
337
- <input name="receiver" placeholder="Target Agent" className="input" />
338
- <textarea name="inputs" placeholder='{"key": "value"}' className="textarea" />
339
- <button type="submit" disabled={isPending} className="btn">
340
- {isPending ? 'Sending...' : 'Send Envelope'}
341
- </button>
342
- </form>
343
- );
330
+ return <ul>{rows.map(w => <li key={w.id}>{w.name}</li>)}</ul>;
344
331
  }
345
332
  ```
346
333
 
347
- ### Promise Tracker with Transitions
334
+ `useSurfaceRefresh(slug, dimension, onRefresh)` subscribes to the `inbox:{slug}`
335
+ topic on `/api/analytics/watch` and fires only for `surface:refresh` frames
336
+ matching `dimension`. It handles reconnect backoff and debounce itself.
348
337
 
349
- ```tsx
350
- // src/components/promises/PromiseTracker.tsx
351
- import { useState, useTransition } from 'react';
352
- import { AgentPromise } from '@/engine/types';
338
+ **3. Fetch in an effect.** Still the common shape for one-shot loads
339
+ (`VideoRoomManager` does this). Acceptable; just keep the `.catch(() => {})` so a
340
+ failed load never leaves an unhandled rejection in a Worker.
353
341
 
354
- interface Props {
355
- promises: AgentPromise[];
356
- }
342
+ ### @oneie/react — for consumers, not for this app
357
343
 
358
- export function PromiseTracker({ promises }: Props) {
359
- const [filter, setFilter] = useState<'all' | 'pending' | 'resolved'>('all');
360
- const [isPending, startTransition] = useTransition();
344
+ `packages/react/` publishes `@oneie/react`: `SubstrateProvider` + `useSubstrate`,
345
+ **10 data hooks** (`useAgent`, `useAgentList`, `useDiscover`, `useHealth`,
346
+ `useHighways`, `useRecall`, `useRevenue`, `useStats`, `useWallet`, `useFn`), two
347
+ optimistic helpers (`useOptimisticMark`, `useOptimisticPay`), and two stream
348
+ helpers (`streamChat`, `streamTail`).
361
349
 
362
- const filtered = promises.filter(p =>
363
- filter === 'all' || p.status === filter
364
- );
365
-
366
- return (
367
- <div>
368
- <div className="flex gap-2 mb-4">
369
- {(['all', 'pending', 'resolved'] as const).map(f => (
370
- <button
371
- key={f}
372
- onClick={() => startTransition(() => setFilter(f))}
373
- className={filter === f ? 'btn-active' : 'btn'}
374
- >
375
- {f}
376
- </button>
377
- ))}
378
- </div>
379
- <ul className={isPending ? 'opacity-50' : ''}>
380
- {filtered.map(promise => (
381
- <li key={promise.id} className="flex items-center gap-2">
382
- <span className={`status-dot status-${promise.status}`} />
383
- <span>{promise.label}</span>
384
- </li>
385
- ))}
386
- </ul>
387
- </div>
388
- );
389
- }
390
- ```
350
+ **`one.ie/web` does not depend on `@oneie/react`.** Its only `@oneie` dependency
351
+ is `@oneie/sdk` (`file:../../packages/sdk`). Do not import these hooks into a web
352
+ component — the package is for external apps embedding the substrate. Inside
353
+ this app, use the three data paths above.
391
354
 
392
355
  ## TypeScript Patterns
393
356
 
394
357
  ### Event Handlers
395
358
 
396
359
  ```tsx
397
- // Type-safe event handlers
398
360
  function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
399
361
  e.preventDefault();
400
362
  }
@@ -408,84 +370,22 @@ function handleSubmit(e: React.FormEvent<HTMLFormElement>) {
408
370
  }
409
371
  ```
410
372
 
411
- ### Component Props
412
-
413
- ```tsx
414
- // Prefer interfaces for props
415
- interface AgentCardProps {
416
- agent: Agent;
417
- onSelect?: (id: string) => void;
418
- className?: string;
419
- children?: React.ReactNode;
420
- }
421
-
422
- // Use PropsWithChildren for components with children
423
- import { PropsWithChildren } from 'react';
424
-
425
- interface PanelProps {
426
- title: string;
427
- }
428
-
429
- function Panel({ title, children }: PropsWithChildren<PanelProps>) {
430
- return (
431
- <div>
432
- <h2>{title}</h2>
433
- {children}
434
- </div>
435
- );
436
- }
437
- ```
438
-
439
- ## State Management Patterns
440
-
441
- ### Local state for UI
442
-
443
- ```tsx
444
- const [isOpen, setIsOpen] = useState(false);
445
- const [selectedTab, setSelectedTab] = useState('agent-a');
446
- ```
447
-
448
- ### useReducer for complex state
449
-
450
- ```tsx
451
- type Action =
452
- | { type: 'SELECT_AGENT'; agentId: string }
453
- | { type: 'ADD_ENVELOPE'; envelope: Envelope }
454
- | { type: 'UPDATE_PROMISE'; promiseId: string; status: string };
455
-
456
- interface State {
457
- selectedAgent: string;
458
- envelopes: Envelope[];
459
- promises: AgentPromise[];
460
- }
461
-
462
- function reducer(state: State, action: Action): State {
463
- switch (action.type) {
464
- case 'SELECT_AGENT':
465
- return { ...state, selectedAgent: action.agentId };
466
- case 'ADD_ENVELOPE':
467
- return { ...state, envelopes: [...state.envelopes, action.envelope] };
468
- case 'UPDATE_PROMISE':
469
- return {
470
- ...state,
471
- promises: state.promises.map(p =>
472
- p.id === action.promiseId ? { ...p, status: action.status } : p
473
- ),
474
- };
475
- }
476
- }
477
- ```
373
+ With an Action you do not write `handleSubmit` at all — `<form action={dispatch}>`
374
+ receives `FormData` directly and never needs `preventDefault`.
478
375
 
479
376
  ## Best Practices
480
377
 
481
- 1. **Use Actions for forms**: Cleaner than manual `onSubmit`
482
- 2. **Wrap async with Suspense**: When using `use()` with promises
483
- 3. **Use transitions for tabs/filters**: Keeps UI responsive
484
- 4. **Optimistic updates for UX**: Show expected state immediately
485
- 5. **Type everything**: No `any` types
486
- 6. **Prefer `interface` over `type`**: For props and objects
378
+ 1. **Props before fetch**: let the Astro page do the query; hydrate with data.
379
+ 2. **`useSurfaceRefresh` before polling**: one SSE topic already carries every
380
+ resolver mutation.
381
+ 3. **Actions for forms**: `<form action={dispatch}>` beats a manual `onSubmit`.
382
+ 4. **Transitions for tabs and filters**: the repo's most-used React 19 primitive.
383
+ 5. **`use()` needs a stable promise**: created in render, it never resolves.
384
+ 6. **`useOptimistic` only inside an Action or transition**: it throws otherwise.
385
+ 7. **Never import `@oneie/react` into `one.ie/web`**: it is not a dependency.
386
+ 8. **Emit the click signal first**: `emitClick(...)` then the local handler.
487
387
 
488
388
  ---
489
389
 
490
- **Version**: 1.0.0
491
- **Tech**: React 19.1
390
+ **Tech**: React 19.2.6 · Astro 6.3.7 · Tailwind 4
391
+ **Tree**: `one.ie/web` — for `template/site` (Astro 7) use `template:react19`