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.
Files changed (85) hide show
  1. package/README.md +8 -1
  2. package/package.json +1 -1
  3. package/r/annotate.json +4 -4
  4. package/r/approval-request.json +2 -2
  5. package/r/audio-player.json +2 -2
  6. package/r/auth-form.json +2 -2
  7. package/r/board.json +2 -2
  8. package/r/calendar-view.json +3 -3
  9. package/r/cart.json +2 -2
  10. package/r/chart.json +5 -5
  11. package/r/chat.json +3 -3
  12. package/r/code-editor.json +2 -2
  13. package/r/comment-box.json +3 -3
  14. package/r/contact-section.json +2 -2
  15. package/r/core.json +7 -1
  16. package/r/csv-import.json +3 -3
  17. package/r/data-table.json +2 -2
  18. package/r/diagram.json +2 -2
  19. package/r/doc-editor.json +2 -2
  20. package/r/doc-page.json +3 -3
  21. package/r/email-render.json +2 -2
  22. package/r/faq.json +2 -2
  23. package/r/file-upload.json +2 -2
  24. package/r/flow-canvas.json +3 -3
  25. package/r/footer.json +3 -3
  26. package/r/gantt.json +3 -3
  27. package/r/job-panel.json +2 -2
  28. package/r/json-editor.json +3 -3
  29. package/r/map.json +2 -2
  30. package/r/menubar.json +2 -2
  31. package/r/mind-map.json +4 -4
  32. package/r/navigation-menu.json +2 -2
  33. package/r/order-detail.json +2 -2
  34. package/r/org-chart.json +4 -4
  35. package/r/page-builder.json +3 -3
  36. package/r/payment-methods.json +2 -2
  37. package/r/pdf-document.json +2 -2
  38. package/r/pdf-viewer.json +3 -3
  39. package/r/product-detail.json +2 -2
  40. package/r/product-grid.json +3 -3
  41. package/r/record-form.json +2 -2
  42. package/r/record-notes.json +4 -4
  43. package/r/registry.json +126 -121
  44. package/r/search-dialog.json +2 -2
  45. package/r/settings-list.json +2 -2
  46. package/r/setup-checklist.json +2 -2
  47. package/r/share-dialog.json +2 -2
  48. package/r/sidebar.json +3 -3
  49. package/r/signature-pad.json +2 -2
  50. package/r/sketch-canvas.json +3 -3
  51. package/r/spreadsheet.json +2 -2
  52. package/r/stat-cards.json +3 -3
  53. package/r/task-list.json +3 -3
  54. package/r/task-model.json +2 -2
  55. package/r/testimonials.json +2 -2
  56. package/r/transaction-list.json +2 -2
  57. package/r/whiteboard.json +4 -4
  58. package/registry/blocks/annotate/README.md +14 -11
  59. package/registry/blocks/chart/cartesian.jsx +1 -1
  60. package/registry/blocks/chart/core.jsx +33 -23
  61. package/registry/blocks/chart/pie.jsx +1 -1
  62. package/registry/blocks/chat/README.md +4 -1
  63. package/registry/blocks/chat/demo.jsx +2 -2
  64. package/registry/blocks/flow-canvas/README.md +15 -8
  65. package/registry/blocks/footer/README.md +3 -2
  66. package/registry/blocks/mind-map/README.md +4 -2
  67. package/registry/blocks/org-chart/README.md +4 -2
  68. package/registry/blocks/page-builder/README.md +1 -1
  69. package/registry/blocks/page-builder/demo.jsx +3 -3
  70. package/registry/blocks/pdf-viewer/README.md +4 -1
  71. package/registry/blocks/sidebar/README.md +23 -13
  72. package/registry/blocks/sketch-canvas/README.md +15 -9
  73. package/registry/blocks/whiteboard/README.md +20 -14
  74. package/src/cli.mjs +2 -2
  75. package/src/install.mjs +23 -6
  76. package/src/registry.mjs +34 -3
  77. package/templates/next/files/README.md +291 -11
  78. package/templates/next/files/app/shell.jsx +54 -37
  79. package/templates/next/files/check/run.mjs +76 -4
  80. package/templates/next/files/components/ui/index.js +1 -0
  81. package/templates/next/files/components/ui/layout.jsx +307 -0
  82. package/templates/next/files/lib/claw.js +4 -1
  83. package/templates/next/files/lib/hooks.js +71 -2
  84. package/templates/next/files/lib/platform.js +93 -4
  85. package/templates/next/template.json +10 -1
@@ -5,7 +5,7 @@
5
5
  "title": "SearchDialog",
6
6
  "description": "One ⌘K search over every kind of record — chats, documents, images, projects, people. Before typing it shows your sections (Last opened, Recent); while typing, ranked matches with the typed text marked, a date, image thumbnails, and kind chips (All, Chats, Images…) to narrow them. Searches your list in the browser, or your server through an async onSearch. Built on the kit's Command; SearchPanel is the same box inline.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -44,7 +44,7 @@
44
44
  "docs": "Installed to components/makersclaw/blocks/search-dialog. Import: import { SearchDialog, SearchPanel } from \"@/components/makersclaw/blocks/search-dialog\". Props and an example: components/makersclaw/blocks/search-dialog/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
45
45
  "meta": {
46
46
  "makersclaw": {
47
- "version": "0.7.0",
47
+ "version": "0.8.0",
48
48
  "useWhen": "An app has more than one kind of record and wants one global search box or ⌘K palette to find any of them, or a search page with type filters."
49
49
  }
50
50
  }
@@ -5,7 +5,7 @@
5
5
  "title": "SettingsList",
6
6
  "description": "A card of settings, one row each — icon, label, description, meta lines, a badge, and its control: a Switch, a Select, a Button, a remove button or a menu of actions, with optional details that open under the row. Every change saves on its own through an async callback; the row shows the new value at once, a spinner while it saves, and on failure the old value back with the reason and Try again. variant=\"matrix\" is the notification grid: events by group down the side, channels (email, push, SMS) across, a switch per cell, stacked on a phone.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -59,7 +59,7 @@
59
59
  "docs": "Installed to components/makersclaw/blocks/settings-list. Import: import { SettingsList } from \"@/components/makersclaw/blocks/settings-list\". Props and an example: components/makersclaw/blocks/settings-list/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
60
60
  "meta": {
61
61
  "makersclaw": {
62
- "version": "0.7.0",
62
+ "version": "0.8.0",
63
63
  "useWhen": "An account or workspace settings screen where each setting applies the moment it is changed: preferences, two-step sign-in methods, signed-in devices, connected integrations, billing profiles, notification preferences per channel. Not for a form of fields saved together with one button (record-form)."
64
64
  }
65
65
  }
@@ -5,7 +5,7 @@
5
5
  "title": "SetupChecklist",
6
6
  "description": "The steps to get set up: which are done, how far along the whole is (a ring, a bar, or \"Step 2 of 4\"), and the button for the next one, sent out through an async onAction that can fail on the step. Four shapes: folding rows in a card with a dismiss menu, numbered step cards, a plain step list with a help panel, and bordered accordion rows that can be locked until the step before is done.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -44,7 +44,7 @@
44
44
  "docs": "Installed to components/makersclaw/blocks/setup-checklist. Import: import { SetupChecklist } from \"@/components/makersclaw/blocks/setup-checklist\". Props and an example: components/makersclaw/blocks/setup-checklist/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
45
45
  "meta": {
46
46
  "makersclaw": {
47
- "version": "0.7.0",
47
+ "version": "0.8.0",
48
48
  "useWhen": "A new account, workspace or shop has a few things to do before it is useful (complete the profile, invite the team, connect a tool, launch) and the founder should see what is left. Not for a running job's stages (kit Steps or job-panel), and not for a multi-page form wizard."
49
49
  }
50
50
  }
@@ -5,7 +5,7 @@
5
5
  "title": "ShareDialog",
6
6
  "description": "Who can see a record and how to let more people in: invite by email (comma separated, with an access level), each person with their access in a menu (can view, can edit, remove), sharing switches, and a link with a copy button that turns to a check. A dialog, or the same body inline (SharePanel). Every change is an async callback; a failure shows and the old value comes back.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -45,7 +45,7 @@
45
45
  "docs": "Installed to components/makersclaw/blocks/share-dialog. Import: import { ShareDialog, SharePanel } from \"@/components/makersclaw/blocks/share-dialog\". Props and an example: components/makersclaw/blocks/share-dialog/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
46
46
  "meta": {
47
47
  "makersclaw": {
48
- "version": "0.7.0",
48
+ "version": "0.8.0",
49
49
  "useWhen": "A record, project, document or workspace can be shared: an \"Invite\" or \"Share\" button, a members list with roles, a public link to copy."
50
50
  }
51
51
  }
package/r/sidebar.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "title": "Sidebar",
6
6
  "description": "An app's navigation down the left edge: app name or workspace switcher, groups of items with icons, counts and nested items, the signed-in person with their menu; collapses to icons (remembered) and becomes a Sheet behind a trigger on a phone. Optional: inset or floating look, a ⌘K search over every item, folding groups with colour dots, a second column (a mail list, or a parent's children to drill into), a \"New\" button and a \"…\" menu per item (a chat history).",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -31,7 +31,7 @@
31
31
  "path": "registry/blocks/sidebar/README.md",
32
32
  "type": "registry:file",
33
33
  "target": "@components/makersclaw/blocks/sidebar/README.md",
34
- "content": "# Sidebar\n\nAn app's navigation down the left edge: the app's name (or a workspace\nswitcher), groups of items with icons, counts and nested items, a slot of\nyour own, and the signed-in person at the foot with their menu. On a wide\nwindow it collapses to icons — every label stays, as a tooltip — and\nremembers that. On a phone it is not a column at all: it is a Sheet that\n`SidebarTrigger` opens (from the leading edge; a bottom sheet under\n640px), and it closes when an item is chosen.\n\n```jsx\nimport { Sidebar, SidebarLayout, SidebarTrigger } from \"../components/blocks/sidebar\"; // a Makersclaw app (\"./blocks/sidebar\" from components/)\nimport { Sidebar, SidebarLayout, SidebarTrigger } from \"@/components/makersclaw/blocks/sidebar\"; // a shadcn project\n```\n\nIt is a client component. In a Makersclaw app it belongs in\n`app/shell.jsx`, around the surfaces — the shell's nav is what it replaces.\n\n## The data\n\n`groups` is a list of `{ label?, items }`. An item:\n\n- `label` — what it says (required).\n- `href` — where it goes; rendered as a link (base path applied). Or\n- `onSelect(item)` — what choosing it does. Both may be given.\n- `icon` — a kit icon name. Needed for the icon rail (a missing one shows\n `file`).\n- `active` — true on the current page: `aria-current=\"page\"` and the\n highlighted look. The parent of an active item is open to begin with.\n- `badge` — a count or a word, shown on the right; a dot in the rail.\n- `items` — nested items (same shape, one level): the item becomes a\n collapsible row. In the rail its icon widens the sidebar with it open.\n With `drilldown` it opens them instead (see Sidebar).\n- `hue` — 1..5: a colour dot in place of the icon (a favourite, a label).\n- `description` — a muted second line (shown on children in drill-in).\n- `actions` — kit `Menu` items: a \"…\" at the row's end, shown on hover\n or focus (always on a touch screen). Each `onSelect(item)` may be async\n and return `{ ok, error? }`; `{ ok: false }` or a throw shows an error\n toast. Not drawn in the icon rail.\n- `keywords` — extra words the ⌘K search matches, never shown.\n- `disabled` · `key` (default `href`, then `label`).\n\nA group is `{ label?, items, collapsible?, defaultOpen?, key? }`.\n`collapsible: true` makes its heading fold it; `defaultOpen` (default\ntrue) is where it starts. The icon rail shows every group unfolded.\n\n## Props\n\n### SidebarLayout\n\n- `sidebar` — the `<Sidebar>` element.\n- `children` — the page beside it. Put `<SidebarTrigger />` in its header.\n- `storageKey` — remember collapsed/expanded in this browser under this\n key. Optional; without it every visit starts at `defaultCollapsed`.\n- `defaultCollapsed` — default `false`.\n- `collapsed` + `onCollapsedChange(next)` — control it yourself instead.\n- `shortcut` — Cmd/Ctrl + this key toggles it; default `\"b\"`, `null` for none.\n- `variant` — `\"inset\"` draws the page as a card on the frame's ground.\n Default: the Sidebar's own `variant`, so you rarely pass it here.\n- `className` — the frame's height and edges (`min-h-dvh` in a shell).\n Never colour.\n\n### Sidebar\n\n- `groups` — see The data. `items` is the one-group shorthand.\n- `title` — the app's or workspace's name, at the top and on the sheet.\n- `subtitle` — a muted line under it (a plan, a team).\n- `logo` — a node beside the title; default a square `Avatar` of `title`.\n- `switcher` — kit `Menu` items (`[{ label, onSelect, icon? }]`): the title\n becomes a menu for changing workspace. `switcherLabel` names it\n (default \"Switch workspace\"); a reader hears \"<title>, <switcherLabel>\",\n so a voice user who says the title still hits it.\n- `user` — `{ name, email?, src? }`, shown at the foot with an `Avatar`.\n- `userMenu` — kit `Menu` items (`[{ label, onSelect, icon?, tone?: \"danger\",\n disabled? }]`, and groups and separators): the person becomes a menu\n button opening these upward (beside the rail when it is icons), flipping\n where there is no room. A choice also closes the phone sheet.\n- `children` — your own content under the groups (a target, a promo, a\n help link). Hidden in the icon rail.\n- `label` — the nav landmark's name, default \"Main\".\n- `className` — layout only. Never colour.\n\nShapes and extras (all optional; leave them out and it is the column\nabove):\n\n- `variant` — `\"sidebar\"` (default: a bordered column) · `\"inset\"` (no\n border; the page beside it becomes a rounded card) · `\"floating\"` (a\n rounded card held off the window's edge). A phone is a sheet either way.\n- `headerActions` — a node at the top right: a notifications `Menu`, a\n button. Hidden in the icon rail; first row of the phone sheet.\n- `switcherPlacement` — `\"header\"` (default) · `\"footer\"`: the switcher\n moves to the foot, over the person, and the top keeps only `logo`.\n- `search` — `true` or `{ placeholder?, hotkey?, label?, empty?,\n groupLabel? }`: a search row under the top that opens the kit\n `CommandDialog` over every item (children listed under their parent's\n name). `hotkey` defaults to `\"k\"` (⌘K / Ctrl+K), `null` for none. A\n palette choice runs the item's `onSelect`; an item with only `href`\n is followed as a page load (the app's base path applied). Give items\n `onSelect` with `router.push` for a client-side jump.\n- `primaryAction` — `{ label, icon?, onSelect?, href? }`: one button above\n the groups (\"New chat\"). `onSelect` may return `{ ok: false, error }`,\n shown as a toast.\n- `footerActions` — kit `Menu` items behind a \"+\" beside the person (New\n deal, Invite). `footerActionsLabel` names it, default \"Create\". Same\n `{ ok }` rule as `actions`.\n- `drilldown` — `\"inline\"` (default: a parent folds open) · `\"panel\"`\n (its children open in a second column with a close button; Esc closes\n it; the icon rail stays narrow beside it) · `\"replace\"` (its children\n replace the list, under a Back row). The parent holding the active\n item starts open. On a phone both are the replace form.\n- `panel` — a second column beside the nav from 768px, 320px wide: your\n own node, or `{ title?, actions?, search?, items, loading?, error?,\n onRetry?, empty?, onSelect?, query?, onQueryChange?, filter? }`.\n - `items`: `[{ key?, title, subtitle?, description?, meta?, icon?,\n media?, active?, href?, onSelect?, disabled? }]` — a mail is sender\n (title), date (meta), subject (subtitle), teaser (description).\n - `search`: `true` or `{ placeholder }`: a field that filters the items\n here; `filter: false` when you filter them yourself from\n `onQueryChange(query)`.\n - `loading` draws skeleton rows, `error` (text) a notice with Try\n again (`onRetry`), and no items the `empty` line.\n - On a phone it is the sheet's second screen: choosing a nav item (or\n the panel's own row at the top) shows it, with a Back row.\n\n### SidebarTrigger\n\nThe header button: opens the sheet on a phone, collapses to icons from\n768px. `className` for placement.\n\n### useSidebar()\n\n`{ collapsed, setCollapsed, toggle, mobileOpen, setMobileOpen }` inside a\n`SidebarLayout`, for a control of your own; `null` outside one.\n\n## Example\n\n```jsx\n// app/shell.jsx — the app's shell, the surfaces as the sidebar's items\n\"use client\";\nimport { usePathname } from \"next/navigation\";\nimport { Sidebar, SidebarLayout, SidebarTrigger } from \"../components/blocks/sidebar\";\n\nconst ICONS = { inbox: \"inbox\", deals: \"dollar\", contacts: \"users\", reports: \"chart\" };\n\nexport default function Shell({ name, surfaces, children }) {\n const pathname = usePathname() ?? \"\";\n const activeKey = decodeURIComponent(/^\\/s\\/([^/?#]+)/.exec(pathname)?.[1] ?? \"\");\n const items = surfaces.map((s) => ({\n key: s.key,\n label: s.label,\n href: `/s/${encodeURIComponent(s.key)}`,\n icon: ICONS[s.key] ?? \"file\",\n active: s.key === activeKey,\n badge: s.built ? undefined : \"planned\",\n }));\n return (\n <SidebarLayout\n storageKey={`${name}.sidebar`}\n className=\"min-h-dvh\"\n sidebar={<Sidebar title={name} items={items} label={`${name} surfaces`} />}\n >\n <header className=\"flex min-h-14 items-center gap-2 border-b border-border px-2 md:hidden\">\n <SidebarTrigger />\n <span className=\"font-semibold\">{name}</span>\n </header>\n <main className=\"min-w-0 flex-1 p-4 sm:p-6\">{children}</main>\n </SidebarLayout>\n );\n}\n```\n\n## What it will not do\n\n- Know the route: say which item is `active` (from `usePathname()`).\n- Nest deeper than one level: a second level is a page of its own.\n- Cover the page on a wide window or dock on the right: floating is a\n card beside the page, not over it; it is on the left from 768px and a\n sheet below that.\n- Hold the panel's data: the list, its loading and its errors are yours.\n- Take a colour: it wears the workspace theme.\n\nA sidebar it cannot be is your own component in `components/`, built from\nthe kit — not an edit to this block. Its files are fixed: `npm run check`\nrefuses a change, and `makersclaw add sidebar` restores them.\n\n## Credits\n\nThe inset, floating, search, folding-group, panel, drill-in and item-menu\nshapes follow blocks.so (https://blocks.so, MIT) — a rewrite on the\nMakersclaw kit, none of its code.\n"
34
+ "content": "# Sidebar\n\nAn app's navigation down the left edge: the app's name (or a workspace\nswitcher), groups of items with icons, counts and nested items, a slot of\nyour own, and the signed-in person at the foot with their menu. On a wide\nwindow it collapses to icons — every label stays, as a tooltip — and\nremembers that. On a phone it is not a column at all: it is a Sheet that\n`SidebarTrigger` opens (from the leading edge; a bottom sheet under\n640px), and it closes when an item is chosen.\n\n```jsx\nimport { Sidebar, SidebarLayout, SidebarTrigger } from \"../components/blocks/sidebar\"; // a Makersclaw app (\"./blocks/sidebar\" from components/)\nimport { Sidebar, SidebarLayout, SidebarTrigger } from \"@/components/makersclaw/blocks/sidebar\"; // a shadcn project\n```\n\nIt is a client component. In a Makersclaw app it belongs in\n`app/shell.jsx`, around the surfaces — the shell's nav is what it replaces.\n\n## The data\n\n`groups` is a list of `{ label?, items }`. An item:\n\n- `label` — what it says (required).\n- `href` — where it goes; rendered as a link (base path applied). Or\n- `onSelect(item)` — what choosing it does. Both may be given.\n- `icon` — a kit icon name. Needed for the icon rail (a missing one shows\n `file`).\n- `active` — true on the current page: `aria-current=\"page\"` and the\n highlighted look. The parent of an active item is open to begin with.\n- `badge` — a count or a word, shown on the right; a dot in the rail.\n- `items` — nested items (same shape, one level): the item becomes a\n collapsible row. In the rail its icon widens the sidebar with it open.\n With `drilldown` it opens them instead (see Sidebar).\n- `hue` — 1..5: a colour dot in place of the icon (a favourite, a label).\n- `description` — a muted second line (shown on children in drill-in).\n- `actions` — kit `Menu` items: a \"…\" at the row's end, shown on hover\n or focus (always on a touch screen). Each `onSelect(item)` may be async\n and return `{ ok, error? }`; `{ ok: false }` or a throw shows an error\n toast. Not drawn in the icon rail.\n- `keywords` — extra words the ⌘K search matches, never shown.\n- `disabled` · `key` (default `href`, then `label`).\n\nA group is `{ label?, items, collapsible?, defaultOpen?, key? }`.\n`collapsible: true` makes its heading fold it; `defaultOpen` (default\ntrue) is where it starts. The icon rail shows every group unfolded.\n\n## Props\n\n### SidebarLayout\n\n- `sidebar` — the `<Sidebar>` element.\n- `children` — the page beside it. Put `<SidebarTrigger />` in its header.\n- `storageKey` — remember collapsed/expanded in this browser under this\n key. Optional; without it every visit starts at `defaultCollapsed`.\n- `defaultCollapsed` — default `false`.\n- `collapsed` + `onCollapsedChange(next)` — control it yourself instead.\n- `shortcut` — Cmd/Ctrl + this key toggles it; default `\"b\"`, `null` for none.\n- `variant` — `\"inset\"` draws the page as a card on the frame's ground.\n Default: the Sidebar's own `variant`, so you rarely pass it here.\n- `className` — the frame's edges, and its height from its parent: in a\n shell it sits in the kit's `AppLayout contentMode=\"fill\"` with\n `min-h-0 flex-1` (the Example). The kit's `AppLayout` owns the\n window's height; `min-h-dvh` or `h-dvh` here is refused by `npm run\n check`. Never colour.\n\n### Sidebar\n\n- `groups` — see The data. `items` is the one-group shorthand.\n- `title` — the app's or workspace's name, at the top and on the sheet.\n- `subtitle` — a muted line under it (a plan, a team).\n- `logo` — a node beside the title; default a square `Avatar` of `title`.\n- `switcher` — kit `Menu` items (`[{ label, onSelect, icon? }]`): the title\n becomes a menu for changing workspace. `switcherLabel` names it\n (default \"Switch workspace\"); a reader hears \"<title>, <switcherLabel>\",\n so a voice user who says the title still hits it.\n- `user` — `{ name, email?, src? }`, shown at the foot with an `Avatar`.\n- `userMenu` — kit `Menu` items (`[{ label, onSelect, icon?, tone?: \"danger\",\n disabled? }]`, and groups and separators): the person becomes a menu\n button opening these upward (beside the rail when it is icons), flipping\n where there is no room. A choice also closes the phone sheet.\n- `children` — your own content under the groups (a target, a promo, a\n help link). Hidden in the icon rail.\n- `label` — the nav landmark's name, default \"Main\".\n- `className` — layout only. Never colour.\n\nShapes and extras (all optional; leave them out and it is the column\nabove):\n\n- `variant` — `\"sidebar\"` (default: a bordered column) · `\"inset\"` (no\n border; the page beside it becomes a rounded card) · `\"floating\"` (a\n rounded card held off the window's edge). A phone is a sheet either way.\n- `headerActions` — a node at the top right: a notifications `Menu`, a\n button. Hidden in the icon rail; first row of the phone sheet.\n- `switcherPlacement` — `\"header\"` (default) · `\"footer\"`: the switcher\n moves to the foot, over the person, and the top keeps only `logo`.\n- `search` — `true` or `{ placeholder?, hotkey?, label?, empty?,\n groupLabel? }`: a search row under the top that opens the kit\n `CommandDialog` over every item (children listed under their parent's\n name). `hotkey` defaults to `\"k\"` (⌘K / Ctrl+K), `null` for none. A\n palette choice runs the item's `onSelect`; an item with only `href`\n is followed as a page load (the app's base path applied). Give items\n `onSelect` with `router.push` for a client-side jump.\n- `primaryAction` — `{ label, icon?, onSelect?, href? }`: one button above\n the groups (\"New chat\"). `onSelect` may return `{ ok: false, error }`,\n shown as a toast.\n- `footerActions` — kit `Menu` items behind a \"+\" beside the person (New\n deal, Invite). `footerActionsLabel` names it, default \"Create\". Same\n `{ ok }` rule as `actions`.\n- `drilldown` — `\"inline\"` (default: a parent folds open) · `\"panel\"`\n (its children open in a second column with a close button; Esc closes\n it; the icon rail stays narrow beside it) · `\"replace\"` (its children\n replace the list, under a Back row). The parent holding the active\n item starts open. On a phone both are the replace form.\n- `panel` — a second column beside the nav from 768px, 320px wide: your\n own node, or `{ title?, actions?, search?, items, loading?, error?,\n onRetry?, empty?, onSelect?, query?, onQueryChange?, filter? }`.\n - `items`: `[{ key?, title, subtitle?, description?, meta?, icon?,\n media?, active?, href?, onSelect?, disabled? }]` — a mail is sender\n (title), date (meta), subject (subtitle), teaser (description).\n - `search`: `true` or `{ placeholder }`: a field that filters the items\n here; `filter: false` when you filter them yourself from\n `onQueryChange(query)`.\n - `loading` draws skeleton rows, `error` (text) a notice with Try\n again (`onRetry`), and no items the `empty` line.\n - On a phone it is the sheet's second screen: choosing a nav item (or\n the panel's own row at the top) shows it, with a Back row.\n\n### SidebarTrigger\n\nThe header button: opens the sheet on a phone, collapses to icons from\n768px. `className` for placement.\n\n### useSidebar()\n\n`{ collapsed, setCollapsed, toggle, mobileOpen, setMobileOpen }` inside a\n`SidebarLayout`, for a control of your own; `null` outside one.\n\n## Example\n\n```jsx\n// app/shell.jsx — the app's shell, the surfaces as the sidebar's items\n\"use client\";\nimport { usePathname } from \"next/navigation\";\nimport { AppLayout } from \"../components/ui\";\nimport { Sidebar, SidebarLayout, SidebarTrigger } from \"../components/blocks/sidebar\";\n\nconst ICONS = { inbox: \"inbox\", deals: \"dollar\", contacts: \"users\", reports: \"chart\" };\n\nexport default function Shell({ name, surfaces, children }) {\n const pathname = usePathname() ?? \"\";\n const activeKey = decodeURIComponent(/^\\/s\\/([^/?#]+)/.exec(pathname)?.[1] ?? \"\");\n const items = surfaces.map((s) => ({\n key: s.key,\n label: s.label,\n href: `/s/${encodeURIComponent(s.key)}`,\n icon: ICONS[s.key] ?? \"file\",\n active: s.key === activeKey,\n badge: s.built ? undefined : \"planned\",\n }));\n // The outer AppLayout owns the window; the inner one (it fills its\n // slot) is the content that scrolls, back to its top on a new route.\n return (\n <AppLayout contentMode=\"fill\">\n <SidebarLayout\n storageKey={`${name}.sidebar`}\n className=\"min-h-0 flex-1\"\n sidebar={<Sidebar title={name} items={items} label={`${name} surfaces`} />}\n >\n <header className=\"flex min-h-14 items-center gap-2 border-b border-border px-2 md:hidden\">\n <SidebarTrigger />\n <span className=\"font-semibold\">{name}</span>\n </header>\n <AppLayout contentClassName=\"p-4 sm:p-6\" scrollKey={pathname}>\n {children}\n </AppLayout>\n </SidebarLayout>\n </AppLayout>\n );\n}\n```\n\n## What it will not do\n\n- Know the route: say which item is `active` (from `usePathname()`).\n- Nest deeper than one level: a second level is a page of its own.\n- Cover the page on a wide window or dock on the right: floating is a\n card beside the page, not over it; it is on the left from 768px and a\n sheet below that.\n- Hold the panel's data: the list, its loading and its errors are yours.\n- Take a colour: it wears the workspace theme.\n\nA sidebar it cannot be is your own component in `components/`, built from\nthe kit — not an edit to this block. Its files are fixed: `npm run check`\nrefuses a change, and `makersclaw add sidebar` restores them.\n\n## Credits\n\nThe inset, floating, search, folding-group, panel, drill-in and item-menu\nshapes follow blocks.so (https://blocks.so, MIT) — a rewrite on the\nMakersclaw kit, none of its code.\n"
35
35
  }
