makersclaw 0.3.2 → 0.4.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/README.md +3 -1
- package/package.json +1 -1
- package/r/annotate.json +3 -3
- package/r/approval-request.json +2 -2
- package/r/audio-player.json +2 -2
- package/r/auth-form.json +2 -2
- package/r/board.json +2 -2
- package/r/calendar-view.json +3 -3
- package/r/cart.json +2 -2
- package/r/chart.json +2 -2
- package/r/chat.json +2 -2
- package/r/code-editor.json +2 -2
- package/r/comment-box.json +3 -3
- package/r/contact-section.json +2 -2
- package/r/core.json +12 -6
- package/r/csv-import.json +3 -3
- package/r/data-table.json +2 -2
- package/r/diagram.json +2 -2
- package/r/doc-editor.json +2 -2
- package/r/doc-page.json +3 -3
- package/r/email-render.json +2 -2
- package/r/faq.json +2 -2
- package/r/file-upload.json +2 -2
- package/r/flow-canvas.json +2 -2
- package/r/footer.json +2 -2
- package/r/gantt.json +3 -3
- package/r/job-panel.json +8 -5
- package/r/json-editor.json +3 -3
- package/r/map.json +2 -2
- package/r/menubar.json +2 -2
- package/r/mind-map.json +3 -3
- package/r/navigation-menu.json +2 -2
- package/r/order-detail.json +2 -2
- package/r/org-chart.json +3 -3
- package/r/page-builder.json +2 -2
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +2 -2
- package/r/product-detail.json +2 -2
- package/r/product-grid.json +3 -3
- package/r/record-form.json +2 -2
- package/r/record-notes.json +4 -4
- package/r/registry.json +131 -123
- package/r/search-dialog.json +2 -2
- package/r/settings-list.json +2 -2
- package/r/setup-checklist.json +2 -2
- package/r/share-dialog.json +2 -2
- package/r/sidebar.json +2 -2
- package/r/signature-pad.json +2 -2
- package/r/sketch-canvas.json +2 -2
- package/r/spreadsheet.json +2 -2
- package/r/stat-cards.json +3 -3
- package/r/task-list.json +3 -3
- package/r/task-model.json +2 -2
- package/r/testimonials.json +2 -2
- package/r/transaction-list.json +2 -2
- package/r/whiteboard.json +3 -3
- package/registry/blocks/job-panel/README.md +40 -3
- package/registry/blocks/job-panel/block.json +2 -2
- package/registry/blocks/job-panel/demo.jsx +75 -6
- package/registry/blocks/job-panel/job-panel.jsx +60 -11
- package/src/shadcn.mjs +1 -1
- package/templates/next/files/README.md +339 -73
- package/templates/next/files/app/auto-refresh.jsx +72 -10
- package/templates/next/files/app/claw-alive/route.js +17 -0
- package/templates/next/files/app/error-relay.jsx +9 -4
- package/templates/next/files/app/escape-relay.jsx +27 -0
- package/templates/next/files/app/frame.js +218 -0
- package/templates/next/files/app/layout.jsx +18 -3
- package/templates/next/files/check/run.mjs +25 -8
- package/templates/next/files/components/parts/fixture.jsx +29 -4
- package/templates/next/files/components/parts/index.js +2 -1
- package/templates/next/files/components/parts/live-work.jsx +235 -0
- package/templates/next/files/components/ui/button.jsx +6 -1
- package/templates/next/files/components/ui/date-picker.jsx +15 -2
- package/templates/next/files/components/ui/icon.jsx +37 -2
- package/templates/next/files/lib/claw.js +127 -28
- package/templates/next/files/lib/hooks.js +454 -33
- package/templates/next/files/lib/platform.js +113 -15
- package/templates/next/files/surfaces/README.md +3 -0
- package/templates/next/template.json +17 -6
|
@@ -62,13 +62,14 @@ components/depictions/ pictures of things that exist elsewhere, in their own col
|
|
|
62
62
|
Keep — the contract; do not edit:
|
|
63
63
|
|
|
64
64
|
```
|
|
65
|
-
app/layout.jsx <html data-theme>, the _claw/theme.css link, AutoRefresh, ErrorRelay, Shell
|
|
65
|
+
app/layout.jsx <html data-theme>, the _claw/theme.css link, AutoRefresh, ErrorRelay, EscapeRelay, Examples, Shell
|
|
66
66
|
app/globals.css the workspace theme's tokens (§1) — no palette of your own goes here
|
|
67
67
|
app/page.jsx / → the home surface (or "No surfaces yet" while the manifest is empty)
|
|
68
68
|
app/s/[key]/page.jsx renders the surface with that key; planned ones say so
|
|
69
|
-
app/error.jsx, app/global-error.jsx, app/not-found.jsx, app/error-relay.jsx, app/auto-refresh.jsx
|
|
69
|
+
app/error.jsx, app/global-error.jsx, app/not-found.jsx, app/error-relay.jsx, app/auto-refresh.jsx,
|
|
70
|
+
app/escape-relay.jsx, app/frame.js, app/claw-alive/route.js the app inside its window: refresh while shown, Escape from the desk
|
|
70
71
|
components/ui/ the kit: Button, Field, Input, Select, Sheet, Tabs, Menu, Notice, Icon, Depiction… (§3b, §3c)
|
|
71
|
-
components/parts/ the parts: RecordSwitcher, useFixture + FixtureNotice (§3b)
|
|
72
|
+
components/parts/ the parts: RecordSwitcher, LiveEmpty + LiveWork, useFixture + FixtureNotice (§3a, §3b)
|
|
72
73
|
check/pins.json the fixed files by hash; `npm run check` refuses one that differs
|
|
73
74
|
lib/* db, data, actions, claw, platform, hooks, sample (§5)
|
|
74
75
|
check/ `npm run check` (§6)
|
|
@@ -95,7 +96,8 @@ Before any code, write `app/DESIGN.md` — one screen of text:
|
|
|
95
96
|
`font-mono`, `font-display`). Numbers tabular (`data-nums` on the element).
|
|
96
97
|
- Layout and nav: where the surfaces live, what the home surface shows in
|
|
97
98
|
the first second.
|
|
98
|
-
- Density, empty states
|
|
99
|
+
- Density, empty states (§3a: what each surface says while its table is
|
|
100
|
+
empty), error states.
|
|
99
101
|
- What makes this app look like ITS domain and not another.
|
|
100
102
|
|
|
101
103
|
Then `app/shell.jsx`, then the surfaces in the order the founder uses
|
|
@@ -109,15 +111,74 @@ The starter `app/shell.jsx` is a placeholder; replace it first.
|
|
|
109
111
|
|
|
110
112
|
What the OS does **not** own is asking the agent for something. Those
|
|
111
113
|
tabs are where the founder goes to look back; the app is where they set
|
|
112
|
-
work going and
|
|
113
|
-
"Generate a launch plan" beside "New launch plan" — is
|
|
114
|
-
tab, and so is the panel it opens while the work runs.
|
|
114
|
+
work going and see where it has got (`useJob`, `ask()`, §5). A verb on an
|
|
115
|
+
object list — "Generate a launch plan" beside "New launch plan" — is
|
|
116
|
+
part of the App tab, and so is the panel it opens while the work runs.
|
|
117
|
+
The conversation is not: since 0.4.0 an ask is a request, and its
|
|
118
|
+
conversation — the agent's questions, the founder's answers, the yes
|
|
119
|
+
before it acts — lives in the desk's agent column, which the ask opens.
|
|
115
120
|
|
|
116
121
|
**The rule: no two apps in a workspace should be mistaken for each other
|
|
117
122
|
at a glance.** The design comes from the domain, not from a default. The
|
|
118
123
|
colours are the same across the workspace by design; what tells the apps
|
|
119
124
|
apart is the shape of each surface — which is your work.
|
|
120
125
|
|
|
126
|
+
### 3a. Empty states — the work coming, never an example
|
|
127
|
+
|
|
128
|
+
**A workspace app opens empty, and its empty state shows the live work
|
|
129
|
+
— never an example.** The workspace's tables are shared by every app and
|
|
130
|
+
every teammate, so an example there reads as a real row; and the member
|
|
131
|
+
asked for results, not a demo. A table is empty because its agent has
|
|
132
|
+
not got to it yet, or is on it right now. So an empty surface says which:
|
|
133
|
+
|
|
134
|
+
```jsx
|
|
135
|
+
// components/Deals.jsx (client)
|
|
136
|
+
"use client";
|
|
137
|
+
import { useState } from "react";
|
|
138
|
+
import { useRequests } from "../lib/hooks";
|
|
139
|
+
import { LiveEmpty } from "./parts";
|
|
140
|
+
|
|
141
|
+
export default function Deals({ rows }) {
|
|
142
|
+
const work = useRequests({ surface: "deals" });
|
|
143
|
+
const [adding, setAdding] = useState(false);
|
|
144
|
+
if (!rows.length && !adding)
|
|
145
|
+
return <LiveEmpty work={work} surface="deals" icon="inbox" title="No deals yet"
|
|
146
|
+
line="Ask the agent to find some, or add one."
|
|
147
|
+
action={{ label: "Add a deal", onClick: () => setAdding(true) }} />;
|
|
148
|
+
/* … the deals, and the form while adding */
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
With work in flight for this surface or its job — or the app's scheduled
|
|
153
|
+
run going — `LiveEmpty` shows it as one row (its line, whose ask it is,
|
|
154
|
+
what it waits behind, Answer when it needs the founder) over "Rows appear
|
|
155
|
+
here as they land". With none, it is the plain empty block: what would
|
|
156
|
+
be here, how it gets here, and the manual way to add one. Props: §3b.
|
|
157
|
+
|
|
158
|
+
Name the job as well as the surface when its work can be asked for from
|
|
159
|
+
the desk — `job: "find-deals"` to `useRequests` and `job="find-deals"`
|
|
160
|
+
to `LiveEmpty`. An ask made on this surface carries the surface; one the
|
|
161
|
+
desk hands to the app carries only its job, so a surface alone misses
|
|
162
|
+
it. With neither, any of the app's live work shows.
|
|
163
|
+
|
|
164
|
+
Examples show only OUTSIDE a workspace: in the store's authoring loop,
|
|
165
|
+
where the app runs on its own with nothing but `DATABASE_URL`, and in a
|
|
166
|
+
shadcn project. There a surface may still open on a worked example —
|
|
167
|
+
`useFixture(rows, SAMPLE)`, §3b — so its author sees it filled.
|
|
168
|
+
`app/layout.jsx` decides per request: examples are off whenever
|
|
169
|
+
`CLAW_ARTIFACT_ID` is set (`product_run` sets it for every workspace app)
|
|
170
|
+
and on otherwise; `CLAW_EXAMPLES=1` forces them on. Inside a workspace
|
|
171
|
+
`useFixture` hands back the real rows — none — so the same surface falls
|
|
172
|
+
through to `LiveEmpty`. Up to 0.3.x a workspace app opened on its example
|
|
173
|
+
until its table had a row; since 0.4.0 it opens empty.
|
|
174
|
+
|
|
175
|
+
An example is never rendered any other way: `npm run check` refuses a
|
|
176
|
+
`SAMPLE` constant (any `…SAMPLE…`, `…EXAMPLE…`, `…FIXTURE…` or `…DEMO…`
|
|
177
|
+
name) in a file that renders without calling `useFixture()`, because only
|
|
178
|
+
`useFixture` knows it is inside a workspace. `markSample` alone does not
|
|
179
|
+
count (it did up to 0.3.x): it only gives rows their ids, so an example
|
|
180
|
+
marked by hand would show to every member.
|
|
181
|
+
|
|
121
182
|
### 3b. Parts — assemble, never hand-roll
|
|
122
183
|
|
|
123
184
|
Every control in the app is a part from `components/ui`; the pieces that
|
|
@@ -143,10 +204,12 @@ import { Button, IconButton, ButtonGroup, ButtonGroupSeparator, Toggle, ToggleGr
|
|
|
143
204
|
Stat, StatGroup, Delta, StatusDot, Legend, Meter, Distribution, Swatch, Steps,
|
|
144
205
|
toast, Toaster, Depiction, Icon, ICON_NAMES, usePopoverPosition, panelClass, cx }
|
|
145
206
|
from "../components/ui"; // "./ui" from a component
|
|
146
|
-
import { RecordSwitcher, useFixture, FixtureNotice, isSample
|
|
207
|
+
import { RecordSwitcher, LiveEmpty, LiveWork, liveWords, useFixture, FixtureNotice, isSample,
|
|
208
|
+
Examples, useExamples } from "../components/parts";
|
|
147
209
|
```
|
|
148
210
|
|
|
149
|
-
- `Button` — `variant` primary (one per surface
|
|
211
|
+
- `Button` — `variant` primary (one per surface, in the app's accent —
|
|
212
|
+
say it: a Button with no `variant` is secondary) · secondary · quiet ·
|
|
150
213
|
danger · link (inline text: no padding or height, wraps and truncates
|
|
151
214
|
with its sentence; `size="inherit"` takes the sentence's size;
|
|
152
215
|
`tone="muted"` for a secondary link) · nav (a full-width navigation row, start-aligned, normal
|
|
@@ -214,7 +277,8 @@ import { RecordSwitcher, useFixture, FixtureNotice, isSample } from "../componen
|
|
|
214
277
|
`numberOfMonths`, `captionLayout` dropdown · none (you show and move a
|
|
215
278
|
controlled `month`), `showWeekNumbers`, `weekStartsOn`, `marked` with
|
|
216
279
|
`emphasizeMarked` and `onlyMarked`) and
|
|
217
|
-
`DateInput` (a typed date with the Calendar a button away
|
|
280
|
+
`DateInput` (a typed date with the Calendar a button away;
|
|
281
|
+
`clearable` for an ×). Dates are
|
|
218
282
|
`"YYYY-MM-DD"` strings in and out — never a Date — and `addDays`,
|
|
219
283
|
`addMonths`, `formatDate`… do the arithmetic.
|
|
220
284
|
- `RadioGroup` (`options` or `<Radio>` children, each with a
|
|
@@ -248,7 +312,8 @@ import { RecordSwitcher, useFixture, FixtureNotice, isSample } from "../componen
|
|
|
248
312
|
fill · code — a version tag or a commit hash in the mono face; `hue`
|
|
249
313
|
1..5 for a KIND of thing — a platform, a content type — never a state:
|
|
250
314
|
tone is status, hue is category), `Kicker` (the small line above a heading), `Empty` (a filter
|
|
251
|
-
or search with no matches — not the landing state
|
|
315
|
+
or search with no matches — not the landing state: an empty table is
|
|
316
|
+
`LiveEmpty`, below).
|
|
252
317
|
- `Spinner` (size sm · md · lg), `Skeleton` (shape line · circle · rect,
|
|
253
318
|
sized by className), `Progress` (`value` of `max`, no value for
|
|
254
319
|
indeterminate; tone; `label`) — waiting, never a hand-drawn loader.
|
|
@@ -285,17 +350,46 @@ import { RecordSwitcher, useFixture, FixtureNotice, isSample } from "../componen
|
|
|
285
350
|
counter; `loop`, `autoplay` only when asked), `ResizablePanelGroup`
|
|
286
351
|
+ `ResizablePanel defaultSize minSize` + `ResizableHandle grip`
|
|
287
352
|
(`storageKey` remembers; a horizontal group stacks below md).
|
|
288
|
-
- `Icon name="…"` — one sprite, `ICON_NAMES` lists it
|
|
289
|
-
the
|
|
353
|
+
- `Icon name="…" size` — one sprite, `ICON_NAMES` lists it; `size` is
|
|
354
|
+
pixels or xs · sm · md (16, the default) · lg · xl. Missing one? Pick the nearest in
|
|
355
|
+
`ICON_NAMES`: the sprite is fixed like the rest of the kit, so a new
|
|
356
|
+
glyph comes with a template release. Never an `<svg>` in a surface.
|
|
290
357
|
- `RecordSwitcher` — pick a saved record or make a new one. With nothing
|
|
291
358
|
saved it shows only New. A disabled dropdown reading "No saved…" is the
|
|
292
359
|
failure this part exists to prevent.
|
|
293
|
-
- `
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
360
|
+
- `LiveEmpty work surface job icon title line action children` — what
|
|
361
|
+
an empty collection shows (§3a). `work` is what `useRequests()`
|
|
362
|
+
returns (§5); with none (a shadcn project) it is only the empty block.
|
|
363
|
+
`surface` and `job` pick the work that lands here: an ask made on this
|
|
364
|
+
surface, or work on this job wherever it was asked for — either is a
|
|
365
|
+
way in; neither means any live work. With work in flight it draws a
|
|
366
|
+
`LiveWork` row and "Rows appear here as they land"; else the empty
|
|
367
|
+
block — `icon` (default `inbox`), `title` (what would be here: "No
|
|
368
|
+
deals yet"), `line` (how it gets here), `action` (`{ label, onClick,
|
|
369
|
+
href, icon, variant }` for a Button — the manual way in — or an
|
|
370
|
+
element as it is). `children` go under either state.
|
|
371
|
+
- `LiveWork request run onOpen onStop` — one piece of work in flight, as
|
|
372
|
+
a row: a pulsing dot, the request's line, "from Ana's request" when it
|
|
373
|
+
is not yours, "Queued · after …" while it waits its turn. A request
|
|
374
|
+
that needs an answer takes the warning tone and, for the one who
|
|
375
|
+
asked, **Answer** (calls `onOpen(request)`: the question is in the
|
|
376
|
+
desk's agent column); your own ask that is still going gets **Stop**
|
|
377
|
+
(calls `onStop(id)`). With no `request`, a `run` that is going shows
|
|
378
|
+
its title. `LiveEmpty` draws it for you; draw it yourself for work
|
|
379
|
+
beside a list that is not empty. `liveWords(requestOrStatus)` is the
|
|
380
|
+
status word alone: Working, Needs you, Answered, Done, Stopped, Didn't
|
|
381
|
+
finish.
|
|
382
|
+
- `useFixture(rows, SAMPLE)` → `{ rows, isFixture, clear, restore }` +
|
|
383
|
+
`FixtureNotice` — the worked example, for the store's authoring loop
|
|
384
|
+
only (§3a): shown only outside a workspace, only while the real
|
|
385
|
+
collection is empty, and **never INSERTed**. Inside a workspace it
|
|
386
|
+
returns the real rows (none) with `isFixture` false, and `clear` does
|
|
387
|
+
nothing. The workspace database is shared by every app; an invented
|
|
388
|
+
row is a real row somewhere else. Clearing writes nothing. Editing a
|
|
389
|
+
fixture row copies its values into a new draft and saves THAT (the
|
|
390
|
+
platform refuses an ask about one: `row._sample` is not the founder's
|
|
391
|
+
data). `<Examples allowed>` is the switch `app/layout.jsx` sets and
|
|
392
|
+
`useExamples()` reads; the layout is fixed, so you never mount it.
|
|
299
393
|
|
|
300
394
|
A control with nothing to act on is not drawn.
|
|
301
395
|
|
|
@@ -437,7 +531,7 @@ import { moveRow } from "../lib/actions";
|
|
|
437
531
|
import { Card, Notice } from "./ui";
|
|
438
532
|
import { FixtureNotice, useFixture } from "./parts";
|
|
439
533
|
|
|
440
|
-
const SAMPLE = [{ name: "Ada Ventures", stage: "warm" }, /* … */];
|
|
534
|
+
const SAMPLE = [{ name: "Ada Ventures", stage: "warm" }, /* … */]; // shown only outside a workspace (§3a)
|
|
441
535
|
|
|
442
536
|
export default function InvestorBoard({ rows: real, error, highlight }) {
|
|
443
537
|
const { rows, isFixture, clear } = useFixture(real, SAMPLE);
|
|
@@ -449,6 +543,27 @@ Every surface component receives `surface` (`{ key, kind, label, source
|
|
|
449
543
|
}`) and `searchParams` (the URL query; `?row=<id>` is the convention for
|
|
450
544
|
"highlight this row" — an activity line opens its surface with it).
|
|
451
545
|
|
|
546
|
+
### Views by link
|
|
547
|
+
|
|
548
|
+
A result points into the app. When a job is done, the desk tells the
|
|
549
|
+
member where it landed ("50 emails sent · Pipeline CRM › Sales") and
|
|
550
|
+
that link opens the app's window on one surface with a query: `/s/<key>?…`
|
|
551
|
+
inside the app, `/a/<id>/app/s/<key>?…` on the desk. So the query is
|
|
552
|
+
part of the contract, the same way `?row=` is:
|
|
553
|
+
|
|
554
|
+
- `?list=<value>` shows one list. An app that keeps separate lists in
|
|
555
|
+
shared tables declares which column names them (`lists: { table,
|
|
556
|
+
column }`), and every surface over that table reads `list` and filters
|
|
557
|
+
by that column — `loadRows({ source, filter: searchParams.list ? {
|
|
558
|
+
[column]: searchParams.list } : undefined })`. With no `list` it shows
|
|
559
|
+
every list: a filter of `{ [column]: undefined }` matches no row at
|
|
560
|
+
all. It is the one key every app with lists honours.
|
|
561
|
+
- A job's view may declare a filter (`week=this&list={list}`; `{list}`
|
|
562
|
+
and `{since}` are filled in per request). Its keys are query params
|
|
563
|
+
the surface ALREADY reads: declare one only for a param the surface
|
|
564
|
+
honours, and honour every one you declare. `t`, `theme`, `chat` and
|
|
565
|
+
any key starting `_` belong to the platform.
|
|
566
|
+
|
|
452
567
|
### Linking — `next/link`, never a bare `<a href="/…">`
|
|
453
568
|
|
|
454
569
|
The app is served under a prefix (`/p/<label>` in local dev, nothing in
|
|
@@ -560,40 +675,87 @@ is a new function in `lib/actions.js` that also calls `logActivity`.
|
|
|
560
675
|
|
|
561
676
|
Typed wrappers over `lib/claw.js`, one per `_claw` route an app has a
|
|
562
677
|
reason to call. Every call resolves `{ ok, status, data }` or `{ ok:
|
|
563
|
-
false, status, error, unavailable }`; nothing throws. `unavailable:
|
|
564
|
-
(404 or no network) means the gateway has
|
|
565
|
-
quiet "not connected yet", not an error.
|
|
678
|
+
false, status, error, unavailable, code }`; nothing throws. `unavailable:
|
|
679
|
+
true` (a 404 that names no refusal, or no network) means the gateway has
|
|
680
|
+
no such route yet: show a quiet "not connected yet", not an error.
|
|
681
|
+
`code` names a refusal a caller can act on (`not_your_thread`,
|
|
682
|
+
`needs_answer`, `queue_full`, `unknown_job`, …).
|
|
566
683
|
|
|
567
684
|
```js
|
|
568
685
|
import { platform } from "../lib/platform"; // or the named functions
|
|
569
|
-
platform.me() GET _claw/me
|
|
570
|
-
platform.
|
|
571
|
-
|
|
686
|
+
platform.me() GET _claw/me → { artifact: { id, name, vehicle, status, home }, user: { id, email }, blueprint, desk_origin }
|
|
687
|
+
platform.ask({ text, row, how, job, list, surface, sessionId, open })
|
|
688
|
+
POST _claw/ask → { ok, request, desk_origin }, and the column opens on it
|
|
689
|
+
platform.requests() GET _claw/requests → { requests, run, desk_origin }
|
|
690
|
+
platform.requests({ id }) GET _claw/requests?id=<id> → { request }
|
|
691
|
+
platform.stopRequest(id) POST _claw/requests/<id>/stop → { ok, request }
|
|
692
|
+
platform.openRequest(requestOrId) open its conversation in the desk's agent column → true when the desk was asked
|
|
693
|
+
platform.surfaceOfPath(pathname) the <key> of /s/<key> in a path, or undefined
|
|
572
694
|
platform.reportError({ message, stack, url, source }) POST _claw/events
|
|
695
|
+
platform.startJob(message, { sessionId, onEvent }) POST _claw/chat → streams session · delta · tool · done · error (legacy)
|
|
696
|
+
platform.cancelJob(sessionId) POST _claw/cancel (legacy)
|
|
573
697
|
```
|
|
574
698
|
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
699
|
+
A request, as `ask` and `requests` hand it back: `{ id, title, words,
|
|
700
|
+
status, worker, line, live_line, job_key, list_key, surface, progress: {
|
|
701
|
+
n, of, noun, at } | null, result: { sentence, count, badge, view: {
|
|
702
|
+
surface, label, path, window_path } | null } | null, queued: { reason,
|
|
703
|
+
after_id, after_title } | null, app_session_id, mine, asker_name,
|
|
704
|
+
created_at, updated_at, ended_at }`. `status` is one of eleven: seven
|
|
705
|
+
live — routing, queued, installing, setting_up, building, working,
|
|
706
|
+
needs_you — then answered (open, but no work is coming), done, stopped,
|
|
707
|
+
didnt_finish. `line` is the one line every screen shows for it
|
|
708
|
+
("Drafting · 12 of 50", "Needs you · Send 50 emails from you@acme.com?");
|
|
709
|
+
show it as it is, never a status word of your own. `app_session_id` is
|
|
710
|
+
set only on your own requests.
|
|
711
|
+
|
|
712
|
+
Named exports: `me, ask, requests, stopRequest, openRequest,
|
|
713
|
+
surfaceOfPath, startJob, cancelJob, reportError, platform`. That is the
|
|
714
|
+
whole list. Note what is **not** there: the runs, the chat history, Run
|
|
715
|
+
now, Pause / Resume. Those are MONITORING and they are the OS's — the
|
|
578
716
|
window's tabs and title bar — so an app never calls `_claw/agent`,
|
|
579
|
-
`_claw/runs`, `_claw/run`, `_claw/pause` or `_claw/resume`.
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
717
|
+
`_claw/runs`, `_claw/run`, `_claw/pause` or `_claw/resume`. Asking is the
|
|
718
|
+
opposite thing, and it belongs here (`ask()` and `useJob`, below).
|
|
719
|
+
`requests` is not monitoring either: it is this app's asks in flight, so
|
|
720
|
+
an empty surface can show the work coming (§3a). `startJob` and
|
|
721
|
+
`cancelJob` are the 0.3.x door to the same work, kept so `useJob` can
|
|
722
|
+
fall back to it on a claw that has no `_claw/ask`; new code asks. The
|
|
723
|
+
artifact's status lives on `<html data-claw-status>` and the desk's
|
|
724
|
+
origin on `<html data-claw-desk-origin>` (`publishStatus`, `publishDesk`
|
|
725
|
+
in `lib/claw.js`); `app/error-relay.jsx` and `useArtifact()` keep both
|
|
726
|
+
current. An ask tells the desk which request to open with a message to
|
|
727
|
+
that exact origin — never `"*"` — and only while the app is framed.
|
|
583
728
|
|
|
584
729
|
### Hooks — `lib/hooks.js` ("use client")
|
|
585
730
|
|
|
586
|
-
|
|
587
|
-
and
|
|
588
|
-
minute while `unavailable`), streaming and cleanup
|
|
589
|
-
renders what comes back and owns every pixel.
|
|
731
|
+
Four hooks: a generic fetch over one `_claw` route, the app's identity,
|
|
732
|
+
a job, and the app's requests. Each owns fetching, polling (while the
|
|
733
|
+
tab is visible; once a minute while `unavailable`), streaming and cleanup
|
|
734
|
+
on unmount. A surface renders what comes back and owns every pixel.
|
|
590
735
|
|
|
591
736
|
```js
|
|
592
|
-
useClaw(path, { every, enabled = true })
|
|
593
|
-
useArtifact()
|
|
594
|
-
useJob()
|
|
737
|
+
useClaw(path, { every, enabled = true }) → { data, error, loading, unavailable, reload }
|
|
738
|
+
useArtifact() → { artifact, user, blueprint, loading, error, unavailable, reload }
|
|
739
|
+
useJob() → { running, status, queued, queuedAfter, needsYou, liveLine, count, target,
|
|
740
|
+
steps, text, progress, error, sessionId, requestId, request, finishedAt,
|
|
741
|
+
start, cancel, reset, open }
|
|
742
|
+
useRequests({ surface, job, every = 5000 }) → { requests, live, run, loading, error, unavailable, reload, open, stop }
|
|
743
|
+
jobFromRequest(request) → what useJob shows for one request (pure; for tests)
|
|
595
744
|
```
|
|
596
745
|
|
|
746
|
+
`useRequests` is the work asked of this app's agent — from a screen or
|
|
747
|
+
from the desk, by anyone in the workspace. `requests` is every one the
|
|
748
|
+
gateway returned (the live ones and those that ended in the last day);
|
|
749
|
+
`live` is the ones not ended that belong here — `surface`: asks made on
|
|
750
|
+
this surface; `job`: work on this job wherever it was asked for; either
|
|
751
|
+
is a way in, neither means all; `run` is the app's scheduled run while
|
|
752
|
+
one is going, else null. `open(request)` opens its conversation in the
|
|
753
|
+
desk's agent column; `stop(id)` stops one and looks again. It looks
|
|
754
|
+
every `every` ms while work goes, every 20 s while none does, and only
|
|
755
|
+
while the App tab is on screen (behind the window's other tabs it asks
|
|
756
|
+
nothing; coming back looks at once); a start through `useJob` on the
|
|
757
|
+
same page makes it look at once. Hand it to `LiveEmpty` (§3a).
|
|
758
|
+
|
|
597
759
|
There is no chat, runs or activity hook: the OS draws those tabs.
|
|
598
760
|
Activity has no hook either — it is a Postgres table, read server-side
|
|
599
761
|
with `loadActivity` by any domain surface that wants a slice of it (a
|
|
@@ -602,9 +764,21 @@ row's history beside the row, say).
|
|
|
602
764
|
### Commissioning work — `useJob()`
|
|
603
765
|
|
|
604
766
|
The founder asks the app's agent to make something, from inside the app,
|
|
605
|
-
and
|
|
606
|
-
blunt: with invocation only on the window's Agent
|
|
607
|
-
work and then stares at an app where nothing
|
|
767
|
+
and sees it happen where the result will land. This is SOP §2.3 and the
|
|
768
|
+
reason it exists is blunt: with invocation only on the window's Agent
|
|
769
|
+
tab, a founder starts work and then stares at an app where nothing
|
|
770
|
+
changes.
|
|
771
|
+
|
|
772
|
+
Since 0.4.0 a job is a request. `job.start(text)` asks through `ask()`
|
|
773
|
+
(below): the platform makes the ask a request, hands it to the app's
|
|
774
|
+
agent, and the desk opens its conversation in the agent column. **The
|
|
775
|
+
conversation and the yes live in the desk's agent column** — the agent's
|
|
776
|
+
questions, the founder's answers, the "Send 50 emails?" before anything
|
|
777
|
+
public or paid. The app follows the request's status and draws the
|
|
778
|
+
panel from it. Up to 0.3.x the job streamed into the app over
|
|
779
|
+
`_claw/chat` and the panel carried the agent's running text; on a claw
|
|
780
|
+
that has no `_claw/ask` yet, `useJob` still does exactly that (`text`
|
|
781
|
+
filled, `requestId` null), so a 0.4.0 app runs on an older claw.
|
|
608
782
|
|
|
609
783
|
```jsx
|
|
610
784
|
const job = useJob();
|
|
@@ -612,24 +786,64 @@ const products = useProducts(); // your own data hook
|
|
|
612
786
|
|
|
613
787
|
useEffect(() => { products.reload(); }, [job.progress]); // rows as they land
|
|
614
788
|
|
|
615
|
-
<
|
|
789
|
+
<Button onClick={() => job.start("Draft a launch checklist for Rowhouse")} disabled={job.running}>
|
|
616
790
|
Generate a launch plan
|
|
617
|
-
</
|
|
618
|
-
|
|
619
|
-
{job.running || job.finishedAt ? (
|
|
620
|
-
<aside> {/* your design, not ours */}
|
|
621
|
-
{job.steps.map((s, i) => <li key={i} data-status={s.status}>{s.label}</li>)}
|
|
622
|
-
<p>{job.text}</p>
|
|
623
|
-
{job.running ? <button onClick={job.cancel}>Stop</button>
|
|
624
|
-
: <button onClick={job.reset}>Close</button>}
|
|
625
|
-
</aside>
|
|
626
|
-
) : null}
|
|
791
|
+
</Button>
|
|
792
|
+
<JobPanel job={job} title="Drafting the launch plan" doneTitle="Launch plan drafted" />
|
|
627
793
|
```
|
|
628
794
|
|
|
629
795
|
The panel is a block — `makersclaw add job-panel` — that keeps the four
|
|
630
|
-
rules below for you
|
|
631
|
-
|
|
632
|
-
|
|
796
|
+
rules below for you and says what the request says: its status and its
|
|
797
|
+
count ("Drafting · 12 of 50"), "Needs your answer" with **Answer** while
|
|
798
|
+
the agent waits on the founder, "Queued · after …" while the job waits
|
|
799
|
+
its turn. Draw your own only when it cannot say what the founder needs
|
|
800
|
+
to see, and from the same fields: mounted on `job.running ||
|
|
801
|
+
job.finishedAt || job.error`, `job.liveLine` as its line, `job.needsYou`
|
|
802
|
+
→ a button calling `job.open()`, `job.queued` →
|
|
803
|
+
`job.queuedAfter?.after_title`, Stop → `job.cancel()`, Close →
|
|
804
|
+
`job.reset()`.
|
|
805
|
+
|
|
806
|
+
**Which job.** `start` sends `job: true` unless you pass a key, and the
|
|
807
|
+
platform picks: the job whose view is this surface, else the app's only
|
|
808
|
+
job, else none. Pass `job.start(text, { job: "<key>" })` when a surface
|
|
809
|
+
starts a job whose view is ANOTHER surface — "Find 20 leads" pressed on
|
|
810
|
+
Pipeline, landing on Leads — or it picks the wrong one, or none. A key
|
|
811
|
+
the app does not declare is refused (`unknown_job`).
|
|
812
|
+
|
|
813
|
+
**One job per list runs at a time.** A start on a job that is already
|
|
814
|
+
running for that list does not run beside it: it answers `queued`
|
|
815
|
+
(`job.queued`; `job.queuedAfter.after_title` names what it waits for)
|
|
816
|
+
and starts on its own when that one ends. A job that repeats on a
|
|
817
|
+
schedule waits the same way behind the app's scheduled run. It counts as
|
|
818
|
+
running meanwhile — the panel stays, and Stop takes it out of the
|
|
819
|
+
queue. With ten asks waiting, the app refuses the next (`queue_full`).
|
|
820
|
+
|
|
821
|
+
**`start` resolves on the ask, not on the work:** `{ ok: true, data: {
|
|
822
|
+
request } }` once the platform took it, or the refusal (`{ ok: false,
|
|
823
|
+
error, code }` — `needs_answer` while the thread waits on an answer in
|
|
824
|
+
the column, `queue_full`, `unknown_job`, `no_credits`, …; the panel
|
|
825
|
+
shows the error). The hook then follows the request — every 2.5 s while
|
|
826
|
+
the App tab is on screen — until it ends. On the 0.3.x path it resolves
|
|
827
|
+
when the job ends, as it always did.
|
|
828
|
+
|
|
829
|
+
What it holds, for the panel:
|
|
830
|
+
|
|
831
|
+
- `running` — the work is live: any of the seven live statuses, queued
|
|
832
|
+
and needs-you included. `status` is the request's own.
|
|
833
|
+
- `needsYou` — the agent waits on the founder; `open()` takes them to
|
|
834
|
+
the question in the column.
|
|
835
|
+
- `queued`, `queuedAfter` (`{ reason, after_id, after_title }`).
|
|
836
|
+
- `liveLine` — the desk's line for it; `count`, `target` — the counted
|
|
837
|
+
progress (12 and 50), or null.
|
|
838
|
+
- `steps` — one: the request, its line as the `label`. `text` — empty:
|
|
839
|
+
the account is in the column.
|
|
840
|
+
- `progress` — bumps whenever the work moves (its status, its count).
|
|
841
|
+
- `error` — the line when it didn't finish, "Stopped" when stopped.
|
|
842
|
+
- `sessionId`, `requestId`, `request` (the whole request), `finishedAt`.
|
|
843
|
+
|
|
844
|
+
Actions: `start(text, { sessionId, row, how, job, list, open })` — the
|
|
845
|
+
options are `ask()`'s; `cancel()` stops the request itself, not only the
|
|
846
|
+
panel; `reset()` closes the panel; `open()` opens the conversation.
|
|
633
847
|
|
|
634
848
|
Four rules, and a build is wrong without them:
|
|
635
849
|
|
|
@@ -638,18 +852,65 @@ Four rules, and a build is wrong without them:
|
|
|
638
852
|
Never a nav item named after the agent, and never the only way to
|
|
639
853
|
create that object — the manual form has to exist too.
|
|
640
854
|
2. **The panel does not exist when no job is running.** Mount it from
|
|
641
|
-
`job.running || job.finishedAt
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
855
|
+
`job.running || job.finishedAt || job.error` (a refused ask has an
|
|
856
|
+
error and no finish); `job.reset()` closes it. A panel that is always
|
|
857
|
+
there is the monitoring console (§2.7).
|
|
858
|
+
3. **The deliverable is ROWS.** Not `job.text` — the agent's own
|
|
859
|
+
account, and empty on a request, whose account is in the column —
|
|
860
|
+
and not the request's line. That is why `job.progress` reloads your
|
|
861
|
+
data hook, and why the panel closing leaves a fuller list behind it.
|
|
647
862
|
4. **Show `step.label`, never `step.name`.** `label` is the founder's
|
|
648
|
-
phrase ("Searching the web"
|
|
863
|
+
phrase ("Drafting · 12 of 50"; "Searching the web" on the 0.3.x
|
|
864
|
+
path); `name` is `request`, or a tool's `web_search`.
|
|
865
|
+
|
|
866
|
+
`job.sessionId` is the thread, and on a request it is set only when the
|
|
867
|
+
thread is yours. Keep it on the row the job produced and pass it back as
|
|
868
|
+
`job.start(msg, { sessionId })` to refine that one thing where it lives
|
|
869
|
+
— one conversation per object, never a global chat. A thread that is a
|
|
870
|
+
teammate's, or one the claw does not know (a 0.3.x session kept on a
|
|
871
|
+
row), is refused; `start` then asks once more without it, and the ask
|
|
872
|
+
gets a thread of its own.
|
|
873
|
+
|
|
874
|
+
### Asking the agent — `ask()`
|
|
875
|
+
|
|
876
|
+
`useJob` is `ask()` plus a panel to follow it. Call `ask()` from
|
|
877
|
+
`lib/platform.js` yourself when there is nothing for the app to follow:
|
|
878
|
+
a question about a row ("What do we know about Acme?"), a one-off whose
|
|
879
|
+
answer is words in the column, not rows. It takes one object:
|
|
880
|
+
|
|
881
|
+
- `text` — what the member reads. It is the request's words on the desk
|
|
882
|
+
(the Requests list, the column), so write it the way they would say
|
|
883
|
+
it: "Find 20 leads like Acme", never "You are a lead finder…".
|
|
884
|
+
Required; at most 2,000 characters.
|
|
885
|
+
- `how` — for the agent only: the steps, the table to write, the
|
|
886
|
+
format. The member never sees it. At most 4,000 characters.
|
|
887
|
+
- `row` — the row it is about: the agent's context (at most 8 KB as
|
|
888
|
+
JSON). Never an example row — one marked `_sample` is refused.
|
|
889
|
+
- `job`, `list` — name the job: a declared key, or `true` to let the
|
|
890
|
+
platform pick (what `useJob` sends by default), and the list its work
|
|
891
|
+
belongs to, for an app that declares lists (§4, Views by link). With
|
|
892
|
+
no `job` the ask is not a job: nothing queues, nothing is counted.
|
|
893
|
+
- `surface` — defaults to the `<key>` of `/s/<key>` in the page's path.
|
|
894
|
+
- `sessionId` — continue your own thread: while its request is open the
|
|
895
|
+
ask joins it; once it has closed, a new request follows it in the
|
|
896
|
+
same thread. While that request waits on an answer (`needs_answer`)
|
|
897
|
+
or in the queue (`queued`, 409 both) the ask is refused: the answer
|
|
898
|
+
goes in the column, and a queued one starts on its own. A teammate's
|
|
899
|
+
thread is refused (`not_your_thread`, 403) — ask again without it to
|
|
900
|
+
start your own; `useJob` does that for you.
|
|
901
|
+
- `open` — the ask opens its request in the desk's agent column, where
|
|
902
|
+
the conversation lives; `false` leaves the column as it is.
|
|
903
|
+
|
|
904
|
+
```js
|
|
905
|
+
"use client";
|
|
906
|
+
import { ask } from "../lib/platform";
|
|
907
|
+
|
|
908
|
+
const r = await ask({ text: `What do we know about ${row.name}?`, row, how: "Read the Wiki's customer pages first." });
|
|
909
|
+
if (!r.ok) setError(r.error); // r.code: needs_answer, queue_full, …
|
|
910
|
+
```
|
|
649
911
|
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
where it lives — one conversation per object, never a global chat.
|
|
912
|
+
It resolves once the platform took the ask (`{ ok: true, data: {
|
|
913
|
+
request, desk_origin } }`) or refused it; it never waits for the work.
|
|
653
914
|
|
|
654
915
|
### Activity — the contract
|
|
655
916
|
|
|
@@ -706,10 +967,10 @@ second build there dies with `EACCES: .next/trace`.
|
|
|
706
967
|
check: 5 files · 2 surfaces
|
|
707
968
|
ok colours no colour outside the workspace theme (1 depiction excepted)
|
|
708
969
|
ok controls every control is a kit part
|
|
709
|
-
ok examples
|
|
970
|
+
ok examples examples show only outside a workspace (useFixture); a workspace app opens empty
|
|
710
971
|
ok manifest 2 surfaces: drafts, sent
|
|
711
972
|
ok icons every icon name exists
|
|
712
|
-
ok kit
|
|
973
|
+
ok kit 70 fixed files match the template
|
|
713
974
|
ok depictions 1 picture of elsewhere, framed on 1 surface: x-post
|
|
714
975
|
ok smoke built in 41s · / → 307 · /s/drafts 200 · /s/sent 200 (0.6s)
|
|
715
976
|
all clear
|
|
@@ -726,7 +987,9 @@ The smoke test starts that build on a loopback port and loads every
|
|
|
726
987
|
surface; `--static` skips the build and the smoke. Then, by eye:
|
|
727
988
|
|
|
728
989
|
- Renders at 360px, 768px and 1280px without horizontal scroll.
|
|
729
|
-
-
|
|
990
|
+
- In a workspace, an empty table shows `LiveEmpty`: the work in flight,
|
|
991
|
+
else what would be here and the manual way to add it — never an
|
|
992
|
+
example (§3a). A saved row replaces it.
|
|
730
993
|
- Error state shows the text from `{ error }`.
|
|
731
994
|
- Keyboard focus visible on every control.
|
|
732
995
|
- Numbers tabular (`data-nums`).
|
|
@@ -747,7 +1010,8 @@ surface; `--static` skips the build and the smoke. Then, by eye:
|
|
|
747
1010
|
server — `command "npx next dev -H 127.0.0.1"`, `build ""` — so each
|
|
748
1011
|
edit shows in the founder's live preview without a rebuild.
|
|
749
1012
|
`product_run` sets `CLAW_BASE_PATH`, `CLAW_PRODUCT`,
|
|
750
|
-
`CLAW_PRODUCT_TOKEN` and `CLAW_ARTIFACT_ID
|
|
1013
|
+
`CLAW_PRODUCT_TOKEN` and `CLAW_ARTIFACT_ID` — the last is what turns
|
|
1014
|
+
examples off (§3a).
|
|
751
1015
|
|
|
752
1016
|
Every `artifact_files` write under `app/` comes back with the console's
|
|
753
1017
|
state; a 5xx carries the log tail. Fix it before moving on — the founder
|
|
@@ -755,6 +1019,8 @@ is watching the preview.
|
|
|
755
1019
|
|
|
756
1020
|
Every page is `dynamic = "force-dynamic"`: nothing is prerendered, so a
|
|
757
1021
|
build never needs the database. `app/auto-refresh.jsx` re-renders from
|
|
758
|
-
the server every 5 s while the tab is
|
|
759
|
-
|
|
1022
|
+
the server every 5 s while the App tab is on screen and nobody is typing
|
|
1023
|
+
(never while a sheet is open, a field they typed in holds focus, or
|
|
1024
|
+
within 10 s of a keystroke) and the app answers `/claw-alive` (a fixed
|
|
1025
|
+
route) with a 204, so the agent's writes appear. The founder reads "artifact", "app", "agent", "automation" —
|
|
760
1026
|
nothing else.
|