# Syokan

> Build and POST a JSON snapshot envelope to syokan (召喚 — a verb: summon data into a rich, living view). Use when the user says "syokan this", "syokan X", "show in syokan", "post a snapshot", "preview this markdown", or in Japanese 『〜を syokan』『syokan に出して/表示して/投げて』『snapshot を作って/送って』『syokan のUIで見たい』『Markdown をプレビューして/md ファイルをブラウザで見たい』 — for RSS feeds, in-progress PR reviews, review risk panels, meeting notes, today's TODO, dashboards, or any aggregated data the user wants to see as structured UI. Whenever the word syokan appears, use this skill even if the user does not explicitly say snapshot.

- Skill: `wwwyo/syokan` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add wwwyo/syokan`
- Raw SKILL.md: https://api.skillmd.com/api/skills/wwwyo/syokan/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Docs & Writing
- License: MIT
- Author: wwwyo (https://skillmd.com/u/wwwyo)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/wwwyo/syokan

---


# syokan

**syokan (召喚, "summon") is a verb — LLMs summon rich UI.** When the user says "syokan X" (JA: 「Xを syokan」), that is a request to summon that data into a view. Under the hood it is a schema-driven view layer: instead of writing JSX, you speak a JSON incantation — a tree of catalog components — and syokan renders it with predefined components.
Your job is to assemble the data into an envelope and POST it to syokan.
In prose, use syokan as a bare transitive verb (never "do syokan" / never 「syokan する」 — syokan stands on its own).

If syokan is not installed yet (`syokan --help` fails), or the user says "onboarding", "set it up", or "first time using it", walk them through [references/onboarding.md](references/onboarding.md) up to their first snapshot. Do not silently run environment-changing operations such as installs: report that it is not installed and get the user's approval before executing (never install on your own).

## Non-negotiables

- **Snapshots are ephemeral**: a posted snapshot has no persistence guarantee. Only put reconstructible, transient data there (today's RSS, an in-progress review, etc.). For layouts you reuse, save a template to reproduce them (templates persist — see "Templates for reproducibility" below).
- **JSON only — no free-form markdown ingest**: the server accepts nothing but a JSON envelope; you can't POST a raw `.md` file. `Markdown` is a catalog node like any other, and it is deliberately restricted to prose flow: paragraphs, plain bullet/numbered lists (nested ok), bold/italic/strikethrough, inline code, fenced code, blockquotes, links. Block structure and data still belong to their own node — the server 400s a `Markdown` body containing a heading (use `Heading`), a GFM table (`Table`), a task-list item (`Checklist`), raw HTML, an image, or a non-http(s) link, naming the replacement in the error. A ` ```mermaid ` fence inside `Markdown` renders as a plain code block, not a diagram — put diagrams in their own `Mermaid` node. For anything outside that prose subset, structure it into catalog nodes yourself: headings → `Heading`, code fences needing their own filename/copy affordance → `Code`, mermaid fences → `Mermaid`, preformatted or verbatim text outside prose (raw logs, ASCII tables) → `Code` with no `lang`.
- **Strict schema**: props are validated strictly. Keys not in the schema are rejected — do not invent extra keys.
- **Leaves cannot have children**: only containers (`Stack`, `Card`, `Checklist`, `Collapsible`) accept children; check `childrenTypes` in `syokan catalog`. Attaching children to a leaf node is rejected at ingest.
- **Probes are predefined checks only**: `Probe.check` must be one of the kinds accepted by `Probe`'s `check` props schema (see `syokan catalog`). There is no way to run an arbitrary command from a view — do not try.

## From composing to viewing

1. Check the available types and props with `syokan catalog` (see "Catalog" below).
2. Map the data you want to show onto those components. The top level is usually a container (`Stack` etc.) stacking items vertically.
3. Turn it into an envelope JSON (see "Envelope shape" below). Only `root` is required.
4. Post it with the CLI (see "Posting" below). On success the view URL is printed.

If you have built a similar view before, do not start from scratch — base it on a saved template (see "Templates for reproducibility" below).

## Envelope shape

Only the following may go in the POST body.
Never include `id`, `createdAt`, or `url` — the server assigns them.

```jsonc
{
  "root": { "type": "Stack", "props": {}, "children": [ /* catalog nodes; full examples in references/examples.md */ ] }, // required. the view tree
  "title": "Today's RSS",                                // optional. shown in the list and the view header
  "schemaVersion": 1,                                    // optional. server fills it in
  "idempotencyKey": "rss-2026-06-28"                     // optional. names this view so it can be refreshed later instead of duplicated
}
```