36
36
  ],
37
37
  "categories": [
@@ -62,7 +62,7 @@
62
62
  "docs": "Installed to components/makersclaw/blocks/sidebar. Import: import { Sidebar, SidebarLayout, SidebarTrigger, useSidebar } from \"@/components/makersclaw/blocks/sidebar\". Props and an example: components/makersclaw/blocks/sidebar/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
63
63
  "meta": {
64
64
  "makersclaw": {
65
- "version": "0.7.0",
65
+ "version": "0.8.0",
66
66
  "useWhen": "An app with more than a handful of places to go (inbox, deals, contacts, reports, settings, projects) that wants a real app shell: compose it in app/shell.jsx around the surfaces. Not for in-page tabs (Tabs) or a short strip of links."
67
67
  }
68
68
  }
@@ -5,7 +5,7 @@
5
5
  "title": "SignaturePad",
6
6
  "description": "A signature field (signature_pad) in the workspace theme: a full-name field, then draw on a paper box (smooth pen strokes, undo the last stroke, clear with Undo) or type the name set as a signature. Sign returns a record: a cropped transparent PNG in dark ink, the typed name, the mode and the time; the signed view shows it with Sign again. Works as a form field too, through a handle (toPNG, getSignature).",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [
11
11
  "signature_pad@5.1.4"
@@ -65,7 +65,7 @@
65
65
  "docs": "Installed to components/makersclaw/blocks/signature-pad. Import: import { SignaturePad, useSignaturePad, signatureRecord, formatSignedAt } from \"@/components/makersclaw/blocks/signature-pad\". Props and an example: components/makersclaw/blocks/signature-pad/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
66
66
  "meta": {
67
67
  "makersclaw": {
68
- "version": "0.7.0",
68
+ "version": "0.8.0",
69
69
  "useWhen": "A screen needs someone to sign: accepting a proposal or a quote, a delivery or work-done sign-off, a waiver, a consent form, an approval on a record. The proposal-accept block is built on it. It records an acceptance with a picture of the signature — it is not a certified legal e-signature (no identity check, no audit trail beyond what you store)."
70
70
  }
71
71
  }
@@ -5,7 +5,7 @@
5
5
  "title": "SketchCanvas",
6
6
  "description": "A freeform drawing canvas (Excalidraw) in the workspace theme: a Miro-style tool rail (select, pan, draw, shapes, arrows, text, image, eraser, frames), a floating toolbar over the selection with colours from the theme's tokens, zoom and undo controls, a friendly empty state, and stamp tools that place ready-made drawings (UI wireframe pieces built in). Optional locked background image for annotating a screenshot. The sketch is plain JSON; helpers read its words for an agent, and the handle exports a PNG.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [
11
11
  "@excalidraw/excalidraw@0.18.1"
@@ -69,7 +69,7 @@
69
69
  "path": "registry/blocks/sketch-canvas/README.md",
70
70
  "type": "registry:file",
71
71
  "target": "@components/makersclaw/blocks/sketch-canvas/README.md",
72
- "content": "# SketchCanvas\n\nA freeform drawing canvas in the workspace theme, friendly like Miro. A\ntool rail on the left (a bar along the bottom on a phone): select, pan,\ndraw, rectangle, ellipse, diamond, arrow, line, text, image, eraser, frame\n— each with its one-key shortcut. Select something and a small toolbar\nfloats above it: colour and fill from the theme's palette, stroke, text\nsize, bring to front / send to back, duplicate, delete. Undo and redo sit\nbottom left, zoom and fit-to-screen bottom right, a menu top right\n(download PNG, snap to grid, shortcuts, clear). An empty canvas says how\nto start. Stamp tools place ready-made drawings with one click — the six\nUI wireframe pieces come built in.\n\nIt is [Excalidraw](https://github.com/excalidraw/excalidraw) (MIT)\nunderneath: its canvas, selection, text editing, history and shortcuts;\nour rail, toolbars, palette and empty state, in the kit. Excalidraw's own\ntool bar, properties panel, main menu, library, share / collaboration\nbuttons, welcome screen and sidebars are hidden. The blocks built on this\none — `whiteboard`, `annotate`, `sketch-to-app` — import it, never\n`@excalidraw/excalidraw`.\n\n```jsx\nimport { SketchCanvas, sketchToText } from \"../components/blocks/sketch-canvas\"; // a Makersclaw app (\"./blocks/sketch-canvas\" from components/)\nimport { SketchCanvas, sketchToText } from \"@/components/makersclaw/blocks/sketch-canvas\"; // a shadcn project\n```\n\n`SketchCanvas` is a client component: render it from a `\"use client\"`\ncomponent. The helpers (`sketchToText`, `sketchItems`, `emptySketch`,\n`isSketchEmpty`, `toSketch`) are plain functions and work on the server.\n\n`makersclaw add sketch-canvas` adds `@excalidraw/excalidraw` (pinned,\nexact) to package.json: run `npm install` after. It loads with the page\n(no `import()`); the canvas mounts after the first render, and until then\n— on the server — the frame shows \"Loading the canvas…\", with the\nsketch's words for a screen reader.\n\n## The data\n\nA sketch is plain JSON. Store it as it comes out of `onChange` / `onSave`\n(a `jsonb` column).\n\n```js\n{\n type: \"sketch\",\n version: 1,\n elements: [ /* Excalidraw elements, non-deleted, in paint order (first = back) */ ],\n files: { \"<fileId>\": { id, mimeType: \"image/png\", dataURL: \"data:image/png;base64,…\", created } },\n appState: { gridModeEnabled: false },\n}\n```\n\n- `elements` are Excalidraw's own element objects: `type` is `rectangle`,\n `ellipse`, `diamond`, `arrow`, `line`, `freedraw`, `text`, `image` or\n `frame`; each has `id`, `x`, `y`, `width`, `height`, `strokeColor`,\n `backgroundColor`, `strokeWidth`, `roughness`, `groupIds`, `frameId`…\n A `text` has `text`, `fontSize`, and `containerId` when it is the label\n of a shape; a shape lists its label and arrows in `boundElements`.\n- **Colours are tokens.** Every colour picked in the canvas is a theme\n token, and the element keeps it in `customData.tokens`:\n `{ strokeColor: \"primary\", backgroundColor: \"chart-2:soft\" }`. The\n concrete colour beside it (`strokeColor`) is what the token resolved to\n when it was saved; on load, and whenever the theme or light/dark\n changes, the canvas resolves every token again and repaints. A colour\n with no token (pasted from elsewhere) stays as it is.\n Tokens: `foreground`, `muted-foreground`, `primary`, `destructive`,\n `warning`, `success`, `info`, `chart-2` … `chart-5` (ink), and for\n fills also `none`, `muted` and `<slot>:soft` (the slot mixed into the\n page background — a sticky-note tint). `STROKES` and `FILLS` list them\n with labels.\n- **Stamps** mark what they place: every element of one stamp shares a\n group and carries `customData: { stamp: \"button\", stampId }`.\n- `files` holds the images the elements show (only those). Images are\n data URLs inside the JSON — keep them small, or store the file\n elsewhere and keep the sketch light.\n- The `background` image is never in the value; it comes from the prop.\n- Written by hand (a starter template, a seed), an element may leave out\n what Excalidraw can default (`version`, `seed`, `strokeWidth`,\n `roughness`…) and give its colours only as tokens:\n `{ id: \"a\", type: \"rectangle\", x: 0, y: 0, width: 160, height: 72, customData: { tokens: { strokeColor: \"foreground\", backgroundColor: \"chart-2:soft\" } } }`.\n A text needs `text`, `fontSize` and a rough `width`/`height` (it is\n re-measured). Or skip hand-written elements: insert skeletons through\n the handle (below).\n- `sketchToText(sketch)` reads the words out for an agent; see Helpers.\n\n## Props\n\n- `value` — the sketch, controlled. A new `value` that differs from what\n the canvas last sent replaces the drawing (loading another record) and\n clears undo. Pass back exactly what `onChange` gave you and nothing\n resets. Also takes a bare element array or Excalidraw's own export\n (`{ elements, files, appState }`).\n- `defaultValue` — the starting sketch, uncontrolled. With neither, it\n starts empty.\n- `onChange(sketch)` — the whole sketch, after an edit settles\n (debounced by `changeDelay`). Not called for pan, zoom or selection.\n- `onSave(sketch)` — `async`, returns `{ ok: true }` or\n `{ ok: false, error? }`. Called with the same sketch, right after\n `onChange`, and on Cmd/Ctrl+S. The canvas shows \"Saving…\", \"Saved\",\n or the error with a Retry. A throw counts as a failure.\n- `changeDelay` — ms of quiet before `onChange` / `onSave` (default 600).\n- `readOnly` — `false` (default). `true` shows the sketch to look at: pan\n and zoom, no rail, no toolbar, no edits.\n- `background` — `{ src, width?, height? }`: an image laid at the back,\n locked (it cannot be selected, moved, erased or deleted), top-left at\n (0, 0). For marking up a screenshot (annotate). `src` is a data URL, or\n a same-origin URL (a cross-origin one draws, but spoils `exportImage`).\n Without `width`/`height` the image's own size is used. When it first\n lands on an empty sketch, the view fits it.\n- `tools` — ids of the built-in tools to offer, in rail order:\n `[\"select\", \"hand\", \"draw\", \"rectangle\", \"ellipse\", \"diamond\", \"arrow\",\n \"line\", \"text\", \"image\", \"eraser\", \"frame\"]` (all, the default). A tool\n left out is also unreachable by its shortcut. `SKETCH_TOOLS` lists them\n with labels, icons and keys.\n- `stamps` — stamp tools after the built-ins:\n `[{ id, label, icon, create() }]`. `create()` returns element\n skeletons (below) laid out from (0, 0); a click on the canvas places\n them centred on the pointer (Enter: the middle of the view; Esc\n cancels), grouped, selected. `WIREFRAME_STAMPS` are button, input, card,\n nav bar, table and image placeholder.\n- `grid` — `true` (default): a dotted background that pans and zooms with\n the drawing. `false`: plain.\n- `empty` — what an empty, editable canvas shows in its middle. Omit for\n the built-in hint (\"Start sketching…\"); `false` for nothing; or a node\n (starter templates: give its buttons `pointer-events-auto`, the layer\n lets clicks through to the canvas). Shown while nothing is drawn and the\n select or pan tool is active.\n- `actions` — nodes in the top-right corner, before the menu (a Share\n button, a title chip).\n- `canvasRef` — a ref (`useSketchCanvas()`) that receives the handle.\n- `onReady(handle)` — once, when the canvas has mounted.\n- `label` — the accessible name of the canvas region (default \"Sketch\").\n- `className` — layout: width, margin, height. The default height is\n `h-[32rem]`; a height class of yours (`h-dvh`, `h-full`, `min-h-…`,\n `flex-1`) replaces it. Never colour.\n\n### The handle\n\n`canvasRef.current` (and `onReady`'s argument):\n\n- `getScene({ withBackground? })` — the sketch now (what `onChange`\n sends), without waiting for the debounce.\n- `setScene(sketch)` — replace the drawing (undoable).\n- `insertElements(skeletons, opts?)` → the new element ids. Skeletons are\n Excalidraw's `convertToExcalidrawElements` input:\n `{ type: \"rectangle\" | \"ellipse\" | \"diamond\", x, y, width, height, label?: { text, fontSize?, textAlign? } }`,\n `{ type: \"text\", x, y, text, fontSize? }`,\n `{ type: \"arrow\" | \"line\", x, y, points: [[0, 0], [dx, dy]], start?: { id }, end?: { id } }`,\n `{ type: \"frame\", children: [ids], name }`, `{ type: \"image\", fileId, x, y, width, height }`.\n Colours may be token names (`strokeColor: \"primary\"`,\n `backgroundColor: \"warning:soft\"`); a shape with no stroke gets\n `foreground`; text is the system font. Options:\n - `at` — `{ x, y }` in canvas coordinates; the skeletons' box is\n centred there (or its top-left, with `anchor: \"top-left\"`). Default:\n the middle of the view. `false`: keep the skeletons' own x/y.\n - `group` — group them (move as one). `stamp: \"<kind>\"` groups too and\n marks them for `sketchToText`.\n - `customData` — merged into every new element's customData (annotate:\n `{ note: 3 }`).\n - `files` — `[{ id, dataURL, mimeType, created }]` to add first, for\n image skeletons.\n - `select` — `true` (default): select what was inserted.\n- `exportImage(opts?)` → `Promise<Blob>`, a PNG made by Excalidraw's own\n export, in the theme's colours now, on the theme's background. Options:\n `scale` (default 2), `padding` (px, default 16), `transparent: true`\n (no background), `withBackground: false` (leave the background image\n out), `onlySelected: true`.\n- `zoomToFit({ animate? })` — fit everything in view (Shift+1).\n- `setTool(id)` — a built-in tool id or a stamp id.\n- `undo()`, `redo()`, `clear()`, `focus()`.\n- `save()` — send `onChange` / `onSave` now.\n- `palette()` — token → the concrete colour it resolves to now.\n- `repaint()` — re-read the theme and repaint (the canvas already does\n this when `data-theme` changes or a theme stylesheet loads).\n- `api` — Excalidraw's own imperative API, for anything else. Reaching for\n it ties you to Excalidraw 0.18; prefer the methods above.\n\n### Helpers\n\nPlain functions; they take a sketch (or an element array) and never throw\non a partial one:\n\n- `emptySketch()` — `{ type: \"sketch\", version: 1, elements: [], files: {}, appState: {} }`.\n- `isSketchEmpty(sketch)` — nothing drawn (deleted elements and the\n background image do not count).\n- `toSketch(value)` — any accepted input → a sketch.\n- `sketchToText(sketch, { stamps = true })` — the words, for an agent or a\n reader: one line per text or stamp in reading order (top to bottom,\n then left to right), grouped under `# <frame name>` per frame (then\n `# Outside frames`); a shape's label reads as its text; a stamp reads\n `[button] Sign up`, an image `[image]`. `stamps: false` drops the\n brackets. An empty sketch gives `\"\"`.\n- `sketchItems(sketch)` — the same, structured: `[{ kind: \"frame\" | \"stamp\" | \"text\" | \"image\", id, text?, name?, stamp?, container?, frame, x, y, width, height }]`.\n- `STROKES`, `FILLS` — `[{ token, label }]`, the palette the toolbar offers.\n- `SKETCH_TOOLS`, `WIREFRAME_STAMPS` — as above.\n\n## Theming, colours and fonts\n\n- **Chrome.** Excalidraw's own CSS variables (its menus, dialogs, context\n menu, hints) point at the workspace tokens (`theme.js`). Light or dark\n follows the nearest `data-theme` (else `color-scheme`, else the OS).\n- **Canvas colours.** Excalidraw needs concrete colours; they are read at\n run time from the tokens' CSS variables and never written in source.\n Excalidraw's dark mode (which inverts the whole canvas with a filter) is\n switched off: the tokens are already the dark scheme's.\n- **Fonts.** Every text is drawn in the device's own sans-serif\n (Excalidraw's \"Helvetica\" family — Helvetica, Arial…), so no font is ever\n fetched: Excalidraw's hand-drawn fonts would otherwise load from a\n third-party CDN. Text pasted from another Excalidraw is moved into it.\n The strokes keep their hand-drawn look (Sketchy / Clean in the toolbar).\n- **Stylesheet.** The block imports `@excalidraw/excalidraw/index.css`\n from the package; the app's bundler ships it like any CSS.\n\n## Example\n\nA whiteboard that saves itself:\n\n```jsx\n// surfaces/sketch.jsx — client: one board, saved as it is drawn\n\"use client\";\nimport { useSearchParams } from \"next/navigation\";\nimport { useRow } from \"../lib/data\";\nimport { updateRow } from \"../lib/actions\";\nimport { SketchCanvas } from \"../components/blocks/sketch-canvas\";\n\nexport default function Board() {\n const id = useSearchParams().get(\"row\");\n const { row: board, loading } = useRow({ source: \"boards\", id });\n if (loading || !board) return null;\n return (\n <SketchCanvas\n key={board.id}\n label={board.title}\n defaultValue={board.sketch} // jsonb, may be null; read once per board\n onSave={(sketch) => updateRow({ source: \"boards\", id: board.id, patch: { sketch } })}\n className=\"h-dvh rounded-none border-0\"\n />\n );\n}\n```\n\nIn a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.\n\nMarking up a screenshot, with numbered notes:\n\n```jsx\nconst canvas = useSketchCanvas();\nconst addNote = (n) =>\n canvas.current?.insertElements(\n [{ type: \"ellipse\", x: 0, y: 0, width: 32, height: 32, strokeColor: \"destructive\", backgroundColor: \"destructive:soft\", label: { text: String(n) } }],\n { group: true, customData: { note: n } },\n );\n\n<SketchCanvas canvasRef={canvas} background={{ src: screenshotDataUrl }} tools={[\"select\", \"hand\", \"draw\", \"rectangle\", \"arrow\", \"text\", \"eraser\"]} onChange={setSketch} />\nconst image = await canvas.current.exportImage(); // the screenshot with the marks, as a PNG\n```\n\nA wireframe for an agent:\n\n```jsx\nimport { SketchCanvas, WIREFRAME_STAMPS, sketchToText, useSketchCanvas } from \"./blocks/sketch-canvas\";\n\nconst canvas = useSketchCanvas();\n<SketchCanvas canvasRef={canvas} stamps={WIREFRAME_STAMPS} tools={[\"select\", \"hand\", \"draw\", \"rectangle\", \"arrow\", \"text\", \"eraser\", \"frame\"]} onChange={setSketch} />\n\nconst build = async () => onBuild({ image: await canvas.current.exportImage(), sketch, text: sketchToText(sketch), prompt });\n// sketchToText → \"# Login\\nWelcome back\\n[input] Email\\n[button] Sign in\"\n```\n\n## What it will not do\n\n- Collaborate live (several cursors): Excalidraw's collaboration is a\n server of its own, not wired here. Saves are last-write-wins.\n- Offer Excalidraw's library, share links, \"text to diagram\" / AI, or its\n hand-drawn fonts. Its command palette (Cmd+/) and help dialog (?) still\n open, themed; the palette may name actions that do nothing here.\n- Show a minimap.\n- Keep images out of the JSON: an image lives as a data URL in `files`.\n- Render on the server: the server shows the frame and the sketch's\n words; the drawing appears when the page runs.\n- Pick an arbitrary colour: only the theme's tokens.\n\nA canvas it cannot be is your own component in `components/` — not an edit\nto this block. Its files are fixed: `npm run check` refuses a change, and\n`makersclaw add sketch-canvas` restores them.\n\n## Credits\n\nExcalidraw (https://github.com/excalidraw/excalidraw), MIT — installed from\nnpm as a dependency, not copied here. See THIRD-PARTY-NOTICES.md.\n"
72
+ "content": "# SketchCanvas\n\nA freeform drawing canvas in the workspace theme, friendly like Miro. A\ntool rail on the left (a bar along the bottom on a phone): select, pan,\ndraw, rectangle, ellipse, diamond, arrow, line, text, image, eraser, frame\n— each with its one-key shortcut. Select something and a small toolbar\nfloats above it: colour and fill from the theme's palette, stroke, text\nsize, bring to front / send to back, duplicate, delete. Undo and redo sit\nbottom left, zoom and fit-to-screen bottom right, a menu top right\n(download PNG, snap to grid, shortcuts, clear). An empty canvas says how\nto start. Stamp tools place ready-made drawings with one click — the six\nUI wireframe pieces come built in.\n\nIt is [Excalidraw](https://github.com/excalidraw/excalidraw) (MIT)\nunderneath: its canvas, selection, text editing, history and shortcuts;\nour rail, toolbars, palette and empty state, in the kit. Excalidraw's own\ntool bar, properties panel, main menu, library, share / collaboration\nbuttons, welcome screen and sidebars are hidden. The blocks built on this\none — `whiteboard`, `annotate`, `sketch-to-app` — import it, never\n`@excalidraw/excalidraw`.\n\n```jsx\nimport { SketchCanvas, sketchToText } from \"../components/blocks/sketch-canvas\"; // a Makersclaw app (\"./blocks/sketch-canvas\" from components/)\nimport { SketchCanvas, sketchToText } from \"@/components/makersclaw/blocks/sketch-canvas\"; // a shadcn project\n```\n\n`SketchCanvas` is a client component: render it from a `\"use client\"`\ncomponent. The helpers (`sketchToText`, `sketchItems`, `emptySketch`,\n`isSketchEmpty`, `toSketch`) are plain functions and work on the server.\n\n`makersclaw add sketch-canvas` adds `@excalidraw/excalidraw` (pinned,\nexact) to package.json: run `npm install` after. It loads with the page\n(no `import()`); the canvas mounts after the first render, and until then\n— on the server — the frame shows \"Loading the canvas…\", with the\nsketch's words for a screen reader.\n\n## The data\n\nA sketch is plain JSON. Store it as it comes out of `onChange` / `onSave`\n(a `jsonb` column).\n\n```js\n{\n type: \"sketch\",\n version: 1,\n elements: [ /* Excalidraw elements, non-deleted, in paint order (first = back) */ ],\n files: { \"<fileId>\": { id, mimeType: \"image/png\", dataURL: \"data:image/png;base64,…\", created } },\n appState: { gridModeEnabled: false },\n}\n```\n\n- `elements` are Excalidraw's own element objects: `type` is `rectangle`,\n `ellipse`, `diamond`, `arrow`, `line`, `freedraw`, `text`, `image` or\n `frame`; each has `id`, `x`, `y`, `width`, `height`, `strokeColor`,\n `backgroundColor`, `strokeWidth`, `roughness`, `groupIds`, `frameId`…\n A `text` has `text`, `fontSize`, and `containerId` when it is the label\n of a shape; a shape lists its label and arrows in `boundElements`.\n- **Colours are tokens.** Every colour picked in the canvas is a theme\n token, and the element keeps it in `customData.tokens`:\n `{ strokeColor: \"primary\", backgroundColor: \"chart-2:soft\" }`. The\n concrete colour beside it (`strokeColor`) is what the token resolved to\n when it was saved; on load, and whenever the theme or light/dark\n changes, the canvas resolves every token again and repaints. A colour\n with no token (pasted from elsewhere) stays as it is.\n Tokens: `foreground`, `muted-foreground`, `primary`, `destructive`,\n `warning`, `success`, `info`, `chart-2` … `chart-5` (ink), and for\n fills also `none`, `muted` and `<slot>:soft` (the slot mixed into the\n page background — a sticky-note tint). `STROKES` and `FILLS` list them\n with labels.\n- **Stamps** mark what they place: every element of one stamp shares a\n group and carries `customData: { stamp: \"button\", stampId }`.\n- `files` holds the images the elements show (only those). Images are\n data URLs inside the JSON — keep them small, or store the file\n elsewhere and keep the sketch light.\n- The `background` image is never in the value; it comes from the prop.\n- Written by hand (a starter template, a seed), an element may leave out\n what Excalidraw can default (`version`, `seed`, `strokeWidth`,\n `roughness`…) and give its colours only as tokens:\n `{ id: \"a\", type: \"rectangle\", x: 0, y: 0, width: 160, height: 72, customData: { tokens: { strokeColor: \"foreground\", backgroundColor: \"chart-2:soft\" } } }`.\n A text needs `text`, `fontSize` and a rough `width`/`height` (it is\n re-measured). Or skip hand-written elements: insert skeletons through\n the handle (below).\n- `sketchToText(sketch)` reads the words out for an agent; see Helpers.\n\n## Props\n\n- `value` — the sketch, controlled. A new `value` that differs from what\n the canvas last sent replaces the drawing (loading another record) and\n clears undo. Pass back exactly what `onChange` gave you and nothing\n resets. Also takes a bare element array or Excalidraw's own export\n (`{ elements, files, appState }`).\n- `defaultValue` — the starting sketch, uncontrolled. With neither, it\n starts empty.\n- `onChange(sketch)` — the whole sketch, after an edit settles\n (debounced by `changeDelay`). Not called for pan, zoom or selection.\n- `onSave(sketch)` — `async`, returns `{ ok: true }` or\n `{ ok: false, error? }`. Called with the same sketch, right after\n `onChange`, and on Cmd/Ctrl+S. The canvas shows \"Saving…\", \"Saved\",\n or the error with a Retry. A throw counts as a failure.\n- `changeDelay` — ms of quiet before `onChange` / `onSave` (default 600).\n- `readOnly` — `false` (default). `true` shows the sketch to look at: pan\n and zoom, no rail, no toolbar, no edits.\n- `background` — `{ src, width?, height? }`: an image laid at the back,\n locked (it cannot be selected, moved, erased or deleted), top-left at\n (0, 0). For marking up a screenshot (annotate). `src` is a data URL, or\n a same-origin URL (a cross-origin one draws, but spoils `exportImage`).\n Without `width`/`height` the image's own size is used. When it first\n lands on an empty sketch, the view fits it.\n- `tools` — ids of the built-in tools to offer, in rail order:\n `[\"select\", \"hand\", \"draw\", \"rectangle\", \"ellipse\", \"diamond\", \"arrow\",\n \"line\", \"text\", \"image\", \"eraser\", \"frame\"]` (all, the default). A tool\n left out is also unreachable by its shortcut. `SKETCH_TOOLS` lists them\n with labels, icons and keys.\n- `stamps` — stamp tools after the built-ins:\n `[{ id, label, icon, create() }]`. `create()` returns element\n skeletons (below) laid out from (0, 0); a click on the canvas places\n them centred on the pointer (Enter: the middle of the view; Esc\n cancels), grouped, selected. `WIREFRAME_STAMPS` are button, input, card,\n nav bar, table and image placeholder.\n- `grid` — `true` (default): a dotted background that pans and zooms with\n the drawing. `false`: plain.\n- `empty` — what an empty, editable canvas shows in its middle. Omit for\n the built-in hint (\"Start sketching…\"); `false` for nothing; or a node\n (starter templates: give its buttons `pointer-events-auto`, the layer\n lets clicks through to the canvas). Shown while nothing is drawn and the\n select or pan tool is active.\n- `actions` — nodes in the top-right corner, before the menu (a Share\n button, a title chip).\n- `canvasRef` — a ref (`useSketchCanvas()`) that receives the handle.\n- `onReady(handle)` — once, when the canvas has mounted.\n- `label` — the accessible name of the canvas region (default \"Sketch\").\n- `className` — layout: width, margin, height. The default height is\n `h-[32rem]`; a height class of yours (`h-full`, `min-h-…`, `min-h-0\n flex-1`) replaces it. A canvas that is the whole surface sits in an\n `AppLayout contentMode=\"fill\"` from the kit with `min-h-0 flex-1` (the\n Example): never `h-dvh` or a `vh` height, which `npm run check`\n refuses. Never colour.\n\n### The handle\n\n`canvasRef.current` (and `onReady`'s argument):\n\n- `getScene({ withBackground? })` — the sketch now (what `onChange`\n sends), without waiting for the debounce.\n- `setScene(sketch)` — replace the drawing (undoable).\n- `insertElements(skeletons, opts?)` → the new element ids. Skeletons are\n Excalidraw's `convertToExcalidrawElements` input:\n `{ type: \"rectangle\" | \"ellipse\" | \"diamond\", x, y, width, height, label?: { text, fontSize?, textAlign? } }`,\n `{ type: \"text\", x, y, text, fontSize? }`,\n `{ type: \"arrow\" | \"line\", x, y, points: [[0, 0], [dx, dy]], start?: { id }, end?: { id } }`,\n `{ type: \"frame\", children: [ids], name }`, `{ type: \"image\", fileId, x, y, width, height }`.\n Colours may be token names (`strokeColor: \"primary\"`,\n `backgroundColor: \"warning:soft\"`); a shape with no stroke gets\n `foreground`; text is the system font. Options:\n - `at` — `{ x, y }` in canvas coordinates; the skeletons' box is\n centred there (or its top-left, with `anchor: \"top-left\"`). Default:\n the middle of the view. `false`: keep the skeletons' own x/y.\n - `group` — group them (move as one). `stamp: \"<kind>\"` groups too and\n marks them for `sketchToText`.\n - `customData` — merged into every new element's customData (annotate:\n `{ note: 3 }`).\n - `files` — `[{ id, dataURL, mimeType, created }]` to add first, for\n image skeletons.\n - `select` — `true` (default): select what was inserted.\n- `exportImage(opts?)` → `Promise<Blob>`, a PNG made by Excalidraw's own\n export, in the theme's colours now, on the theme's background. Options:\n `scale` (default 2), `padding` (px, default 16), `transparent: true`\n (no background), `withBackground: false` (leave the background image\n out), `onlySelected: true`.\n- `zoomToFit({ animate? })` — fit everything in view (Shift+1).\n- `setTool(id)` — a built-in tool id or a stamp id.\n- `undo()`, `redo()`, `clear()`, `focus()`.\n- `save()` — send `onChange` / `onSave` now.\n- `palette()` — token → the concrete colour it resolves to now.\n- `repaint()` — re-read the theme and repaint (the canvas already does\n this when `data-theme` changes or a theme stylesheet loads).\n- `api` — Excalidraw's own imperative API, for anything else. Reaching for\n it ties you to Excalidraw 0.18; prefer the methods above.\n\n### Helpers\n\nPlain functions; they take a sketch (or an element array) and never throw\non a partial one:\n\n- `emptySketch()` — `{ type: \"sketch\", version: 1, elements: [], files: {}, appState: {} }`.\n- `isSketchEmpty(sketch)` — nothing drawn (deleted elements and the\n background image do not count).\n- `toSketch(value)` — any accepted input → a sketch.\n- `sketchToText(sketch, { stamps = true })` — the words, for an agent or a\n reader: one line per text or stamp in reading order (top to bottom,\n then left to right), grouped under `# <frame name>` per frame (then\n `# Outside frames`); a shape's label reads as its text; a stamp reads\n `[button] Sign up`, an image `[image]`. `stamps: false` drops the\n brackets. An empty sketch gives `\"\"`.\n- `sketchItems(sketch)` — the same, structured: `[{ kind: \"frame\" | \"stamp\" | \"text\" | \"image\", id, text?, name?, stamp?, container?, frame, x, y, width, height }]`.\n- `STROKES`, `FILLS` — `[{ token, label }]`, the palette the toolbar offers.\n- `SKETCH_TOOLS`, `WIREFRAME_STAMPS` — as above.\n\n## Theming, colours and fonts\n\n- **Chrome.** Excalidraw's own CSS variables (its menus, dialogs, context\n menu, hints) point at the workspace tokens (`theme.js`). Light or dark\n follows the nearest `data-theme` (else `color-scheme`, else the OS).\n- **Canvas colours.** Excalidraw needs concrete colours; they are read at\n run time from the tokens' CSS variables and never written in source.\n Excalidraw's dark mode (which inverts the whole canvas with a filter) is\n switched off: the tokens are already the dark scheme's.\n- **Fonts.** Every text is drawn in the device's own sans-serif\n (Excalidraw's \"Helvetica\" family — Helvetica, Arial…), so no font is ever\n fetched: Excalidraw's hand-drawn fonts would otherwise load from a\n third-party CDN. Text pasted from another Excalidraw is moved into it.\n The strokes keep their hand-drawn look (Sketchy / Clean in the toolbar).\n- **Stylesheet.** The block imports `@excalidraw/excalidraw/index.css`\n from the package; the app's bundler ships it like any CSS.\n\n## Example\n\nA whiteboard that saves itself:\n\n```jsx\n// surfaces/sketch.jsx — client: one board, saved as it is drawn\n\"use client\";\nimport { useSearchParams } from \"next/navigation\";\nimport { useRow } from \"../lib/data\";\nimport { updateRow } from \"../lib/actions\";\nimport { AppLayout } from \"../components/ui\";\nimport { SketchCanvas } from \"../components/blocks/sketch-canvas\";\n\nexport default function Board() {\n const id = useSearchParams().get(\"row\");\n const { row: board, loading } = useRow({ source: \"boards\", id });\n if (loading || !board) return null;\n return (\n <AppLayout contentMode=\"fill\">\n <SketchCanvas\n key={board.id}\n label={board.title}\n defaultValue={board.sketch} // jsonb, may be null; read once per board\n onSave={(sketch) => updateRow({ source: \"boards\", id: board.id, patch: { sketch } })}\n className=\"min-h-0 flex-1 rounded-none border-0\"\n />\n </AppLayout>\n );\n}\n```\n\nIn a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.\n\nMarking up a screenshot, with numbered notes:\n\n```jsx\nconst canvas = useSketchCanvas();\nconst addNote = (n) =>\n canvas.current?.insertElements(\n [{ type: \"ellipse\", x: 0, y: 0, width: 32, height: 32, strokeColor: \"destructive\", backgroundColor: \"destructive:soft\", label: { text: String(n) } }],\n { group: true, customData: { note: n } },\n );\n\n<SketchCanvas canvasRef={canvas} background={{ src: screenshotDataUrl }} tools={[\"select\", \"hand\", \"draw\", \"rectangle\", \"arrow\", \"text\", \"eraser\"]} onChange={setSketch} />\nconst image = await canvas.current.exportImage(); // the screenshot with the marks, as a PNG\n```\n\nA wireframe for an agent:\n\n```jsx\nimport { SketchCanvas, WIREFRAME_STAMPS, sketchToText, useSketchCanvas } from \"./blocks/sketch-canvas\";\n\nconst canvas = useSketchCanvas();\n<SketchCanvas canvasRef={canvas} stamps={WIREFRAME_STAMPS} tools={[\"select\", \"hand\", \"draw\", \"rectangle\", \"arrow\", \"text\", \"eraser\", \"frame\"]} onChange={setSketch} />\n\nconst build = async () => onBuild({ image: await canvas.current.exportImage(), sketch, text: sketchToText(sketch), prompt });\n// sketchToText → \"# Login\\nWelcome back\\n[input] Email\\n[button] Sign in\"\n```\n\n## What it will not do\n\n- Collaborate live (several cursors): Excalidraw's collaboration is a\n server of its own, not wired here. Saves are last-write-wins.\n- Offer Excalidraw's library, share links, \"text to diagram\" / AI, or its\n hand-drawn fonts. Its command palette (Cmd+/) and help dialog (?) still\n open, themed; the palette may name actions that do nothing here.\n- Show a minimap.\n- Keep images out of the JSON: an image lives as a data URL in `files`.\n- Render on the server: the server shows the frame and the sketch's\n words; the drawing appears when the page runs.\n- Pick an arbitrary colour: only the theme's tokens.\n\nA canvas it cannot be is your own component in `components/` — not an edit\nto this block. Its files are fixed: `npm run check` refuses a change, and\n`makersclaw add sketch-canvas` restores them.\n\n## Credits\n\nExcalidraw (https://github.com/excalidraw/excalidraw), MIT — installed from\nnpm as a dependency, not copied here. See THIRD-PARTY-NOTICES.md.\n"
73
73
  }
74
74
  ],
75
75
  "categories": [
@@ -92,7 +92,7 @@
92
92
  "docs": "Installed to components/makersclaw/blocks/sketch-canvas. Import: import { SketchCanvas, useSketchCanvas, SKETCH_TOOLS, WIREFRAME_STAMPS, STROKES, FILLS, emptySketch, isSketchEmpty, sketchToText, sketchItems, toSketch } from \"@/components/makersclaw/blocks/sketch-canvas\". Props and an example: components/makersclaw/blocks/sketch-canvas/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
93
93
  "meta": {
94
94
  "makersclaw": {
95
- "version": "0.7.0",
95
+ "version": "0.8.0",
96
96
  "useWhen": "A screen needs freehand drawing: a whiteboard, marking up a screenshot, sketching a UI wireframe, a diagram drawn by hand. The whiteboard, annotate and sketch-to-app blocks are built on it. Not for structured boards of cards and connectors (flow-canvas), charts (chart) or a kanban (board)."
97
97
  }
98
98
  }
@@ -5,7 +5,7 @@
5
5
  "title": "Spreadsheet",
6
6
  "description": "A table you edit like Google Sheets or Airtable, virtualised for 10,000+ rows: typed columns (text, number, currency, date, single and multiple select, person, checkbox, link, rating) each with its own in-cell editor, a cell cursor and range selection with the mouse and arrow keys, typing to edit, copy and paste ranges as TSV (to and from Sheets and Excel), drag-to-fill and ⌘D fill down, undo/redo, column resize, drag to reorder, hide, freeze, rename, change type and add fields, row insert and delete with Undo, sort and filter panels, search, a sum/average of the selection, validation marks, and a phone layout. Edits go out as async callbacks answering { ok }, applied at once and rolled back on failure. A column-type registry and an imperative handle for blocks built on it.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [
11
11
  "@tanstack/react-virtual@3.14.13"
@@ -83,7 +83,7 @@
83
83
  "docs": "Installed to components/makersclaw/blocks/spreadsheet. Import: import { Spreadsheet, CellValue, CellPanel, OptionBadge, editorFor, TYPE_ORDER, sortWords, COLUMN_TYPES, typeOf, parseCell, convertValue, guessType, parseNumberText, parseDateText, optionValue, isBlank, emptyView, normalizeView, orderedColumns, visibleColumns, processRows, passesFilters, parseTSV, toTSV, pasteChanges, fillChanges, clearChanges, applyChanges, invertChanges } from \"@/components/makersclaw/blocks/spreadsheet\". Props and an example: components/makersclaw/blocks/spreadsheet/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
84
84
  "meta": {
85
85
  "makersclaw": {
86
- "version": "0.7.0",
86
+ "version": "0.8.0",
87
87
  "useWhen": "People need to enter or clean up many records in a grid, fast, with the keyboard: a lead list, an inventory, a budget, a content calendar, a table view over any collection, the grid under an Airtable-like base. Not for a read-mostly list to scan and act on row by row (data-table), records moving through stages (board), or a single record (record-form)."
88
88
  }
89
89
  }
package/r/stat-cards.json CHANGED
@@ -5,8 +5,8 @@
5
5
  "title": "Stat cards",
6
6
  "description": "A row of key numbers, each with its name and its change: one bordered strip, or a card each with a change in the corner or after the value, a \"from\" value, a status dot and a goal meter, a sparkline, a \"View more\" footer; or a usage tile per amount against its limit (a bar or a ring, a split by kind with what is free, a warning line near the cap, an action and a limit the founder can change); or a plain list of values with their change. Loading, empty and error states.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json",
9
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/chart.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json",
9
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/chart.json"
10
10
  ],
11
11
  "dependencies": [],
12
12
  "files": [
@@ -59,7 +59,7 @@
59
59
  "docs": "Installed to components/makersclaw/blocks/stat-cards. Import: import { StatCards } from \"@/components/makersclaw/blocks/stat-cards\". Props and an example: components/makersclaw/blocks/stat-cards/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
60
60
  "meta": {
61
61
  "makersclaw": {
62
- "version": "0.7.0",
62
+ "version": "0.8.0",
63
63
  "useWhen": "The few numbers at the top of a dashboard or a report: revenue, users, conversion, spend against budget, seats and storage against a plan. Not for a trend worth reading on its own (a chart), many rows of records (a table), or one subject scored against thresholds (a scorecard)."
64
64
  }
65
65
  }
package/r/task-list.json CHANGED
@@ -5,8 +5,8 @@
5
5
  "title": "Task list",
6
6
  "description": "The List view of work, like ClickUp's or Linear's: tasks under headings that fold (group by status, assignee, priority, project, due date or any field), a round check to complete each, the title edited in place, subtasks indented under their parent with their own fold, the fields you choose (assignees, due date, priority, tags, project, estimate) as inline pickers, a quick-add row at the end of every group, drag to reorder and to move between groups (saving the new status, assignee or due date), multi-select with a bulk bar (status, assign, due, priority, delete with undo), a toolbar with search, filter chips, group by, sort and fields, and friendly empty states with starter templates. One async onChange saves every change; failures roll back with a notice.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json",
9
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/task-model.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json",
9
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/task-model.json"
10
10
  ],
