@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.
- package/agents/w1-recon.md +9 -4
- package/agents/w2-decide.md +51 -28
- package/agents/w3-edit.md +70 -78
- package/agents/w4-verify.md +164 -69
- package/commands/browser.md +98 -35
- package/commands/cc-connect.md +6 -0
- package/commands/chat.md +13 -1
- package/commands/close.md +45 -19
- package/commands/create.md +20 -10
- package/commands/db-sync.md +5 -1
- package/commands/deploy.md +174 -87
- package/commands/do-autonomous.md +31 -20
- package/commands/do-improve.md +4 -4
- package/commands/do-show.md +4 -3
- package/commands/do.md +53 -13
- package/commands/go.md +75 -0
- package/commands/improve.md +4 -2
- package/commands/kill.md +6 -8
- package/commands/notify.md +15 -2
- package/commands/oo-push.md +24 -9
- package/commands/rag.md +6 -0
- package/commands/release.md +46 -25
- package/commands/restart.md +5 -4
- package/commands/see.md +45 -35
- package/commands/skill-create.md +38 -5
- package/commands/sync.md +21 -11
- package/hooks/scripts/auto-continue.sh +70 -0
- package/hooks/scripts/branch-pin.sh +85 -0
- package/hooks/scripts/git-add-guard.sh +16 -5
- package/hooks/scripts/post-edit-check.sh +16 -0
- package/hooks/scripts/session-start.sh +13 -8
- package/hooks/scripts/sync-priority-todo.sh +5 -0
- package/package.json +1 -1
- package/rules/api.md +9 -0
- package/rules/design.md +13 -3
- package/rules/documentation.md +30 -71
- package/rules/engine.md +8 -1
- package/rules/ui.md +5 -1
- package/scripts/__pycache__/do-rank.cpython-313.pyc +0 -0
- package/scripts/browser-check.mjs +44 -103
- package/scripts/cc-events-proof.sh +13 -12
- package/scripts/chrome.mjs +381 -0
- package/scripts/do-analyze.sh +109 -4
- package/scripts/do-auto.sh +350 -43
- package/scripts/do-consumer-sweep.sh +72 -0
- package/scripts/do-derives-check.sh +88 -0
- package/scripts/do-fleet.sh +50 -1
- package/scripts/do-killswitch-audit.py +33 -14
- package/scripts/do-promise-lint.sh +150 -4
- package/scripts/do-promise-settle.sh +131 -9
- package/scripts/do-prove.sh +197 -17
- package/scripts/do-rank.py +606 -20
- package/scripts/do-rubric.py +21 -2
- package/scripts/do-signal.sh +157 -17
- package/scripts/do-smoke.sh +9 -0
- package/scripts/do-substrate-check.sh +49 -0
- package/scripts/do-tasks-wire-check.sh +81 -0
- package/scripts/do-tier.sh +90 -5
- package/scripts/do-ui-gate.sh +332 -0
- package/scripts/do-untracked-gate.sh +231 -0
- package/scripts/do-walk.sh +344 -0
- package/scripts/factory-check.sh +747 -0
- package/scripts/factory-repo.sh +1361 -0
- package/scripts/factory-tasks-check.sh +85 -0
- package/scripts/outcome-pull.ts +279 -0
- package/scripts/promise-manifest.mjs +167 -0
- package/scripts/reconcile-allow.txt +5 -0
- package/scripts/rubric-weights.json +64 -0
- package/scripts/spine-canary.sh +20 -0
- package/scripts/typedb-cluster-status.sh +128 -0
- package/scripts/typedb-env.sh +182 -0
- package/scripts/typedb-flap-recorder.sh +85 -0
- package/scripts/typedb-probes/containment-probe.py +88 -0
- package/scripts/typedb-probes/panic-probe-version.py +92 -0
- package/scripts/typedb-probes/panic-probe.py +77 -0
- package/scripts/typedb-scratch.sh +73 -0
- package/scripts/urls-lint.sh +58 -0
- package/scripts/w4-rubric.ts +96 -12
- package/scripts/wf-check.mjs +75 -0
- package/skills/ai-ui/SKILL.md +1 -1
- package/skills/astro/SKILL.md +177 -132
- package/skills/astro/add-content-collection.md +29 -13
- package/skills/astro/check-build.md +19 -13
- package/skills/astro/create-component.md +36 -9
- package/skills/astro/create-page.md +39 -10
- package/skills/astro/optimize-performance.md +36 -8
- package/skills/cli/SKILL.md +254 -220
- package/skills/dev/SKILL.md +1 -1
- package/skills/directory-autofill/SKILL.md +68 -0
- package/skills/docs/SKILL.md +4 -4
- package/skills/mcp/SKILL.md +186 -156
- package/skills/promise-make/SKILL.md +2 -2
- package/skills/puck/SKILL.md +465 -250
- package/skills/react19/SKILL.md +196 -296
- package/skills/reactflow/SKILL.md +387 -412
- package/skills/sdk/SKILL.md +167 -106
- package/skills/shadcn/SKILL.md +245 -201
- package/skills/signal/SKILL.md +5 -0
- package/skills/sui/SKILL.md +6 -1
- package/skills/tutorial/SKILL.md +15 -0
- package/skills/typedb/SKILL.md +427 -217
- package/skills/typedb/examples/python-patterns.py +11 -4
- package/skills/typedb/examples/query-patterns.tql +8 -3
- package/skills/typedb/examples/schema-patterns.tql +4 -2
- package/skills/typedb/reference/migration-2x-3x.md +19 -1
- package/skills/typedb/reference/python-driver.md +17 -1
- package/skills/voice/SKILL.md +117 -0
- package/skills/writer/SKILL.md +29 -12
- package/templates/template-feature.md +26 -1
- package/templates/template-tests.md +4 -0
- package/templates/template-todo.md +65 -10
package/skills/react19/SKILL.md
CHANGED
|
@@ -1,177 +1,198 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: react19
|
|
3
|
-
description: React 19 patterns
|
|
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
|
-
|
|
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
|
-
| `/
|
|
17
|
-
| `/astro` | The component lives in an Astro page — hydration directives (`client:load`, `client:only`) decide worker bundle size. |
|
|
18
|
-
| `/
|
|
19
|
-
| `/
|
|
20
|
-
| `/
|
|
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
|
-
|
|
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
|
|
42
|
+
- Build a React island in `one.ie/web/src/components/`
|
|
27
43
|
- Implement form handling with Actions
|
|
28
|
-
- Use
|
|
44
|
+
- Use `use()` for promises/context
|
|
29
45
|
- Apply transitions for non-blocking updates
|
|
30
|
-
-
|
|
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
|
-
|
|
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
|
|
43
|
-
|
|
72
|
+
interface CreateRoomState {
|
|
73
|
+
ok: boolean;
|
|
44
74
|
error?: string;
|
|
45
|
-
|
|
75
|
+
slug?: string;
|
|
46
76
|
}
|
|
47
77
|
|
|
48
|
-
async function
|
|
49
|
-
|
|
78
|
+
async function createRoom(
|
|
79
|
+
_prev: CreateRoomState | null,
|
|
50
80
|
formData: FormData
|
|
51
|
-
): Promise<
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
|
|
81
|
+
): Promise<CreateRoomState> {
|
|
82
|
+
const name = formData.get('name') as string;
|
|
83
|
+
const slug = formData.get('slug') as string;
|
|
55
84
|
try {
|
|
56
|
-
|
|
57
|
-
|
|
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 {
|
|
88
|
+
return { ok: false, error: (e as Error).message };
|
|
61
89
|
}
|
|
62
90
|
}
|
|
63
91
|
|
|
64
|
-
export function
|
|
65
|
-
const [state,
|
|
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={
|
|
72
|
-
<input name="
|
|
73
|
-
<input name="
|
|
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 ? '
|
|
100
|
+
{isPending ? 'Creating…' : 'Create room'}
|
|
76
101
|
</button>
|
|
77
|
-
{state
|
|
78
|
-
{state
|
|
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
|
-
|
|
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
|
|
93
|
-
agents: Agent[];
|
|
94
|
-
promises: AgentPromise[];
|
|
95
|
-
}
|
|
123
|
+
interface Stats { actors: number; signals: number }
|
|
96
124
|
|
|
97
|
-
function
|
|
98
|
-
const
|
|
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>
|
|
103
|
-
<h2>
|
|
130
|
+
<h2>Actors: {stats.actors}</h2>
|
|
131
|
+
<h2>Signals: {stats.signals}</h2>
|
|
104
132
|
</div>
|
|
105
133
|
);
|
|
106
134
|
}
|
|
107
135
|
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
114
|
-
<
|
|
140
|
+
<Suspense fallback={<div>Loading…</div>}>
|
|
141
|
+
<StatsDisplay statsPromise={statsPromise} />
|
|
115
142
|
</Suspense>
|
|
116
143
|
);
|
|
117
144
|
}
|
|
118
145
|
```
|
|
119
146
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
150
|
+
```tsx
|
|
151
|
+
import { use } from 'react';
|
|
152
|
+
import { EditorSlugContext } from '@/components/puck/editor-slug-context';
|
|
129
153
|
|
|
130
|
-
|
|
131
|
-
return
|
|
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
|
-
|
|
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
|
|
143
|
-
id: string;
|
|
144
|
-
name: string;
|
|
145
|
-
envelopes: Envelope[];
|
|
146
|
-
}
|
|
170
|
+
interface Actor { id: string; name: string }
|
|
147
171
|
|
|
148
|
-
export function
|
|
149
|
-
const [
|
|
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
|
|
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="
|
|
163
|
-
{
|
|
180
|
+
<div className="flex gap-2">
|
|
181
|
+
{actors.map(a => (
|
|
164
182
|
<button
|
|
165
183
|
key={a.id}
|
|
166
|
-
onClick={() =>
|
|
167
|
-
|
|
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
|
-
{
|
|
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
|
-
|
|
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
|
|
189
|
-
id: string;
|
|
190
|
-
status: 'pending' | 'resolved' | 'rejected';
|
|
191
|
-
}
|
|
213
|
+
interface Task { id: string; title: string; status: string }
|
|
192
214
|
|
|
193
|
-
export function
|
|
194
|
-
const [
|
|
195
|
-
|
|
196
|
-
(state,
|
|
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
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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={
|
|
215
|
-
<input name="
|
|
216
|
-
<button type="submit">
|
|
230
|
+
<form action={createTask}>
|
|
231
|
+
<input name="title" />
|
|
232
|
+
<button type="submit">Add</button>
|
|
217
233
|
</form>
|
|
218
234
|
<ul>
|
|
219
|
-
{
|
|
220
|
-
<li key={
|
|
221
|
-
{
|
|
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
|
-
|
|
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="
|
|
266
|
+
return <Input ref={inputRef} placeholder="Receiver" />;
|
|
254
267
|
}
|
|
255
268
|
```
|
|
256
269
|
|
|
257
270
|
### 6. Document Metadata
|
|
258
271
|
|
|
259
|
-
|
|
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
|
-
|
|
278
|
+
interface Room { id: string; name: string }
|
|
279
|
+
|
|
280
|
+
function RoomView({ room }: { room: Room }) {
|
|
263
281
|
return (
|
|
264
282
|
<div>
|
|
265
|
-
<title>{
|
|
266
|
-
<
|
|
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
|
-
###
|
|
292
|
+
### How an island gets its data
|
|
276
293
|
|
|
277
|
-
|
|
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
|
-
|
|
291
|
-
|
|
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
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
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
|
-
|
|
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
|
-
|
|
310
|
-
import {
|
|
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
|
-
|
|
316
|
-
const { runtime, refresh } = use(RuntimeContext)!;
|
|
316
|
+
interface Workflow { id: string; name: string }
|
|
317
317
|
|
|
318
|
-
|
|
319
|
-
|
|
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
|
-
|
|
327
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
350
|
-
|
|
351
|
-
|
|
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
|
-
|
|
355
|
-
promises: AgentPromise[];
|
|
356
|
-
}
|
|
342
|
+
### @oneie/react — for consumers, not for this app
|
|
357
343
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
-
|
|
363
|
-
|
|
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
|
-
|
|
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. **
|
|
482
|
-
2.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
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
|
-
**
|
|
491
|
-
**
|
|
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`
|