makersclaw 0.7.0 → 0.8.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 +8 -1
- package/package.json +1 -1
- package/r/annotate.json +4 -4
- 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 +5 -5
- package/r/chat.json +3 -3
- 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 +7 -1
- 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 +3 -3
- package/r/footer.json +3 -3
- package/r/gantt.json +3 -3
- package/r/job-panel.json +2 -2
- package/r/json-editor.json +3 -3
- package/r/map.json +2 -2
- package/r/menubar.json +2 -2
- package/r/mind-map.json +4 -4
- package/r/navigation-menu.json +2 -2
- package/r/order-detail.json +2 -2
- package/r/org-chart.json +4 -4
- package/r/page-builder.json +3 -3
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +3 -3
- 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 +126 -121
- 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 +3 -3
- package/r/signature-pad.json +2 -2
- package/r/sketch-canvas.json +3 -3
- 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 +4 -4
- package/registry/blocks/annotate/README.md +14 -11
- package/registry/blocks/chart/cartesian.jsx +1 -1
- package/registry/blocks/chart/core.jsx +33 -23
- package/registry/blocks/chart/pie.jsx +1 -1
- package/registry/blocks/chat/README.md +4 -1
- package/registry/blocks/chat/demo.jsx +2 -2
- package/registry/blocks/flow-canvas/README.md +15 -8
- package/registry/blocks/footer/README.md +3 -2
- package/registry/blocks/mind-map/README.md +4 -2
- package/registry/blocks/org-chart/README.md +4 -2
- package/registry/blocks/page-builder/README.md +1 -1
- package/registry/blocks/page-builder/demo.jsx +3 -3
- package/registry/blocks/pdf-viewer/README.md +4 -1
- package/registry/blocks/sidebar/README.md +23 -13
- package/registry/blocks/sketch-canvas/README.md +15 -9
- package/registry/blocks/whiteboard/README.md +20 -14
- package/src/cli.mjs +2 -2
- package/src/install.mjs +23 -6
- package/src/registry.mjs +34 -3
- package/templates/next/files/README.md +291 -11
- package/templates/next/files/app/shell.jsx +54 -37
- package/templates/next/files/check/run.mjs +76 -4
- package/templates/next/files/components/ui/index.js +1 -0
- package/templates/next/files/components/ui/layout.jsx +307 -0
- package/templates/next/files/lib/claw.js +4 -1
- package/templates/next/files/lib/hooks.js +71 -2
- package/templates/next/files/lib/platform.js +93 -4
- package/templates/next/template.json +10 -1
|
@@ -60,7 +60,7 @@ leaves out four (Per mode, below). Which to build: §4.
|
|
|
60
60
|
Yours — write and rewrite freely:
|
|
61
61
|
|
|
62
62
|
```
|
|
63
|
-
app/shell.jsx the inside of the window: nav, layout, type, density
|
|
63
|
+
app/shell.jsx the inside of the window: nav, layout, type, density — an AppLayout (§3b, Layouts)
|
|
64
64
|
app/DESIGN.md the design, written before the code (§3)
|
|
65
65
|
surfaces/<key>.jsx one file per domain surface
|
|
66
66
|
surfaces.js the manifest entries (import + component when built)
|
|
@@ -78,7 +78,7 @@ app/page.jsx / → the home surface (or "No surfaces yet" while the ma
|
|
|
78
78
|
app/s/[key]/page.jsx renders the surface with that key; planned ones say so
|
|
79
79
|
app/error.jsx, app/global-error.jsx, app/not-found.jsx, app/error-relay.jsx, app/auto-refresh.jsx,
|
|
80
80
|
app/escape-relay.jsx, app/frame.js, app/claw-alive/route.js the app inside its window: refresh while shown (a surface opts out, §7), Escape from the desk
|
|
81
|
-
components/ui/ the kit: Button, Field, Input, Select, Sheet, Tabs, Menu, Notice, Icon, Depiction… (§3b, §3c)
|
|
81
|
+
components/ui/ the kit: AppLayout, DocumentLayout, Button, Field, Input, Select, Sheet, Tabs, Menu, Notice, Icon, Depiction… (§3b, §3c)
|
|
82
82
|
components/parts/ the parts: RecordSwitcher, LiveEmpty + LiveWork, useFixture + FixtureNotice (§3a, §3b)
|
|
83
83
|
check/pins.json the fixed files by hash; `npm run check` refuses one that differs
|
|
84
84
|
check/ `npm run check` (§6)
|
|
@@ -99,7 +99,7 @@ lib/brain.js the company brain from the browser, in both modes; its ho
|
|
|
99
99
|
lib/brain-server.js the brain's reads for a server component — a server app's
|
|
100
100
|
lib/claw.js the _claw API the gateway answers, from the browser
|
|
101
101
|
lib/platform.js typed wrappers over lib/claw.js (Platform)
|
|
102
|
-
lib/hooks.js the React hooks over them: useJob, useArtifact… (Hooks)
|
|
102
|
+
lib/hooks.js the React hooks over them: useJob, useLlm, useArtifact… (Hooks)
|
|
103
103
|
lib/sample.js markSample, for sample rows (§3a)
|
|
104
104
|
```
|
|
105
105
|
|
|
@@ -147,7 +147,12 @@ Before any code, write `app/DESIGN.md` — one screen of text:
|
|
|
147
147
|
- Type: scale and weight within the workspace's faces (`font-sans`,
|
|
148
148
|
`font-mono`, `font-display`). Numbers tabular (`data-nums` on the element).
|
|
149
149
|
- Layout and nav: where the surfaces live, what the home surface shows in
|
|
150
|
-
the first second.
|
|
150
|
+
the first second. Who scrolls: the shell is an `AppLayout` (§3b,
|
|
151
|
+
Layouts), the one owner of the window's height; say for each surface
|
|
152
|
+
whether its content scrolls (a list, an article) or fills (a canvas, a
|
|
153
|
+
grid that scrolls itself), whether it has an inspector and how wide,
|
|
154
|
+
and what sits in a footer that must stay in view. Decide it before
|
|
155
|
+
the shell, not after a long page has pushed a button out of reach.
|
|
151
156
|
- Density, empty states (§3a: what each surface says while its table is
|
|
152
157
|
empty), error states.
|
|
153
158
|
- What makes this app look like ITS domain and not another.
|
|
@@ -159,7 +164,8 @@ to design: Agent, Runs and Activity are tabs the OS draws on the window,
|
|
|
159
164
|
and the app's settings — model, prompt, skills, connections, schedule —
|
|
160
165
|
are the window's Settings tab, the same for every artifact. The keys
|
|
161
166
|
`agent`, `runs` and `activity` are reserved; a surface may not take one.
|
|
162
|
-
The starter `app/shell.jsx` is a placeholder; replace it first
|
|
167
|
+
The starter `app/shell.jsx` is a placeholder; replace it first, and
|
|
168
|
+
keep it an `AppLayout`.
|
|
163
169
|
|
|
164
170
|
What the OS does **not** own is asking the agent for something. Those
|
|
165
171
|
tabs are where the founder goes to look back; the app is where they set
|
|
@@ -256,12 +262,64 @@ import { Button, IconButton, ButtonGroup, ButtonGroupSeparator, Toggle, ToggleGr
|
|
|
256
262
|
Table, TableHeader, TableBody, TableFooter, TableRow, TableHead, TableCell, TableCaption,
|
|
257
263
|
Spinner, Skeleton, Progress, Separator, Kbd, KbdGroup, AspectRatio,
|
|
258
264
|
Stat, StatGroup, Delta, StatusDot, Legend, Meter, Distribution, Swatch, Steps,
|
|
259
|
-
toast, Toaster, Depiction, Icon, ICON_NAMES, usePopoverPosition, panelClass, cx
|
|
265
|
+
toast, Toaster, Depiction, Icon, ICON_NAMES, usePopoverPosition, panelClass, cx,
|
|
266
|
+
AppLayout, DocumentLayout }
|
|
260
267
|
from "../components/ui"; // "./ui" from a component
|
|
261
268
|
import { RecordSwitcher, LiveEmpty, LiveWork, liveWords, useFixture, FixtureNotice, isSample,
|
|
262
269
|
Examples, useExamples } from "../components/parts";
|
|
263
270
|
```
|
|
264
271
|
|
|
272
|
+
- **Layouts** — who owns the window's height, and who scrolls. Every
|
|
273
|
+
design-first build of 2026-10-07 shipped a shell of `min-h-dvh` that
|
|
274
|
+
grew with its content: no pane scrolled on its own, and a footer or a
|
|
275
|
+
button after a long article sat past the end of the window, where
|
|
276
|
+
nothing could reach it. So the kit owns the window, once, and `npm
|
|
277
|
+
run check` refuses anything else that sizes by it (`viewport`, §6).
|
|
278
|
+
|
|
279
|
+
`AppLayout` is the app frame and the one viewport owner: the
|
|
280
|
+
outermost one is exactly the window's height; one inside another
|
|
281
|
+
fills the slot it is in instead, so there is never a second owner.
|
|
282
|
+
The shell is one. Slots and props:
|
|
283
|
+
`header` (the top bar, in view; it draws its own bottom border) ·
|
|
284
|
+
`navigation` (a rail from 768px, `navigationWidth` px, 176 by default;
|
|
285
|
+
below 768px a Sheet behind the menu button, titled `navigationLabel`;
|
|
286
|
+
a press on a link closes it) · `children`, the content, with
|
|
287
|
+
`contentMode` `scroll` (the default: its region scrolls) or `fill` (a
|
|
288
|
+
bounded column whose child scrolls itself: a canvas, a grid, a
|
|
289
|
+
spreadsheet), `contentLabel`, `contentClassName` (spacing and token
|
|
290
|
+
backgrounds only) and `scrollKey` (pass the route: when it changes the
|
|
291
|
+
content scrolls back to its top and the drawer closes) · `inspector`
|
|
292
|
+
(beside the content from `inspectorAt` md · lg · xl, lg by default;
|
|
293
|
+
`inspectorWidth` px, 360 by default, 240 to 560; `inspectorLabel`) ·
|
|
294
|
+
`footer` (under the content, in view) · `height` viewport · parent.
|
|
295
|
+
Below `inspectorAt` there is no room for two columns: in `scroll` mode
|
|
296
|
+
the content and the inspector share one scroll, the inspector after
|
|
297
|
+
the content, so everything is reachable and nothing hides behind a
|
|
298
|
+
button; in `fill` mode the inspector is a bounded pane under the
|
|
299
|
+
content (at most 45% of the height) that scrolls on its own. A
|
|
300
|
+
surface that needs an inspector or a fill of its own returns an
|
|
301
|
+
`AppLayout` as its outermost element; inside the shell it fills the
|
|
302
|
+
content edge to edge (the content's padding drops for it):
|
|
303
|
+
```jsx
|
|
304
|
+
<AppLayout
|
|
305
|
+
header={<EditorBar draft={draft} />}
|
|
306
|
+
inspector={<ReviewPanel notes={notes} />}
|
|
307
|
+
inspectorWidth={440}
|
|
308
|
+
footer={<SaveState saved={saved} />}
|
|
309
|
+
contentClassName="p-6"
|
|
310
|
+
contentLabel="Draft"
|
|
311
|
+
>
|
|
312
|
+
<DraftText draft={draft} />
|
|
313
|
+
</AppLayout>
|
|
314
|
+
```
|
|
315
|
+
`DocumentLayout` is a page that flows, for print and export: no
|
|
316
|
+
height of its own, it sits in the content and the content scrolls it.
|
|
317
|
+
`toolbar` (held at the top, never printed), `width` prose · page (A4)
|
|
318
|
+
· letter · full, `paper` (a sheet on screen, plain in print), `align`
|
|
319
|
+
start · center, `label`. In print the frame lets go (no bound, no
|
|
320
|
+
navigation, header, inspector or footer) and the document flows onto
|
|
321
|
+
pages. Inside either, bound a box by its parent (`h-full`, `flex-1
|
|
322
|
+
min-h-0`) or by a size (`max-h-96`, `ScrollArea`), never by the window.
|
|
265
323
|
- `Button` — `variant` primary (one per surface, in the app's accent —
|
|
266
324
|
say it: a Button with no `variant` is secondary) · secondary · quiet ·
|
|
267
325
|
danger · link (inline text: no padding or height, wraps and truncates
|
|
@@ -1274,6 +1332,8 @@ import { platform } from "../lib/platform"; // or the named functions
|
|
|
1274
1332
|
platform.me() GET _claw/me → { artifact: { id, name, vehicle, status, home }, user: { id, email }, blueprint, desk_origin }
|
|
1275
1333
|
platform.ask({ text, row, how, job, list, surface, sessionId, open })
|
|
1276
1334
|
POST _claw/ask → { ok, request, desk_origin }, and the column opens on it
|
|
1335
|
+
platform.llm(input, { system, schema, maxTokens, tier, task, signal })
|
|
1336
|
+
POST _claw/llm → { ok: true, text } | { ok: true, data } | { ok: false, error, code } (Calling a model)
|
|
1277
1337
|
platform.requests() GET _claw/requests → { requests, run, desk_origin }
|
|
1278
1338
|
platform.requests({ id }) GET _claw/requests?id=<id> → { request }
|
|
1279
1339
|
platform.stopRequest(id) POST _claw/requests/<id>/stop → { ok, request }
|
|
@@ -1297,13 +1357,16 @@ didnt_finish. `line` is the one line every screen shows for it
|
|
|
1297
1357
|
show it as it is, never a status word of your own. `app_session_id` is
|
|
1298
1358
|
set only on your own requests.
|
|
1299
1359
|
|
|
1300
|
-
Named exports: `me, ask, requests, stopRequest, openRequest,
|
|
1360
|
+
Named exports: `me, ask, llm, requests, stopRequest, openRequest,
|
|
1301
1361
|
surfaceOfPath, startJob, cancelJob, reportError, platform`. That is the
|
|
1302
1362
|
whole list. Note what is **not** there: the runs, the chat history, Run
|
|
1303
1363
|
now, Pause / Resume. Those are MONITORING and they are the OS's — the
|
|
1304
1364
|
window's tabs and title bar — so an app never calls `_claw/agent`,
|
|
1305
1365
|
`_claw/runs`, `_claw/run`, `_claw/pause` or `_claw/resume`. Asking is the
|
|
1306
1366
|
opposite thing, and it belongs here (`ask()` and `useJob`, below).
|
|
1367
|
+
`llm()` is neither: one call to a model, answered in the page (Calling a
|
|
1368
|
+
model, below). It is the one wrapper that resolves the platform's answer
|
|
1369
|
+
as it is, `{ ok: true, text }` and not `{ ok: true, status, data }`.
|
|
1307
1370
|
`requests` is not monitoring either: it is this app's asks in flight, so
|
|
1308
1371
|
an empty surface can show the work coming (§3a). `startJob` and
|
|
1309
1372
|
`cancelJob` are the 0.3.x door to the same work, kept so `useJob` can
|
|
@@ -1316,8 +1379,8 @@ that exact origin — never `"*"` — and only while the app is framed.
|
|
|
1316
1379
|
|
|
1317
1380
|
### Hooks — `lib/hooks.js` ("use client")
|
|
1318
1381
|
|
|
1319
|
-
|
|
1320
|
-
a job,
|
|
1382
|
+
Five hooks: a generic fetch over one `_claw` route, the app's identity,
|
|
1383
|
+
a job, the app's requests, and a model call. Each owns fetching, polling (while the
|
|
1321
1384
|
tab is visible; once a minute while `unavailable`), streaming and cleanup
|
|
1322
1385
|
on unmount. A surface renders what comes back and owns every pixel.
|
|
1323
1386
|
|
|
@@ -1328,6 +1391,7 @@ useJob() → { running, status, queued, que
|
|
|
1328
1391
|
steps, text, progress, error, sessionId, requestId, request, finishedAt,
|
|
1329
1392
|
start, cancel, reset, open }
|
|
1330
1393
|
useRequests({ surface, job, every = 5000 }) → { requests, live, run, loading, error, unavailable, reload, open, stop }
|
|
1394
|
+
useLlm() → { run, loading, result, error, unavailable } (Calling a model)
|
|
1331
1395
|
jobFromRequest(request) → what useJob shows for one request (pure; for tests)
|
|
1332
1396
|
```
|
|
1333
1397
|
|
|
@@ -1504,6 +1568,198 @@ if (!r.ok) setError(r.error); // r.code: needs_answer, queue_fu
|
|
|
1504
1568
|
It resolves once the platform took the ask (`{ ok: true, data: {
|
|
1505
1569
|
request, desk_origin } }`) or refused it; it never waits for the work.
|
|
1506
1570
|
|
|
1571
|
+
### Calling a model — `llm()`
|
|
1572
|
+
|
|
1573
|
+
`llm()` is for one-shot text work on what is on screen: sort these
|
|
1574
|
+
mails, draft this reply, translate this note, summarise this thread,
|
|
1575
|
+
pull the date out of this line. The answer comes back in the page, and
|
|
1576
|
+
the screen does what it likes with it.
|
|
1577
|
+
|
|
1578
|
+
**`llm()` for one-shot text work on what the screen holds; `ask()` for
|
|
1579
|
+
work that needs tools, the workspace, or more than one step.** Finding
|
|
1580
|
+
leads, reading the Wiki, writing rows for forty threads, anything that
|
|
1581
|
+
should show in the agent column and the Requests list: that is `ask()`.
|
|
1582
|
+
The app's agent does it, with its tools. `llm()` has none: it sees the
|
|
1583
|
+
words you hand it and nothing else.
|
|
1584
|
+
|
|
1585
|
+
```js
|
|
1586
|
+
"use client";
|
|
1587
|
+
import { updateRow } from "../lib/actions";
|
|
1588
|
+
import { llm } from "../lib/platform";
|
|
1589
|
+
|
|
1590
|
+
// Text.
|
|
1591
|
+
const r = await llm(thread.body, {
|
|
1592
|
+
system: "Draft a short, warm reply from the founder. Plain text, no greeting.",
|
|
1593
|
+
task: "draft-reply",
|
|
1594
|
+
});
|
|
1595
|
+
if (r.ok) setDraft(r.text); // r.error says why not
|
|
1596
|
+
|
|
1597
|
+
// Data: a schema in, an answer checked against it out.
|
|
1598
|
+
const SORT = {
|
|
1599
|
+
type: "object",
|
|
1600
|
+
required: ["label", "urgent"],
|
|
1601
|
+
properties: { label: { enum: ["sales", "support", "billing", "other"] }, urgent: { type: "boolean" } },
|
|
1602
|
+
};
|
|
1603
|
+
const s = await llm(`From: ${mail.from}\nSubject: ${mail.subject}\n\n${mail.body}`, { system: "Sort this mail.", schema: SORT, task: "sort-mail" });
|
|
1604
|
+
if (s.ok) await updateRow({ source: "mail", id: mail.id, patch: { label: s.data.label, urgent: s.data.urgent } });
|
|
1605
|
+
```
|
|
1606
|
+
|
|
1607
|
+
`data` is the model's JSON as it came: **a key your schema does not name
|
|
1608
|
+
is kept**. Read the keys you asked for, as above, and do not spread
|
|
1609
|
+
`data` into a row.
|
|
1610
|
+
|
|
1611
|
+
`llm(input, opts)` takes:
|
|
1612
|
+
|
|
1613
|
+
- `input` — what to work on: a string, or a conversation, `[{ role:
|
|
1614
|
+
"user" | "assistant", content }]`: at most 100 messages, none empty,
|
|
1615
|
+
the last one the user's (the one to answer). Required. An empty one
|
|
1616
|
+
asks nothing of the platform and resolves `bad_input`.
|
|
1617
|
+
- `system` — how to go about it, at most 8,000 characters.
|
|
1618
|
+
- `schema` — a JSON Schema for the answer, an object, at most 16 KB
|
|
1619
|
+
once written out. The answer then comes back as `data`, checked
|
|
1620
|
+
against it, not as `text`. If the model's reply misses, the platform
|
|
1621
|
+
sends it back once with what was wrong; a second miss is
|
|
1622
|
+
`schema_mismatch`. The platform runs your schema on the model's words,
|
|
1623
|
+
so what it may hold is bounded (below).
|
|
1624
|
+
- `maxTokens` — the answer's ceiling, a whole number, 1 or more. 1,024
|
|
1625
|
+
unless you say; over 4,096 is cut to 4,096. (It travels as
|
|
1626
|
+
`max_tokens`.)
|
|
1627
|
+
- `tier` — `"fast"` (the default) or `"smart"` for the harder job. An
|
|
1628
|
+
app never names a model. `smart` costs more and is slower: a long
|
|
1629
|
+
answer from it can run into the 60 second limit, so keep `maxTokens`
|
|
1630
|
+
modest there.
|
|
1631
|
+
- `task` — a short label for the usage line, `a-z`, `0-9` and `-`, at
|
|
1632
|
+
most 40 characters (`"sort-mail"`).
|
|
1633
|
+
- `signal` — an `AbortSignal`. An abort resolves `{ ok: false, aborted:
|
|
1634
|
+
true }`.
|
|
1635
|
+
|
|
1636
|
+
**What a schema may hold.** Write it out as a tree, small. The call
|
|
1637
|
+
refuses a schema with:
|
|
1638
|
+
|
|
1639
|
+
- `pattern` or `patternProperties` (`bad_input`): a regular expression
|
|
1640
|
+
run on a model's words can hang the platform. Use `enum`, `format` or
|
|
1641
|
+
length bounds.
|
|
1642
|
+
- `$ref`, `$defs` or `definitions` (`bad_input`; `$dynamicRef` and
|
|
1643
|
+
`$recursiveRef` too): each reference is checked in full where it is
|
|
1644
|
+
used, so a few can mean a tree of any size. Write the shape out where
|
|
1645
|
+
it is used.
|
|
1646
|
+
- nesting deeper than 20 levels, a list inside a list counted
|
|
1647
|
+
(`bad_input`).
|
|
1648
|
+
- more than 1,000 parts (`too_large`): every object in it, every `enum`
|
|
1649
|
+
value and every type listed in a `type` array. A real answer's schema
|
|
1650
|
+
is a few dozen.
|
|
1651
|
+
- a schema it cannot read, such as a `type` it does not know
|
|
1652
|
+
(`bad_input`).
|
|
1653
|
+
|
|
1654
|
+
A reply with so many values that checking it against the schema would
|
|
1655
|
+
cost too much is a miss like any other (`schema_mismatch`, and the
|
|
1656
|
+
model is told why): ask for fewer values, or a smaller schema. Make the
|
|
1657
|
+
root an object (`type: "object"`): providers' structured answers mostly
|
|
1658
|
+
want one, and when one refuses the format the platform asks again in
|
|
1659
|
+
words alone, which is less sure. Everything else in JSON Schema is let
|
|
1660
|
+
through.
|
|
1661
|
+
|
|
1662
|
+
It resolves, and never throws:
|
|
1663
|
+
|
|
1664
|
+
- `{ ok: true, text }` — no schema.
|
|
1665
|
+
- `{ ok: true, data }` — with a schema.
|
|
1666
|
+
- `{ ok: false, error, code, status }` — a refusal, as the platform
|
|
1667
|
+
sent it. Branch on `code`. **Show `error` as it is for the codes that
|
|
1668
|
+
speak to the member, and a plain line of your own for the rest.** The
|
|
1669
|
+
rest speak to you, the developer ("schema uses "pattern""), and a
|
|
1670
|
+
member should not read them.
|
|
1671
|
+
|
|
1672
|
+
Words for the member, shown as they are:
|
|
1673
|
+
|
|
1674
|
+
- `no_credits` (402): the workspace's wallet is empty, the workspace
|
|
1675
|
+
is suspended, or the Free plan's daily limit is used up.
|
|
1676
|
+
`member_cap` (402): the member's monthly spend cap is used up.
|
|
1677
|
+
`app_cap` (402): this app's weekly cap is used up. Say so and stop;
|
|
1678
|
+
do not retry.
|
|
1679
|
+
- `rate_limited` (429): 30 calls a minute per member. Wait a moment,
|
|
1680
|
+
then again.
|
|
1681
|
+
- `model_failed` (502), `try_again` (503) and `failed` (500): the
|
|
1682
|
+
model, the membership check or the platform could not answer just
|
|
1683
|
+
now. Try again.
|
|
1684
|
+
- `timeout` (504): a call is held to 60 seconds, a schema retry
|
|
1685
|
+
included, and one the clock cuts is still charged. Ask for less (a
|
|
1686
|
+
lower `maxTokens`, a shorter input) rather than the same again.
|
|
1687
|
+
- `no_member_identity` (401) and `not_a_member` (403): the page is not
|
|
1688
|
+
running in a member's own session (`no_member_identity` says to open
|
|
1689
|
+
the app from the desk again), or the member was removed from the
|
|
1690
|
+
workspace.
|
|
1691
|
+
|
|
1692
|
+
Words for you, not shown:
|
|
1693
|
+
|
|
1694
|
+
- `bad_input` (400): the call itself is wrong; fix the code.
|
|
1695
|
+
- `too_large` (413): the body is over 128 KB, `system` over 8,000
|
|
1696
|
+
characters, `schema` over 16 KB or 1,000 parts, or `input` over 100
|
|
1697
|
+
messages. Send less.
|
|
1698
|
+
- `schema_mismatch` (422): the model could not fit the schema, twice;
|
|
1699
|
+
`error` names where it missed. Loosen the schema or ask for less.
|
|
1700
|
+
- `cross_origin` (403): not the app's own page (a typed URL, a form
|
|
1701
|
+
post).
|
|
1702
|
+
- `not_configured` (503): the platform has no model to give.
|
|
1703
|
+
- `{ ok: false, unavailable: true }` — the platform has no such door (a
|
|
1704
|
+
claw from before it) or there is no network. Render the quiet "not
|
|
1705
|
+
connected yet" of every `_claw` door, or leave the button out.
|
|
1706
|
+
|
|
1707
|
+
`useLlm()` is the same call from a component: `{ run, loading, result,
|
|
1708
|
+
error, unavailable }`. `run(input, opts)` takes the same arguments and
|
|
1709
|
+
resolves the same answer; `result` is that answer when it was ok, `error`
|
|
1710
|
+
the refusal's words, and `unavailable` is true on a claw with no door (or
|
|
1711
|
+
no network), when `error` stays null: that is the quiet "not connected
|
|
1712
|
+
yet", not an alert, as for `useClaw`. A new run aborts the one before it,
|
|
1713
|
+
so a slow answer never lands over a newer one, and leaving the page
|
|
1714
|
+
aborts the call in flight. `error` is the sentence whatever the code, so
|
|
1715
|
+
a screen that must tell the money (`no_credits`, `member_cap`, `app_cap`)
|
|
1716
|
+
from the rest reads `code` from what `run()` resolves.
|
|
1717
|
+
|
|
1718
|
+
```jsx
|
|
1719
|
+
"use client";
|
|
1720
|
+
import { Button, Notice } from "../components/ui";
|
|
1721
|
+
import { useLlm } from "../lib/hooks";
|
|
1722
|
+
|
|
1723
|
+
export default function Translate({ note }) {
|
|
1724
|
+
const { run, loading, result, error, unavailable } = useLlm();
|
|
1725
|
+
if (unavailable) return <p className="text-sm text-muted-foreground">Translation is not connected yet.</p>;
|
|
1726
|
+
return (
|
|
1727
|
+
<div className="grid gap-2">
|
|
1728
|
+
<Button loading={loading} onClick={() => run(`Translate to French:\n${note.text}`, { task: "translate" })}>
|
|
1729
|
+
Translate
|
|
1730
|
+
</Button>
|
|
1731
|
+
{error ? <Notice tone="danger">{error}</Notice> : result ? <p className="text-sm">{result.text}</p> : null}
|
|
1732
|
+
</div>
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
```
|
|
1736
|
+
|
|
1737
|
+
What to keep in mind:
|
|
1738
|
+
|
|
1739
|
+
- **Browser only, in both modes.** The call rides the member's session:
|
|
1740
|
+
`llm()` and `useLlm()` run in a `"use client"` file. A server
|
|
1741
|
+
component or a server action has no member to charge and cannot call
|
|
1742
|
+
it.
|
|
1743
|
+
- **It spends the workspace's credits**, like any inference, and counts
|
|
1744
|
+
against the member's monthly cap and the app's weekly cap. A schema
|
|
1745
|
+
call the model misses is asked again once, and both are charged; so is
|
|
1746
|
+
a call the 60 second clock cuts. Call it on a press, never on every
|
|
1747
|
+
render or every keystroke, and never once per row of a long list: that
|
|
1748
|
+
is a batch, and a batch is `ask()`'s.
|
|
1749
|
+
- **The answer is words, not markup.** Render `text` as text. Never put a
|
|
1750
|
+
model's answer in `dangerouslySetInnerHTML`.
|
|
1751
|
+
- **The answer is not saved.** What matters goes into a row
|
|
1752
|
+
(`updateRow`, `insertRow`) before the page closes.
|
|
1753
|
+
- **No streaming.** The answer arrives whole.
|
|
1754
|
+
- **Nothing appears on the desk.** No request, no thread, no run: the
|
|
1755
|
+
agent column and the Requests list are `ask()`'s. The call is metered
|
|
1756
|
+
to the member who made it; the usage line names the app, the tier and
|
|
1757
|
+
the `task` label. Claw does not log the request or the reply.
|
|
1758
|
+
- **Never by its path.** A string naming `_claw/llm` fails
|
|
1759
|
+
`npm run check` (§6, `llm`): the two functions are the contract, and
|
|
1760
|
+
they handle what a bare `fetch` would not (a claw without the door, a
|
|
1761
|
+
dropped network, an abort).
|
|
1762
|
+
|
|
1507
1763
|
### Activity — the contract
|
|
1508
1764
|
|
|
1509
1765
|
`claw_app.activity` is how the founder sees the work. It is the
|
|
@@ -1577,11 +1833,13 @@ ok controls every control is a kit part
|
|
|
1577
1833
|
ok examples examples show only outside a workspace (useFixture); a workspace app opens empty
|
|
1578
1834
|
ok manifest 2 surfaces: drafts, sent
|
|
1579
1835
|
ok icons every icon name exists
|
|
1580
|
-
ok kit
|
|
1836
|
+
ok kit 74 fixed files match the template
|
|
1581
1837
|
ok depictions 1 picture of elsewhere, framed on 1 surface: x-post
|
|
1582
1838
|
ok agent no SQL write in app code; the agent's brief names every action with its call (1), every table written (2) and every server action that writes (0)
|
|
1583
1839
|
ok tables its own schema (makersclaw.json): every table named bare, and each bare name one of the 2 its schema makes (../install/schema.sql); claw. and claw_app. are the platform's
|
|
1584
1840
|
ok brain no brain block in makersclaw.json; no lib/brain.js call names a kind
|
|
1841
|
+
ok viewport nothing in the app sizes itself by the window
|
|
1842
|
+
ok llm no screen names _claw/llm: a model is called through llm() or useLlm() (README §5)
|
|
1585
1843
|
ok smoke built in 41s · / → 307 · /s/drafts 200 · /s/sent 200 (0.6s)
|
|
1586
1844
|
all clear
|
|
1587
1845
|
```
|
|
@@ -1595,7 +1853,13 @@ ok smoke built in 38s (copy at ~/.claw-check/app) · export 2.1 MB in 64
|
|
|
1595
1853
|
|
|
1596
1854
|
`kit` refuses a fixed file that differs from the template (they are
|
|
1597
1855
|
hashed in `check/pins.json`) and any file of yours under `components/ui`
|
|
1598
|
-
or `components/parts`. `
|
|
1856
|
+
or `components/parts`. `viewport` refuses anything in your code that
|
|
1857
|
+
sizes itself by the window: `min-h-dvh`, `min-h-screen`, `h-screen`,
|
|
1858
|
+
`h-dvh` (and their `svh`, `lvh` and `max-h-` kin), and any `vh` unit —
|
|
1859
|
+
`h-[80vh]`, `max-h-[60vh]`, `h-[calc(100vh-4rem)]`, `style={{ height:
|
|
1860
|
+
"100vh" }}`. The window has one owner, the kit's `AppLayout` (§3b,
|
|
1861
|
+
Layouts); a box inside it is bounded by its parent (`h-full`, `flex-1
|
|
1862
|
+
min-h-0`) or by a size (`max-h-96`). `depictions` reads both directions: nothing of
|
|
1599
1863
|
the workspace inside a depiction (a token, `font-sans`, `dark:`, a rem
|
|
1600
1864
|
utility), nothing of the depiction outside its frame (a named import, a
|
|
1601
1865
|
picture in the shell or a `Card`, a dead file nobody renders).
|
|
@@ -1692,6 +1956,16 @@ kind has `kind`, `label`, `plural` and `fields`. A read's kind
|
|
|
1692
1956
|
hands the app only what `reads` and `writes` name. A kind held in a
|
|
1693
1957
|
variable is not read here; the door refuses it on the member's screen.
|
|
1694
1958
|
|
|
1959
|
+
`llm` reads every app, in both modes: no string in the code names
|
|
1960
|
+
`_claw/llm`, and none handed to `claw()`, `clawUrl()` or `clawStream()`
|
|
1961
|
+
starts `llm`. A model is called with `llm()` or `useLlm()` (§5, Calling a
|
|
1962
|
+
model), which handle a claw without the door, a dropped network and an
|
|
1963
|
+
abort; a `fetch` of the path handles none of them. The path is read as
|
|
1964
|
+
a browser reads it (`claw("//llm")`, `claw("./llm")` and `"/_claw/./llm"`
|
|
1965
|
+
are the door too). A path built from pieces or held in a variable is not
|
|
1966
|
+
read here, as for rule 8. A longer name (`_claw/llm-notes`) is another
|
|
1967
|
+
route, and a comment or text in JSX is not a string of code.
|
|
1968
|
+
|
|
1695
1969
|
`browser` reads a browser app only (`"mode": "browser"`): its pages are
|
|
1696
1970
|
files built once, and everything they read or write goes through the
|
|
1697
1971
|
platform's data door. So it refuses, file and line, a `"use server"`
|
|
@@ -1713,6 +1987,12 @@ page. That proves every page prerenders, not that a surface renders
|
|
|
1713
1987
|
with data: the rows come in the browser. Then, by eye:
|
|
1714
1988
|
|
|
1715
1989
|
- Renders at 360px, 768px and 1280px without horizontal scroll.
|
|
1990
|
+
- The page itself never scrolls: the shell is the window's height
|
|
1991
|
+
(`AppLayout`), and each pane scrolls inside it. With the longest
|
|
1992
|
+
content a surface can hold (a 30-page article, a 40-message thread,
|
|
1993
|
+
a tall inspector), the last line and every button after it are
|
|
1994
|
+
reachable, at 375px and at 1280px; the header and the footer stay in
|
|
1995
|
+
view.
|
|
1716
1996
|
- In a workspace, an empty table shows `LiveEmpty`: the work in flight,
|
|
1717
1997
|
else what would be here and the manual way to add it — never an
|
|
1718
1998
|
example (§3a). A saved row replaces it.
|
|
@@ -9,14 +9,26 @@
|
|
|
9
9
|
// Activity; never repeat any of them. This shell is the App tab: the
|
|
10
10
|
// domain surfaces and nothing else.
|
|
11
11
|
//
|
|
12
|
-
//
|
|
13
|
-
// along the top, and grows a side rail from 640px. Start narrow, widen.
|
|
12
|
+
// TWO THINGS TO KEEP when you replace it.
|
|
14
13
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
14
|
+
// 1. AppLayout (components/ui) as the outermost element. It is the one
|
|
15
|
+
// owner of the window's height: the shell is exactly the window, and
|
|
16
|
+
// the content scrolls inside it, so a footer, an inspector or a button
|
|
17
|
+
// after a long page stays reachable. Fill its slots — header,
|
|
18
|
+
// navigation, inspector, footer — and pick contentMode, but never
|
|
19
|
+
// size anything by the window yourself (min-h-dvh, h-screen, 100vh,
|
|
20
|
+
// h-[80vh]): `npm run check` refuses it (README §3b, Layouts). A
|
|
21
|
+
// surface that needs an inspector or a canvas of its own returns an
|
|
22
|
+
// AppLayout too; inside this one it fills the content, edge to edge.
|
|
23
|
+
//
|
|
24
|
+
// 2. `next/link`. The app runs under a base path and <Link> applies it; a
|
|
25
|
+
// plain <a href="/s/..."> resolves against the origin instead and the
|
|
26
|
+
// click leaves the app, loading the Makersclaw dashboard inside this
|
|
27
|
+
// frame. The page still answers 200, so nothing catches it but a click
|
|
28
|
+
// (README §4).
|
|
29
|
+
//
|
|
30
|
+
// Mobile first: below 768px the surfaces sit behind the menu button, in
|
|
31
|
+
// a drawer; from 768px they are a rail beside the content.
|
|
20
32
|
//
|
|
21
33
|
// Props: name (the app's name — for aria labels, not for display),
|
|
22
34
|
// surfaces (surfaceNav() from surfaces.js: { key, label, kind, source,
|
|
@@ -24,41 +36,46 @@
|
|
|
24
36
|
|
|
25
37
|
import Link from "next/link";
|
|
26
38
|
import { usePathname } from "next/navigation";
|
|
27
|
-
import { cx } from "../components/ui";
|
|
39
|
+
import { AppLayout, cx } from "../components/ui";
|
|
28
40
|
|
|
29
41
|
export default function Shell({ name, surfaces, children }) {
|
|
30
42
|
const pathname = usePathname() ?? "";
|
|
31
43
|
const activeKey = decodeURIComponent(/^\/s\/([^/?#]+)/.exec(pathname)?.[1] ?? "");
|
|
44
|
+
const active = surfaces.find((s) => s.key === activeKey);
|
|
32
45
|
|
|
33
46
|
return (
|
|
34
|
-
<
|
|
35
|
-
|
|
36
|
-
aria-label={`${name} surfaces`}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
47
|
+
<AppLayout
|
|
48
|
+
navigation={
|
|
49
|
+
<nav aria-label={`${name} surfaces`} className="p-2 md:p-3">
|
|
50
|
+
<ul className="flex flex-col gap-1">
|
|
51
|
+
{surfaces.map((s) => {
|
|
52
|
+
const current = s.key === activeKey;
|
|
53
|
+
return (
|
|
54
|
+
<li key={s.key}>
|
|
55
|
+
<Link
|
|
56
|
+
href={`/s/${encodeURIComponent(s.key)}`}
|
|
57
|
+
aria-current={current ? "page" : undefined}
|
|
58
|
+
className={cx(
|
|
59
|
+
"flex min-h-9 items-center justify-between gap-2 rounded-md px-2 text-sm outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
60
|
+
current ? "bg-accent text-accent-foreground" : "hover:bg-accent",
|
|
61
|
+
!s.built && "text-muted-foreground",
|
|
62
|
+
)}
|
|
63
|
+
>
|
|
64
|
+
<span>{s.label}</span>
|
|
65
|
+
{s.built ? null : <span className="text-2xs uppercase tracking-wide">planned</span>}
|
|
66
|
+
</Link>
|
|
67
|
+
</li>
|
|
68
|
+
);
|
|
69
|
+
})}
|
|
70
|
+
</ul>
|
|
71
|
+
</nav>
|
|
72
|
+
}
|
|
73
|
+
navigationLabel="Surfaces"
|
|
74
|
+
contentLabel={active?.label ?? name}
|
|
75
|
+
contentClassName="p-4 sm:p-6"
|
|
76
|
+
scrollKey={pathname}
|
|
77
|
+
>
|
|
78
|
+
{children}
|
|
79
|
+
</AppLayout>
|
|
63
80
|
);
|
|
64
81
|
}
|
|
@@ -18,6 +18,10 @@
|
|
|
18
18
|
// screen, long after the build (brain). Number 10 is browser mode's rule
|
|
19
19
|
// (claw's STATIC-SPEC §3): a browser app is files and a door, and the
|
|
20
20
|
// build refuses its server code late and unclearly, or not at all.
|
|
21
|
+
// Number 12 is the shell that grew with its content: a footer and the
|
|
22
|
+
// buttons after a long article sat past the window's end (viewport).
|
|
23
|
+
// Number 13 (llm, 0.8.0) keeps a model call on llm(): a screen that
|
|
24
|
+
// fetched the model door by its path got none of what the function does.
|
|
21
25
|
//
|
|
22
26
|
// npm run check source rules, then smoke
|
|
23
27
|
// npm run check -- --no-build source rules only: no build, no smoke
|
|
@@ -94,7 +98,7 @@ const ACTIONS_ONLY = process.argv.includes("--actions");
|
|
|
94
98
|
// .jsx (a components/ui/depiction.js beside depiction.jsx won); Next 16's
|
|
95
99
|
// Turbopack tries .jsx before .js (seen on 16.3.7), so the kit's own file
|
|
96
100
|
// wins there, but the rule holds for every bundler.
|
|
97
|
-
const KIT = /^components\/(?:ui\/(?:cx|index)\.js|ui\/(?:radio|toggle|button-group|avatar|breadcrumb|pagination|button|collapsible|item|table|depiction|feedback|stat|distribution|swatch|steps|separator|scroll-area|field|icon|menu|context-menu|popover|calendar|date-picker|tooltip|notice|sheet|tabs|slider|input-group|image|file-input|input-otp|command|combobox|rating|toast|carousel|resizable)\.jsx|parts\/(?:index|sample)\.js|parts\/(?:fixture|record-switcher|live-work)\.jsx)$/;
|
|
101
|
+
const KIT = /^components\/(?:ui\/(?:cx|index)\.js|ui\/(?:radio|toggle|button-group|avatar|breadcrumb|pagination|button|collapsible|item|table|depiction|feedback|stat|distribution|swatch|steps|separator|scroll-area|layout|field|icon|menu|context-menu|popover|calendar|date-picker|tooltip|notice|sheet|tabs|slider|input-group|image|file-input|input-otp|command|combobox|rating|toast|carousel|resizable)\.jsx|parts\/(?:index|sample)\.js|parts\/(?:fixture|record-switcher|live-work)\.jsx)$/;
|
|
98
102
|
|
|
99
103
|
// The pins: sha256 of every fixed file, written by `makersclaw init` and
|
|
100
104
|
// `makersclaw add` (check/pins.json). Read first, because a BLOCK is
|
|
@@ -228,13 +232,20 @@ const COLOUR_PREFIXES = "bg|text|border(?:-[xytrbles])?|ring(?:-offset)?|inset-r
|
|
|
228
232
|
|
|
229
233
|
/** Source without its comments, newlines kept so line numbers hold: a
|
|
230
234
|
* rule reads code, not the prose beside it (a header that says
|
|
231
|
-
* "window." or names <a> is not browser access or a link).
|
|
232
|
-
|
|
235
|
+
* "window." or names <a> is not browser access or a link). A line comment
|
|
236
|
+
* is a `//` that follows anything but a `:` or a backslash, which also
|
|
237
|
+
* blanks a string that opens with two slashes ("//llm") or holds a
|
|
238
|
+
* doubled one ("/_claw//llm") to the end of its line. `slashStrings`
|
|
239
|
+
* reads those as strings: a `//` is a comment only at the start of a line
|
|
240
|
+
* or after a space or one of ; , ( ) { } [ ]. For a rule that reads a
|
|
241
|
+
* URL's slashes (12, llm). */
|
|
242
|
+
function stripComments(text, { slashStrings = false } = {}) {
|
|
233
243
|
const blank = (m) => m.replace(/[^\n]/g, " ");
|
|
244
|
+
const line = slashStrings ? /(^|[\s;,(){}[\]])\/\/[^\n]*/g : /(^|[^:\\])\/\/[^\n]*/g;
|
|
234
245
|
return text
|
|
235
246
|
.replace(/\{\/\*[\s\S]*?\*\/\}/g, blank)
|
|
236
247
|
.replace(/\/\*[\s\S]*?\*\//g, blank)
|
|
237
|
-
.replace(
|
|
248
|
+
.replace(line, (m, lead) => lead + blank(m.slice(lead.length)));
|
|
238
249
|
}
|
|
239
250
|
/** JSX attributes up to the tag's `>`: strings and balanced braces may hold a `>` or `=>`. */
|
|
240
251
|
const ATTRS = String.raw`(?:"[^"]*"|'[^']*'|\{(?:[^{}]|\{[^{}]*\})*\}|[^>"'{])*`;
|
|
@@ -1871,6 +1882,67 @@ function columnFrom(code, at) {
|
|
|
1871
1882
|
}
|
|
1872
1883
|
}
|
|
1873
1884
|
|
|
1885
|
+
// 12. One viewport owner: the kit's AppLayout. Every design-first build
|
|
1886
|
+
// of 2026-10-07 shipped a shell of min-h-dvh that grew with its
|
|
1887
|
+
// content: the page scrolled, no pane scrolled on its own, and a
|
|
1888
|
+
// footer or a button after a 30-page article sat past the end of the
|
|
1889
|
+
// window, where nothing could reach it. So the app's code never sizes
|
|
1890
|
+
// anything by the window: no min-h-dvh, min-h-screen, h-screen, h-dvh
|
|
1891
|
+
// (nor their svh, lvh and max-h- kin), and no vh unit at all
|
|
1892
|
+
// (h-[80vh], max-h-[60vh], h-[calc(100vh-4rem)], style={{ height:
|
|
1893
|
+
// "100vh" }}). The kit is skipped: AppLayout in components/ui is the
|
|
1894
|
+
// one place the window's height is written. Depictions are read too:
|
|
1895
|
+
// a picture is drawn in px, never by the window.
|
|
1896
|
+
{
|
|
1897
|
+
const L = limiter("viewport");
|
|
1898
|
+
const DO =
|
|
1899
|
+
'the window has one owner, AppLayout from components/ui (the shell\'s frame: the content scrolls inside it, contentMode="fill" for a canvas, an inspector beside it), and a page for print is DocumentLayout; inside it bound a box by its parent (h-full, flex-1 min-h-0) or by a size (max-h-96)';
|
|
1900
|
+
scan(/(?<![\w-])(?:min-|max-)?h-(?:screen|dvh|svh|lvh)(?![\w-])/, (f, m, line) => L.hit(`${f.path}:${line}`, m[0], `${m[0]} sizes by the window, so what follows a long page cannot be reached; ${DO}`), files);
|
|
1901
|
+
scan(/(?<![\w.])\d+(?:\.\d+)?[dsl]?vh(?![A-Za-z0-9_])/, (f, m, line) => {
|
|
1902
|
+
// The whole class or value it sits in: h-[calc(100vh-4rem)], "100vh".
|
|
1903
|
+
const from = f.code.slice(0, m.index).search(/[^\s"'`{}]*$/);
|
|
1904
|
+
const token = f.code.slice(from).match(/^[^\s"'`{}]*/)[0];
|
|
1905
|
+
L.hit(`${f.path}:${line}`, token, `${token} is a fraction of the window, so what follows it cannot be reached; ${DO}`);
|
|
1906
|
+
}, files);
|
|
1907
|
+
if (L.count > 4) fail("viewport", `${L.count - 4} more`, "more of the same: sizes taken from the window");
|
|
1908
|
+
if (!L.count) ok("viewport", "nothing in the app sizes itself by the window");
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// 13. A model is called through llm() (0.8.0, the platform's _claw/llm
|
|
1912
|
+
// door). A screen that wants one answer from a model (sort, draft,
|
|
1913
|
+
// translate) calls llm() from lib/platform.js, or useLlm() from
|
|
1914
|
+
// lib/hooks.js, and never reaches the door by its URL: the path is the
|
|
1915
|
+
// platform's to move, the two functions are the contract, and an
|
|
1916
|
+
// app that fetches its own copy skips what they do (nothing throws, a
|
|
1917
|
+
// claw from before the door comes back `unavailable`, an abort
|
|
1918
|
+
// answers `aborted`). So no string in the app's code names `_claw/llm`,
|
|
1919
|
+
// and none handed to claw(), clawUrl() or clawStream() starts `llm`.
|
|
1920
|
+
// "Names" is as the browser reads it: clawUrl() drops every leading
|
|
1921
|
+
// slash, and a `./` or a doubled slash in a URL is dropped on the way
|
|
1922
|
+
// (`claw("//llm")`, `claw("./llm")`, `/_claw/./llm` all reach the door;
|
|
1923
|
+
// it reads the code with `slashStrings` so a `//` inside a string is
|
|
1924
|
+
// not taken for a comment). It reads literals, so a path built from
|
|
1925
|
+
// pieces is not seen, as in rule 8. Every mode and every tables
|
|
1926
|
+
// setting: the door is the same for all.
|
|
1927
|
+
{
|
|
1928
|
+
const L = limiter("llm");
|
|
1929
|
+
const LLM_PATH = /_claw\/(?:\.?\/)*llm(?![\w-])/;
|
|
1930
|
+
const LLM_NAME = /^(?:\.?\/)*llm(?![\w-])/;
|
|
1931
|
+
for (const f of jsFiles) {
|
|
1932
|
+
const code = stripComments(f.text, { slashStrings: true });
|
|
1933
|
+
const toClaw = new Set();
|
|
1934
|
+
for (const m of code.matchAll(/(?<![\w$.])claw(?:Url|Stream)?\s*(?:\?\.\s*)?\(\s*(?=["'`])/g)) toClaw.add(m.index + m[0].length);
|
|
1935
|
+
for (const { start, end } of literalsOf(code)) {
|
|
1936
|
+
const inner = code.slice(start + 1, end);
|
|
1937
|
+
if (!(LLM_PATH.test(inner) || (toClaw.has(start) && LLM_NAME.test(inner)))) continue;
|
|
1938
|
+
const path = inner.replace(/\s+/g, " ").slice(0, 60);
|
|
1939
|
+
L.hit(`${f.path}:${lineOf(code, start)}`, "path", `"${path}": the model door is reached through llm(input, { system, schema }) in lib/platform.js, or useLlm() in lib/hooks.js, never by its path (README §5, Calling a model)`);
|
|
1940
|
+
}
|
|
1941
|
+
}
|
|
1942
|
+
if (L.count > 4) fail("llm", `${L.count - 4} more`, "strings that name the model door's path instead of calling llm()");
|
|
1943
|
+
if (!L.count) ok("llm", "no screen names _claw/llm: a model is called through llm() or useLlm() (README §5)");
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1874
1946
|
// 6. Smoke: the app, built and started, answers every surface.
|
|
1875
1947
|
|
|
1876
1948
|
/** Newest mtime among the app's own sources (what a build reads): walk
|