11
11
  "dependencies": [],
12
12
  "files": [
@@ -56,7 +56,7 @@
56
56
  "docs": "Installed to components/makersclaw/blocks/task-list. Import: import { TaskList } from \"@/components/makersclaw/blocks/task-list\". Props and an example: components/makersclaw/blocks/task-list/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
57
57
  "meta": {
58
58
  "makersclaw": {
59
- "version": "0.7.0",
59
+ "version": "0.8.0",
60
60
  "useWhen": "A screen that lists work to get done and moves it along: a team's tasks, a launch checklist, a sprint, a personal to-do list, onboarding steps, content to publish. Not for records that are not work (data-table), for stage columns side by side (board), or for dates on a grid (calendar)."
61
61
  }
62
62
  }
package/r/task-model.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "title": "Task model",
6
6
  "description": "The shared work-item model every task view reads: one task shape (status, assignees, start/due, estimate, progress, priority, parent, dependencies, location, tags, project, extra fields) with statuses, people and projects; pure helpers to search, filter, group by any field, sort, nest subtasks, do date math (week and month grids, working days, overlaps, time scales), sum capacity per person, follow dependencies and the critical path, and apply, undo and persist changes; the shared parts (StatusPill, AssigneeStack, DueLabel, PriorityFlag, TaskChip, CompleteToggle), the ViewToolbar (search, filter chips, group by, sort, show fields), drag primitives on @dnd-kit, a hook for optimistic saves with rollback and undo, and adapters that drive the Board and DataTable blocks from tasks.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [
11
11
  "@dnd-kit/core@6.3.1",
@@ -91,7 +91,7 @@
91
91
  "docs": "Installed to components/makersclaw/blocks/task-model. Import: import { NONE, PRIORITIES, DEFAULT_STATUSES, TASK_FIELDS, DEFAULT_VIEW, normalizeTask, normalizeTasks, createTaskContext, fieldOf, isDone, doneStatusId, openStatusId, toggleDoneChange, newTaskId, draftTask, valuesOf, optionsOf, labelOf, searchText, searchTasks, filterTasks, orderOf, sortTasks, sortOptions, groupTasks, groupOptions, movePatch, applyView, activeFilterCount, rankBetween, placeChange, childrenMap, buildTree, flattenTree, descendantIds, ancestorIds, subtaskCounts, rollup, progressOf, dependencyEdges, blockersOf, wouldCycle, dateConflicts, criticalPath, hoursPerDay, workload, sumOf, applyChange, invertChange, changeWrites, changedIds, deleteChange, toBoard, toTableRows, taskFacets, StatusPill, CompleteToggle, AssigneeStack, PersonLabel, DueLabel, PriorityFlag, TagList, SubtaskCount, ProgressLine, TaskChip, GroupLabel, taskColumns, TONE_TEXT, HUE_EDGE, HUE_BG, TaskDragProvider, SortableGroup, useSortableTask, useDraggableTask, useDropTarget, DragHandle, DropLine, LIFTED, useReducedMotion, arrayMove, isDay, dayNumber, fromDayNumber, dayOf, minutesOf, withTime, todayDay, addDays, diffDays, weekdayOf, startOfWeek, endOfWeek, weekDays, startOfMonth, endOfMonth, addMonths, daysBetween, monthGrid, WEEKEND, isWeekend, isWorkingDay, workingDaysBetween, addWorkingDays, spanOf, spansOverlap, tasksInRange, tasksOnDay, shiftDates, spanPatch, dueBucket, DUE_BUCKETS, dayForBucket, formatDay, formatTime, formatDate, timeScale, rangeOfTasks, isNewId } from \"@/components/makersclaw/blocks/task-model\". Props and an example: components/makersclaw/blocks/task-model/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
92
92
  "meta": {
93
93
  "makersclaw": {
94
- "version": "0.7.0",
94
+ "version": "0.8.0",
95
95
  "useWhen": "You are building a view of work items — a list, calendar, gantt, workload, roadmap or a switcher between them — or driving Board / DataTable from tasks. Views import it; a surface rarely renders it alone. Not for records that are not work (use data-table or board directly)."
96
96
  }
97
97
  }
@@ -5,7 +5,7 @@
5
5
  "title": "Testimonials",
6
6
  "description": "A marketing section of customer quotes: name, role, company, face or logo, the quote, an optional star rating, a verified mark and a \"view on X\" source, and optional headline numbers (stats). Layouts: a grid of cards (plain, card or raised; hover can light one and dim the rest; a \"write a review\" tile), one large quote, a switcher of faces, and a carousel, optionally over a picture.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -45,7 +45,7 @@
45
45
  "docs": "Installed to components/makersclaw/blocks/testimonials. Import: import { Testimonials } from \"@/components/makersclaw/blocks/testimonials\". Props and an example: components/makersclaw/blocks/testimonials/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
46
46
  "meta": {
47
47
  "makersclaw": {
48
- "version": "0.7.0",
48
+ "version": "0.8.0",
49
49
  "useWhen": "Social proof on a public page: a landing page, a pricing page, a product page, a case-study teaser. Not for reviews a signed-in user writes and manages (a record list or form), or an app's own activity feed."
50
50
  }
51
51
  }
@@ -5,7 +5,7 @@
5
5
  "title": "TransactionList",
6
6
  "description": "Money records as a plain list, in two variants: history (payments in and out, grouped by month, a received / paid / pending / failed mark and a signed amount) and invoices (number, date, a Paid / Due in N days / N days overdue badge, amount, download). Optional date-range filter, Export and per-row download. Empty, loading and error states built in.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json"
9
9
  ],
10
10
  "dependencies": [],
