mulmoclaude 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/mulmoclaude.js +1 -1
- package/client/assets/{JsonEditor-C_RDoefj.js → JsonEditor-o5--tPQH.js} +1 -1
- package/client/assets/{index-BG_JJcKI.js → index-9lmYSaus.js} +246 -215
- package/client/assets/index-tOu5ArRZ.css +2 -0
- package/client/assets/{lib-Dpph7PBN.js → lib-D6Xy0IFc.js} +1 -1
- package/client/assets/{marp-cCGismx0.js → marp-D6GXA-EB.js} +67 -67
- package/client/assets/purify.es-B27wDFIb-51iYcXuK.js +3 -0
- package/client/assets/runtime-protocol-vue-pU0Mw7Zm.js +1 -0
- package/client/index.html +6 -6
- package/package.json +28 -15
- package/server/agent/activeTools.ts +5 -1
- package/server/agent/backend/claude-code.ts +22 -3
- package/server/agent/backend/fake-echo.ts +2 -1
- package/server/agent/backgroundSessions.ts +45 -0
- package/server/agent/config.ts +105 -12
- package/server/agent/mcp-server.ts +137 -84
- package/server/agent/mcp-tools/index.ts +10 -2
- package/server/agent/mcp-tools/manageCollection.ts +476 -0
- package/server/agent/mcp-tools/spawnBackgroundChat.ts +135 -0
- package/server/agent/stdioHttpShim.ts +2 -1
- package/server/api/auth/viewToken.ts +146 -0
- package/server/api/routes/agent.ts +88 -55
- package/server/api/routes/chart.ts +21 -92
- package/server/api/routes/collections.ts +269 -7
- package/server/api/routes/pdf.ts +38 -68
- package/server/api/routes/plugins.ts +36 -5
- package/server/api/routes/presentHtml.ts +52 -72
- package/server/api/routes/runtime-plugin.ts +18 -1
- package/server/api/routes/sessions.ts +7 -1
- package/server/api/routes/transcribe.ts +100 -0
- package/server/build/dispatcher.mjs +14963 -54
- package/server/events/collection-change.ts +70 -0
- package/server/events/file-change.ts +41 -70
- package/server/events/scheduler-adapter.ts +23 -298
- package/server/events/task-manager/index.ts +17 -176
- package/server/index.ts +51 -6
- package/server/notifier/engine.ts +42 -503
- package/server/notifier/types.ts +16 -134
- package/server/plugins/builtin-dispatch.ts +28 -0
- package/server/plugins/html-builtin.ts +28 -0
- package/server/plugins/markdown-builtin.ts +70 -0
- package/server/plugins/runtime.ts +12 -1
- package/server/services/translation/llm.ts +2 -1
- package/server/system/config.ts +32 -0
- package/server/system/logs/aaa +737 -0
- package/server/system/logs/bb +446 -0
- package/server/system/optionalDeps.ts +4 -0
- package/server/system/whisper/index.ts +97 -0
- package/server/utils/claudeBin.ts +248 -0
- package/server/utils/files/attachment-store.ts +33 -57
- package/server/utils/files/html-store.ts +11 -10
- package/server/utils/files/image-store.ts +8 -41
- package/server/utils/files/markdown-image-fill.ts +28 -57
- package/server/utils/files/markdown-store.ts +11 -10
- package/server/utils/files/path-validator.ts +49 -0
- package/server/utils/files/safe.ts +89 -0
- package/server/utils/files/spreadsheet-store.ts +6 -35
- package/server/utils/files/store-resolvers.ts +58 -0
- package/server/utils/files/svg-store.ts +4 -10
- package/server/utils/httpError.ts +12 -0
- package/server/workspace/chat-index/summarizer.ts +2 -1
- package/server/workspace/collections/configure.ts +25 -0
- package/server/workspace/collections/index.ts +19 -3
- package/server/workspace/collections/notifications.ts +60 -386
- package/server/workspace/collections/types.ts +6 -334
- package/server/workspace/collections/watcher.ts +18 -397
- package/server/workspace/feeds/engine.ts +14 -6
- package/server/workspace/feeds/ingestTypes.ts +12 -16
- package/server/workspace/feeds/registry.ts +1 -1
- package/server/workspace/hooks/handlers/skillBridge.ts +45 -229
- package/server/workspace/journal/archivist-cli.ts +9 -8
- package/server/workspace/paths.ts +8 -1
- package/server/workspace/skills-preset.ts +15 -582
- package/server/workspace/workspace.ts +8 -14
- package/src/App.vue +122 -38
- package/src/components/ChatAttachmentPreview.vue +8 -1
- package/src/components/ChatInput.vue +211 -43
- package/src/components/ConfirmModal.vue +39 -31
- package/src/components/FileContentRenderer.vue +38 -40
- package/src/components/NotificationBell.vue +64 -16
- package/src/components/SettingsModal.vue +7 -2
- package/src/components/SettingsVoiceTab.vue +183 -0
- package/src/components/collectionTypes.ts +33 -177
- package/src/composables/collections/uiHost.ts +154 -0
- package/src/composables/useDynamicFavicon.ts +3 -2
- package/src/composables/useFileDropZone.ts +3 -6
- package/src/composables/usePluginErrorBoundary.ts +2 -1
- package/src/composables/useVoiceInput.ts +108 -0
- package/src/config/apiRoutes.ts +27 -0
- package/src/config/pubsubChannels.ts +27 -0
- package/src/config/roles.ts +9 -10
- package/src/config/toolNames.ts +4 -0
- package/src/lang/de.ts +27 -96
- package/src/lang/en.ts +26 -96
- package/src/lang/es.ts +26 -96
- package/src/lang/fr.ts +26 -97
- package/src/lang/ja.ts +26 -96
- package/src/lang/ko.ts +24 -96
- package/src/lang/pt-BR.ts +25 -96
- package/src/lang/zh.ts +24 -96
- package/src/main.ts +3 -0
- package/src/plugins/chart/definition.ts +10 -61
- package/src/plugins/chart/index.ts +21 -26
- package/src/plugins/markdown/definition.ts +11 -78
- package/src/plugins/markdown/index.ts +24 -6
- package/src/plugins/presentCollection/definition.ts +10 -23
- package/src/plugins/presentCollection/index.ts +6 -4
- package/src/plugins/presentCollection/plugin.ts +6 -39
- package/src/plugins/presentCollection/types.ts +4 -13
- package/src/plugins/presentForm/definition.ts +7 -121
- package/src/plugins/presentForm/index.ts +20 -11
- package/src/plugins/presentForm/plugin.ts +4 -92
- package/src/plugins/presentForm/types.ts +18 -85
- package/src/plugins/presentHtml/definition.ts +12 -24
- package/src/plugins/presentHtml/index.ts +21 -9
- package/src/plugins/spreadsheet/View.vue +9 -3
- package/src/plugins/spreadsheet/definition.ts +2 -1
- package/src/plugins/spreadsheet/engine/calculator.ts +2 -1
- package/src/types/session.ts +7 -0
- package/src/utils/agent/eventDispatch.ts +4 -0
- package/src/utils/collections/notifiedItems.ts +65 -0
- package/src/utils/collections/presentSeed.ts +98 -0
- package/src/utils/errors.ts +14 -0
- package/src/utils/html/customViewSrcdoc.ts +88 -0
- package/src/utils/html/previewCsp.ts +63 -8
- package/src/utils/id.ts +3 -4
- package/src/utils/image/rewriteMarkdownImageRefs.ts +3 -1
- package/client/assets/index-DCoo3kpR.css +0 -2
- package/client/assets/purify.es-Fx1Nqyry-BufT4RJl.js +0 -2
- package/client/assets/runtime-protocol-vue-D6kcV0wa.js +0 -1
- package/server/agent/mcp-tools/x.ts +0 -210
- package/server/notifier/store.ts +0 -70
- package/server/workspace/collections/delete.ts +0 -186
- package/server/workspace/collections/discovery.ts +0 -730
- package/server/workspace/collections/io.ts +0 -287
- package/server/workspace/collections/paths.ts +0 -125
- package/server/workspace/collections/spawn.ts +0 -213
- package/server/workspace/collections/templatePath.ts +0 -36
- package/server/workspace/helps/billing-clients-worklog.md +0 -215
- package/server/workspace/helps/billing-invoice.md +0 -457
- package/server/workspace/helps/business.md +0 -104
- package/server/workspace/helps/collection-skills.md +0 -664
- package/server/workspace/helps/feeds.md +0 -110
- package/server/workspace/helps/gemini.md +0 -57
- package/server/workspace/helps/github.md +0 -23
- package/server/workspace/helps/guide.md +0 -61
- package/server/workspace/helps/index.md +0 -73
- package/server/workspace/helps/mulmoscript.md +0 -249
- package/server/workspace/helps/portfolio-tracker.md +0 -211
- package/server/workspace/helps/presentation-deck.md +0 -828
- package/server/workspace/helps/presenthtml.md +0 -80
- package/server/workspace/helps/sandbox.md +0 -97
- package/server/workspace/helps/spreadsheet.md +0 -43
- package/server/workspace/helps/storyteller.md +0 -101
- package/server/workspace/helps/telegram.md +0 -136
- package/server/workspace/helps/todo-collection.md +0 -140
- package/server/workspace/helps/vocabulary.md +0 -106
- package/server/workspace/helps/wiki.md +0 -168
- package/server/workspace/skills-preset/mc-cooking-coach/SKILL.md +0 -217
- package/server/workspace/skills-preset/mc-library/SKILL.md +0 -188
- package/server/workspace/skills-preset/mc-manage-automations/SKILL.md +0 -119
- package/server/workspace/skills-preset/mc-manage-skills/SKILL.md +0 -141
- package/server/workspace/skills-preset/mc-wiki-deep-lint/SKILL.md +0 -108
- package/server/workspace/skills-preset/mc-wiki-health-check/SKILL.md +0 -61
- package/server/workspace/skills-preset/mc-wiki-ingest/SKILL.md +0 -182
- package/server/workspace/skills-preset/mc-wiki-promote/SKILL.md +0 -175
- package/src/components/CollectionCalendarView.vue +0 -243
- package/src/components/CollectionDashboardView.vue +0 -181
- package/src/components/CollectionDayView.vue +0 -308
- package/src/components/CollectionEmbedView.vue +0 -69
- package/src/components/CollectionKanbanView.vue +0 -196
- package/src/components/CollectionRecordModal.vue +0 -93
- package/src/components/CollectionRecordPanel.vue +0 -567
- package/src/components/CollectionView.vue +0 -1748
- package/src/components/CollectionsIndexView.vue +0 -152
- package/src/components/FeedsView.vue +0 -225
- package/src/components/collectionEmbed.ts +0 -29
- package/src/composables/collections/useCollectionRendering.ts +0 -350
- package/src/plugins/chart/Preview.vue +0 -49
- package/src/plugins/chart/View.vue +0 -148
- package/src/plugins/markdown/MarpView.vue +0 -301
- package/src/plugins/markdown/Preview.vue +0 -102
- package/src/plugins/markdown/View.vue +0 -869
- package/src/plugins/presentCollection/Preview.vue +0 -30
- package/src/plugins/presentCollection/View.vue +0 -78
- package/src/plugins/presentForm/Preview.vue +0 -88
- package/src/plugins/presentForm/View.vue +0 -682
- package/src/plugins/presentHtml/Preview.vue +0 -18
- package/src/plugins/presentHtml/View.vue +0 -431
- package/src/utils/collections/actionVisible.ts +0 -55
- package/src/utils/collections/calendarGrid.ts +0 -328
- package/src/utils/collections/collectionViewMode.ts +0 -42
- package/src/utils/collections/derivedFormula.ts +0 -364
- package/src/utils/collections/draft.ts +0 -160
- package/src/utils/collections/enumColors.ts +0 -130
- package/src/utils/collections/itemLabel.ts +0 -42
- /package/client/assets/{_plugin-vue_export-helper-BOai-rQB.js → _plugin-vue_export-helper-B67ILkmu.js} +0 -0
- /package/client/assets/{schemas-DuYzyHQc.js → schemas-D_RbFtuQ.js} +0 -0
- /package/client/assets/{vue-D4w8THF_.js → vue-UDIWDtr8.js} +0 -0
|
@@ -1,664 +0,0 @@
|
|
|
1
|
-
# Collection skills — build a data app from a schema
|
|
2
|
-
|
|
3
|
-
A **collection skill** is a skill directory that ships a `schema.json` next to
|
|
4
|
-
its `SKILL.md`. The schema declares an entire data-driven app — its data model,
|
|
5
|
-
cross-record relations, rendered UI, computed fields, and per-record action
|
|
6
|
-
buttons — in one small JSON file. You author the schema, you write the records
|
|
7
|
-
(one JSON file each), and you are the runtime for any behaviour the schema
|
|
8
|
-
can't express declaratively. The host contains **zero** knowledge of any
|
|
9
|
-
specific collection: it just reads the DSL and renders a table / calendar /
|
|
10
|
-
form / detail view, and serves a REST surface. No database, no migration tool, no ORM — a
|
|
11
|
-
`schema.json` plus a folder of `<id>.json` records **is** the app.
|
|
12
|
-
|
|
13
|
-
This is the project philosophy made concrete: *the workspace is the database;
|
|
14
|
-
files are the source of truth; you are the intelligent interface.*
|
|
15
|
-
|
|
16
|
-
## Anatomy of a collection skill
|
|
17
|
-
|
|
18
|
-
You **author** the skill under `data/skills/<slug>/` (a plain, writable data
|
|
19
|
-
dir). A host-side hook then **mirrors** the files into `.claude/skills/<slug>/`,
|
|
20
|
-
which is where the host actually discovers and renders the collection from:
|
|
21
|
-
|
|
22
|
-
```
|
|
23
|
-
data/skills/<slug>/ ← YOU write here (Write / Edit)
|
|
24
|
-
SKILL.md ← instructions you read later (how to CRUD the records)
|
|
25
|
-
schema.json ← the DSL: data model + relations + UI + actions
|
|
26
|
-
templates/*.md ← natural-language bodies for actions (only if it has actions)
|
|
27
|
-
│
|
|
28
|
-
│ host's skill-bridge hook mirrors these three file kinds
|
|
29
|
-
▼
|
|
30
|
-
.claude/skills/<slug>/ ← host reads here (do NOT write here directly)
|
|
31
|
-
SKILL.md · schema.json · templates/*.md
|
|
32
|
-
|
|
33
|
-
data/<name>/items/ ← the records (separate from the skill dir)
|
|
34
|
-
<id>.json ← one record per file (you write; host reads + renders)
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
- **Author under `data/skills/<slug>/`, NEVER `.claude/skills/<slug>/`
|
|
38
|
-
directly.** Claude Code gates writes into `.claude/` (it's the agent's own
|
|
39
|
-
config surface) and the host GUI can't answer that prompt, so a direct write
|
|
40
|
-
hangs/fails. Writing under `data/skills/` has no such gate; the bridge hook
|
|
41
|
-
copies `SKILL.md`, `schema.json`, and `templates/*.md` across for you and
|
|
42
|
-
triggers a re-scan, so the collection appears at `/collections/<slug>`
|
|
43
|
-
without a restart. (Other files you drop in `data/skills/<slug>/` — a README,
|
|
44
|
-
scratch notes — stay put and are NOT mirrored.)
|
|
45
|
-
- **Do NOT use the `mc-` prefix** for skills you create. `mc-*` is reserved for
|
|
46
|
-
the bundled presets (`mc-cooking-coach`, `mc-library`, `mc-wiki-*`,
|
|
47
|
-
`mc-manage-*`); the server overwrites those on every boot, so your edits would
|
|
48
|
-
be lost. (The billing collections — `clients`, `worklog`, `invoice`, `profile`
|
|
49
|
-
— are now recipe-authored under these plain slugs, NOT `mc-` presets; see the
|
|
50
|
-
worked reference below.)
|
|
51
|
-
- **`<slug>` rules**: lowercase letters, digits, hyphens; no leading / trailing
|
|
52
|
-
hyphen; 1–64 chars (e.g. `recipes`, `book-club`, `gym-log`). It doubles as the
|
|
53
|
-
URL (`/collections/<slug>`) and the directory name.
|
|
54
|
-
- The user opens the collection at **`/collections/<slug>`**. Link a specific
|
|
55
|
-
record with `?selected=<id>` (e.g. `/collections/recipes?selected=carbonara`).
|
|
56
|
-
|
|
57
|
-
## SKILL.md
|
|
58
|
-
|
|
59
|
-
Standard skill front-matter plus prose teaching *future-you* how to maintain the
|
|
60
|
-
records. Keep it short and operational:
|
|
61
|
-
|
|
62
|
-
```markdown
|
|
63
|
-
---
|
|
64
|
-
name: recipes
|
|
65
|
-
description: A personal recipe box. Use whenever the user asks to add, list,
|
|
66
|
-
edit, or remove a recipe. Records live at `data/recipes/items/<id>.json`
|
|
67
|
-
(one JSON per recipe); the user views them at `/collections/recipes`,
|
|
68
|
-
rendered from `schema.json` by the host. You do all I/O via Read / Write /
|
|
69
|
-
Edit on the JSON files.
|
|
70
|
-
---
|
|
71
|
-
|
|
72
|
-
# Recipes (schema-driven collection)
|
|
73
|
-
|
|
74
|
-
## Record shape
|
|
75
|
-
- `id` — kebab-case slug, primary key (the filename, no extension)
|
|
76
|
-
- `title` — string, required
|
|
77
|
-
- ... (one bullet per field; note which are host-computed and must NOT be written)
|
|
78
|
-
|
|
79
|
-
## What to do
|
|
80
|
-
**Add / List / Update / Delete** — derive an id, Read/Write/Edit the JSON.
|
|
81
|
-
List the directory first and pick a fresh slug rather than overwriting.
|
|
82
|
-
Don't recite the whole table in chat. After adding or updating a record,
|
|
83
|
-
call `presentCollection` (with the collection slug and the record's id) to
|
|
84
|
-
show it inline; for a plain "show/list" request, call `presentCollection`
|
|
85
|
-
with just the slug.
|
|
86
|
-
```
|
|
87
|
-
|
|
88
|
-
Write the `description` so it tells *you* (in a future session) exactly when to
|
|
89
|
-
reach for this skill and where the records live — that text is what gets matched
|
|
90
|
-
when the user makes a request.
|
|
91
|
-
|
|
92
|
-
## schema.json — the DSL
|
|
93
|
-
|
|
94
|
-
Top-level shape (validated on discovery; a malformed schema is logged and
|
|
95
|
-
skipped, never crashes the host):
|
|
96
|
-
|
|
97
|
-
| Key | Meaning |
|
|
98
|
-
|---|---|
|
|
99
|
-
| `title` | Human name shown in the sidebar / header. Required. |
|
|
100
|
-
| `icon` | A **Material Symbols** name (`receipt_long`, `people`, `schedule`, `menu_book`). Required. |
|
|
101
|
-
| `dataPath` | Workspace-relative records folder, e.g. `data/recipes/items`. Must stay under the workspace. Required. |
|
|
102
|
-
| `primaryKey` | The field name whose value is the filename. That field MUST set `primary: true`. Required. |
|
|
103
|
-
| `singleton` | Optional. When set, at most one record exists, pinned to this exact id (e.g. `me`). Host pre-fills + locks the create form and hides Add once it exists. |
|
|
104
|
-
| `fields` | Ordered map of field-name → field spec. **Insertion order = column order** in the table. Required. |
|
|
105
|
-
| `actions` | Optional array of per-record buttons (see below). |
|
|
106
|
-
| `completionField` | Optional. Name of the field whose value marks an item as "done" — when set, item-create fires a bell notification that clears once the field reaches one of `completionDoneValues`. Must name a real field in `fields`. Paired with `completionDoneValues` (both set, or both omitted). |
|
|
107
|
-
| `completionDoneValues` | Optional. Non-empty array of values that count as "done" for `completionField` (e.g. `["Done"]`, `["paid", "void"]`). Compared as strings. |
|
|
108
|
-
| `notifyWhen` | Optional. A `when` predicate (`{ "field": "...", "in": [...] }`) that **gates** the completion bell: fire it only for records matching the predicate (e.g. `{ "field": "priority", "in": ["high", "urgent"] }`). Requires `completionField`; `field` must name a real field. Absent ⇒ notify for every open record. |
|
|
109
|
-
| `displayField` | Optional. Name of a field whose value is shown as the human-readable label in the completion notification's title (e.g. `Contacts: Jane Doe` instead of the opaque primaryKey). Must name a real field in `fields`. Falls back to the primaryKey value when unset or when the record's value is empty. |
|
|
110
|
-
| `triggerField` | Optional. Name of a `date` field that **delays** the completion bell until that date arrives (instead of firing on create). Requires `completionField` / `completionDoneValues` (the bell still clears via the done value). Must name a real `date` field. See "Time-gated bells" below. |
|
|
111
|
-
| `triggerLeadDays` | Optional. Non-negative integer: fire the bell this many days **before** `triggerField` (e.g. `10` = "remind me 10 days early"). Requires `triggerField`. Default `0` (fire on the trigger date). |
|
|
112
|
-
| `spawn` | Optional. Host-driven **recurrence**: when a record reaches a configured value (e.g. `status: paid`), the host auto-creates the next record with a forward-advanced `triggerField` date. Requires `triggerField`. See "Recurring obligations" below. |
|
|
113
|
-
| `calendarField` | Optional. Name of a `date` (or `datetime`) field that anchors the **calendar view** (a month grid; each record lands on its date cell). When unset, the table↔calendar toggle still appears if the schema has any `date`/`datetime` field — the first one is used, switchable in-view. Set this to pin a specific anchor. Must name a real `date`/`datetime` field. See "Calendar view" below. |
|
|
114
|
-
| `calendarEndField` | Optional. A second `date`/`datetime` field marking the END of a multi-day span on the calendar (the record renders across `calendarField` → this date). Requires `calendarField`. Must name a real `date`/`datetime` field. |
|
|
115
|
-
| `calendarTimeField` | Optional. Name of a string field holding a free-form time or time-range (`"14:00-17:00"`, `"17:00-"`, `"16:30"`) used to place records on the calendar's **day (time-allocation) view**. Consulted only when the date fields are date-only — a `datetime` anchor/end pair carries its own clock and takes precedence. Requires `calendarField`. See "Calendar view" below. |
|
|
116
|
-
| `kanbanField` | Optional. Name of an `enum` field that groups records into columns on the **Kanban board** (one column per declared value). When unset, the Kanban toggle still appears if the schema has any `enum` field — the first one is used, switchable in-view. Set this to pin a specific group field. Must name a real `enum` field. See "Kanban view" below. |
|
|
117
|
-
|
|
118
|
-
### Field types
|
|
119
|
-
|
|
120
|
-
`string` · `text` (multi-line) · `email` · `number` · `date` (`YYYY-MM-DD`) ·
|
|
121
|
-
`datetime` (`YYYY-MM-DDTHH:MM`) · `boolean` · `markdown` · `money` · `enum` ·
|
|
122
|
-
`ref` · `embed` · `table` · `derived` · `image` · `file` · `toggle`
|
|
123
|
-
|
|
124
|
-
Every field spec needs a `type` and a `label`. Extra keys by type:
|
|
125
|
-
|
|
126
|
-
- **`datetime`** — no extra keys. Stored as a `YYYY-MM-DDTHH:MM` string and
|
|
127
|
-
edited with a native date+time picker. Use it (as `calendarField` /
|
|
128
|
-
`calendarEndField`) when an event has a real start/end clock — the calendar's
|
|
129
|
-
day view then draws each record as a proportional time block. For the common
|
|
130
|
-
"date column + separate time column" shape, keep `date` and point
|
|
131
|
-
`calendarTimeField` at the time string instead.
|
|
132
|
-
- **`enum`** — `values: ["draft", "sent", "paid"]` (non-empty strings). Renders
|
|
133
|
-
a `<select>`; stored as a plain string.
|
|
134
|
-
- **`money`** — `currency: "USD"` (ISO 4217, defaults to USD). Stored as a plain
|
|
135
|
-
decimal; currency is display-only.
|
|
136
|
-
- **`ref`** — `to: "<target-slug>"`. Stores the target record's primary-key
|
|
137
|
-
slug; host renders a clickable link + a dropdown picker populated from the
|
|
138
|
-
target collection. Example: `{ "type": "ref", "to": "clients", "label": "Client" }`.
|
|
139
|
-
A `derived` field on the same record can also **dereference** a `ref` to read
|
|
140
|
-
a numeric column off the record it points at — see the cross-collection
|
|
141
|
-
formula syntax below.
|
|
142
|
-
- **`embed`** — `to: "<target-slug>"`, `id: "<record-id>"`. Pulls a *fixed*
|
|
143
|
-
record from another collection into the read-only detail view (display-only,
|
|
144
|
-
**nothing is stored** on this record). Example: an invoice embedding the
|
|
145
|
-
user's own profile: `{ "type": "embed", "to": "profile", "id": "me" }`.
|
|
146
|
-
- **`table`** — `of: { <col>: <sub-field-spec>, ... }`. An array of rows. Each
|
|
147
|
-
sub-field is a flat spec; sub-fields **cannot** be `table` or `derived`
|
|
148
|
-
(no nested tables, no computed columns).
|
|
149
|
-
- **`derived`** — `formula: "<expr>"`, optional `display` (`number` default, or
|
|
150
|
-
`money` / `string` / `date`) and `currency`. **Read-only, host-computed** —
|
|
151
|
-
you NEVER write derived values into the JSON; the host recomputes them on
|
|
152
|
-
every render and the form refuses to persist them.
|
|
153
|
-
- **`image`** — stores a **workspace-relative image path** as a plain string
|
|
154
|
-
(e.g. `data/attachments/2026/05/<id>.jpg` — the exact path from an
|
|
155
|
-
`[Attached file: ...]` marker when the user attaches a photo). The host
|
|
156
|
-
renders it as an `<img>` in the **detail view** (it is intentionally not a
|
|
157
|
-
list-table column — a per-row image fetch is too expensive for a large
|
|
158
|
-
collection). No extra keys. Great for photos like a business card: read the
|
|
159
|
-
details off the attached image and write its path into the image field.
|
|
160
|
-
Write the bare workspace-relative path — never an `/api/files/raw?...` URL.
|
|
161
|
-
- **`file`** — stores a **workspace-relative file path** as a plain string (e.g.
|
|
162
|
-
`artifacts/html/the-solar-system-1777158558023.html`). Rendered as a
|
|
163
|
-
**clickable link** in both the list table and the detail view (unlike `image`,
|
|
164
|
-
which is detail-only — a link is cheap per-row). Clicking an HTML or SVG
|
|
165
|
-
artifact opens its **rendered** form in a new browser tab (the live app /
|
|
166
|
-
drawing); any other path opens in the **File Explorer**. No extra keys. Ideal
|
|
167
|
-
for a "my apps" collection where each record points at a generated HTML app —
|
|
168
|
-
the user launches it straight from the row. Write the bare workspace-relative
|
|
169
|
-
path — never an `/artifacts/...` or `/api/files/raw?...` URL.
|
|
170
|
-
- **`toggle`** — `field: "<enum-field>"`, `onValue`, `offValue`. A checkbox that
|
|
171
|
-
is a pure **projection** of an `enum` field — it **stores nothing** of its own
|
|
172
|
-
(like `derived`/`embed`). Checked when the projected enum equals `onValue`;
|
|
173
|
-
toggling writes `onValue` / `offValue` back to that enum. `onValue`/`offValue`
|
|
174
|
-
must be members of the enum's `values`. Use it to front a kanban `status` with
|
|
175
|
-
a "done" checkbox while keeping the enum as the single source of truth — e.g.
|
|
176
|
-
`{ "type": "toggle", "label": "Done", "field": "status", "onValue": "Done", "offValue": "Todo" }`.
|
|
177
|
-
Renders an interactive checkbox in the list table and on the kanban card (when
|
|
178
|
-
it projects the board's group field); read-only in the detail view.
|
|
179
|
-
**A todo / task list should almost always include one** — it's the row/card
|
|
180
|
-
"done" checkbox. Without it, `status` only shows as a dropdown and a kanban
|
|
181
|
-
column, with no checkbox to tick. `offValue` is the status to return to on
|
|
182
|
-
uncheck (the default open column, e.g. `"Todo"`).
|
|
183
|
-
|
|
184
|
-
### Conditional field visibility (`when`)
|
|
185
|
-
|
|
186
|
-
Any field may carry an optional `when: { field, in: [...] }` predicate to hide
|
|
187
|
-
itself until another field on the same record matches — the same shape used to
|
|
188
|
-
gate `actions`. The field shows only when `String(record[when.field])` is one of
|
|
189
|
-
`in`; absent ⇒ always shown. `when.field` MUST name another top-level field
|
|
190
|
-
(validated on discovery).
|
|
191
|
-
|
|
192
|
-
```json
|
|
193
|
-
"visited": { "type": "boolean", "label": "Visited" },
|
|
194
|
-
"rating": { "type": "number", "label": "Rating", "when": { "field": "visited", "in": ["true"] } }
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Here `rating` stays hidden until `visited` is checked (booleans stringify, so
|
|
198
|
-
match `"true"` / `"false"`). The gate applies everywhere the field renders: the
|
|
199
|
-
list cell goes **blank**, the edit-form input hides/shows **live** as the gating
|
|
200
|
-
field changes, and the detail view omits it. It is **purely presentational** —
|
|
201
|
-
a hidden field's stored value is never cleared, so re-matching the gate restores
|
|
202
|
-
it. Use it for fields that only make sense in a given state (a rating before
|
|
203
|
-
you've visited, a shipped-date before an order ships). Only honoured on
|
|
204
|
-
top-level fields, not inside a `table`'s `of`.
|
|
205
|
-
|
|
206
|
-
### Derived-formula syntax
|
|
207
|
-
|
|
208
|
-
A tiny expression evaluated against the record (pure evaluator, no `eval`;
|
|
209
|
-
returns `null` on any failure). Supported:
|
|
210
|
-
|
|
211
|
-
- arithmetic `+ - * /` and parentheses
|
|
212
|
-
- identifier references to **top-level** fields (`subtotal * taxRate`)
|
|
213
|
-
- `sum(tableField[].col)` — sum a column across table rows
|
|
214
|
-
- `sum(tableField[].col * tableField[].col)` — sum a per-row product
|
|
215
|
-
- `<refField>.<col>` — **dereference a `ref` field** and read a numeric column
|
|
216
|
-
off the record it points at (a live cross-collection lookup)
|
|
217
|
-
|
|
218
|
-
Example: `subtotal` = `sum(lineItems[].quantity * lineItems[].rate)`,
|
|
219
|
-
`tax` = `subtotal * taxRate`, `total` = `subtotal + tax`.
|
|
220
|
-
|
|
221
|
-
#### Cross-collection lookups (`<refField>.<col>`)
|
|
222
|
-
|
|
223
|
-
When a field is a `ref` to another collection, a `derived` formula can reach
|
|
224
|
-
into the referenced record and pull a numeric column out of it. This lets one
|
|
225
|
-
collection compute against data **owned by another** without copying that data.
|
|
226
|
-
|
|
227
|
-
Canonical use — a portfolio that values holdings against a separate price list:
|
|
228
|
-
|
|
229
|
-
```jsonc
|
|
230
|
-
// my-portfolio/schema.json (one record per holding)
|
|
231
|
-
"fields": {
|
|
232
|
-
"ticker": { "type": "ref", "to": "stock-quotes", "label": "Stock" }, // stores e.g. "AAPL"
|
|
233
|
-
"shares": { "type": "number", "label": "Shares" },
|
|
234
|
-
"value": { "type": "derived", "formula": "shares * ticker.price", // ← reads price from the quotes row
|
|
235
|
-
"display": "money", "currency": "USD", "label": "Value" }
|
|
236
|
-
}
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Here `ticker.price` resolves the `ticker` ref to its `stock-quotes` record and
|
|
240
|
-
reads that record's `price`. `price` lives **only** in `stock-quotes`; the
|
|
241
|
-
portfolio never stores a copy, so a quote refresh in `stock-quotes` is the
|
|
242
|
-
single source of truth for every holding's value.
|
|
243
|
-
|
|
244
|
-
Rules and limits:
|
|
245
|
-
|
|
246
|
-
- The left side must be a `ref` field **on this same record**; the right side is
|
|
247
|
-
a single column name. Only the `<field>.<col>` shape — no `a.b.c` chains, no
|
|
248
|
-
dereferencing inside `sum(...)`.
|
|
249
|
-
- The referenced column must hold a number (or a numeric string). A missing
|
|
250
|
-
column, a non-numeric value, or a **dangling ref** (the slug points at a row
|
|
251
|
-
that doesn't exist) makes the whole formula fail soft to an em-dash (`—`),
|
|
252
|
-
same as any other formula error.
|
|
253
|
-
- The referenced column may itself be a **`derived`** field in the target
|
|
254
|
-
collection (the host computes the target's own derived fields before the
|
|
255
|
-
lookup) — *as long as* that target formula is target-local. A target derived
|
|
256
|
-
field that in turn derefs a **third** collection won't resolve (only one hop
|
|
257
|
-
is loaded) and reads as `—`.
|
|
258
|
-
- The target collection is loaded **when the page opens**. If a value changes in
|
|
259
|
-
the target while the viewing collection is already open (e.g. you refresh a
|
|
260
|
-
price in `stock-quotes` in another tab), the derived value updates on the next
|
|
261
|
-
reload — not instantly across tabs.
|
|
262
|
-
- It's still per-record: each holding computes `shares * ticker.price` from its
|
|
263
|
-
own `ticker`/`shares`. To total the portfolio, add a one-record summary
|
|
264
|
-
collection or read the values off the list view — there is no cross-row sum
|
|
265
|
-
over a joined column.
|
|
266
|
-
|
|
267
|
-
### Actions (per-record buttons)
|
|
268
|
-
|
|
269
|
-
Each entry in `actions` renders a button in the read-only detail view. The only
|
|
270
|
-
`kind` today is `"chat"`: clicking it starts a **new chat in a role**, seeded
|
|
271
|
-
with a template + the record data — the role then does the work with its tools.
|
|
272
|
-
This is how hard logic the schema can't express (PDF generation, bookkeeping
|
|
273
|
-
journals, drafting an email) gets delegated to natural language.
|
|
274
|
-
|
|
275
|
-
```json
|
|
276
|
-
{
|
|
277
|
-
"id": "pdf", // unique within the schema
|
|
278
|
-
"label": "Generate PDF", // button text (English)
|
|
279
|
-
"icon": "picture_as_pdf", // Material Symbols name
|
|
280
|
-
"kind": "chat",
|
|
281
|
-
"role": "accounting", // which role the new chat runs in
|
|
282
|
-
"template": "templates/invoice.md", // skill-relative; no `..`, no leading `/`
|
|
283
|
-
"when": { "field": "status", "in": ["paid"] } // optional: show only when record.status ∈ {paid}
|
|
284
|
-
}
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
- `template` is a path **inside the skill dir** (host reads it path-safely).
|
|
288
|
-
Write the action's instructions there in plain English; the host prepends the
|
|
289
|
-
record JSON as sanitized, passive data and hands the whole thing to the role.
|
|
290
|
-
- `when` is both the visibility rule **and** the authorization rule — the host
|
|
291
|
-
re-checks it server-side, so a button gated on `status: paid` can't be invoked
|
|
292
|
-
for a draft. Omit `when` ⇒ always shown.
|
|
293
|
-
- You do **not** trigger actions yourself; point the user at the button.
|
|
294
|
-
|
|
295
|
-
### Completion tracking (bell notifications)
|
|
296
|
-
|
|
297
|
-
Declare `completionField` + `completionDoneValues` at the top level of the
|
|
298
|
-
schema to wire a record's lifecycle into the bell:
|
|
299
|
-
|
|
300
|
-
```json
|
|
301
|
-
{
|
|
302
|
-
"title": "Todos",
|
|
303
|
-
"icon": "check_circle",
|
|
304
|
-
"dataPath": "data/todos/items",
|
|
305
|
-
"primaryKey": "id",
|
|
306
|
-
"fields": {
|
|
307
|
-
"id": { "type": "string", "label": "ID", "primary": true, "required": true },
|
|
308
|
-
"title": { "type": "string", "label": "Title", "required": true },
|
|
309
|
-
"status": { "type": "enum", "values": ["Todo", "Doing", "Done"], "label": "Status", "required": true }
|
|
310
|
-
},
|
|
311
|
-
"completionField": "status",
|
|
312
|
-
"completionDoneValues": ["Done"],
|
|
313
|
-
"displayField": "title"
|
|
314
|
-
}
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
Behaviour:
|
|
318
|
-
|
|
319
|
-
- **On create** the host fires a bell notification (titled
|
|
320
|
-
`<schema.title>: <label>`, where `<label>` is the record's `displayField`
|
|
321
|
-
value when declared — falling back to the primaryKey `<id>` otherwise;
|
|
322
|
-
click-navigates to `/collections/<slug>?selected=<id>` so the item's detail
|
|
323
|
-
opens) — unless the new record is **born done** (its `completionField` value
|
|
324
|
-
is already in `completionDoneValues`), in which case nothing fires. The entry
|
|
325
|
-
is published with `lifecycle: "action"` so it persists prominently in the
|
|
326
|
-
bell until the obligation resolves.
|
|
327
|
-
- **On update** the host clears the notification when `completionField`
|
|
328
|
-
transitions **into** a done value. Un-completing (Done → Todo) does NOT
|
|
329
|
-
re-fire; firing is bound to create, by design.
|
|
330
|
-
- **On delete** the host clears any matching notification so a removed record
|
|
331
|
-
can't leak a stale entry.
|
|
332
|
-
|
|
333
|
-
The pair is bundled — declaring one without the other fails schema validation.
|
|
334
|
-
`completionField` must name a real field; a typo is rejected at load. Works
|
|
335
|
-
with any field type whose stringified value is comparable (`enum`, `string`,
|
|
336
|
-
`boolean`, …) — e.g. `completionField: "status"` + `completionDoneValues:
|
|
337
|
-
["paid", "void"]` on an invoice, or `completionField: "shipped"` +
|
|
338
|
-
`completionDoneValues: ["true"]` on an order.
|
|
339
|
-
|
|
340
|
-
Set `displayField` to make the bell title readable: with `displayField:
|
|
341
|
-
"title"` the notification reads `Todos: Buy milk` instead of `Todos: t-0042`.
|
|
342
|
-
It must name a real field; an empty value on a given record falls back to the
|
|
343
|
-
primaryKey for that record.
|
|
344
|
-
|
|
345
|
-
> **Building a todo / task list?** When your `completionField` is a status enum,
|
|
346
|
-
> also add a `toggle` field (the row/card "done" checkbox) and `notifyWhen`
|
|
347
|
-
> (fire the bell only for high-priority items, not every record). See the full
|
|
348
|
-
> recipe in **"Worked example: a Todo list"** below — that's the canonical
|
|
349
|
-
> template; don't reinvent it from these fragments.
|
|
350
|
-
|
|
351
|
-
### Time-gated bells (`triggerField`)
|
|
352
|
-
|
|
353
|
-
By default the completion bell fires **on create**. Add `triggerField` — the
|
|
354
|
-
name of a `date` field — to instead **hold the bell until that date arrives**.
|
|
355
|
-
The item is still tracked, but its bell stays silent while the trigger date is
|
|
356
|
-
in the future and appears once the clock reaches it (compared at day
|
|
357
|
-
granularity in the server's local timezone). It clears the same way as any
|
|
358
|
-
completion bell — when `completionField` reaches a `completionDoneValues` value.
|
|
359
|
-
|
|
360
|
-
```json
|
|
361
|
-
{
|
|
362
|
-
"title": "Reminders",
|
|
363
|
-
"icon": "event",
|
|
364
|
-
"dataPath": "data/reminders/items",
|
|
365
|
-
"primaryKey": "id",
|
|
366
|
-
"fields": {
|
|
367
|
-
"id": { "type": "string", "label": "ID", "primary": true, "required": true },
|
|
368
|
-
"what": { "type": "string", "label": "What", "required": true },
|
|
369
|
-
"dueOn": { "type": "date", "label": "Remind on", "required": true },
|
|
370
|
-
"status": { "type": "enum", "values": ["pending", "done"], "label": "Status", "required": true }
|
|
371
|
-
},
|
|
372
|
-
"completionField": "status",
|
|
373
|
-
"completionDoneValues": ["done"],
|
|
374
|
-
"displayField": "what",
|
|
375
|
-
"triggerField": "dueOn"
|
|
376
|
-
}
|
|
377
|
-
```
|
|
378
|
-
|
|
379
|
-
This is the "nudge me about this on date X, until I mark it done" pattern. Notes:
|
|
380
|
-
|
|
381
|
-
- `triggerField` **requires** the completion pair (validation rejects it
|
|
382
|
-
otherwise — there'd be no bell to gate or clear).
|
|
383
|
-
- The named field must be type `date`; its value is parsed as `YYYY-MM-DD`.
|
|
384
|
-
- Firing is **derived from the clock, not stored** — so if the server was down
|
|
385
|
-
when the date passed, the bell simply appears at the next boot/check. Pushing
|
|
386
|
-
the date back into the future retracts a bell that already fired.
|
|
387
|
-
- Granularity is whole days (no time-of-day).
|
|
388
|
-
|
|
389
|
-
#### Lead time — fire it early (`triggerLeadDays`)
|
|
390
|
-
|
|
391
|
-
Keep `triggerField` as the **real** due date and add `triggerLeadDays` to fire
|
|
392
|
-
the bell some days ahead of it. "Remind me 10 days before rent is due":
|
|
393
|
-
|
|
394
|
-
```json
|
|
395
|
-
"triggerField": "dueOn",
|
|
396
|
-
"triggerLeadDays": 10
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
The bell now appears once the clock reaches `dueOn − 10 days`, and still clears
|
|
400
|
-
when the item is marked done. The lead is applied at fire time (not stored), so
|
|
401
|
-
it **composes with `spawn`**: every recurred month fires 10 days before its own
|
|
402
|
-
`dueOn`, with no extra bookkeeping. It's a non-negative whole number of days and
|
|
403
|
-
requires `triggerField`. This is a single earlier bell — not an escalating
|
|
404
|
-
multi-stage reminder (info → warning → urgent), which is intentionally out of
|
|
405
|
-
scope for collections.
|
|
406
|
-
|
|
407
|
-
### Recurring obligations (`spawn`)
|
|
408
|
-
|
|
409
|
-
Add a `spawn` block to make a collection **recur**: when a record satisfies a
|
|
410
|
-
predicate (by default, when it becomes "done"), the host automatically creates
|
|
411
|
-
the **next** record with its `triggerField` advanced. Combined with
|
|
412
|
-
`triggerField`, this expresses periodic obligations — rent, subscriptions,
|
|
413
|
-
renewals, recurring payments — with no work from you per cycle: mark this
|
|
414
|
-
month's rent `paid`, and next month's pending record appears on its own.
|
|
415
|
-
|
|
416
|
-
```json
|
|
417
|
-
"triggerField": "dueOn",
|
|
418
|
-
"spawn": {
|
|
419
|
-
"when": { "field": "status", "in": ["paid"] },
|
|
420
|
-
"every": { "unit": "month", "interval": 1, "dayOfMonth": 10 },
|
|
421
|
-
"carry": ["amount", "payee"],
|
|
422
|
-
"set": { "status": "pending" }
|
|
423
|
-
}
|
|
424
|
-
```
|
|
425
|
-
|
|
426
|
-
- **`when`** — a `{ field, in: [...] }` predicate (same shape as field/action
|
|
427
|
-
`when`) that fires the spawn. Omit it to default to "the completion-done
|
|
428
|
-
condition" (i.e. spawn when this record is done).
|
|
429
|
-
- **`every`** — how to advance `triggerField` from this record to the next:
|
|
430
|
-
- `unit`: `day` · `week` · `month` · `year`; `interval`: a positive integer
|
|
431
|
-
(so `unit: "month", interval: 3` = quarterly, `unit: "year", interval: 1`
|
|
432
|
-
= annual).
|
|
433
|
-
- `dayOfMonth` (month/year only): the **canonical** day-of-month anchor
|
|
434
|
-
(1–31, or `"last"` for the month's last day). Use it for day ≥ 29 so
|
|
435
|
-
short months don't cause drift — "31st of every month" yields
|
|
436
|
-
31 → 28/29 → 31 → 30 … correctly. Omit it for days ≤ 28 and the source
|
|
437
|
-
date's day is preserved.
|
|
438
|
-
- **`carry`** — record fields copied verbatim onto the successor (must name
|
|
439
|
-
real fields). Fields not in `carry` / `set` / the trigger+primary keys start
|
|
440
|
-
blank.
|
|
441
|
-
- **`set`** — fields forced to fixed values on the successor (typically
|
|
442
|
-
resetting the status to its pending value).
|
|
443
|
-
|
|
444
|
-
How it behaves (worth understanding so it doesn't surprise you):
|
|
445
|
-
|
|
446
|
-
- The successor's id is **deterministic**: `<stem>-<YYYYMMDD>` (the source id
|
|
447
|
-
with any trailing `-YYYYMMDD` replaced). So `rent` → `rent-20260610` →
|
|
448
|
-
`rent-20260710`. Creation is **create-if-absent** — it never overwrites, so
|
|
449
|
-
re-running is harmless and any edits you make to a successor are preserved.
|
|
450
|
-
- **Forward-only**: un-doing the source (e.g. `paid` → `pending`) does NOT
|
|
451
|
-
delete an already-created successor. And because spawning is convergent,
|
|
452
|
-
deleting the successor while the source still matches `when` will **re-create
|
|
453
|
-
it**. To genuinely **stop a recurrence**, move the source to a status that is
|
|
454
|
-
*not* in `spawn.when` (e.g. an `archived` value) — that's the supported "end
|
|
455
|
-
it" gesture.
|
|
456
|
-
- `spawn` **requires** `triggerField` (the successor's date is `triggerField`
|
|
457
|
-
advanced by `every`).
|
|
458
|
-
|
|
459
|
-
This covers *periodic* obligations. It does **not** do escalating, multi-stage
|
|
460
|
-
reminders over a long prep window (info → warning → urgent) — that is
|
|
461
|
-
intentionally out of scope for collections.
|
|
462
|
-
|
|
463
|
-
### Calendar view
|
|
464
|
-
|
|
465
|
-
Any collection that has at least one `date` (or `datetime`) field gains a
|
|
466
|
-
**table ↔ calendar** toggle in its header — **zero config**. The calendar is a
|
|
467
|
-
month grid where each record lands on the day cell matching its date. Clicking a
|
|
468
|
-
record **chip** opens the same detail/edit panel the table uses; clicking
|
|
469
|
-
anywhere else in a day cell opens the **day (time-allocation) view** — a popup
|
|
470
|
-
vertical timeline of that day (see below), whose **+** button starts a new record
|
|
471
|
-
prefilled to that day.
|
|
472
|
-
|
|
473
|
-
```json
|
|
474
|
-
{
|
|
475
|
-
"title": "Events",
|
|
476
|
-
"icon": "event",
|
|
477
|
-
"dataPath": "data/events/items",
|
|
478
|
-
"primaryKey": "id",
|
|
479
|
-
"fields": {
|
|
480
|
-
"id": { "type": "string", "label": "ID", "primary": true, "required": true },
|
|
481
|
-
"name": { "type": "string", "label": "Name", "required": true },
|
|
482
|
-
"on": { "type": "date", "label": "Date", "required": true },
|
|
483
|
-
"until": { "type": "date", "label": "End" }
|
|
484
|
-
},
|
|
485
|
-
"displayField": "name",
|
|
486
|
-
"calendarField": "on",
|
|
487
|
-
"calendarEndField": "until"
|
|
488
|
-
}
|
|
489
|
-
```
|
|
490
|
-
|
|
491
|
-
Notes:
|
|
492
|
-
|
|
493
|
-
- **No schema change is needed to get the toggle** — it appears whenever a `date`
|
|
494
|
-
field exists. The two keys only *tune* it: `calendarField` pins which date
|
|
495
|
-
anchors the grid (otherwise the first `date` field is used, and the user can
|
|
496
|
-
switch in-view when there are several); `calendarEndField` makes a record span
|
|
497
|
-
multiple days (`calendarField` → `calendarEndField`, inclusive).
|
|
498
|
-
- `displayField` sets the chip label (falls back to the primary key).
|
|
499
|
-
- Records whose anchor date is missing or unparseable are listed in a small
|
|
500
|
-
"No date" tray under the grid — never silently dropped.
|
|
501
|
-
- The calendar is purely a **rendering** of the records: it adds no storage and
|
|
502
|
-
fires nothing. It composes with `triggerField` / `spawn` (which drive bells and
|
|
503
|
-
recurrence) but is independent of them.
|
|
504
|
-
- This is the collection-native calendar — the way to give the user a
|
|
505
|
-
calendar of dated records. (The old standalone Calendar view +
|
|
506
|
-
`manageCalendar` tool were removed; `calendarField` is its replacement.)
|
|
507
|
-
|
|
508
|
-
#### Day view (time allocation)
|
|
509
|
-
|
|
510
|
-
Clicking a day's number badge opens a popup vertical timeline (a 24-hour grid)
|
|
511
|
-
showing how that day's records are allocated across the clock. Records need a
|
|
512
|
-
**time of day** to draw as time blocks; supply it one of two ways:
|
|
513
|
-
|
|
514
|
-
- A `datetime` `calendarField` (and optionally `calendarEndField`) — the clock
|
|
515
|
-
comes from the field value itself (`2026-06-11T14:00`).
|
|
516
|
-
- A `date` `calendarField` **plus** `calendarTimeField` naming a string field
|
|
517
|
-
with a free-form time or range. Recognised shapes:
|
|
518
|
-
|
|
519
|
-
| Time value | Day-view rendering |
|
|
520
|
-
|---|---|
|
|
521
|
-
| `"14:00-17:00"` (a range) | a proportional **time block** from 14:00 to 17:00 |
|
|
522
|
-
| `"17:00-"` or `"16:30"` (start only) | a **single line** at that time (no known end) |
|
|
523
|
-
| `"終日"`, blank, or unparseable | a chip in the **all-day strip** at the bottom |
|
|
524
|
-
|
|
525
|
-
Separators `-`, `–`, `—`, `~`, `〜`, `~` are all accepted. Overlapping blocks
|
|
526
|
-
split into side-by-side lanes; a multi-day `datetime` span is clamped to each
|
|
527
|
-
day with ▲/▼ arrows marking where it continues. Example (`date` + `time`
|
|
528
|
-
column):
|
|
529
|
-
|
|
530
|
-
```json
|
|
531
|
-
{
|
|
532
|
-
"title": "Engagements",
|
|
533
|
-
"icon": "event",
|
|
534
|
-
"dataPath": "data/engagements/items",
|
|
535
|
-
"primaryKey": "id",
|
|
536
|
-
"fields": {
|
|
537
|
-
"id": { "type": "string", "label": "ID", "primary": true, "required": true },
|
|
538
|
-
"title": { "type": "string", "label": "Title", "required": true },
|
|
539
|
-
"date": { "type": "date", "label": "Date", "required": true },
|
|
540
|
-
"time": { "type": "string", "label": "Time" }
|
|
541
|
-
},
|
|
542
|
-
"displayField": "title",
|
|
543
|
-
"calendarField": "date",
|
|
544
|
-
"calendarTimeField": "time"
|
|
545
|
-
}
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
### Kanban view
|
|
549
|
-
|
|
550
|
-
Any collection that has at least one `enum` field gains a **Kanban board** toggle
|
|
551
|
-
in its header — **zero config**. The board renders one column per declared enum
|
|
552
|
-
value (in `values` order), plus a trailing **Uncategorized** column for
|
|
553
|
-
empty/unknown values (omitted when the group enum is `required`). Dragging a card
|
|
554
|
-
between columns writes that enum field; clicking a card opens the same
|
|
555
|
-
detail/edit panel.
|
|
556
|
-
|
|
557
|
-
```json
|
|
558
|
-
{
|
|
559
|
-
"title": "Tasks",
|
|
560
|
-
"icon": "checklist",
|
|
561
|
-
"dataPath": "data/tasks/items",
|
|
562
|
-
"primaryKey": "id",
|
|
563
|
-
"fields": {
|
|
564
|
-
"id": { "type": "string", "label": "ID", "primary": true, "required": true },
|
|
565
|
-
"title": { "type": "string", "label": "Title", "required": true },
|
|
566
|
-
"status": { "type": "enum", "label": "Status", "values": ["Backlog", "Todo", "In Progress", "Done"] },
|
|
567
|
-
"done": { "type": "toggle", "label": "Done", "field": "status", "onValue": "Done", "offValue": "Todo" }
|
|
568
|
-
},
|
|
569
|
-
"displayField": "title",
|
|
570
|
-
"kanbanField": "status"
|
|
571
|
-
}
|
|
572
|
-
```
|
|
573
|
-
|
|
574
|
-
Notes:
|
|
575
|
-
|
|
576
|
-
- **No schema change is needed to get the toggle** — it appears whenever an
|
|
577
|
-
`enum` field exists. `kanbanField` only *tunes* which enum groups the board
|
|
578
|
-
(otherwise the first `enum` field is used, switchable in-view).
|
|
579
|
-
- **The enum is the single source of truth.** For a todo-style "done" checkbox,
|
|
580
|
-
use a `toggle` field projecting the status enum (above) — do NOT add a separate
|
|
581
|
-
stored boolean. Checking the box sets `status` to the done value (and moves the
|
|
582
|
-
card to that column); dragging the card to the Done column checks the box. They
|
|
583
|
-
are the same write.
|
|
584
|
-
- Columns are not draggable (order comes from the enum's `values`) and there is
|
|
585
|
-
no manual ordering within a column — a drop only changes the enum value.
|
|
586
|
-
- Like the calendar, the board is purely a **rendering** of the records: it adds
|
|
587
|
-
no storage. `completionField` / `completionDoneValues` (bells) are independent
|
|
588
|
-
but pair naturally with the Done column.
|
|
589
|
-
- **Building a todo / task list?** Read `config/helps/todo-collection.md` — the
|
|
590
|
-
complete, copy-pasteable recipe (status enum + `done` toggle + priority bells +
|
|
591
|
-
calendar) plus the legacy-`todo-plugin` migration steps.
|
|
592
|
-
|
|
593
|
-
### Worked example: a Todo list
|
|
594
|
-
|
|
595
|
-
The full todo recipe — complete `schema.json`, `SKILL.md`, a sample record, and
|
|
596
|
-
the legacy-`todo-plugin` migration steps — has its own file:
|
|
597
|
-
**`config/helps/todo-collection.md`**. Read it whenever you create or migrate a
|
|
598
|
-
todo / task list; it's the canonical template, so copy it rather than assembling
|
|
599
|
-
one from the fragments above. The one rule to remember: the `status` enum is the
|
|
600
|
-
single source of truth and the "done" checkbox is a `toggle` field projecting it
|
|
601
|
-
— **omit the toggle and the list has no checkbox.**
|
|
602
|
-
|
|
603
|
-
## Records — one JSON object per file
|
|
604
|
-
|
|
605
|
-
- Write each record to `<dataPath>/<id>.json` via the **Write** tool; the `id`
|
|
606
|
-
field's value is the filename (no extension).
|
|
607
|
-
- **List the directory first** and pick a fresh id rather than silently
|
|
608
|
-
overwriting. Update = Read, merge, Write back (preserve fields you weren't
|
|
609
|
-
asked to change). Delete = remove the file.
|
|
610
|
-
- **Never write `derived` fields**, and never write an `embed` field — both are
|
|
611
|
-
display-only / host-computed.
|
|
612
|
-
- Leave optional fields out of the JSON entirely rather than writing empty
|
|
613
|
-
strings.
|
|
614
|
-
- For a `ref` field, write the raw target slug, and make sure that record
|
|
615
|
-
actually exists in the target collection — an invalid slug renders as a broken
|
|
616
|
-
link. The host enforces structure and safety; **you own semantic correctness**
|
|
617
|
-
(valid refs, sane values).
|
|
618
|
-
|
|
619
|
-
## End-to-end: creating a new collection skill
|
|
620
|
-
|
|
621
|
-
1. Pick a `<slug>` (lowercase-hyphen, no `mc-` prefix) and a `dataPath`
|
|
622
|
-
(`data/<name>/items`).
|
|
623
|
-
2. Write `data/skills/<slug>/schema.json` — `title`, `icon`, `dataPath`,
|
|
624
|
-
`primaryKey` (with the matching field flagged `primary: true`), and the
|
|
625
|
-
`fields` map in the order you want columns. Add `actions` +
|
|
626
|
-
`data/skills/<slug>/templates/*.md` only if the collection needs delegated
|
|
627
|
-
behaviour. (The bridge mirrors these into `.claude/skills/<slug>/`.)
|
|
628
|
-
3. Write `data/skills/<slug>/SKILL.md` — front-matter `name` + `description`,
|
|
629
|
-
then the record-shape bullets and CRUD conventions.
|
|
630
|
-
4. Tell the user it's ready at `/collections/<slug>`. The bridge mirrors the
|
|
631
|
-
files and triggers a re-scan, so the host discovers it without a restart and
|
|
632
|
-
with no host code. If it doesn't appear: first confirm you wrote under
|
|
633
|
-
`data/skills/<slug>/` (NOT `.claude/skills/…`, which is gated and won't
|
|
634
|
-
mirror); then check your `schema.json` passed validation — primary key
|
|
635
|
-
flagged `primary: true`, `ref`/`embed` have a valid `to`, `enum` has
|
|
636
|
-
`values`, `table` has `of`, `derived` has `formula`, action ids unique,
|
|
637
|
-
`dataPath` under the workspace, `triggerField` names a real `date` field and
|
|
638
|
-
has the completion pair, `spawn` has `triggerField` and a valid `every`,
|
|
639
|
-
`calendarField` / `calendarEndField` name real `date`/`datetime` fields (and
|
|
640
|
-
`calendarEndField` requires `calendarField`), `calendarTimeField` names a real
|
|
641
|
-
field and requires `calendarField`, `kanbanField` names a real
|
|
642
|
-
`enum` field, any `toggle` field names a real `enum` `field` with its
|
|
643
|
-
`onValue` / `offValue` among that enum's `values`, and `notifyWhen` (if set)
|
|
644
|
-
requires `completionField` and names a real field.
|
|
645
|
-
(A schema that fails validation is logged server-side and silently skipped
|
|
646
|
-
at discovery.)
|
|
647
|
-
|
|
648
|
-
## Worked reference: the billing suite
|
|
649
|
-
|
|
650
|
-
The billing collections are the canonical examples. They ship as **recipes**
|
|
651
|
-
(copy-paste schemas + SKILL bodies), not as boot-overwritten presets — read the
|
|
652
|
-
recipe when the user wants any of them, and copy the schema verbatim:
|
|
653
|
-
|
|
654
|
-
- **`config/helps/billing-clients-worklog.md`** (Bundle A):
|
|
655
|
-
- **`clients`** — flat table (`string` / `email` / `text` / `markdown`). The
|
|
656
|
-
simplest possible collection; everything else `ref`s into it.
|
|
657
|
-
- **`worklog`** — adds a `ref` (`clientId → clients`), a `date`, a `number`, a
|
|
658
|
-
`boolean`. A companion data source.
|
|
659
|
-
- **`config/helps/billing-invoice.md`** (Bundle B):
|
|
660
|
-
- **`profile`** — a `singleton` (one record, id `me`): the issuer identity.
|
|
661
|
-
- **`invoice`** — the full toolkit in one schema: an `embed` issuer
|
|
662
|
-
(`profile/me`), a `ref` client (`clients`), a `table` of line items, three
|
|
663
|
-
`derived` money fields, an `enum` status, and four `actions` (PDF always-on;
|
|
664
|
-
sale / payment / void gated by `status` via `when`).
|