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
|
@@ -144,20 +144,30 @@ on a partial one):
|
|
|
144
144
|
## Example
|
|
145
145
|
|
|
146
146
|
```jsx
|
|
147
|
-
//
|
|
147
|
+
// surfaces/deal.jsx — client: one deal's notes
|
|
148
148
|
"use client";
|
|
149
149
|
import { useState } from "react";
|
|
150
|
+
import { useSearchParams } from "next/navigation";
|
|
151
|
+
import { useRow, useRows } from "../lib/data";
|
|
150
152
|
import { updateRow } from "../lib/actions";
|
|
151
|
-
import { DocEditor } from "
|
|
153
|
+
import { DocEditor } from "../components/blocks/doc-editor";
|
|
154
|
+
|
|
155
|
+
export default function DealSurface() {
|
|
156
|
+
const id = useSearchParams().get("row");
|
|
157
|
+
const { row: deal, loading } = useRow({ source: "deals", id });
|
|
158
|
+
const { rows: team } = useRows({ source: "people", orderBy: "name asc" });
|
|
159
|
+
if (loading || !deal) return null;
|
|
160
|
+
return <DealNotes key={deal.id} deal={deal} team={team} />;
|
|
161
|
+
}
|
|
152
162
|
|
|
153
|
-
|
|
154
|
-
const [doc, setDoc] = useState(deal.notes); // jsonb, may be null
|
|
163
|
+
function DealNotes({ deal, team }) {
|
|
164
|
+
const [doc, setDoc] = useState(deal.notes); // jsonb, may be null; read once per deal, so a read never moves the cursor
|
|
155
165
|
return (
|
|
156
166
|
<DocEditor
|
|
157
167
|
value={doc}
|
|
158
168
|
onChange={(d) => {
|
|
159
169
|
setDoc(d);
|
|
160
|
-
updateRow({ source: "
|
|
170
|
+
updateRow({ source: "deals", id: deal.id, patch: { notes: d } }); // debounce this in a real app
|
|
161
171
|
}}
|
|
162
172
|
mentions={async (q) => team.filter((p) => p.name.toLowerCase().includes(q.toLowerCase())).map((p) => ({ id: p.id, label: p.name }))}
|
|
163
173
|
placeholder="Notes on this deal…"
|
|
@@ -167,13 +177,15 @@ export default function DealNotes({ deal, team }) {
|
|
|
167
177
|
}
|
|
168
178
|
```
|
|
169
179
|
|
|
180
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
181
|
+
|
|
170
182
|
A comment box that posts on Cmd/Ctrl+Enter and clears itself:
|
|
171
183
|
|
|
172
184
|
```jsx
|
|
173
185
|
const box = useDocEditor();
|
|
174
186
|
const post = async (doc) => {
|
|
175
187
|
if (isDocEmpty(doc)) return;
|
|
176
|
-
const res = await insertRow({ source: "
|
|
188
|
+
const res = await insertRow({ source: "comments", values: { body: doc, body_text: docToText(doc) } });
|
|
177
189
|
if (res.ok) box.current?.clear();
|
|
178
190
|
};
|
|
179
191
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// DocEditor — BlockNote, in the workspace theme, behind one set of props.
|
|
4
4
|
//
|
|
5
|
-
// Client only, one
|
|
5
|
+
// Client only, one bundle. BlockNote needs the DOM, and the app's
|
|
6
6
|
// check refuses import() (so no next/dynamic): the modules load with the
|
|
7
7
|
// page, and the editor itself is created only after the first effect
|
|
8
8
|
// (the mount guard). Until then — on the server and in the first client
|
|
@@ -75,22 +75,27 @@ out without waiting for the answer.
|
|
|
75
75
|
## Example
|
|
76
76
|
|
|
77
77
|
```jsx
|
|
78
|
-
//
|
|
78
|
+
// surfaces/wiki.jsx — client: one page, read and saved
|
|
79
79
|
"use client";
|
|
80
|
+
import { useSearchParams } from "next/navigation";
|
|
81
|
+
import { useRow, useRows } from "../lib/data";
|
|
80
82
|
import { updateRow } from "../lib/actions";
|
|
81
|
-
import { DocPage } from "
|
|
83
|
+
import { DocPage } from "../components/blocks/doc-page";
|
|
82
84
|
|
|
83
|
-
export default function WikiPage(
|
|
85
|
+
export default function WikiPage() {
|
|
86
|
+
const id = useSearchParams().get("row");
|
|
87
|
+
const { row: page, loading } = useRow({ source: "wiki_pages", id });
|
|
88
|
+
const { rows: team } = useRows({ source: "people", orderBy: "name asc" });
|
|
89
|
+
if (loading || !page) return null;
|
|
84
90
|
return (
|
|
85
91
|
<DocPage
|
|
86
92
|
id={page.id}
|
|
87
93
|
title={page.title}
|
|
88
94
|
icon={page.icon}
|
|
89
95
|
doc={page.body}
|
|
90
|
-
|
|
91
|
-
meta={`Edited by ${page.edited_by_name}`}
|
|
96
|
+
meta={`Edited by ${page.edited_by}`} // a before-update trigger sets edited_by (the app's README §5)
|
|
92
97
|
onSave={({ id, title, icon, doc }) =>
|
|
93
|
-
updateRow({ source: "
|
|
98
|
+
updateRow({ source: "wiki_pages", id, patch: { title, icon, body: doc } })
|
|
94
99
|
}
|
|
95
100
|
mentions={async (q) =>
|
|
96
101
|
team.filter((p) => p.name.toLowerCase().includes(q.toLowerCase())).map((p) => ({ id: p.id, label: p.name }))
|
|
@@ -100,6 +105,11 @@ export default function WikiPage({ page, team, canEdit }) {
|
|
|
100
105
|
}
|
|
101
106
|
```
|
|
102
107
|
|
|
108
|
+
The page reads `title`, `icon` and `doc` when `id` changes and owns the
|
|
109
|
+
draft in between, so the read after each save never moves the cursor.
|
|
110
|
+
|
|
111
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
112
|
+
|
|
103
113
|
For search or a preview, keep a plain-text copy beside the doc:
|
|
104
114
|
`docToText(doc)` from the doc-editor block, saved in the same `onSave`.
|
|
105
115
|
|
|
@@ -79,7 +79,7 @@ for a file) are exported too, for your own file rows.
|
|
|
79
79
|
## Example
|
|
80
80
|
|
|
81
81
|
```jsx
|
|
82
|
-
// components/DealFiles.jsx — client
|
|
82
|
+
// components/DealFiles.jsx — client, in a server app
|
|
83
83
|
"use client";
|
|
84
84
|
import { FileUpload } from "./blocks/file-upload";
|
|
85
85
|
|
|
@@ -111,6 +111,12 @@ export default function DealFiles({ deal, onSaveFiles }) {
|
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
A server app's only. `put` needs a route of the app's own to take the
|
|
115
|
+
file (`/api/files` here), and a browser app has none: the data door
|
|
116
|
+
takes no files until uploads have a home. In a browser app, leave
|
|
117
|
+
`onUpload` out and use the block as a picker: read the file in the
|
|
118
|
+
browser, as csv-import does.
|
|
119
|
+
|
|
114
120
|
One spreadsheet to import, and a profile picture:
|
|
115
121
|
|
|
116
122
|
```jsx
|
|
@@ -111,7 +111,10 @@ re-route as cards move; handle ids are not stored.
|
|
|
111
111
|
screen). `onExitPresentation()` — Esc and the bar's Exit call it; turn
|
|
112
112
|
`presentation` off there.
|
|
113
113
|
- `height` — the canvas's height, default `560` (px; any CSS length works:
|
|
114
|
-
`"
|
|
114
|
+
`"100%"` inside a sized box, such as the kit's `AppLayout
|
|
115
|
+
contentMode="fill"` for a board that is the whole surface, the
|
|
116
|
+
Example). Never a `vh` length: `npm run check` refuses a size taken
|
|
117
|
+
from the window.
|
|
115
118
|
- `bare` — `false` (default: rounded, bordered). `true`: no border or
|
|
116
119
|
radius, for a full-page board.
|
|
117
120
|
- `label` — the board's accessible name, default `"Board"`; also the list
|
|
@@ -364,26 +367,41 @@ one.
|
|
|
364
367
|
## Example
|
|
365
368
|
|
|
366
369
|
```jsx
|
|
367
|
-
//
|
|
370
|
+
// surfaces/launch.jsx — client: one project's board
|
|
368
371
|
"use client";
|
|
369
372
|
import { useState } from "react";
|
|
373
|
+
import { useSearchParams } from "next/navigation";
|
|
374
|
+
import { useRow } from "../lib/data";
|
|
370
375
|
import { updateRow } from "../lib/actions";
|
|
371
|
-
import {
|
|
376
|
+
import { AppLayout } from "../components/ui";
|
|
377
|
+
import { FlowCanvas } from "../components/blocks/flow-canvas";
|
|
378
|
+
|
|
379
|
+
export default function LaunchSurface() {
|
|
380
|
+
const id = useSearchParams().get("row");
|
|
381
|
+
const { row: project, loading } = useRow({ source: "projects", id });
|
|
382
|
+
if (loading || !project) return null;
|
|
383
|
+
return <LaunchBoard key={project.id} project={project} />;
|
|
384
|
+
}
|
|
372
385
|
|
|
373
|
-
|
|
374
|
-
const [board, setBoard] = useState(project.board); // jsonb, may be null
|
|
386
|
+
function LaunchBoard({ project }) {
|
|
387
|
+
const [board, setBoard] = useState(project.board); // jsonb, may be null; read once per project
|
|
375
388
|
return (
|
|
376
|
-
<
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
389
|
+
<AppLayout contentMode="fill">
|
|
390
|
+
<FlowCanvas
|
|
391
|
+
value={board ?? undefined}
|
|
392
|
+
onChange={setBoard}
|
|
393
|
+
onSave={(b) => updateRow({ source: "projects", id: project.id, patch: { board: b } })}
|
|
394
|
+
label={`${project.name} board`}
|
|
395
|
+
height="100%"
|
|
396
|
+
bare
|
|
397
|
+
/>
|
|
398
|
+
</AppLayout>
|
|
383
399
|
);
|
|
384
400
|
}
|
|
385
401
|
```
|
|
386
402
|
|
|
403
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
404
|
+
|
|
387
405
|
A card drawn with kit parts, and an agent reading the board:
|
|
388
406
|
|
|
389
407
|
```jsx
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// FlowCanvas — a Miro-like board on React Flow, in the workspace theme,
|
|
4
4
|
// behind one set of props (README.md beside this file).
|
|
5
5
|
//
|
|
6
|
-
// Client only, one
|
|
6
|
+
// Client only, one bundle: the modules load with the page (the
|
|
7
7
|
// app's check refuses import()), and the board itself mounts after the
|
|
8
8
|
// first effect (the mount guard). Until then — on the server and in the
|
|
9
9
|
// first client paint — a box of the same size holds the board as text, so
|
|
@@ -87,7 +87,7 @@ button, named to a screen reader ("X", "LinkedIn"), opening a new tab.
|
|
|
87
87
|
"use client";
|
|
88
88
|
import { Icon } from "./ui";
|
|
89
89
|
import { Footer } from "./blocks/footer";
|
|
90
|
-
import {
|
|
90
|
+
import { insertRow } from "../lib/actions";
|
|
91
91
|
|
|
92
92
|
export default function SiteFooter() {
|
|
93
93
|
return (
|
|
@@ -102,7 +102,7 @@ export default function SiteFooter() {
|
|
|
102
102
|
newsletter={{
|
|
103
103
|
body: "One email a month.",
|
|
104
104
|
consent: "I agree to the privacy policy.",
|
|
105
|
-
onSubscribe:
|
|
105
|
+
onSubscribe: (email) => insertRow({ source: "subscribers", values: { email } }), // { ok, id } or { ok: false, error }
|
|
106
106
|
}}
|
|
107
107
|
legal="© 2026 Fieldline Ltd. All rights reserved."
|
|
108
108
|
legalLinks={[{ label: "Privacy", href: "/privacy" }, { label: "Terms", href: "/terms" }]}
|
|
@@ -111,12 +111,15 @@ export default function SiteFooter() {
|
|
|
111
111
|
}
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
115
|
+
|
|
114
116
|
## What it will not do
|
|
115
117
|
|
|
116
118
|
- Change the language or currency by itself: `locales[].onChange` is
|
|
117
119
|
yours.
|
|
118
|
-
- Stick to the bottom of a short page: that is your layout's
|
|
119
|
-
`
|
|
120
|
+
- Stick to the bottom of a short page: that is your layout's. The kit's
|
|
121
|
+
`AppLayout` content is a flex column, so a `flex-1` above the footer
|
|
122
|
+
pushes it down on a short page and lets it follow a long one.
|
|
120
123
|
- Paint itself in the primary colour: the look is the workspace
|
|
121
124
|
theme's.
|
|
122
125
|
- Draw store badges: "Get the app" is two buttons with the Apple and
|
|
@@ -145,15 +145,19 @@ drag saves, as `[{ id, patch }]`), `afterBlockersPatch(task, tasks)`,
|
|
|
145
145
|
## Example
|
|
146
146
|
|
|
147
147
|
```jsx
|
|
148
|
-
// surfaces/plan.jsx —
|
|
149
|
-
|
|
148
|
+
// surfaces/plan.jsx — client: read, hand over
|
|
149
|
+
"use client";
|
|
150
|
+
import { useRows } from "../lib/data";
|
|
150
151
|
import Plan from "../components/Plan";
|
|
151
152
|
|
|
152
|
-
export default
|
|
153
|
-
const
|
|
154
|
-
const people =
|
|
155
|
-
const projects =
|
|
156
|
-
return
|
|
153
|
+
export default function PlanSurface() {
|
|
154
|
+
const tasks = useRows({ source: "tasks", orderBy: "start asc" });
|
|
155
|
+
const people = useRows({ source: "people" });
|
|
156
|
+
const projects = useRows({ source: "projects" });
|
|
157
|
+
return (
|
|
158
|
+
<Plan tasks={tasks.rows} people={people.rows} projects={projects.rows}
|
|
159
|
+
loading={tasks.loading || people.loading || projects.loading} error={tasks.error} />
|
|
160
|
+
);
|
|
157
161
|
}
|
|
158
162
|
```
|
|
159
163
|
|
|
@@ -171,19 +175,19 @@ const COLUMNS = ["id", "title", "status", "assignees", "start", "due", "progress
|
|
|
171
175
|
async function save(change) {
|
|
172
176
|
for (const w of changeWrites(change, { columns: COLUMNS, keepNewIds: false })) {
|
|
173
177
|
const r =
|
|
174
|
-
w.op === "insert" ? await insertRow({ source: "
|
|
175
|
-
: w.op === "update" ? await updateRow({ source: "
|
|
176
|
-
: await deleteRow({ source: "
|
|
178
|
+
w.op === "insert" ? await insertRow({ source: "tasks", values: w.values, detail: w.values.title })
|
|
179
|
+
: w.op === "update" ? await updateRow({ source: "tasks", id: w.id, patch: w.patch })
|
|
180
|
+
: await deleteRow({ source: "tasks", id: w.id });
|
|
177
181
|
if (r?.ok === false) return r;
|
|
178
182
|
}
|
|
179
183
|
return { ok: true };
|
|
180
184
|
}
|
|
181
185
|
|
|
182
|
-
export default function Plan({ tasks, people, projects, error }) {
|
|
186
|
+
export default function Plan({ tasks, people, projects, loading, error }) {
|
|
183
187
|
const [open, setOpen] = useState(null);
|
|
184
188
|
return (
|
|
185
189
|
<>
|
|
186
|
-
<Gantt tasks={tasks} people={people} projects={projects} onChange={save} onOpen={setOpen} error={error}
|
|
190
|
+
<Gantt tasks={tasks} people={people} projects={projects} onChange={save} onOpen={setOpen} loading={loading} error={error}
|
|
187
191
|
starters={[{ label: "Product launch", tasks: [{ title: "Scope", days: 2 }, { title: "Build", days: 10 }, { title: "Launch", milestone: true }] }]} />
|
|
188
192
|
<Sheet open={Boolean(open)} onClose={() => setOpen(null)} title={open?.title}>{/* the task's fields */}</Sheet>
|
|
189
193
|
</>
|
|
@@ -191,6 +195,8 @@ export default function Plan({ tasks, people, projects, error }) {
|
|
|
191
195
|
}
|
|
192
196
|
```
|
|
193
197
|
|
|
198
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
199
|
+
|
|
194
200
|
`keepNewIds: false` lets the table make a new task's id. A link made
|
|
195
201
|
between two tasks created in the same change (a starter) refers to their
|
|
196
202
|
client ids: keep them (`keepNewIds: true`, a text id column) when you
|
|
@@ -166,7 +166,7 @@ export default function PhotoStudio({ photos, reload }) {
|
|
|
166
166
|
options={[{ value: "square", label: "Square" }, { value: "portrait", label: "Portrait" }]} />
|
|
167
167
|
</Field>
|
|
168
168
|
}
|
|
169
|
-
onGenerate={(prompt) => job.start(`Make one ${size} product photo: ${prompt}. Save it to
|
|
169
|
+
onGenerate={(prompt) => job.start(`Make one ${size} product photo: ${prompt}. Save it to the photos table.`)}
|
|
170
170
|
title="Making the photo"
|
|
171
171
|
doneTitle="Photo saved"
|
|
172
172
|
results={photos.map((p) => <Image key={p.id} src={p.url} alt={p.prompt} ratio="square" rounded="lg" />)}
|
|
@@ -98,24 +98,36 @@ tree on its own (`value`, `onEdit(next, { undo?, message? })`, `open`,
|
|
|
98
98
|
## Example
|
|
99
99
|
|
|
100
100
|
```jsx
|
|
101
|
+
// surfaces/webhook.jsx — client: one integration's settings
|
|
101
102
|
"use client";
|
|
102
103
|
import { useState } from "react";
|
|
104
|
+
import { useSearchParams } from "next/navigation";
|
|
105
|
+
import { useRow } from "../lib/data";
|
|
106
|
+
import { updateRow } from "../lib/actions";
|
|
103
107
|
import { JsonEditor } from "../components/blocks/json-editor";
|
|
104
|
-
import { saveIntegrationSettings } from "../lib/actions"; // your server action → { ok, error? }
|
|
105
108
|
|
|
106
|
-
export function
|
|
107
|
-
const
|
|
109
|
+
export default function WebhookSurface() {
|
|
110
|
+
const id = useSearchParams().get("row");
|
|
111
|
+
const { row: integration, loading } = useRow({ source: "integrations", id });
|
|
112
|
+
if (loading || !integration) return null;
|
|
113
|
+
return <WebhookSettings key={integration.id} integration={integration} />;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function WebhookSettings({ integration }) {
|
|
117
|
+
const [settings, setSettings] = useState(integration.settings); // jsonb; read once per integration
|
|
108
118
|
return (
|
|
109
119
|
<JsonEditor
|
|
110
120
|
title="Webhook settings"
|
|
111
121
|
value={settings}
|
|
112
122
|
onChange={setSettings}
|
|
113
|
-
onSave={(v) =>
|
|
123
|
+
onSave={(v) => updateRow({ source: "integrations", id: integration.id, patch: { settings: v } })}
|
|
114
124
|
/>
|
|
115
125
|
);
|
|
116
126
|
}
|
|
117
127
|
```
|
|
118
128
|
|
|
129
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
130
|
+
|
|
119
131
|
Inspect a payload, read-only, as text first:
|
|
120
132
|
|
|
121
133
|
```jsx
|
|
@@ -170,10 +170,23 @@ map.current?.getMap(); // the maplibre Map, for what the props do not cover
|
|
|
170
170
|
|
|
171
171
|
## Example
|
|
172
172
|
|
|
173
|
+
```jsx
|
|
174
|
+
// surfaces/customers.jsx — client: read, hand over
|
|
175
|
+
"use client";
|
|
176
|
+
import { useRows } from "../lib/data";
|
|
177
|
+
import Customers from "../components/Customers";
|
|
178
|
+
|
|
179
|
+
export default function CustomersSurface() {
|
|
180
|
+
const { rows, error, loading } = useRows({ source: "customers", orderBy: "name asc" });
|
|
181
|
+
if (loading) return null; // rows only once read: to useFixture, a read in flight looks like an empty table
|
|
182
|
+
return <Customers customers={rows} error={error} />;
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
173
186
|
```jsx
|
|
174
187
|
// components/Customers.jsx — client: the map, the list, the popup
|
|
175
188
|
"use client";
|
|
176
|
-
import { Badge, Button } from "./ui";
|
|
189
|
+
import { Badge, Button, Notice } from "./ui";
|
|
177
190
|
import { FixtureNotice, useFixture } from "./parts";
|
|
178
191
|
import { MapView } from "./blocks/map";
|
|
179
192
|
|
|
@@ -182,10 +195,11 @@ const SAMPLE = [
|
|
|
182
195
|
{ name: "Acme Bakery", city: "Porto", lat: 41.1405, lng: -8.6132, status: "at risk" },
|
|
183
196
|
];
|
|
184
197
|
|
|
185
|
-
export default function Customers({ customers }) {
|
|
198
|
+
export default function Customers({ customers, error }) {
|
|
186
199
|
const fixture = useFixture(customers, SAMPLE);
|
|
187
200
|
return (
|
|
188
201
|
<div className="grid gap-4">
|
|
202
|
+
{error ? <Notice tone="danger">{error}</Notice> : null}
|
|
189
203
|
{fixture.isFixture ? <FixtureNotice onClear={fixture.clear} /> : null}
|
|
190
204
|
<MapView
|
|
191
205
|
items={fixture.rows}
|
|
@@ -198,7 +212,7 @@ export default function Customers({ customers }) {
|
|
|
198
212
|
<div className="grid gap-2 pr-8">
|
|
199
213
|
<p className="font-medium">{c.name}</p>
|
|
200
214
|
<p className="text-muted-foreground">{c.city}</p>
|
|
201
|
-
<Button size="sm" variant="primary" href={`/customers
|
|
215
|
+
<Button size="sm" variant="primary" href={`/s/customers?row=${c.id}`}>Open</Button>
|
|
202
216
|
</div>
|
|
203
217
|
)}
|
|
204
218
|
/>
|
|
@@ -207,6 +221,8 @@ export default function Customers({ customers }) {
|
|
|
207
221
|
}
|
|
208
222
|
```
|
|
209
223
|
|
|
224
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
225
|
+
|
|
210
226
|
## What it will not do
|
|
211
227
|
|
|
212
228
|
- Geocode. It maps coordinates; turning an address into them is a
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// on them (markers, clusters, the popup) and every control is ours, in the
|
|
6
6
|
// kit and the workspace's tokens.
|
|
7
7
|
//
|
|
8
|
-
// Client only, one
|
|
8
|
+
// Client only, one bundle: maplibre loads with the page (see
|
|
9
9
|
// maplibre.js), and the map itself mounts after the first effect (the
|
|
10
10
|
// mount guard). Until then — on the server and in the first client paint —
|
|
11
11
|
// the frame, the list and a quiet placeholder are already there, so the
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
//
|
|
12
12
|
// mapLib react-map-gl would otherwise load maplibre-gl with an
|
|
13
13
|
// import() of its own. Handing it the module we import here
|
|
14
|
-
// keeps the app one
|
|
14
|
+
// keeps the app one bundle.
|
|
15
15
|
// WORKER_URL maplibre-gl 6 draws tiles in a Web Worker that it loads
|
|
16
16
|
// from a file beside its own module (import.meta.url). Inside
|
|
17
17
|
// an app bundle that URL is a build-time file:// path, so the
|
|
@@ -96,7 +96,9 @@ string, or nothing (a fresh map). The component runs it on `value`.
|
|
|
96
96
|
- `placeholder` — what an empty topic shows while it is typed (default
|
|
97
97
|
"New topic").
|
|
98
98
|
- `height` — px or any CSS length, default `600`. A height class in
|
|
99
|
-
`className` (`h-
|
|
99
|
+
`className` (`h-full`, `min-h-0 flex-1`) replaces it: `min-h-0 flex-1`
|
|
100
|
+
fills what the surface has left (the Example). Never `h-dvh` or a `vh`
|
|
101
|
+
height, which `npm run check` refuses.
|
|
100
102
|
- `label` — the map's accessible name (default "Mind map").
|
|
101
103
|
- `onSelectionChange(topic | null)` — the selected topic changed (a side
|
|
102
104
|
panel with its notes).
|
|
@@ -162,27 +164,38 @@ one as it was.
|
|
|
162
164
|
## Example
|
|
163
165
|
|
|
164
166
|
```jsx
|
|
165
|
-
//
|
|
167
|
+
// surfaces/plan-map.jsx — client: one plan's map
|
|
166
168
|
"use client";
|
|
167
169
|
import { useState } from "react";
|
|
170
|
+
import { useSearchParams } from "next/navigation";
|
|
171
|
+
import { useRow } from "../lib/data";
|
|
168
172
|
import { updateRow } from "../lib/actions";
|
|
169
|
-
import { MindMap, mindMapToText } from "
|
|
173
|
+
import { MindMap, mindMapToText } from "../components/blocks/mind-map";
|
|
170
174
|
|
|
171
|
-
export default function
|
|
172
|
-
const
|
|
175
|
+
export default function PlanMapSurface() {
|
|
176
|
+
const id = useSearchParams().get("row");
|
|
177
|
+
const { row: plan, loading } = useRow({ source: "plans", id });
|
|
178
|
+
if (loading || !plan) return null;
|
|
179
|
+
return <PlanMap key={plan.id} plan={plan} />;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function PlanMap({ plan }) {
|
|
183
|
+
const [map, setMap] = useState(plan.map); // jsonb, may be null; read once per plan
|
|
173
184
|
return (
|
|
174
185
|
<MindMap
|
|
175
186
|
value={map ?? undefined}
|
|
176
187
|
defaultValue={{ text: plan.name }}
|
|
177
188
|
onChange={setMap}
|
|
178
|
-
onSave={(m) => updateRow({ source: "
|
|
189
|
+
onSave={(m) => updateRow({ source: "plans", id: plan.id, patch: { map: m, map_text: mindMapToText(m) } })}
|
|
179
190
|
label={`${plan.name} map`}
|
|
180
|
-
className="h-
|
|
191
|
+
className="min-h-0 flex-1"
|
|
181
192
|
/>
|
|
182
193
|
);
|
|
183
194
|
}
|
|
184
195
|
```
|
|
185
196
|
|
|
197
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
198
|
+
|
|
186
199
|
An agent's outline, straight onto a map:
|
|
187
200
|
|
|
188
201
|
```jsx
|
|
@@ -73,7 +73,9 @@ One row per person, as a table holds them:
|
|
|
73
73
|
reports: kit parts (their open deals, a "Message" button).
|
|
74
74
|
- `onSelectionChange(person | null)` — the selected card changed.
|
|
75
75
|
- `height` — px or any CSS length, default `640`. A height class in
|
|
76
|
-
`className` (`h-
|
|
76
|
+
`className` (`h-full`, `min-h-0 flex-1`) replaces it: `min-h-0 flex-1`
|
|
77
|
+
fills what the surface has left (the Example). Never `h-dvh` or a `vh`
|
|
78
|
+
height, which `npm run check` refuses.
|
|
77
79
|
- `label` — the chart's accessible name (default "Org chart").
|
|
78
80
|
- `className` — layout around it (margin, width, height). Never colour.
|
|
79
81
|
|
|
@@ -106,29 +108,32 @@ One row per person, as a table holds them:
|
|
|
106
108
|
## Example
|
|
107
109
|
|
|
108
110
|
```jsx
|
|
109
|
-
//
|
|
111
|
+
// surfaces/team.jsx — client: the chart, read and rearranged
|
|
110
112
|
"use client";
|
|
111
|
-
import {
|
|
113
|
+
import { useRows, whenLanded } from "../lib/data";
|
|
112
114
|
import { updateRow } from "../lib/actions";
|
|
113
|
-
import { OrgChart } from "
|
|
115
|
+
import { OrgChart } from "../components/blocks/org-chart";
|
|
114
116
|
|
|
115
|
-
export default function TeamChart(
|
|
116
|
-
const
|
|
117
|
+
export default function TeamChart() {
|
|
118
|
+
const { rows: people, loading } = useRows({ source: "employees" });
|
|
119
|
+
if (loading) return null;
|
|
117
120
|
return (
|
|
118
121
|
<OrgChart
|
|
119
122
|
people={people}
|
|
120
123
|
managerKey="manager_id"
|
|
121
124
|
onChangeManager={async (person, managerId) => {
|
|
122
|
-
const res = await updateRow({ source: "
|
|
123
|
-
if (res.ok)
|
|
125
|
+
const res = await updateRow({ source: "employees", id: person.id, patch: { manager_id: managerId } });
|
|
126
|
+
if (res.ok) await whenLanded(); // the people are read again: the chart draws the new manager
|
|
124
127
|
return res;
|
|
125
128
|
}}
|
|
126
|
-
className="h-
|
|
129
|
+
className="min-h-0 flex-1"
|
|
127
130
|
/>
|
|
128
131
|
);
|
|
129
132
|
}
|
|
130
133
|
```
|
|
131
134
|
|
|
135
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
136
|
+
|
|
132
137
|
## What it will not do
|
|
133
138
|
|
|
134
139
|
- Show a person under two managers (dotted lines, matrix orgs): one
|
|
@@ -83,7 +83,7 @@ variables the app's Tailwind utilities read. The built-in blocks' CSS
|
|
|
83
83
|
workspace theme. Tailwind emits a theme variable only when some source
|
|
84
84
|
names it, so this block names every token a page may use
|
|
85
85
|
(`KEEP_TOKENS`). Installing the block is enough.
|
|
86
|
-
- **Outside the app** (a download, a
|
|
86
|
+
- **Outside the app** (a download, a plain file host),
|
|
87
87
|
`standaloneDocument(page, { root })` returns a full HTML document with
|
|
88
88
|
the tokens resolved and inlined. `pageDocument(page, { tokens })` is the
|
|
89
89
|
same function for the server, if you pass it the `:root{…}` string
|
|
@@ -105,7 +105,7 @@ variables the app's Tailwind utilities read. The built-in blocks' CSS
|
|
|
105
105
|
| `actions` | — | Extra top-bar controls before Save, such as landing-builder's Publish. Use kit `Button`s at `size="sm"`. |
|
|
106
106
|
| `saveLabel` | `"Save"` | The Save button's words. |
|
|
107
107
|
| `builderRef` | — | A ref from `usePageBuilder()`. It receives the handle. |
|
|
108
|
-
| `height` | `720` | The builder's height: a number (px) or any CSS length (`"
|
|
108
|
+
| `height` | `720` | The builder's height: a number (px) or any CSS length (`"100%"` in a sized box, such as the kit's `AppLayout contentMode="fill"`; never a `vh` length, which `npm run check` refuses). It fills its width. |
|
|
109
109
|
| `className` | — | Added to the frame. |
|
|
110
110
|
|
|
111
111
|
### The handle
|
|
@@ -208,31 +208,38 @@ the helpers the panel uses.
|
|
|
208
208
|
## Example
|
|
209
209
|
|
|
210
210
|
```jsx
|
|
211
|
+
// surfaces/landing.jsx — client: edit one page
|
|
211
212
|
"use client";
|
|
212
|
-
import {
|
|
213
|
+
import { useSearchParams } from "next/navigation";
|
|
214
|
+
import { useRow } from "../lib/data";
|
|
215
|
+
import { updateRow } from "../lib/actions";
|
|
213
216
|
import { PageBuilder, PageView } from "../components/blocks/page-builder";
|
|
214
|
-
import { saveRow } from "../lib/db";
|
|
215
217
|
|
|
216
|
-
export function LandingEditor(
|
|
218
|
+
export default function LandingEditor() {
|
|
219
|
+
const id = useSearchParams().get("row");
|
|
220
|
+
const { row, loading } = useRow({ source: "pages", id });
|
|
221
|
+
if (loading || !row) return null;
|
|
217
222
|
return (
|
|
218
223
|
<PageBuilder
|
|
219
224
|
key={row.id}
|
|
220
225
|
title={row.title}
|
|
221
226
|
page={row.project ? { project: row.project } : { html: row.html, css: row.css }}
|
|
222
227
|
onSave={async ({ html, css, project }) => {
|
|
223
|
-
const res = await
|
|
228
|
+
const res = await updateRow({ source: "pages", id: row.id, patch: { html, css, project } });
|
|
224
229
|
return res.ok ? { ok: true } : { ok: false, error: "The page was not saved. Check your connection and try again." };
|
|
225
230
|
}}
|
|
226
231
|
/>
|
|
227
232
|
);
|
|
228
233
|
}
|
|
229
234
|
|
|
230
|
-
//
|
|
235
|
+
// The finished page, for a surface of its own that reads the row with the same useRow:
|
|
231
236
|
export function LandingPage({ row }) {
|
|
232
237
|
return <PageView page={row} label={row.title} />;
|
|
233
238
|
}
|
|
234
239
|
```
|
|
235
240
|
|
|
241
|
+
In a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.
|
|
242
|
+
|
|
236
243
|
## What it will not do
|
|
237
244
|
|
|
238
245
|
- No live multiplayer. Two people editing the same page is last save wins.
|
|
@@ -78,7 +78,7 @@ export default function PageBuilderDemo() {
|
|
|
78
78
|
<Landing />
|
|
79
79
|
</TabPanel>
|
|
80
80
|
<TabPanel tab="fresh" value={tab}>
|
|
81
|
-
<PageBuilder title="New page" height=
|
|
81
|
+
<PageBuilder title="New page" height={720} />
|
|
82
82
|
</TabPanel>
|
|
83
83
|
<TabPanel tab="own" value={tab}>
|
|
84
84
|
<PageBuilder
|
|
@@ -86,7 +86,7 @@ export default function PageBuilderDemo() {
|
|
|
86
86
|
builtIns="basics"
|
|
87
87
|
blocks={OWN_BLOCKS}
|
|
88
88
|
templates={OWN_TEMPLATES}
|
|
89
|
-
height=
|
|
89
|
+
height={720}
|
|
90
90
|
onSave={async () => {
|
|
91
91
|
await new Promise((r) => setTimeout(r, 400));
|
|
92
92
|
toast.success("Page saved");
|
|
@@ -122,7 +122,7 @@ function Landing() {
|
|
|
122
122
|
page={{ html: LAUNCH.html }}
|
|
123
123
|
onSave={onSave}
|
|
124
124
|
builderRef={builder}
|
|
125
|
-
height=
|
|
125
|
+
height={760}
|
|
126
126
|
onUploadImage={async (file) => {
|
|
127
127
|
await new Promise((r) => setTimeout(r, 500));
|
|
128
128
|
// In an app: upload to storage and return its public address.
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// GrapesJS runs with <Canvas> from @grapesjs/react, so it creates none of
|
|
13
13
|
// its own panels; theme.js repaints the few things it still draws.
|
|
14
14
|
//
|
|
15
|
-
// Client only, one
|
|
15
|
+
// Client only, one bundle: the modules load with the page (they are
|
|
16
16
|
// safe on the server — see grapes.js); the editor is created in an effect
|
|
17
17
|
// after the mount guard. Until then — on the server and in the first client
|
|
18
18
|
// paint — the same frame shows with a skeleton.
|