11
11
  "files": [
@@ -46,7 +46,7 @@
46
46
  "docs": "Installed to components/makersclaw/blocks/transaction-list. Import: import { TransactionList } from \"@/components/makersclaw/blocks/transaction-list\". Props and an example: components/makersclaw/blocks/transaction-list/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
47
47
  "meta": {
48
48
  "makersclaw": {
49
- "version": "0.7.0",
49
+ "version": "0.8.0",
50
50
  "useWhen": "A billing page's invoices, an account's payment history, a wallet's recent activity: records read top to bottom, newest first. Not for records the founder sorts, filters by column or totals (data-table), or for one order (order-detail)."
51
51
  }
52
52
  }
package/r/whiteboard.json CHANGED
@@ -5,8 +5,8 @@
5
5
  "title": "Whiteboard",
6
6
  "description": "A full-page freeform whiteboard on sketch-canvas: a top bar with the board's title (click to rename), the save state (Saving…, Saved, or the error with Retry), a Board / Text view switch, Export PNG and an optional Share button; under it the Miro-style canvas (tool rail, floating toolbar, zoom, undo). An empty board offers starter templates — blank, a grid of titled areas, a 2×2 matrix — and the Text view lists every word on the board for a screen reader or an agent. With onCreateTask, a selected note or text becomes a task from the top bar (\"Create task\", editable title; several notes make one task each), and a small badge marks it on the board from then on. The sketch is plain JSON; saves are async callbacks answering { ok }.",
7
7
  "registryDependencies": [
8
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/core.json",
9
- "https://cdn.jsdelivr.net/npm/makersclaw@0.7.0/r/sketch-canvas.json"
8
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/core.json",
9
+ "https://cdn.jsdelivr.net/npm/makersclaw@0.8.0/r/sketch-canvas.json"
10
10
  ],
11
11
  "dependencies": [],
12
12
  "files": [
@@ -32,7 +32,7 @@
32
32
  "path": "registry/blocks/whiteboard/README.md",
33
33
  "type": "registry:file",
34
34
  "target": "@components/makersclaw/blocks/whiteboard/README.md",
35
- "content": "# Whiteboard\n\nA full-page board to think on, friendly like Miro. A top bar holds the\nboard's title (click it to rename), the save state, a Board / Text view\nswitch, Export PNG and, when you give it `onShare`, a Share button. Under\nit is the canvas from `sketch-canvas`: the tool rail (a bottom bar on a\nphone), the toolbar that floats over a selection, zoom, undo and redo.\n\nGive it `onCreateTask` and a selected note or text becomes a task from\nthe top bar; the note keeps a small **Task** badge from then on.\n\nAn empty board offers three starting points: **Blank**, **Grid** (six\ntitled areas, three by two) and **2×2 matrix** (impact against effort,\nfour tinted quadrants with axes). The **Text view** lists every word on\nthe board, grouped by frame — the reading of the board for a screen\nreader, and what you hand an agent.\n\n```jsx\nimport { Whiteboard } from \"../components/blocks/whiteboard\"; // a Makersclaw app (\"./blocks/whiteboard\" from components/)\nimport { Whiteboard } from \"@/components/makersclaw/blocks/whiteboard\"; // a shadcn project\n```\n\nIt is a client component: render it from a `\"use client\"` component.\n`makersclaw add whiteboard` adds `sketch-canvas` with it (and its npm\npackage, `@excalidraw/excalidraw`: run `npm install` after). The helpers\nfor reading a board — `sketchToText`, `sketchItems`, `isSketchEmpty` —\ncome from `sketch-canvas`.\n\n## The data\n\nA board is two things you store: its **title** (a string) and its\n**sketch** (plain JSON, a `jsonb` column) — the sketch format is\n`sketch-canvas`'s (its README, \"The data\"). Colours in a sketch are theme\ntokens, so a board repaints when the workspace theme changes.\n\nOne table does it: `boards(id, title text, sketch jsonb, sketch_text text,\nupdated_at)`. Keep `sketch_text` (`sketchToText(sketch)`) for search and\nfor agents.\n\n## Props\n\n- `title` — the board's name, controlled. Change it when `onRename`\n answers ok.\n- `defaultTitle` — the name, uncontrolled (default \"Untitled board\").\n- `onRename(title)` — `async`, returns `{ ok: true }` or\n `{ ok: false, error? }`. Called when the reader renames the board (Enter\n or leaving the field; Esc puts the old name back). While it runs the\n new name shows; a failure keeps it and shows the error with Retry.\n Without it the title still edits, in memory.\n- `value` — the sketch, controlled (see sketch-canvas: pass back exactly\n what `onChange` gave you).\n- `defaultValue` — the sketch, uncontrolled. With neither, the board\n starts empty and shows the templates.\n- `onChange(sketch)` — the whole sketch after an edit settles (debounced).\n- `onSave(sketch)` — `async`, `{ ok }`. Called with every settled edit and\n on Cmd/Ctrl+S. The top bar shows \"Saving…\", \"Saved\", or the error with a\n Retry. A throw counts as a failure.\n- `changeDelay` — ms of quiet before `onChange` / `onSave` (default 600).\n- `templates` — the starting points an empty board offers (default\n `WHITEBOARD_TEMPLATES`: blank, grid, matrix). Each is\n `{ id, label, description?, create(), preview?, icon? }`: `create()`\n returns a sketch (or an element array) to lay on the board, or `null`\n for \"start blank\". `preview` is a small node drawn in the tile (else an\n `icon`, else a frame glyph; the three built-ins draw their own). `[]`\n shows sketch-canvas's plain hint instead.\n- `onShare()` — shows a Share button (primary) that calls it: open your\n `share-dialog`. Without it there is no Share button. `shareLabel`\n renames it (default \"Share\").\n- `onExport(blob, { title, fileName })` — `async`, `{ ok }`: take the PNG\n yourself (attach it, upload it). Without it, Export downloads\n `<title>.png`. Either way a failure shows on the button.\n- `onCreateTask({ title, sourceIds })` — turns notes into tasks. With it,\n selecting a note (a shape with words in it) or a text shows **Create\n task** in the top bar: a small form with the words as the title, ready\n to edit (Enter creates). Several selected notes make one task each.\n `async`, return `{ ok: true, id? }` or `{ ok: false, error? }` (a throw\n counts as failed). `sourceIds` are the board elements it came from (the\n note and its words). While it runs the note shows \"Creating…\"; on\n success the note keeps a **Task** badge (stored in the sketch, as\n `customData.task: { id, title }` on those elements) and is not offered\n again; a failure says so and leaves the note as it was. `taskLabel`\n renames the button (default \"Create task\").\n- `onOpenTask({ id, title, sourceIds })` — makes each Task badge (and the\n success toast) a link: open the task. Without it the badges only mark.\n- `onBack()` — shows a back arrow at the left of the top bar; `backLabel`\n is its name (default \"Back\").\n- `meta` — a short node beside the title on wide screens (\"Edited by Priya\n Raman\", \"View only\").\n- `readOnly` — `false` (default). `true`: the title is plain text, the\n canvas pans and zooms but does not edit, no templates.\n- `defaultView` — `\"board\"` (default) or `\"text\"`: which view opens.\n- `canvasRef` — a ref (`useSketchCanvas()` from sketch-canvas) that\n receives the canvas handle: `exportImage()`, `getScene()`,\n `insertElements()`… (sketch-canvas README, \"The handle\").\n- `className` — layout: width, margin, height. The default height is\n `h-[40rem]`; a height class of yours (`h-dvh` for a full page, `h-full`,\n `flex-1`) replaces it. Never colour.\n\n### Templates\n\n`WHITEBOARD_TEMPLATES` is the default list. `gridSketch(opts)` and\n`matrixSketch(opts)` build the two drawn ones, so your own templates can\nreuse them:\n\n- `gridSketch({ columns = 3, rows = 2, width = 360, height = 260, gap = 48, names })`\n — titled frames; `names` overrides \"Area 1\", \"Area 2\"…\n- `matrixSketch({ x = \"Effort\", y = \"Impact\", quadrants = [\"Quick wins\", \"Big bets\", \"Fill-ins\", \"Not now\"], size = 360 })`\n — four locked, tinted quadrants (notes land on them without moving\n them), two axes, the quadrant names as editable text.\n\n## Example\n\nA board page that saves itself:\n\n```jsx\n// surfaces/board.jsx — client: one board, saved as it changes\n\"use client\";\nimport { useSearchParams } from \"next/navigation\";\nimport { useRow, whenLanded } from \"../lib/data\";\nimport { updateRow } from \"../lib/actions\";\nimport { Whiteboard } from \"../components/blocks/whiteboard\";\nimport { sketchToText } from \"../components/blocks/sketch-canvas\";\n\nexport default function BoardPage() {\n const id = useSearchParams().get(\"row\");\n const { row: board, loading } = useRow({ source: \"boards\", id });\n if (loading || !board) return null;\n return (\n <Whiteboard\n key={board.id}\n title={board.title}\n onRename={async (next) => {\n const res = await updateRow({ source: \"boards\", id: board.id, patch: { title: next } });\n if (res.ok) await whenLanded(); // the board is read again: the title is the new one\n return res;\n }}\n defaultValue={board.sketch} // jsonb, may be null; read once per board\n onSave={(sketch) => updateRow({ source: \"boards\", id: board.id, patch: { sketch, sketch_text: sketchToText(sketch) } })}\n className=\"h-dvh rounded-none border-0\"\n />\n );\n}\n```\n\nIn a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.\n\nNotes that become tasks (a `tasks` table of your own):\n\n```jsx\n<Whiteboard\n defaultValue={board.sketch}\n onSave={save}\n onCreateTask={({ title, sourceIds }) => insertRow({ source: \"tasks\", values: { title, board_id: board.id, source_ids: sourceIds } })}\n onOpenTask={(task) => router.push(`/s/tasks?row=${task.id}`)}\n/>\n```\n\n`insertRow` answers `{ ok, id }`; the id is what the badge keeps.\n\nA retro template of your own, beside the built-in ones:\n\n```jsx\nimport { WHITEBOARD_TEMPLATES, gridSketch } from \"./blocks/whiteboard\";\n\nconst templates = [\n ...WHITEBOARD_TEMPLATES,\n { id: \"retro\", label: \"Retro\", description: \"Went well, to improve, actions\", icon: \"history\",\n create: () => gridSketch({ columns: 3, rows: 1, height: 520, names: [\"Went well\", \"To improve\", \"Actions\"] }) },\n];\n\n<Whiteboard templates={templates} onSave={save} />\n```\n\n## What it will not do\n\n- Collaborate live: no cursors of others, saves are last-write-wins (as\n sketch-canvas).\n- Share by itself: `onShare` is yours — open `share-dialog`, copy a link.\n- Keep a list of boards: one board per component; the list is your page.\n- Turn on snap-to-grid from a template: the canvas menu has it.\n- Show a minimap (sketch-canvas has none).\n\nA board it cannot be is your own component in `components/` — not an edit\nto this block. Its files are fixed: `npm run check` refuses a change, and\n`makersclaw add whiteboard` restores them.\n"
35
+ "content": "# Whiteboard\n\nA full-page board to think on, friendly like Miro. A top bar holds the\nboard's title (click it to rename), the save state, a Board / Text view\nswitch, Export PNG and, when you give it `onShare`, a Share button. Under\nit is the canvas from `sketch-canvas`: the tool rail (a bottom bar on a\nphone), the toolbar that floats over a selection, zoom, undo and redo.\n\nGive it `onCreateTask` and a selected note or text becomes a task from\nthe top bar; the note keeps a small **Task** badge from then on.\n\nAn empty board offers three starting points: **Blank**, **Grid** (six\ntitled areas, three by two) and **2×2 matrix** (impact against effort,\nfour tinted quadrants with axes). The **Text view** lists every word on\nthe board, grouped by frame — the reading of the board for a screen\nreader, and what you hand an agent.\n\n```jsx\nimport { Whiteboard } from \"../components/blocks/whiteboard\"; // a Makersclaw app (\"./blocks/whiteboard\" from components/)\nimport { Whiteboard } from \"@/components/makersclaw/blocks/whiteboard\"; // a shadcn project\n```\n\nIt is a client component: render it from a `\"use client\"` component.\n`makersclaw add whiteboard` adds `sketch-canvas` with it (and its npm\npackage, `@excalidraw/excalidraw`: run `npm install` after). The helpers\nfor reading a board — `sketchToText`, `sketchItems`, `isSketchEmpty` —\ncome from `sketch-canvas`.\n\n## The data\n\nA board is two things you store: its **title** (a string) and its\n**sketch** (plain JSON, a `jsonb` column) — the sketch format is\n`sketch-canvas`'s (its README, \"The data\"). Colours in a sketch are theme\ntokens, so a board repaints when the workspace theme changes.\n\nOne table does it: `boards(id, title text, sketch jsonb, sketch_text text,\nupdated_at)`. Keep `sketch_text` (`sketchToText(sketch)`) for search and\nfor agents.\n\n## Props\n\n- `title` — the board's name, controlled. Change it when `onRename`\n answers ok.\n- `defaultTitle` — the name, uncontrolled (default \"Untitled board\").\n- `onRename(title)` — `async`, returns `{ ok: true }` or\n `{ ok: false, error? }`. Called when the reader renames the board (Enter\n or leaving the field; Esc puts the old name back). While it runs the\n new name shows; a failure keeps it and shows the error with Retry.\n Without it the title still edits, in memory.\n- `value` — the sketch, controlled (see sketch-canvas: pass back exactly\n what `onChange` gave you).\n- `defaultValue` — the sketch, uncontrolled. With neither, the board\n starts empty and shows the templates.\n- `onChange(sketch)` — the whole sketch after an edit settles (debounced).\n- `onSave(sketch)` — `async`, `{ ok }`. Called with every settled edit and\n on Cmd/Ctrl+S. The top bar shows \"Saving…\", \"Saved\", or the error with a\n Retry. A throw counts as a failure.\n- `changeDelay` — ms of quiet before `onChange` / `onSave` (default 600).\n- `templates` — the starting points an empty board offers (default\n `WHITEBOARD_TEMPLATES`: blank, grid, matrix). Each is\n `{ id, label, description?, create(), preview?, icon? }`: `create()`\n returns a sketch (or an element array) to lay on the board, or `null`\n for \"start blank\". `preview` is a small node drawn in the tile (else an\n `icon`, else a frame glyph; the three built-ins draw their own). `[]`\n shows sketch-canvas's plain hint instead.\n- `onShare()` — shows a Share button (primary) that calls it: open your\n `share-dialog`. Without it there is no Share button. `shareLabel`\n renames it (default \"Share\").\n- `onExport(blob, { title, fileName })` — `async`, `{ ok }`: take the PNG\n yourself (attach it, upload it). Without it, Export downloads\n `<title>.png`. Either way a failure shows on the button.\n- `onCreateTask({ title, sourceIds })` — turns notes into tasks. With it,\n selecting a note (a shape with words in it) or a text shows **Create\n task** in the top bar: a small form with the words as the title, ready\n to edit (Enter creates). Several selected notes make one task each.\n `async`, return `{ ok: true, id? }` or `{ ok: false, error? }` (a throw\n counts as failed). `sourceIds` are the board elements it came from (the\n note and its words). While it runs the note shows \"Creating…\"; on\n success the note keeps a **Task** badge (stored in the sketch, as\n `customData.task: { id, title }` on those elements) and is not offered\n again; a failure says so and leaves the note as it was. `taskLabel`\n renames the button (default \"Create task\").\n- `onOpenTask({ id, title, sourceIds })` — makes each Task badge (and the\n success toast) a link: open the task. Without it the badges only mark.\n- `onBack()` — shows a back arrow at the left of the top bar; `backLabel`\n is its name (default \"Back\").\n- `meta` — a short node beside the title on wide screens (\"Edited by Priya\n Raman\", \"View only\").\n- `readOnly` — `false` (default). `true`: the title is plain text, the\n canvas pans and zooms but does not edit, no templates.\n- `defaultView` — `\"board\"` (default) or `\"text\"`: which view opens.\n- `canvasRef` — a ref (`useSketchCanvas()` from sketch-canvas) that\n receives the canvas handle: `exportImage()`, `getScene()`,\n `insertElements()`… (sketch-canvas README, \"The handle\").\n- `className` — layout: width, margin, height. The default height is\n `h-[40rem]`; a height class of yours (`h-full`, `min-h-0 flex-1`)\n replaces it. A board that is the whole surface sits in an `AppLayout\n contentMode=\"fill\"` from the kit with `min-h-0 flex-1` (the Example):\n never `h-dvh` or a `vh` height, which `npm run check` refuses. Never\n colour.\n\n### Templates\n\n`WHITEBOARD_TEMPLATES` is the default list. `gridSketch(opts)` and\n`matrixSketch(opts)` build the two drawn ones, so your own templates can\nreuse them:\n\n- `gridSketch({ columns = 3, rows = 2, width = 360, height = 260, gap = 48, names })`\n — titled frames; `names` overrides \"Area 1\", \"Area 2\"…\n- `matrixSketch({ x = \"Effort\", y = \"Impact\", quadrants = [\"Quick wins\", \"Big bets\", \"Fill-ins\", \"Not now\"], size = 360 })`\n — four locked, tinted quadrants (notes land on them without moving\n them), two axes, the quadrant names as editable text.\n\n## Example\n\nA board page that saves itself:\n\n```jsx\n// surfaces/board.jsx — client: one board, saved as it changes\n\"use client\";\nimport { useSearchParams } from \"next/navigation\";\nimport { useRow, whenLanded } from \"../lib/data\";\nimport { updateRow } from \"../lib/actions\";\nimport { AppLayout } from \"../components/ui\";\nimport { Whiteboard } from \"../components/blocks/whiteboard\";\nimport { sketchToText } from \"../components/blocks/sketch-canvas\";\n\nexport default function BoardPage() {\n const id = useSearchParams().get(\"row\");\n const { row: board, loading } = useRow({ source: \"boards\", id });\n if (loading || !board) return null;\n return (\n <AppLayout contentMode=\"fill\">\n <Whiteboard\n key={board.id}\n title={board.title}\n onRename={async (next) => {\n const res = await updateRow({ source: \"boards\", id: board.id, patch: { title: next } });\n if (res.ok) await whenLanded(); // the board is read again: the title is the new one\n return res;\n }}\n defaultValue={board.sketch} // jsonb, may be null; read once per board\n onSave={(sketch) => updateRow({ source: \"boards\", id: board.id, patch: { sketch, sketch_text: sketchToText(sketch) } })}\n className=\"min-h-0 flex-1 rounded-none border-0\"\n />\n </AppLayout>\n );\n}\n```\n\nIn a server-mode app the same calls are `loadRows` in a server surface and the Server Actions of `lib/actions`.\n\nNotes that become tasks (a `tasks` table of your own):\n\n```jsx\n<Whiteboard\n defaultValue={board.sketch}\n onSave={save}\n onCreateTask={({ title, sourceIds }) => insertRow({ source: \"tasks\", values: { title, board_id: board.id, source_ids: sourceIds } })}\n onOpenTask={(task) => router.push(`/s/tasks?row=${task.id}`)}\n/>\n```\n\n`insertRow` answers `{ ok, id }`; the id is what the badge keeps.\n\nA retro template of your own, beside the built-in ones:\n\n```jsx\nimport { WHITEBOARD_TEMPLATES, gridSketch } from \"./blocks/whiteboard\";\n\nconst templates = [\n ...WHITEBOARD_TEMPLATES,\n { id: \"retro\", label: \"Retro\", description: \"Went well, to improve, actions\", icon: \"history\",\n create: () => gridSketch({ columns: 3, rows: 1, height: 520, names: [\"Went well\", \"To improve\", \"Actions\"] }) },\n];\n\n<Whiteboard templates={templates} onSave={save} />\n```\n\n## What it will not do\n\n- Collaborate live: no cursors of others, saves are last-write-wins (as\n sketch-canvas).\n- Share by itself: `onShare` is yours — open `share-dialog`, copy a link.\n- Keep a list of boards: one board per component; the list is your page.\n- Turn on snap-to-grid from a template: the canvas menu has it.\n- Show a minimap (sketch-canvas has none).\n\nA board it cannot be is your own component in `components/` — not an edit\nto this block. Its files are fixed: `npm run check` refuses a change, and\n`makersclaw add whiteboard` restores them.\n"
36
36
  }
37
37
  ],
