makersclaw 0.6.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 +56 -9
- package/package.json +1 -1
- package/r/annotate.json +4 -4
- package/r/approval-request.json +3 -3
- package/r/audio-player.json +3 -3
- package/r/auth-form.json +2 -2
- package/r/board.json +4 -4
- package/r/calendar-view.json +4 -4
- package/r/cart.json +3 -3
- package/r/chart.json +6 -6
- package/r/chat.json +3 -3
- package/r/code-editor.json +4 -4
- package/r/comment-box.json +4 -4
- package/r/contact-section.json +3 -3
- package/r/core.json +10 -4
- package/r/csv-import.json +4 -4
- package/r/data-table.json +3 -3
- package/r/diagram.json +3 -3
- package/r/doc-editor.json +4 -4
- package/r/doc-page.json +4 -4
- package/r/email-render.json +2 -2
- package/r/faq.json +2 -2
- package/r/file-upload.json +3 -3
- package/r/flow-canvas.json +4 -4
- package/r/footer.json +3 -3
- package/r/gantt.json +4 -4
- package/r/job-panel.json +3 -3
- package/r/json-editor.json +4 -4
- package/r/map.json +5 -5
- 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 +6 -6
- package/r/payment-methods.json +2 -2
- package/r/pdf-document.json +2 -2
- package/r/pdf-viewer.json +6 -6
- package/r/product-detail.json +3 -3
- package/r/product-grid.json +4 -4
- package/r/record-form.json +3 -3
- package/r/record-notes.json +5 -5
- package/r/registry.json +126 -121
- package/r/search-dialog.json +3 -3
- package/r/settings-list.json +3 -3
- package/r/setup-checklist.json +3 -3
- package/r/share-dialog.json +3 -3
- package/r/sidebar.json +3 -3
- package/r/signature-pad.json +3 -3
- package/r/sketch-canvas.json +5 -5
- package/r/spreadsheet.json +3 -3
- package/r/stat-cards.json +4 -4
- package/r/task-list.json +4 -4
- package/r/task-model.json +3 -3
- package/r/testimonials.json +2 -2
- package/r/transaction-list.json +3 -3
- package/r/whiteboard.json +4 -4
- package/registry/blocks/annotate/README.md +24 -12
- package/registry/blocks/approval-request/README.md +15 -8
- package/registry/blocks/audio-player/README.md +17 -10
- package/registry/blocks/board/README.md +13 -9
- package/registry/blocks/board/board.jsx +1 -1
- package/registry/blocks/calendar-view/README.md +28 -4
- package/registry/blocks/cart/README.md +14 -10
- package/registry/blocks/chart/README.md +14 -7
- 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 +47 -14
- package/registry/blocks/chat/demo.jsx +2 -2
- package/registry/blocks/code-editor/README.md +4 -0
- package/registry/blocks/code-editor/code-editor.jsx +1 -1
- package/registry/blocks/comment-box/README.md +20 -13
- package/registry/blocks/contact-section/README.md +7 -6
- package/registry/blocks/csv-import/README.md +34 -12
- package/registry/blocks/data-table/README.md +41 -8
- package/registry/blocks/diagram/diagram.jsx +1 -1
- package/registry/blocks/doc-editor/README.md +18 -6
- package/registry/blocks/doc-editor/doc-editor.jsx +1 -1
- package/registry/blocks/doc-page/README.md +16 -6
- package/registry/blocks/file-upload/README.md +7 -1
- package/registry/blocks/flow-canvas/README.md +30 -12
- package/registry/blocks/flow-canvas/flow-canvas.jsx +1 -1
- package/registry/blocks/footer/README.md +7 -4
- package/registry/blocks/gantt/README.md +18 -12
- package/registry/blocks/job-panel/README.md +1 -1
- package/registry/blocks/json-editor/README.md +16 -4
- package/registry/blocks/map/README.md +19 -3
- package/registry/blocks/map/map-view.jsx +1 -1
- package/registry/blocks/map/maplibre.js +1 -1
- package/registry/blocks/mind-map/README.md +20 -7
- package/registry/blocks/org-chart/README.md +14 -9
- package/registry/blocks/page-builder/README.md +14 -7
- package/registry/blocks/page-builder/demo.jsx +3 -3
- package/registry/blocks/page-builder/page-builder.jsx +1 -1
- package/registry/blocks/page-builder/page-view.jsx +1 -1
- package/registry/blocks/page-builder/page.js +1 -1
- package/registry/blocks/pdf-viewer/README.md +26 -21
- package/registry/blocks/pdf-viewer/pdf-viewer.jsx +1 -1
- package/registry/blocks/pdf-viewer/pdfjs.js +1 -1
- package/registry/blocks/pdf-viewer/ssr-shim.js +1 -1
- package/registry/blocks/product-detail/README.md +13 -7
- package/registry/blocks/product-detail/demo.jsx +1 -1
- package/registry/blocks/product-grid/README.md +12 -10
- package/registry/blocks/record-form/README.md +26 -9
- package/registry/blocks/record-notes/README.md +42 -30
- package/registry/blocks/search-dialog/README.md +38 -7
- package/registry/blocks/settings-list/README.md +29 -17
- package/registry/blocks/setup-checklist/README.md +12 -14
- package/registry/blocks/share-dialog/README.md +1 -1
- package/registry/blocks/sidebar/README.md +23 -13
- package/registry/blocks/signature-pad/README.md +15 -4
- package/registry/blocks/sketch-canvas/README.md +25 -10
- package/registry/blocks/sketch-canvas/sketch-canvas.jsx +1 -1
- package/registry/blocks/sketch-canvas/ssr-shim.js +3 -3
- package/registry/blocks/spreadsheet/README.md +17 -8
- package/registry/blocks/stat-cards/README.md +17 -8
- package/registry/blocks/task-list/README.md +14 -11
- package/registry/blocks/task-model/README.md +3 -3
- package/registry/blocks/transaction-list/README.md +15 -7
- package/registry/blocks/whiteboard/README.md +34 -22
- package/src/cli.mjs +33 -6
- package/src/install.mjs +285 -26
- package/src/registry.mjs +109 -1
- package/templates/next/browser/app/auto-refresh.jsx +67 -0
- package/templates/next/browser/app/layout.jsx +94 -0
- package/templates/next/browser/app/page.jsx +60 -0
- package/templates/next/browser/app/s/[key]/page.jsx +95 -0
- package/templates/next/browser/lib/actions.js +16 -0
- package/templates/next/browser/lib/data.js +43 -0
- package/templates/next/files/README.md +1100 -89
- package/templates/next/files/app/frame.js +128 -4
- package/templates/next/files/app/shell.jsx +54 -37
- package/templates/next/files/check/run.mjs +1514 -45
- package/templates/next/files/components/ui/index.js +1 -0
- package/templates/next/files/components/ui/item.jsx +2 -2
- package/templates/next/files/components/ui/layout.jsx +307 -0
- package/templates/next/files/components/ui/notice.jsx +1 -1
- package/templates/next/files/components/ui/separator.jsx +1 -1
- package/templates/next/files/lib/actions.js +170 -51
- package/templates/next/files/lib/brain-server.js +96 -0
- package/templates/next/files/lib/brain.js +413 -0
- package/templates/next/files/lib/claw.js +15 -1
- package/templates/next/files/lib/data.js +119 -29
- package/templates/next/files/lib/db.js +97 -17
- package/templates/next/files/lib/door.js +680 -0
- package/templates/next/files/lib/hooks.js +71 -2
- package/templates/next/files/lib/platform.js +93 -4
- package/templates/next/files/next.config.mjs +36 -2
- package/templates/next/files/surfaces.js +3 -2
- package/templates/next/template.json +18 -3
- package/templates/next/files/schema.sql +0 -22
|
@@ -57,8 +57,11 @@ true) is where it starts. The icon rail shows every group unfolded.
|
|
|
57
57
|
- `shortcut` — Cmd/Ctrl + this key toggles it; default `"b"`, `null` for none.
|
|
58
58
|
- `variant` — `"inset"` draws the page as a card on the frame's ground.
|
|
59
59
|
Default: the Sidebar's own `variant`, so you rarely pass it here.
|
|
60
|
-
- `className` — the frame's
|
|
61
|
-
|
|
60
|
+
- `className` — the frame's edges, and its height from its parent: in a
|
|
61
|
+
shell it sits in the kit's `AppLayout contentMode="fill"` with
|
|
62
|
+
`min-h-0 flex-1` (the Example). The kit's `AppLayout` owns the
|
|
63
|
+
window's height; `min-h-dvh` or `h-dvh` here is refused by `npm run
|
|
64
|
+
check`. Never colour.
|
|
62
65
|
|
|
63
66
|
### Sidebar
|
|
64
67
|
|
|
@@ -138,6 +141,7 @@ The header button: opens the sheet on a phone, collapses to icons from
|
|
|
138
141
|
// app/shell.jsx — the app's shell, the surfaces as the sidebar's items
|
|
139
142
|
"use client";
|
|
140
143
|
import { usePathname } from "next/navigation";
|
|
144
|
+
import { AppLayout } from "../components/ui";
|
|
141
145
|
import { Sidebar, SidebarLayout, SidebarTrigger } from "../components/blocks/sidebar";
|
|
142
146
|
|
|
143
147
|
const ICONS = { inbox: "inbox", deals: "dollar", contacts: "users", reports: "chart" };
|
|
@@ -153,18 +157,24 @@ export default function Shell({ name, surfaces, children }) {
|
|
|
153
157
|
active: s.key === activeKey,
|
|
154
158
|
badge: s.built ? undefined : "planned",
|
|
155
159
|
}));
|
|
160
|
+
// The outer AppLayout owns the window; the inner one (it fills its
|
|
161
|
+
// slot) is the content that scrolls, back to its top on a new route.
|
|
156
162
|
return (
|
|
157
|
-
<
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
<
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
163
|
+
<AppLayout contentMode="fill">
|
|
164
|
+
<SidebarLayout
|
|
165
|
+
storageKey={`${name}.sidebar`}
|
|
166
|
+
className="min-h-0 flex-1"
|
|
167
|
+
sidebar={<Sidebar title={name} items={items} label={`${name} surfaces`} />}
|
|
168
|
+
>
|
|
169
|
+
<header className="flex min-h-14 items-center gap-2 border-b border-border px-2 md:hidden">
|
|
170
|
+
<SidebarTrigger />
|
|
171
|
+
<span className="font-semibold">{name}</span>
|
|
172
|
+
</header>
|
|
173
|
+
<AppLayout contentClassName="p-4 sm:p-6" scrollKey={pathname}>
|
|
174
|
+
{children}
|
|
175
|
+
</AppLayout>
|
|
176
|
+
</SidebarLayout>
|
|
177
|
+
</AppLayout>
|
|
168
178
|
);
|
|
169
179
|
}
|
|
170
180
|
```
|
|
@@ -109,25 +109,36 @@ under the paper says so. Undo and clear are buttons with names.
|
|
|
109
109
|
Accepting a quote in a Makersclaw app.
|
|
110
110
|
|
|
111
111
|
```jsx
|
|
112
|
+
// surfaces/quote.jsx — client: one quote, accepted and signed
|
|
112
113
|
"use client";
|
|
114
|
+
import { useSearchParams } from "next/navigation";
|
|
115
|
+
import { useArtifact } from "../lib/hooks";
|
|
116
|
+
import { useRow } from "../lib/data";
|
|
113
117
|
import { updateRow } from "../lib/actions";
|
|
114
|
-
import { SignaturePad } from "
|
|
118
|
+
import { SignaturePad } from "../components/blocks/signature-pad";
|
|
115
119
|
|
|
116
|
-
export default function AcceptQuote(
|
|
120
|
+
export default function AcceptQuote() {
|
|
121
|
+
const id = useSearchParams().get("row");
|
|
122
|
+
const { user } = useArtifact(); // the member signed in: { id, email }
|
|
123
|
+
const { row: quote, loading } = useRow({ source: "quotes", id });
|
|
124
|
+
if (loading || !quote) return null;
|
|
117
125
|
return (
|
|
118
126
|
<SignaturePad
|
|
119
|
-
signer={
|
|
127
|
+
signer={user?.email}
|
|
120
128
|
value={quote.signature ?? undefined}
|
|
121
129
|
submitLabel="Accept and sign"
|
|
122
130
|
note="Signing records that you accept this quote. It is not a certified e-signature."
|
|
131
|
+
// signed_by: a before-update trigger sets it from the member, never the page (the app's README §5)
|
|
123
132
|
onSubmit={(signature) =>
|
|
124
|
-
updateRow({ source: "
|
|
133
|
+
updateRow({ source: "quotes", id: quote.id, patch: { status: "accepted", signature }, detail: "accepted" })
|
|
125
134
|
}
|
|
126
135
|
/>
|
|
127
136
|
);
|
|
128
137
|
}
|
|
129
138
|
```
|
|
130
139
|
|
|
140
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
141
|
+
|
|
131
142
|
## What it will not do
|
|
132
143
|
|
|
133
144
|
- Verify who signed, send the document for signature, or keep an audit
|
|
@@ -130,8 +130,11 @@ A sketch is plain JSON. Store it as it comes out of `onChange` / `onSave`
|
|
|
130
130
|
- `onReady(handle)` — once, when the canvas has mounted.
|
|
131
131
|
- `label` — the accessible name of the canvas region (default "Sketch").
|
|
132
132
|
- `className` — layout: width, margin, height. The default height is
|
|
133
|
-
`h-[32rem]`; a height class of yours (`h-
|
|
134
|
-
|
|
133
|
+
`h-[32rem]`; a height class of yours (`h-full`, `min-h-…`, `min-h-0
|
|
134
|
+
flex-1`) replaces it. A canvas that is the whole surface sits in an
|
|
135
|
+
`AppLayout contentMode="fill"` from the kit with `min-h-0 flex-1` (the
|
|
136
|
+
Example): never `h-dvh` or a `vh` height, which `npm run check`
|
|
137
|
+
refuses. Never colour.
|
|
135
138
|
|
|
136
139
|
### The handle
|
|
137
140
|
|
|
@@ -215,22 +218,34 @@ on a partial one:
|
|
|
215
218
|
A whiteboard that saves itself:
|
|
216
219
|
|
|
217
220
|
```jsx
|
|
221
|
+
// surfaces/sketch.jsx — client: one board, saved as it is drawn
|
|
218
222
|
"use client";
|
|
219
|
-
import {
|
|
223
|
+
import { useSearchParams } from "next/navigation";
|
|
224
|
+
import { useRow } from "../lib/data";
|
|
220
225
|
import { updateRow } from "../lib/actions";
|
|
226
|
+
import { AppLayout } from "../components/ui";
|
|
227
|
+
import { SketchCanvas } from "../components/blocks/sketch-canvas";
|
|
221
228
|
|
|
222
|
-
export default function Board(
|
|
229
|
+
export default function Board() {
|
|
230
|
+
const id = useSearchParams().get("row");
|
|
231
|
+
const { row: board, loading } = useRow({ source: "boards", id });
|
|
232
|
+
if (loading || !board) return null;
|
|
223
233
|
return (
|
|
224
|
-
<
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
234
|
+
<AppLayout contentMode="fill">
|
|
235
|
+
<SketchCanvas
|
|
236
|
+
key={board.id}
|
|
237
|
+
label={board.title}
|
|
238
|
+
defaultValue={board.sketch} // jsonb, may be null; read once per board
|
|
239
|
+
onSave={(sketch) => updateRow({ source: "boards", id: board.id, patch: { sketch } })}
|
|
240
|
+
className="min-h-0 flex-1 rounded-none border-0"
|
|
241
|
+
/>
|
|
242
|
+
</AppLayout>
|
|
230
243
|
);
|
|
231
244
|
}
|
|
232
245
|
```
|
|
233
246
|
|
|
247
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
248
|
+
|
|
234
249
|
Marking up a screenshot, with numbered notes:
|
|
235
250
|
|
|
236
251
|
```jsx
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// SketchCanvas — Excalidraw, in the workspace theme, behind one set of props.
|
|
4
4
|
//
|
|
5
|
-
// Client only, one
|
|
5
|
+
// Client only, one bundle. Excalidraw needs the DOM, and the app's
|
|
6
6
|
// check refuses import() (so no next/dynamic): the modules load with the
|
|
7
7
|
// page (on the server too — see excalidraw.js and ssr-shim.js for how that
|
|
8
8
|
// is made safe), and the canvas itself mounts only after the first effect
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
// devicePixelRatio, document.createElement("img"), an Element.prototype
|
|
6
6
|
// polyfill), so evaluating it on the server throws "window is not defined"
|
|
7
7
|
// and the page fails to render — even though the component itself only
|
|
8
|
-
// ever renders in the browser (the mount guard). The app is one
|
|
9
|
-
//
|
|
10
|
-
//
|
|
8
|
+
// ever renders in the browser (the mount guard). The app is one bundle
|
|
9
|
+
// (no import(), no next/dynamic), so the module cannot be kept off the
|
|
10
|
+
// server; instead it is let load against these stand-ins.
|
|
11
11
|
//
|
|
12
12
|
// The stand-ins live only for that load: excalidraw.js imports this file,
|
|
13
13
|
// then @excalidraw/excalidraw, then ssr-shim-end.js, which removes them —
|
|
@@ -241,10 +241,12 @@ Shift+F10 the row menu.
|
|
|
241
241
|
## Example
|
|
242
242
|
|
|
243
243
|
```jsx
|
|
244
|
+
// surfaces/deals.jsx — client: the deals as a grid
|
|
244
245
|
"use client";
|
|
245
246
|
import { useState } from "react";
|
|
246
|
-
import {
|
|
247
|
-
import {
|
|
247
|
+
import { useRows } from "../lib/data";
|
|
248
|
+
import { deleteRow, insertRow, updateRow } from "../lib/actions";
|
|
249
|
+
import { Spreadsheet } from "../components/blocks/spreadsheet";
|
|
248
250
|
|
|
249
251
|
const COLUMNS = [
|
|
250
252
|
{ key: "name", name: "Name", type: "text", required: true },
|
|
@@ -253,20 +255,25 @@ const COLUMNS = [
|
|
|
253
255
|
{ key: "close", name: "Close date", type: "date" },
|
|
254
256
|
];
|
|
255
257
|
|
|
256
|
-
export default function Deals(
|
|
258
|
+
export default function Deals() {
|
|
259
|
+
const deals = useRows({ source: "deals", orderBy: "created_at asc" });
|
|
260
|
+
const team = useRows({ source: "people", orderBy: "name asc" });
|
|
257
261
|
const [view, setView] = useState({ sort: [{ key: "amount", dir: "desc" }] });
|
|
262
|
+
// Each change below is one write, and the door takes 300 a minute: a
|
|
263
|
+
// paste larger than that belongs in one action (runAction).
|
|
258
264
|
return (
|
|
259
265
|
<Spreadsheet
|
|
260
266
|
label="Deals"
|
|
261
267
|
noun="deal"
|
|
262
|
-
rows={deals}
|
|
268
|
+
rows={deals.rows}
|
|
269
|
+
loading={deals.loading}
|
|
263
270
|
columns={COLUMNS}
|
|
264
|
-
people={team}
|
|
271
|
+
people={team.rows}
|
|
265
272
|
view={view}
|
|
266
273
|
onViewChange={setView}
|
|
267
274
|
onChange={async (changes) => {
|
|
268
275
|
for (const c of changes) {
|
|
269
|
-
const r = await updateRow({ source: "
|
|
276
|
+
const r = await updateRow({ source: "deals", id: c.rowId, patch: { [c.key]: c.value } });
|
|
270
277
|
if (!r.ok) return r;
|
|
271
278
|
}
|
|
272
279
|
return { ok: true };
|
|
@@ -274,7 +281,7 @@ export default function Deals({ deals, team }) {
|
|
|
274
281
|
onAddRows={async (rows) => {
|
|
275
282
|
const made = [];
|
|
276
283
|
for (const { id, ...values } of rows) {
|
|
277
|
-
const r = await insertRow({ source: "
|
|
284
|
+
const r = await insertRow({ source: "deals", values: { stage: "lead", ...values } });
|
|
278
285
|
if (!r.ok) return r;
|
|
279
286
|
made.push({ id: r.id }); // the database's id replaces the one the grid made up
|
|
280
287
|
}
|
|
@@ -282,7 +289,7 @@ export default function Deals({ deals, team }) {
|
|
|
282
289
|
}}
|
|
283
290
|
onDeleteRows={async (ids) => {
|
|
284
291
|
for (const id of ids) {
|
|
285
|
-
const r = await deleteRow({ source: "
|
|
292
|
+
const r = await deleteRow({ source: "deals", id });
|
|
286
293
|
if (!r.ok) return r;
|
|
287
294
|
}
|
|
288
295
|
return { ok: true };
|
|
@@ -292,6 +299,8 @@ export default function Deals({ deals, team }) {
|
|
|
292
299
|
}
|
|
293
300
|
```
|
|
294
301
|
|
|
302
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
303
|
+
|
|
295
304
|
## What it will not do
|
|
296
305
|
|
|
297
306
|
- No formulas. A value is what was typed; a derived column is computed by
|
|
@@ -125,27 +125,34 @@ States
|
|
|
125
125
|
## Example
|
|
126
126
|
|
|
127
127
|
```jsx
|
|
128
|
-
//
|
|
128
|
+
// surfaces/overview.jsx — client: the numbers, read in the browser
|
|
129
129
|
"use client";
|
|
130
|
-
import {
|
|
130
|
+
import { useRows } from "../lib/data";
|
|
131
131
|
import { updateRow } from "../lib/actions";
|
|
132
|
+
import { StatCards } from "../components/blocks/stat-cards";
|
|
132
133
|
|
|
133
|
-
export default function Overview(
|
|
134
|
+
export default function Overview() {
|
|
135
|
+
const summary = useRows({ source: "kpi_summary", limit: 1 }); // a view of the app's: this week's numbers, one row
|
|
136
|
+
const usage = useRows({ source: "quotas", orderBy: "name asc" });
|
|
137
|
+
const kpis = summary.rows[0] ?? {};
|
|
134
138
|
return (
|
|
135
139
|
<div className="grid gap-6">
|
|
136
140
|
<StatCards
|
|
141
|
+
loading={summary.loading}
|
|
142
|
+
error={summary.error}
|
|
137
143
|
items={[
|
|
138
|
-
{ id: "mrr", label: "MRR", value: kpis.mrr, delta: kpis.
|
|
139
|
-
{ id: "churn", label: "Logo churn", value: kpis.churn, delta: kpis.
|
|
140
|
-
{ id: "trials", label: "Active trials", value: kpis.trials, previous: kpis.
|
|
144
|
+
{ id: "mrr", label: "MRR", value: kpis.mrr, delta: kpis.mrr_change, trend: kpis.mrr_weeks, href: "/s/revenue" },
|
|
145
|
+
{ id: "churn", label: "Logo churn", value: kpis.churn, delta: kpis.churn_change, invert: true },
|
|
146
|
+
{ id: "trials", label: "Active trials", value: kpis.trials, previous: kpis.trials_last_week, delta: kpis.trials_change },
|
|
141
147
|
]}
|
|
142
148
|
variant="cards"
|
|
143
149
|
/>
|
|
144
150
|
<StatCards
|
|
145
151
|
variant="usage"
|
|
146
152
|
title="Plan usage"
|
|
147
|
-
|
|
148
|
-
|
|
153
|
+
loading={usage.loading}
|
|
154
|
+
items={usage.rows.map((u) => ({ id: u.id, label: u.name, used: u.used, limit: u.limit, unit: u.unit }))}
|
|
155
|
+
onLimitChange={(item, limit) => updateRow({ source: "quotas", id: item.id, patch: { limit } })}
|
|
149
156
|
limitLabel="Change budget"
|
|
150
157
|
/>
|
|
151
158
|
</div>
|
|
@@ -153,6 +160,8 @@ export default function Overview({ kpis, usage }) {
|
|
|
153
160
|
}
|
|
154
161
|
```
|
|
155
162
|
|
|
163
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
164
|
+
|
|
156
165
|
## What it will not do
|
|
157
166
|
|
|
158
167
|
- Fetch or compute: numbers come in formatted, changes already worked out.
|
|
@@ -98,14 +98,15 @@ drag-to-reorder saves; tasks without one keep the order given.
|
|
|
98
98
|
## Example
|
|
99
99
|
|
|
100
100
|
```jsx
|
|
101
|
-
// surfaces/tasks.jsx —
|
|
102
|
-
|
|
101
|
+
// surfaces/tasks.jsx — client: read, hand over
|
|
102
|
+
"use client";
|
|
103
|
+
import { useRows } from "../lib/data";
|
|
103
104
|
import Tasks from "../components/Tasks";
|
|
104
105
|
|
|
105
|
-
export default
|
|
106
|
-
const
|
|
107
|
-
const people =
|
|
108
|
-
return <Tasks tasks={rows} people={people.rows} error={error} />;
|
|
106
|
+
export default function TasksSurface() {
|
|
107
|
+
const tasks = useRows({ source: "tasks", orderBy: "created_at asc" });
|
|
108
|
+
const people = useRows({ source: "people" });
|
|
109
|
+
return <Tasks tasks={tasks.rows} people={people.rows} loading={tasks.loading || people.loading} error={tasks.error} />;
|
|
109
110
|
}
|
|
110
111
|
```
|
|
111
112
|
|
|
@@ -132,19 +133,19 @@ const COLUMNS = ["id", "title", "status", "assignees", "due", "priority", "tags"
|
|
|
132
133
|
async function save(change) {
|
|
133
134
|
for (const w of changeWrites(change, { columns: COLUMNS, keepNewIds: false })) {
|
|
134
135
|
const r =
|
|
135
|
-
w.op === "insert" ? await insertRow({ source: "
|
|
136
|
-
: w.op === "update" ? await updateRow({ source: "
|
|
137
|
-
: await deleteRow({ source: "
|
|
136
|
+
w.op === "insert" ? await insertRow({ source: "tasks", values: w.values, detail: w.values.title })
|
|
137
|
+
: w.op === "update" ? await updateRow({ source: "tasks", id: w.id, patch: w.patch })
|
|
138
|
+
: await deleteRow({ source: "tasks", id: w.id });
|
|
138
139
|
if (r?.ok === false) return r;
|
|
139
140
|
}
|
|
140
141
|
return { ok: true };
|
|
141
142
|
}
|
|
142
143
|
|
|
143
|
-
export default function Tasks({ tasks, people, error }) {
|
|
144
|
+
export default function Tasks({ tasks, people, loading, error }) {
|
|
144
145
|
const [open, setOpen] = useState(null);
|
|
145
146
|
return (
|
|
146
147
|
<>
|
|
147
|
-
<TaskList tasks={tasks} statuses={STATUSES} people={people} onChange={save} onOpen={setOpen} error={error}
|
|
148
|
+
<TaskList tasks={tasks} statuses={STATUSES} people={people} onChange={save} onOpen={setOpen} loading={loading} error={error}
|
|
148
149
|
starters={[{ label: "Weekly planning", tasks: [{ title: "Review last week" }, { title: "Pick three priorities" }] }]} />
|
|
149
150
|
<Sheet open={Boolean(open)} onClose={() => setOpen(null)} title={open?.title}>{/* the task's fields */}</Sheet>
|
|
150
151
|
</>
|
|
@@ -152,6 +153,8 @@ export default function Tasks({ tasks, people, error }) {
|
|
|
152
153
|
}
|
|
153
154
|
```
|
|
154
155
|
|
|
156
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
157
|
+
|
|
155
158
|
Grouped by person, only open work, showing the project:
|
|
156
159
|
|
|
157
160
|
```jsx
|
|
@@ -113,9 +113,9 @@ const COLUMNS = ["id", "title", "status", "assignees", "due", "priority", "paren
|
|
|
113
113
|
|
|
114
114
|
async function onChange(change) {
|
|
115
115
|
for (const w of changeWrites(change, { columns: COLUMNS, keepNewIds: false })) {
|
|
116
|
-
const r = w.op === "insert" ? await insertRow({ source: "
|
|
117
|
-
: w.op === "update" ? await updateRow({ source: "
|
|
118
|
-
: await deleteRow({ source: "
|
|
116
|
+
const r = w.op === "insert" ? await insertRow({ source: "tasks", values: w.values })
|
|
117
|
+
: w.op === "update" ? await updateRow({ source: "tasks", id: w.id, patch: w.patch })
|
|
118
|
+
: await deleteRow({ source: "tasks", id: w.id });
|
|
119
119
|
if (r.ok === false) return r;
|
|
120
120
|
}
|
|
121
121
|
return { ok: true };
|
|
@@ -79,22 +79,28 @@ An invoice row:
|
|
|
79
79
|
## Example
|
|
80
80
|
|
|
81
81
|
```jsx
|
|
82
|
-
//
|
|
82
|
+
// surfaces/billing.jsx — client: invoices and payments
|
|
83
83
|
"use client";
|
|
84
|
-
import {
|
|
84
|
+
import { useRows } from "../lib/data";
|
|
85
|
+
import { TransactionList } from "../components/blocks/transaction-list";
|
|
85
86
|
|
|
86
|
-
export default function Billing(
|
|
87
|
+
export default function Billing() {
|
|
88
|
+
const invoices = useRows({ source: "invoices", orderBy: "date desc" });
|
|
89
|
+
const payments = useRows({ source: "payments", orderBy: "date desc" });
|
|
87
90
|
return (
|
|
88
91
|
<div className="grid gap-6 lg:grid-cols-2">
|
|
89
92
|
<TransactionList
|
|
90
93
|
variant="invoices"
|
|
91
|
-
rows={invoices}
|
|
92
|
-
|
|
94
|
+
rows={invoices.rows}
|
|
95
|
+
loading={invoices.loading}
|
|
96
|
+
error={invoices.error}
|
|
93
97
|
description="Every invoice we have sent you."
|
|
94
|
-
downloadHref={(inv) => inv.
|
|
98
|
+
downloadHref={(inv) => inv.pdf_url}
|
|
95
99
|
/>
|
|
96
100
|
<TransactionList
|
|
97
|
-
rows={payments}
|
|
101
|
+
rows={payments.rows}
|
|
102
|
+
loading={payments.loading}
|
|
103
|
+
error={payments.error}
|
|
98
104
|
dateFilter
|
|
99
105
|
description="Money in and out of the account."
|
|
100
106
|
onExport={async (rows) => {
|
|
@@ -109,6 +115,8 @@ export default function Billing({ invoices, payments, error }) {
|
|
|
109
115
|
}
|
|
110
116
|
```
|
|
111
117
|
|
|
118
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
119
|
+
|
|
112
120
|
## What it will not do
|
|
113
121
|
|
|
114
122
|
- Sort, page, or filter by column: rows come in ordered, and a record set
|
|
@@ -93,8 +93,11 @@ for agents.
|
|
|
93
93
|
receives the canvas handle: `exportImage()`, `getScene()`,
|
|
94
94
|
`insertElements()`… (sketch-canvas README, "The handle").
|
|
95
95
|
- `className` — layout: width, margin, height. The default height is
|
|
96
|
-
`h-[40rem]`; a height class of yours (`h-
|
|
97
|
-
|
|
96
|
+
`h-[40rem]`; a height class of yours (`h-full`, `min-h-0 flex-1`)
|
|
97
|
+
replaces it. A board that is the whole surface sits in an `AppLayout
|
|
98
|
+
contentMode="fill"` from the kit with `min-h-0 flex-1` (the Example):
|
|
99
|
+
never `h-dvh` or a `vh` height, which `npm run check` refuses. Never
|
|
100
|
+
colour.
|
|
98
101
|
|
|
99
102
|
### Templates
|
|
100
103
|
|
|
@@ -113,39 +116,48 @@ reuse them:
|
|
|
113
116
|
A board page that saves itself:
|
|
114
117
|
|
|
115
118
|
```jsx
|
|
119
|
+
// surfaces/board.jsx — client: one board, saved as it changes
|
|
116
120
|
"use client";
|
|
117
|
-
import {
|
|
118
|
-
import {
|
|
119
|
-
import { sketchToText } from "./blocks/sketch-canvas";
|
|
121
|
+
import { useSearchParams } from "next/navigation";
|
|
122
|
+
import { useRow, whenLanded } from "../lib/data";
|
|
120
123
|
import { updateRow } from "../lib/actions";
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
+
import { AppLayout } from "../components/ui";
|
|
125
|
+
import { Whiteboard } from "../components/blocks/whiteboard";
|
|
126
|
+
import { sketchToText } from "../components/blocks/sketch-canvas";
|
|
127
|
+
|
|
128
|
+
export default function BoardPage() {
|
|
129
|
+
const id = useSearchParams().get("row");
|
|
130
|
+
const { row: board, loading } = useRow({ source: "boards", id });
|
|
131
|
+
if (loading || !board) return null;
|
|
124
132
|
return (
|
|
125
|
-
<
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
133
|
+
<AppLayout contentMode="fill">
|
|
134
|
+
<Whiteboard
|
|
135
|
+
key={board.id}
|
|
136
|
+
title={board.title}
|
|
137
|
+
onRename={async (next) => {
|
|
138
|
+
const res = await updateRow({ source: "boards", id: board.id, patch: { title: next } });
|
|
139
|
+
if (res.ok) await whenLanded(); // the board is read again: the title is the new one
|
|
140
|
+
return res;
|
|
141
|
+
}}
|
|
142
|
+
defaultValue={board.sketch} // jsonb, may be null; read once per board
|
|
143
|
+
onSave={(sketch) => updateRow({ source: "boards", id: board.id, patch: { sketch, sketch_text: sketchToText(sketch) } })}
|
|
144
|
+
className="min-h-0 flex-1 rounded-none border-0"
|
|
145
|
+
/>
|
|
146
|
+
</AppLayout>
|
|
137
147
|
);
|
|
138
148
|
}
|
|
139
149
|
```
|
|
140
150
|
|
|
151
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
152
|
+
|
|
141
153
|
Notes that become tasks (a `tasks` table of your own):
|
|
142
154
|
|
|
143
155
|
```jsx
|
|
144
156
|
<Whiteboard
|
|
145
157
|
defaultValue={board.sketch}
|
|
146
158
|
onSave={save}
|
|
147
|
-
onCreateTask={({ title, sourceIds }) => insertRow({ source: "
|
|
148
|
-
onOpenTask={(task) => router.push(`/tasks
|
|
159
|
+
onCreateTask={({ title, sourceIds }) => insertRow({ source: "tasks", values: { title, board_id: board.id, source_ids: sourceIds } })}
|
|
160
|
+
onOpenTask={(task) => router.push(`/s/tasks?row=${task.id}`)}
|
|
149
161
|
/>
|
|
150
162
|
```
|
|
151
163
|
|
package/src/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// --json is the same result as one object, for the platform.
|
|
5
5
|
|
|
6
6
|
import { delimiter, resolve } from "node:path";
|
|
7
|
-
import { CliError, add, diff, init, install, remove, update } from "./install.mjs";
|
|
7
|
+
import { CliError, add, diff, init, install, mode, remove, update } from "./install.mjs";
|
|
8
8
|
import { loadItems, loadTemplate, packageInfo, readmeOf, searchItems, suggest, templateNames, useExtraBlockRoots } from "./registry.mjs";
|
|
9
9
|
|
|
10
10
|
const COMMANDS = {
|
|
@@ -23,6 +23,7 @@ const COMMANDS = {
|
|
|
23
23
|
upgrade: "update",
|
|
24
24
|
install: "install",
|
|
25
25
|
i: "install",
|
|
26
|
+
mode: "mode",
|
|
26
27
|
help: "help",
|
|
27
28
|
};
|
|
28
29
|
|
|
@@ -32,6 +33,8 @@ const VALUE_FLAGS = new Map([
|
|
|
32
33
|
["--template", "template"],
|
|
33
34
|
["--type", "type"],
|
|
34
35
|
["--limit", "limit"],
|
|
36
|
+
["--tables", "tables"],
|
|
37
|
+
["--mode", "mode"],
|
|
35
38
|
]);
|
|
36
39
|
const BOOL_FLAGS = new Map([
|
|
37
40
|
["--json", "json"],
|
|
@@ -76,6 +79,8 @@ function helpText() {
|
|
|
76
79
|
return `makersclaw ${version} — the Makersclaw app template, its kit and its blocks
|
|
77
80
|
|
|
78
81
|
npx makersclaw init [dir] [-t next] lay the template: base files, the kit, the check
|
|
82
|
+
[--tables own|public] where its tables live (new apps: own; never changes an app's)
|
|
83
|
+
[--mode browser|server] its mode (new apps: server; browser writes tables own too)
|
|
79
84
|
npx makersclaw add <name...> install blocks, or restore a kit component
|
|
80
85
|
npx makersclaw search <words...> find blocks and components by what they do
|
|
81
86
|
npx makersclaw view <name> one item: props, data, an example
|
|
@@ -85,6 +90,7 @@ function helpText() {
|
|
|
85
90
|
npx makersclaw update [--major] lay this version's fixed files and blocks
|
|
86
91
|
[--adopt] …or take on an app laid before makersclaw (kit era)
|
|
87
92
|
npx makersclaw install the app's packages: pnpm, from pnpm-lock.yaml
|
|
93
|
+
npx makersclaw mode <browser|server> switch the app's mode: its fixed files, and what its code must change
|
|
88
94
|
|
|
89
95
|
--cwd <dir> the app (default: this folder) --json one JSON object
|
|
90
96
|
--force init over an app that exists (your own files stay)
|
|
@@ -101,7 +107,7 @@ const listLine = (paths, n = 6) => (paths.length > n ? `${paths.slice(0, n).join
|
|
|
101
107
|
|
|
102
108
|
function packageLines(changes) {
|
|
103
109
|
if (!changes.length) return [];
|
|
104
|
-
return [` package.json: ${changes.map((c) => `${c.name} ${c.from ?? "(new)"} → ${c.to}`).join(" · ")}`];
|
|
110
|
+
return [` package.json: ${changes.map((c) => `${c.name} ${c.from ?? "(new)"} → ${c.to ?? "(removed)"}`).join(" · ")}`];
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
/** The lock's line, when it was written. What it left out is a note. */
|
|
@@ -127,7 +133,7 @@ function layText(r, verb) {
|
|
|
127
133
|
if (r.blocks.length) lines.push(` blocks: ${r.blocks.join(", ")}`);
|
|
128
134
|
lines.push(...packageLines(r.package));
|
|
129
135
|
lines.push(...lockLine(r.lock, r.written.includes("pnpm-lock.yaml")));
|
|
130
|
-
lines.push(` ${plural(r.pinned, "fixed file")} pinned in check/pins.json · makersclaw.json records ${r.version}`);
|
|
136
|
+
lines.push(` ${plural(r.pinned, "fixed file")} pinned in check/pins.json · makersclaw.json records ${r.version}, tables ${r.tables}, mode ${r.mode}`);
|
|
131
137
|
for (const n of r.notes) lines.push(` note: ${n}`);
|
|
132
138
|
const next = [];
|
|
133
139
|
if (r.install) next.push("npx makersclaw install");
|
|
@@ -200,14 +206,31 @@ function listText(items) {
|
|
|
200
206
|
return lines.join("\n");
|
|
201
207
|
}
|
|
202
208
|
|
|
209
|
+
function modeText(r) {
|
|
210
|
+
const lines = [
|
|
211
|
+
r.changed
|
|
212
|
+
? `makersclaw ${r.version} · template ${r.template} · mode ${r.was} → ${r.mode}`
|
|
213
|
+
: `makersclaw ${r.version} · template ${r.template} · already ${r.mode}: its fixed files laid again`,
|
|
214
|
+
];
|
|
215
|
+
if (r.written.length) lines.push(` wrote ${plural(r.written.length, "file")}: ${listLine(r.written)}`);
|
|
216
|
+
if (r.removed.length) lines.push(` removed (${r.mode} mode lays none): ${listLine(r.removed)}`);
|
|
217
|
+
lines.push(...packageLines(r.package));
|
|
218
|
+
lines.push(` ${plural(r.pinned, "fixed file")} pinned in check/pins.json · makersclaw.json records mode ${r.mode}`);
|
|
219
|
+
for (const n of r.notes) lines.push(` note: ${n}`);
|
|
220
|
+
lines.push(`the app's own code${r.changed ? " must change" : ", in this mode"}:`);
|
|
221
|
+
for (const c of r.change) lines.push(` - ${c}`);
|
|
222
|
+
lines.push(`next: ${[...(r.install ? ["npx makersclaw install"] : []), "npm run check"].join(" · ")}`);
|
|
223
|
+
return lines.join("\n");
|
|
224
|
+
}
|
|
225
|
+
|
|
203
226
|
function diffText(r) {
|
|
204
|
-
const lines = [`makersclaw ${r.from} → ${r.to} · ${r.compatible ? "compatible" : "changes the contract — update --major"}`];
|
|
227
|
+
const lines = [`makersclaw ${r.from} → ${r.to} · ${r.compatible ? "compatible" : "changes the contract — update --major"} · mode ${r.mode}`];
|
|
205
228
|
if (!r.entries.length && !r.package.length) {
|
|
206
229
|
lines.push(` nothing to update — the app matches makersclaw ${r.to}`);
|
|
207
230
|
return lines.join("\n");
|
|
208
231
|
}
|
|
209
232
|
for (const e of r.entries) lines.push(` ${e.item.padEnd(14)} ${e.status.padEnd(8)} ${e.path}${e.detail ? ` — ${e.detail}` : ""}`);
|
|
210
|
-
for (const c of r.package) lines.push(` ${"package.json".padEnd(14)} ${c.key}.${c.name}: ${c.from ?? "(new)"} → ${c.to}`);
|
|
233
|
+
for (const c of r.package) lines.push(` ${"package.json".padEnd(14)} ${c.key}.${c.name}: ${c.from ?? "(new)"} → ${c.to ?? "(removed)"}`);
|
|
211
234
|
lines.push(`next: npx makersclaw update${r.compatible ? "" : " --major"}`);
|
|
212
235
|
return lines.join("\n");
|
|
213
236
|
}
|
|
@@ -240,7 +263,7 @@ export function run(opts, { stderr = process.stderr, env = process.env } = {}) {
|
|
|
240
263
|
return { result: packageInfo(), text: packageInfo().version };
|
|
241
264
|
case "init": {
|
|
242
265
|
const dir = opts.args[0] ? resolve(cwd, opts.args[0]) : cwd;
|
|
243
|
-
const result = init(dir, { template: opts.template ?? "next", force: Boolean(opts.force) });
|
|
266
|
+
const result = init(dir, { template: opts.template ?? "next", force: Boolean(opts.force), tables: opts.tables, mode: opts.mode });
|
|
244
267
|
return { result: { dir, ...result }, text: layText(result, "init") };
|
|
245
268
|
}
|
|
246
269
|
case "update": {
|
|
@@ -262,6 +285,10 @@ export function run(opts, { stderr = process.stderr, env = process.env } = {}) {
|
|
|
262
285
|
const result = install(cwd, { env, onNote: (line) => stderr.write(`makersclaw: ${line}\n`) });
|
|
263
286
|
return { result: { dir: cwd, ...result }, text: installText(result) };
|
|
264
287
|
}
|
|
288
|
+
case "mode": {
|
|
289
|
+
const result = mode(cwd, opts.args[0] ?? opts.mode);
|
|
290
|
+
return { result: { dir: cwd, ...result }, text: modeText(result) };
|
|
291
|
+
}
|
|
265
292
|
case "diff": {
|
|
266
293
|
const result = diff(cwd);
|
|
267
294
|
return { result: { dir: cwd, ...result }, text: diffText(result) };
|