For daily or recurring views, include something like the date in the `idempotencyKey`. The syokan CLI handles the rest: it targets that key first, and only creates a new view if none exists yet — so the first post creates the view and every later post with the same key refreshes that same view in place instead of duplicating it. You don't need to track whether it's the first post yourself. (If you call the HTTP API directly instead of the CLI: `POST /api/snapshots` always creates and tags the key; `PUT /api/snapshots` targets an existing key and 404s if it's not there yet — there is no create-on-miss endpoint, which is why the CLI tries `PUT` first and falls back to `POST` on a 404.)

## Catalog

Get the available `type`s and their props definitions **from `syokan catalog`** (never transcribe them into md — pull them from here every time).

The output is `{ "items": [{ "type", "props", "childrenTypes", "notes" }], "envelope": <JSON Schema for the envelope's own keys> }`.

- `props`: the type's props as JSON Schema. Satisfy `required` / `enum` / `format` (httpUrl is `uri`, `Time.datetime` is `date-time`) / `additionalProperties:false` (unknown keys are rejected) exactly as given. `Probe`'s `check` props schema is where its predefined check kinds live.
- `childrenTypes`: `null` means a container that accepts children, `[]` means a leaf that accepts none, `[..]` means only the listed types may be children.
- `notes`: usage contract the props schema can't express (e.g. `Checklist` pairs `children[i]` with `items[i]`). Read it before using a type.
- `envelope`: the JSON Schema for the envelope's own keys (`root` / `title` / `schemaVersion` / `idempotencyKey`) — the SSOT for "Envelope shape" above. `root` is left opaque there; what may go inside it is `items`, so a body is valid only when it satisfies both.

For complete examples combining the components, see [references/examples.md](references/examples.md).

## Spacing (Stack)

`Stack` spaces its children by nesting depth: the outermost stack separates sections, and each level further in tightens the spacing (deeper than three levels stays at the tightest). **Leave `gap` unset** — that default is what keeps views from drifting apart visually. Set it (`none` / `sm` / `md` / `lg`) only when the depth-derived spacing is wrong for a specific group, e.g. `none` for a row of items meant to read as one strip. A horizontal `Stack` scrolls inside itself when its children exceed the width, so lining many up never breaks the page.

## Cross-cutting node field (id)

Besides `type` / `props` / `children` / `key`, any node may carry `id`: it makes the node addressable. A `Link` with `href: "#<id>"` jumps to it inside the view (revealing it if folded). It is also the identity for viewer-local UI state — **give an `id` to every `Checklist` / `Collapsible` / `Probe`** so checks, folds, and probe reruns survive reloads. An `id` must be unique tree-wide; a duplicate 400s.

Interaction state (checks, folds, probe re-runs) lives in the viewer's browser, never in the envelope — post the *initial* state (`checked`, `defaultOpen`, `result`) and let the reader take it from there.

## Interactive views (risk panels, TODO, dashboards)

Typical composition for a review risk panel (condensed envelope in [references/examples.md](references/examples.md) Example 5):

- `Stat` row up top for the counts; `Table` as the cockpit where each row `Link`s (`#id`) to its finding `Card`.
- Low-priority detail (evidence hunks, verified-None sections) goes inside `Collapsible` instead of being deleted.
- "No findings" claims carry a `Probe` whose `check` re-measures the claim (search count, diff cleanliness, file existence). Include the `result` you measured at generation time — you can run it via `POST /api/probes/run` with the same `check` — or omit it and the reader runs it. On public shares, probe args/results are stripped unless you set `shareVisible: true`.
- `Graph` (roles: added/removed/hotspot/neutral, colors fixed by syokan) side by side in a horizontal `Stack` for before/after dependency contrasts. Prefer it over `Mermaid` when the diagram is a plain node/edge sketch — it cannot fail to parse.
- `Checklist` for reviewer progress; checked items fold to one line.

## Posting

Pass the assembled envelope as a file or via stdin; on success the view URL is printed to stdout (`syokan snapshot.json` / `cat snapshot.json | syokan` / `claude -p '…JSON…' | syokan`).

For everything else — commands, subcommands, env vars, exit codes — consult `syokan --help --json`; for types and props, `syokan catalog`.

## Live-syncing a tree file (TreeDoc)

To keep updating a view without re-posting, **write the catalog tree to a JSON file and syokan the path**. A file holding a bare catalog tree (`{ "type": ..., "props": ... }`, no envelope) is auto-wrapped in a live `TreeDoc`: the CLI resolves it to an absolute path, and **while the view is open it follows every save of the file**. Rewrite the file to update the view.

```bash
syokan ./dashboard.json   # summons the tree; every save re-renders the view
```

- `syokan <path>` accepts JSON only: an envelope posts once (static); a bare catalog tree live-syncs; anything else (markdown / log / txt / other JSON) is rejected with `unsupported_input`.
- Mid-write invalid JSON is safe: the view keeps the last valid render and shows an unobtrusive error until the file is valid again.
- To mix a synced subtree with static nodes, place `TreeDoc` nodes yourself (props: `path`, **absolute paths only**, no URLs). A `TreeDoc` cannot appear inside a synced tree (nesting is rejected).

To show a local markdown / text file, there is no file viewer: read it and post the result as catalog nodes (see "JSON only — no free-form markdown ingest" above). Prose sections (paragraphs, lists, links, fenced code, blockquotes) can go straight into a `Markdown` node's `body`; pull out headings into `Heading`, GFM tables into `Table`, and task lists into `Checklist` first — `Markdown` rejects those. For raw text, `jq --rawfile` streams a body in without worrying about JSON escaping.

```bash
jq -n --rawfile body app.log \
  '{title:"app.log", root:{type:"Code",props:{code:$body}}}' \
  | syokan
```

## Templates for reproducibility

Do not rebuild a favorite view from scratch every time — save it as a template in syokan and reuse it.
A template is "the saved envelope itself". You may put markers like `{{...}}` in the skeleton and fill them in yourself.

If a matching template exists, do not build from zero: find the id in the list → fetch it → swap in the data → post. When you produce a new view worth keeping, save it and reproduce it next time. For subcommand syntax (`templates list|add|get|rm`), consult `syokan --help --json`.