38
38
  "categories": [
@@ -59,7 +59,7 @@
59
59
  "docs": "Installed to components/makersclaw/blocks/whiteboard. Import: import { Whiteboard, WHITEBOARD_TEMPLATES, gridSketch, matrixSketch } from \"@/components/makersclaw/blocks/whiteboard\". Props and an example: components/makersclaw/blocks/whiteboard/README.md. Makersclaw builds whole apps from these blocks: https://makersclaw.com",
60
60
  "meta": {
61
61
  "makersclaw": {
62
- "version": "0.7.0",
62
+ "version": "0.8.0",
63
63
  "useWhen": "A screen is one board to think on: a team whiteboard, a planning or prioritisation board, a place to sketch an idea and keep it with a record. Not for marking up a screenshot (annotate), sketching a UI to build (sketch-to-app), a structured board of cards and connectors (flow-canvas), or a kanban (board)."
64
64
  }
65
65
  }
@@ -145,6 +145,7 @@ A feedback widget that saves itself and files a ticket:
145
145
  import { useSearchParams } from "next/navigation";
146
146
  import { useRow } from "../lib/data";
147
147
  import { insertRow, updateRow } from "../lib/actions";
148
+ import { AppLayout } from "../components/ui";
148
149
  import { Annotate } from "../components/blocks/annotate";
149
150
 
150
151
  export default function Feedback() {
@@ -152,17 +153,19 @@ export default function Feedback() {
152
153
  const { row: ticket, loading } = useRow({ source: "tickets", id });
153
154
  if (loading || !ticket) return null;
154
155
  return (
155
- <Annotate
156
- key={ticket.id}
157
- title="What should change?"
158
- defaultValue={ticket.annotation} // jsonb, may be null; read once per ticket
159
- onSave={(annotation) => updateRow({ source: "tickets", id: ticket.id, patch: { annotation } })}
160
- onSubmit={({ notes, text }) => // image: a PNG Blob, to upload if you keep it
161
- insertRow({ source: "bug_reports", values: { ticket_id: ticket.id, summary: text, note_count: notes.length } })
162
- }
163
- submitLabel="File the bug"
164
- className="h-dvh"
165
- />
156
+ <AppLayout contentMode="fill">
157
+ <Annotate
158
+ key={ticket.id}
159
+ title="What should change?"
160
+ defaultValue={ticket.annotation} // jsonb, may be null; read once per ticket
161
+ onSave={(annotation) => updateRow({ source: "tickets", id: ticket.id, patch: { annotation } })}
162
+ onSubmit={({ notes, text }) => // image: a PNG Blob, to upload if you keep it
163
+ insertRow({ source: "bug_reports", values: { ticket_id: ticket.id, summary: text, note_count: notes.length } })
164
+ }
165
+ submitLabel="File the bug"
166
+ className="min-h-0 flex-1"
167
+ />
168
+ </AppLayout>
166
169
  );
167
170
  }
168
171
  ```
@@ -523,7 +523,7 @@ function Cartesian({
523
523
  });
524
524
 
525
525
  return (
526
- <div className={cx("grid min-w-0 gap-3", className)}>
526
+ <div className={cx("relative grid min-w-0 gap-3", className)}>
527
527
  <div
528
528
  ref={boxRef}
529
529
  tabIndex={0}
@@ -244,32 +244,42 @@ export function TooltipBox({ at, title, rows, footer }) {
244
244
  }
245
245
 
246
246
  /** The numbers behind the picture, for a screen reader: a real table,
247
- * visually hidden. `head` the column names, `body` rows of strings. */
247
+ * visually hidden. `head` the column names, `body` rows of strings.
248
+ *
249
+ * The hiding is a div's, never the table's: a table takes `width: 1px`
250
+ * as a least width and grows to its rows, and a browser need not clip a
251
+ * table's overflow, so `sr-only` on the table itself left a box as wide
252
+ * as its longest row, which pushed a phone's page past the window (the
253
+ * store's probe at 375px, 2026-10-08). The div is 1px, clips what it
254
+ * holds and is absolute inside the chart's own box (each chart's root
255
+ * is `relative`); the table in it is a plain table, read as one. */
248
256
  export function DataTable({ caption, head, body }) {
249
257
  return (
250
- <table className="sr-only">
251
- <caption>{caption}</caption>
252
- <thead>
253
- <tr>
254
- {head.map((h, i) => (
255
- <th key={i} scope="col">{h}</th>
256
- ))}
257
- </tr>
258
- </thead>
259
- <tbody>
260
- {body.map((cells, r) => (
261
- <tr key={r}>
262
- {cells.map((c, i) =>
263
- i === 0 ? (
264
- <th key={i} scope="row">{c}</th>
265
- ) : (
266
- <td key={i}>{c}</td>
267
- ),
268
- )}
258
+ <div className="sr-only">
259
+ <table>
260
+ <caption>{caption}</caption>
261
+ <thead>
262
+ <tr>
263
+ {head.map((h, i) => (
264
+ <th key={i} scope="col">{h}</th>
265
+ ))}
269
266
  </tr>
270
- ))}
271
- </tbody>
272
- </table>
267
+ </thead>
268
+ <tbody>
269
+ {body.map((cells, r) => (
270
+ <tr key={r}>
271
+ {cells.map((c, i) =>
272
+ i === 0 ? (
273
+ <th key={i} scope="row">{c}</th>
274
+ ) : (
275
+ <td key={i}>{c}</td>
276
+ ),
277
+ )}
278
+ </tr>
279
+ ))}
280
+ </tbody>
281
+ </table>
282
+ </div>
273
283
  );
274
284
  }
275
285
 
@@ -168,7 +168,7 @@ export function PieChart({
168
168
  : { value: format(total), label: totalLabel };
169
169
 
170
170
  return (
171
- <div className={cx("grid min-w-0 gap-3", className)}>
171
+ <div className={cx("relative grid min-w-0 gap-3", className)}>
172
172
  <div
173
173
  ref={boxRef}
174
174
  tabIndex={0}