Skip to content

feat: reusable diagram templates — save a board, reuse it by id in future sessions#179

Merged
ivanmkc merged 1 commit into
masterfrom
feat/diagram-templates
Jun 11, 2026
Merged

feat: reusable diagram templates — save a board, reuse it by id in future sessions#179
ivanmkc merged 1 commit into
masterfrom
feat/diagram-templates

Conversation

@ivanmkc

@ivanmkc ivanmkc commented Jun 11, 2026

Copy link
Copy Markdown
Owner

What

Decouples a diagram's structure/styling from its data so a good diagram can be saved as a reusable template (a tpl_… id) and reused in independent/future sessions for new data.

Reuse is AI-driven (per the chosen model): a template stores the working diagram as a concrete example plus natural-language hints, and a later agent does template get <id>, reads the hints, authors fresh data, and pushes. No deterministic server-side "apply".

Templates vs recipes

Complementary, not overlapping:

  • Recipes (this skill) = shipped, generic authoring-time starting patterns, addressed by name.
  • Templates (this PR) = the user's own saved diagrams, addressed by a runtime id, reusable across sessions. A "Templates ≠ recipes" note is added to the skill so agents reach for the right one.

How it works

  • TemplateStore (mirrors ShareStore): each template is a Payload row in the reserved, never-servable __templates__ collection, so it rides the existing persistence backend — reusable by id across sessions/containers with zero new persistence code.
  • Server: POST /w/<wsid>/template (save a board — tokenless + wsid-gated, like clear/share, since the UI holds no push token), global GET /templates, GET /template/<id>, DELETE /template/<id>. __templates__ blocked from serving.
  • CLI: termchart template save|list|get|delete. get prints HINTS first, then the example content.
  • Viewer UI: a Templates button → popover to save the current board (name + a hints textarea prefilled with a WHAT / DATA SLOTS / ADAPT / KEEP scaffold) and browse the library (copy id, inline hints, delete). Hidden on the read-only demo so the public showcase doesn't expose the library.
  • Docs: a "Reusable templates" section + reuse workflow in the diagram-recipes SKILL and the diagram-remote command.

Verification

  • Unit: viewer 449 (incl. template-store.test with a cross-session persistence round-trip — hydrate a fresh store from the saved rows, resolve the template by id → proves reuse in a new session), CLI 171 (incl. 8 template tests).
  • Build exit 0 (viewer + CLI); NUL-byte check clean.
  • e2e test:e2e:nobuild → rich 62/62, board-sort 12/12, new template 11/11 (save in the UI → library → GET api → adapt + reuse renders → delete; __templates__ not servable). Verified locally + screenshotted the popover.

Notes

  • Auth posture: template create/delete are tokenless but wsid-gated, consistent with the existing clear/share UI actions (the viewer client has no push token). Acceptable for this personal-companion tool; tightenable to token-gated if multi-tenant.
  • The viewer + its HTTP API work on deploy; the CLI template commands reach users on the (user-gated) npm republish, and the recipe/skill docs on the (user-gated) plugin bump.

…ture sessions

Decouples a diagram's structure/styling from its data so a good diagram can be
SAVED as a reusable template (a `tpl_…` id) and reused in independent/future
sessions for new data. Reuse is AI-driven: a template stores the working diagram
as a concrete EXAMPLE plus natural-language HINTS, and a later agent fetches it,
reads the hints, authors fresh data, and pushes — there is no deterministic
server-side "apply".

Templates are a GLOBAL library, distinct from the shipped recipes: recipes are
generic authoring-time starting patterns; templates are the user's own saved
diagrams, addressed by id at runtime.

- TemplateStore (mirrors ShareStore): each template is a Payload row in the
  reserved, never-servable `__templates__` collection, so it rides the existing
  persistence backend — reusable by id across sessions/containers with no new
  persistence code.
- Server: POST /w/<wsid>/template (save a board — tokenless + wsid-gated, like
  clear/share, since the UI has no push token), GET /templates, GET/DELETE
  /template/<id>. __templates__ is blocked from being served.
- CLI: `termchart template save|list|get|delete`. `get` prints the HINTS first,
  then the example content, so a consuming agent is guided before it adapts.
- Viewer UI: a Templates button → a popover to save the current board (name +
  hints prefilled with a WHAT/DATA SLOTS/ADAPT/KEEP scaffold) and browse the
  library (copy id, view hints inline, delete). Hidden on the read-only demo.
- Docs: a "Reusable templates (≠ recipes)" section + reuse workflow in the
  diagram-recipes SKILL and the diagram-remote command, with the hints scaffold.

Tests: template-store.test (incl. a cross-session persistence round-trip —
hydrate a fresh store from the saved rows and resolve the template by id),
CLI template.test (8), and template.e2e (save in the UI → library → GET api →
adapt+reuse → delete; __templates__ not servable), chained into the e2e CI.
@ivanmkc ivanmkc merged commit fe415cf into master Jun 11, 2026
5 checks passed
@ivanmkc ivanmkc deleted the feat/diagram-templates branch June 11, 2026 16:09
ivanmkc added a commit that referenced this pull request Jun 11, 2026
…l GET/DELETE) (#180)

Code review of #179 found the template library's read/delete routes were
ORIGIN-rooted and ungated: they sat above the /w/<wsid> dispatch with no wsid
and no token. Because GET /templates returned every template id+name, any
anonymous caller (the public demo shares the deployment's origin) could
enumerate the whole global library and DELETE /template/<id> to wipe it — the
"unguessable id gates it" comment was false.

Fix: move list/get/delete under /w/<wsid>/ so the unguessable workspace id is
the capability (consistent with clear/share), and exclude the read-only demo
(isReadOnlyWsid) so the public showcase can't enumerate or mutate the library.
Create was already wsid-scoped + demo-blocked. The library stays GLOBAL (any of
your workspaces sees every template — cross-session reuse by id is preserved);
only the access path is now gated.

- server.ts: remove the top-level /templates and /template/<id> routes; add
  GET /w/<wsid>/templates and GET|DELETE /w/<wsid>/template/<id>, both
  demo-excluded; fix the misleading comment.
- cli/template.ts: list/get/delete now use ${base} (…/w/<wsid>) instead of
  deriving the origin — simpler and matches the new routes.
- client/viewer.ts: the Templates popover fetches ${apiBase}/templates and
  /template/<id>.

Tests: server.test.ts gains a "template library" suite (create/list/get/delete,
missing-board 404, 405, demo 403/404, origin-rooted routes gone, __templates__
not servable); CLI test asserts the wsid-scoped paths; template.e2e asserts the
origin routes are gone + demo can't enumerate. Full chain: server 76, CLI 8,
e2e rich 62 / board-sort 12 / template 14.

Co-authored-by: Ivan Cheung <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants