# origin-product-spec-management (skill) --- name: origin-product-spec-management description: Read, author, validate, and stay in sync with OriginAI product design specs written in RPML. Prefer OriginAI MCP tools when connected; otherwise use the originai CLI. Use when reading a project's design specs, implementing code from them, tracking release diffs, writing specs back from a codebase, or checking that code matches the defined product. --- # Origin Product Spec Management This skill connects [OriginAI](https://getoriginai.com) product design specs to your coding agent. Origin specs are **RPML** documents — each `.rpml` file is one screen/region describing every state, permission variant, and edge case in a single annotated layout. **Tools (preferred order):** 1. **Origin MCP tools** — when `get_diff`, `list_documents`, `whoami`, etc. are available in this session, **use them**. Do not shell out to the CLI for the same operation. (Claude Code plugin runs local `originai mcp`; remote clients may use `https://mcp.getoriginai.com`.) 2. **`originai` CLI** (`bunx originai` / `npx originai`) — fallback when MCP is not connected (Codex, Pi, Hermes, OpenCode, skills.sh installs, CI). Never invent raw HTTP/curl against the API. ## Setup **Once per machine (auth):** ```bash npx originai login # stores token in ~/.origin/settings.json — never commit ``` **Once per product repo (bind project + optional skill files):** ```bash npx originai link --project # or: --skill / --claude-code / --codex / --cursor / --all ``` Project config is in `.origin.json` (committed, no secrets): ```json { "api_url": "", "project_id": "", "release_hash": null, "sync_readme_badge": false } ``` (`sync_readme_badge` is optional — omit until the user has been asked; `true` keeps the README badge updated on `sync`.) If this file is missing placeholders, run `npx originai link --project ` with real values. **Token model** - **Humans (default):** `npx originai login` → `~/.origin/settings.json`. Claude Code plugin MCP uses local `originai mcp`, which reads this store. - **CI / headless / remote-only MCP hosts:** `ORIGIN_TOKEN=oat_…` (create under Origin Settings → Access tokens). Never commit. - Diagnose: `npx originai doctor`. ## Sync model (how the repo and Origin stay in sync) `.origin.json` binds this repo to one Origin project and carries the sync pointer: - `project_id` — the bound Origin project. - `release_hash` — the last **published release** this repo synced to. This is the anchor for two-way sync. Origin shows every release's hash in its UI, so the hash in `.origin.json` tells you exactly which release the code reflects. - `proposal_id` — optional **implementation bind**. Set it only when this repo's next GitHub PR is implementing that change request. The GitHub App then reviews `code diff` against `release hash <> proposal`. `write-document` never writes this field — staging a spec correction is not an implementation bind. Set it with `get-proposal-diff --bind`, or by editing `.origin.json`. `sync` clears it only after a new release hash exists (the request was applied or closed, then published). Clear it yourself before a PR that only implements a published release. All read commands (`list-documents`, `get-document`, `grep`, `find`, `get-diff`) read the **latest published release snapshot** by default — never the live workspace. So you always work against formally released specs, not in-progress edits. Pass `--read-type workspace` to read the live `rpml_files` tree instead (use this when indexing a pre-release project — see Workflow C), or `--release-tag ` to pin a read to a specific published release. **Before staging anything, call `list-proposals --status all`** (or MCP `list_proposals` with `status: 'all'`). If a similar change was dismissed, read `dismiss_reason` and do not file the same Change Request again. If your previous batch was applied, the spec has moved — re-read before continuing. Agents are not notified when a token-authored change is decided; asking is the only channel. What you can observe: `list-proposals` adds `decided_items[]` (each with `decision` and `dismiss_reason`) to every change request that has a decision, `get-proposal ` shows the same per item plus write-wave `batches[]` and unified `diff`s (`--with-content` adds bodies), `list-proposal-comments ` returns the discussion, and `list-proposal-reviews ` returns Ready / Not ready conclusions (a stale row was recorded before the latest write). **Writes after a release become Change Requests.** There is no `create_change_request` tool — the first `write-document` / MCP `write_document` creates the CR (`suggested: true` + `proposal_id`). Reads still default to the latest published release. Report that to the user. Keep writing to the same CR by passing `proposal_id` on `write-document` (or MCP `proposal_id`). Use `--new-proposal` / `new_proposal: true` to open a second CR — never to split README vs screens of the same indexing pass. One indexing pass is one Change Request. Do **not** copy that id into `.origin.json` unless this repo is about to implement it in code. `commit-proposal` seals the current write-wave without marking the set ready; `describe-proposal` writes the Change Request **message** a human reads: **Issue** (`title` = one line, `note` = the problem), **Decisions** (what you chose and why) and **Changelog** (which documents changed and what each change does) in `rationale`. Do not leave this empty. `submit-proposal` seals the wave **and** marks the set ready (and can take the same fields). Use `comment-proposal` for follow-up discussion. You may stage, describe, commit, submit, and comment — **never apply, dismiss, or record a conclusion**. Direct writes only happen when the project has **no release yet**, or when `project_write_mode` is `direct` (rare). There are two directions, and knowing which one you are in matters: - **Origin → repo (pull & implement).** If `.origin.json` has `proposal_id`, run `get-diff` (it becomes `get_proposal_diff`: release <> that change set) or MCP `get_proposal_diff`. Otherwise run `get-diff` between published releases. The response always includes a unified `diff`. **Read `diff` first.** Then `sync` to advance `release_hash` only after the change is a published release (apply in Origin, publish, then sync). Do **not** script a `get-document` loop over the diff ids. This is Workflow A/B below. - **Repo → Origin (index & write).** If the Origin project has **no release yet**, read the codebase, author RPML content, `validate --content`, then `write-document --content` to push it **directly to Origin** — do **not** save `.rpml` files locally. Needs a read-write token. If this repo is unbound, `create-project` then `npx originai link --project `. Read back what you wrote with `list-documents --read-type workspace` (the default release read returns 404 until a release is published). The user then publishes a release in Origin, producing a new hash your next `get-diff` will sync to. **After the first release, `write-document` creates a Change Request** — a human applies it in Origin. Always `list-proposals --status all` first. Indexing a released project is **one** Change Request (README + every screen). Do not submit after the README. `describe-proposal` then `submit-proposal` when the whole pass is done. This is Workflow C (pre-release) or Workflow D (after a release) below. Always keep `.origin.json` committed so every teammate shares the same sync pointer; never commit the token. ## Release badge (optional README) — how to add it Public release pages ship a badge image: `https://spec.getoriginai.com///badge-dark.svg` (also `badge-light.svg`). **Use HTML for the link** (`` wrapping ``), not `[![…](…)](…)` — CommonMark cannot open a new tab. GitHub and most Markdown hosts allow this subset. **Never add or edit the badge silently.** Follow this procedure when the user asks for a badge, or after a successful `sync` if `.origin.json` has no `sync_readme_badge` field yet (ask once, then stop asking). ### Step-by-step (agent) 1. **Read** `.origin.json` and take `project_id` + `release_hash`. - If `release_hash` is `null`, run `bunx originai get-diff` (or MCP `get_diff`) and use the response `to_hash` as the release hash. If there is no published release yet, tell the user to publish in Origin first — do not invent a hash. 2. **Ask the user** (quote this intent): “Add an OriginAI release badge to `README.md` and keep it updated on `originai sync`? (yes / no)” 3. **On decline**: set in `.origin.json`: `"sync_readme_badge": false` (keep other fields). Commit if appropriate. Stop. 4. **On agree**: - Set `"sync_readme_badge": true` in `.origin.json` (merge; do not wipe `api_url` / `project_id` / `release_hash`). - Ensure root `README.md` exists (create a minimal one if missing). - Insert or replace this **exact** marker block (substitute real ids from step 1; prefer `badge-dark`). The `` is required: ```md OriginAI ``` - Place the block after the first `#` heading, or at the top of the file. - If the markers already exist, replace only the content between them. 5. **Optional**: run `bunx originai sync` so a newer CLI can refresh the same marker block when `release_hash` advances. If sync asks about the badge and the field is already set, it will not ask again. 6. Show the user the badge Markdown and remind them to commit `.origin.json` + `README.md`. ### Rules - Do **not** set `sync_readme_badge` without an explicit yes/no from the user. - Do **not** edit README badge content when `sync_readme_badge` is `false`. - When `sync_readme_badge` is `true` and you advance `release_hash` via sync, update the marker block to the new hash (or rely on `originai sync` to do it). ## Understanding RPML (read this before authoring) RPML replaces time with space: one `.rpml` = one `` with exactly one ``, snapshot built from RPML primitives, `data-pin="N"` on every meaningful region, and a matching top-level `` per pin. Conditional states go in ``; cross-cutting notes in ``. Read the bundled references **in `rpml/`** (do not re-derive them): - `rpml/references/spec-summary.md` — root structure, attributes, rules at a glance. - `rpml/references/element-index.md` — every element + its attributes. - `rpml/references/practise.md` — the authoring method (IA-first, visual-weight mapping, update restructure, recursive decomposition, coverage matrix). - `rpml/references/example-reference.rpml` — a complete worked example (the quality bar). - `rpml/prompts/generate-rpml.md` — author a new `.rpml` from requirements/code (IA gate before layout). - `rpml/prompts/rpml-to-code.md` — extract a spec from `.rpml` and implement it. - `rpml/prompts/rpml-diff-impact.md` — classify what changed between two versions. - `rpml/prompts/review-rpml.md` — check an existing `.rpml` for completeness. ## Tools & commands **Never construct raw HTTP (curl/fetch) to Origin yourself.** Use MCP tools or the `originai` CLI only. ### A. Origin MCP (preferred when connected) Local bridge (Claude plugin default): `npx originai mcp` (uses `originai login`). Remote HTTP (advanced/CI): `https://mcp.getoriginai.com` with Bearer `ORIGIN_TOKEN`. Tool names match origin-api actions (snake_case). Pass `project_id` from `.origin.json` when required. Default reads = latest **published release**. | MCP tool | Use for | |----------|---------| | `whoami` | Token owner + project count | | `list_projects` | Owned projects + latest release hash | | `create_project` | New empty project | | `list_documents` | File tree (`read_type`: release\|workspace) | | `get_document` | One file + content | | `get_diff` | Unified diff between hashes; default embeds to-side content. Prefer this over looping `get_document`. | | `grep_documents` / `find_documents` | Search content / names | | `write_document` / `delete_document` / `delete_documents` | Workspace writes. After a release these **create or extend a Change Request** (`suggested: true` + `proposal_id`). There is no `create_change_request` tool. Pass `proposal_id` or `new_proposal`. | | `list_proposals` / `get_proposal` / `get_proposal_diff` / `describe_proposal` / `submit_proposal` / `commit_proposal` | Change review loop; describe_proposal writes Issue / Decisions / Changelog; release <> proposal diff | | `comment_proposal` / `list_proposal_comments` / `list_proposal_reviews` | Discuss a change request; read Ready / Not ready (agents never conclude) | | `validate` | RPML check (`source` and/or `file_id`) | | `search_shots` / `get_shot` / `list_shot_facets` | Layout shots. MCP: `search_shots`. In-app agent uses `retrieve_shots` (same catalog, different name). Pick by platform / business / IA; widget galleries are composition only | | `sync_origin_json` | Compute the `.origin.json` pointer to write after implementing a published release | | `list_webhooks` / `create_webhook` / `delete_webhook` | Outbound events (`release.published`, `proposal.decided`, `proposal.commented`) | After implementing a release, call MCP `sync_origin_json` and write `origin_json` into `.origin.json`, or run CLI `bunx originai sync`. ### B. OriginAI CLI (fallback) Prefer `bunx originai ` (~40ms) over `npx originai` (~1.2s). | Command | Description | |---------|-------------| | `list-projects` | List accessible projects | | `create-project --name "" [--description ""]` | Create a new empty project (read-write) | | `list-documents` | File tree from latest release (or `--read-type workspace` for the live tree) | | `get-document --id ` | Get single document with content | | `get-diff` (`diff`) | Diff to implement. Bound `proposal_id` → release <> proposal; else last sync vs latest release. | | `sync` | Advance `release_hash` to the latest release (after implementing a diff) | | `grep --pattern "" -p ` | Search content across files in latest release | | `find --file-pattern "" -p ` | Find files by name in latest release | | `validate --content ""` (`-c`) | Validate an inline RPML string **locally** (no network, no token) | | `validate --id ` (`-i`) | Validate a released document (remote — reads latest release) | | `write-document --name "" --content ""` | Create/update file (`--proposal ` / `--new-proposal`) | | `write-document --id --name "" --content ""` | Update existing file | | `delete-document --id ` | Delete file (read-write token) | | `delete-documents --ids ,,...` | Batch delete files (read-write token) | | `list-proposals --status all` | Change requests (call before staging). Decided sets carry `decided_items[]` with `dismiss_reason` | | `get-proposal [--with-content]` | Items, batches, diffs, purpose; `--with-content` adds bodies | | `get-proposal-diff [--bind]` | Release <> proposal; `--bind` writes `.origin.json` `proposal_id` | | `describe-proposal --title "…" [--note "…" --rationale "…" ]` | Change request message: Issue (`title`/`note`), Decisions + Changelog (`rationale`) | | `submit-proposal --title "…"` | Mark ready. Same Issue / Decisions / Changelog fields if not described yet | | `commit-proposal [--note "…" ]` | Seal the current write-wave without marking ready | | `comment-proposal --body "…" [--item ]` | Discuss a change (never conclude) | | `list-proposal-comments ` | Read the discussion on a change request | | `list-proposal-reviews ` | Ready / Not ready conclusions | Short aliases: `ls`, `ls-docs`, `get`, `create`, `write`, `delete`/`rm`, `diff`. Flags: `-p` project, `-i` id, `-n` name, `-c` content. Project-scoped commands need `-p` or `.origin.json`. **CLI output:** data commands print pure JSON on stdout (pipe to `jq`); tips go to stderr. All reads default to the **latest published release**. `--read-type workspace` (or MCP `read_type: "workspace"`) for live pre-release trees. ## Workflows Pick the entry that matches how you arrived. **Both directions are first-class.** - **Repo → Origin** (existing codebase; empty or unbound project): **C**. If unbound, `create-project` then `npx originai link --project `. - **Origin → repo** (implement a published release): **A** / **B**. - **After a release, update specs from code or an agent:** **D**. - GitHub `originai / spec-review` is a **check, not a merge gate** (findings are informational / `neutral`). **A. Understand a project's specs → implement** 1. If `.origin.json` has `proposal_id`, `get-diff` / MCP `get_proposal_diff` (release <> that change request). Otherwise `get-diff` (last release vs latest). Response includes `summary` + `files[]` with unified **`diff`**. **Implement from `diff` first**. 2. `get-document` only for an unchanged dependency; prefer `grep`/`find` to locate it. Follow `rpml/prompts/rpml-to-code.md`. 3. `sync` to advance `release_hash` once the code reflects the latest release. 4. **Release badge (optional):** if `.origin.json` has no `sync_readme_badge` yet, follow **Release badge** above — ask the user, then write the flag + README marker block (never silently). **B. Track a new release → implement the diff (scenario: spec changed)** 1. `get-diff` returns added/modified/renamed/deleted files with **unified `diff` (+/- markers) always**, plus to-side full `content` by default (omit bodies with `--content-mode none`). 2. Classify impact per `rpml/prompts/rpml-diff-impact.md`, then implement only what changed — apply each file's `diff` first; use `content` when you need the full to-side body. 3. `sync` to advance `release_hash` once implemented. 4. If `sync_readme_badge` is `true`, refresh the README badge marker block to the new `release_hash` (see **Release badge**). **C. Index a codebase → write specs directly to Origin (no local files)** Judge the project phase by running `list-projects` (check `latest_release`) and, if a release exists, `list-documents`: - **Empty project (no release, no documents)** — If this repo is not linked, `create-project` then `npx originai link --project ` so `.origin.json` exists. Initialize **fully** in one pass. Write `README.rpml` **first** — it is the product-design document (`mode="doc"`), not a prototype screen. Author it from your understanding of the codebase, `validate --content`, then `write-document --name "README.rpml" --content ""` to push it to Origin. Then **immediately continue**: for every page/route listed in the README's page/route planning, author one `.rpml` prototype screen spec, `validate --content`, and `write-document` it to Origin. Do **not** stop and wait after the README — drive the whole initialization to completion in this pass. Only pause if the user explicitly asks to review the README before screens. `README.rpml` must cover: product overview, functional modules, page/route planning (complete and self-consistent — include login/signup flow, admin screens, and core product logic with no gaps), key interaction flows (`` with Mermaid), and roles/permissions if applicable. For mobile pages, include tab structure and main UX flow descriptions. The page/route planning section is the worklist for the screen specs you write next — make it exhaustive, because every entry becomes a `.rpml`. Skeleton: ```html Product Name Overview… Product flow // core user flow flowchart LR A[User login] --> B[Enter home] // core feature conversion flowchart LR A[User login] --> B[Enter home] Feature breakdown by priority ``` After writing all specs, run `list-documents --read-type workspace` and confirm every page/route in the README has a corresponding `.rpml`. Report what was written and any gaps to the user, and remind them to publish a release in Origin. - **Only README.rpml exists** — Continue the initialization: author the remaining prototype screen specs (one `.rpml` per page/route the README planned that hasn't been written yet), validate + write each, until every planned page exists in Origin. Don't wait to be asked — drive it to completion, then report and remind the user to publish a release. - **Prototype screens already exist, no release yet** — Keep writing the workspace directly: create, update, or delete specs as needed, then remind the user to publish. - **A release already exists** — Do **not** write the workspace directly. Follow **Workflow D**, still as **one** Change Request for the whole index: README + every screen, then submit once. Do not submit after the README and do not pass `new_proposal` to split them. For every spec you author: 0. **Retrieve → constrain.** `list_shot_facets` returns a path array. Pick by platform / business / IA, then MCP `search_shots` (in-app agent: `retrieve_shots`) with those paths — listed paths always have data. Use the hit's IA (`summary`, `primary_action`, `ia_text`) and RPML recipe as the structural standard. Widget galleries are composition only. 1. Read the relevant code; author RPML content following `rpml/prompts/generate-rpml.md` and the references (IA gate before layout). 2. `validate --content ""` — **local** check, no network needed. Fix every error before writing. Do **not** save `.rpml` files locally; keep the content inline and write directly. 3. `write-document --name ".rpml" --content ""` (read-write token). Use `create-project` then `npx originai link --project ` if no project is bound yet. Before the first release this writes the workspace directly. After a release, follow Workflow D: keep staging into the same CR; submit only when the whole pass is complete — do not submit after the README. **Before the first release**, remind the user to **publish** — default reads (`list-documents`, `get-diff`, etc.) cannot see workspace content until a release exists. To read back what you just wrote, use `list-documents --read-type workspace` / `get-document --read-type workspace --id `. **D. Iterate specs after a release (change request loop)** Once a release exists, writes from agents no longer mutate the workspace. There is no `create_change_request` tool. Use this loop every time you update specs (new screen, confirmed code/spec gap, copy fix). `suggested: true` is success. 1. **Read decisions first.** `list-proposals --status all` (MCP `list_proposals` with `status: "all"`). If a similar change was dismissed, read `dismiss_reason` and do not file the same dismissed Change Request unless you addressed the feedback. 2. **Read the current spec from the release** (`get-document` / `grep` / `find` — default release reads). Do not assume live workspace content. 3. Author RPML, `validate --content`, then `write-document` (or MCP `write_document`). Expect `suggested: true` and a `proposal_id`. 4. Keep staging into the **same** request: pass `proposal_id` / `--proposal` on later writes (including deletes). Use `--new-proposal` / `new_proposal: true` only for a second, unrelated set — never to split README vs screens of the same indexing pass. One indexing pass is one Change Request: write README.rpml first, then every screen, all on this id. Do not submit after the README. 5. **Describe** with `describe-proposal` / `describe_proposal`: Issue (`title`, `note`), Decisions + Changelog (`rationale`). Do not leave this empty. Safe to call before more documents; it does not finish the CR. 6. **Submit** with `submit-proposal` / `submit_proposal` when the whole pass is done (README + every planned screen). Optionally `comment-proposal` for follow-up. You may stage, describe, commit, submit, and comment — **never apply, dismiss, or record a conclusion**. 7. Tell the user: open **Change requests** in Origin, apply or dismiss, then **publish a new release**. Staging is not publishing; default reads stay on the old snapshot until they publish. 8. After they publish: `get-diff` → implement code → `sync`. Bind `.origin.json` `proposal_id` with `get-proposal-diff --bind` **only** when this repo's next PR implements that request. Do **not** copy a staging id into `.origin.json` just because you wrote it. **E. Consistency review (scenario: code has behavior not defined in the specs)** This is guidance for *you, the agent* to perform — Origin does not auto-detect gaps. When you notice code implementing a feature/behavior: 1. Search the released specs for it: `grep --pattern ""` and `find --file-pattern ""`. 2. If no spec covers it, treat it as an **undefined product behavior**. Do NOT silently invent or assume the intended design. 3. Report a clear analysis to the user: what the code does, which screen/spec it would belong to, and why it appears undefined. Ask whether it should be specced, changed, or removed. 4. Only after the user confirms the intended behavior, author the spec (`rpml/prompts/generate-rpml.md` → `validate` → `write-document`). Before a release this writes the workspace; after a release follow **Workflow D**. Always `validate --content` RPML before `write-document`. Validation runs **locally** (no network, no token). Never save `.rpml` files locally — author content inline and write directly to Origin. --- ## File: rpml/SKILL.md --- name: rapid-prototype-implement description: Generate static RPML UI prototypes from product requirements, screenshots, existing UI code, or design notes. Each .rpml file is one screen or functional region (root ``) that lays out every interaction state, permission variant, and loading/empty/error/validation branch in a single annotated layout; a multi-screen product becomes a set of such files, browsed together as a gallery. The result is a product definition engineering can build from and QA can test against. Use when the user wants to prototype, spec, or visualize one or more product screens, turn requirements or a design into a reviewable UI artifact, or document a page's states and edge cases, even if they don't explicitly say "RPML" or "prototype". --- # RPUI Prototype Implementation Skill Turn product requirements, screenshots, existing UI code, or design notes into a static **RPML** prototype. Each `.rpml` file is **one screen or functional region** — a single readable document that bakes every interaction state, permission variant, and loading/empty/error/validation branch into one spatial layout, at a depth engineering can implement from and QA can derive test cases from, without running the app. A multi-screen product is a **set** of these files (one per page/region), browsed together as a gallery via `serve`, the compiler, or the playground. RPML does not simulate interaction; it replaces time with space. The two words that govern quality are **complex** (cover a real production page's information density) and **complete** (no state, branch, permission, or edge case left implicit). For the _why_, see `spec/00-overview.md`. > If a reviewer finishes reading the prototype and still has to ask "but what happens when…", it is not done. ## Capabilities (generation-first) | Capability | Prompt | Use when | | ----------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------- | | **Generate** (flagship) | [`prompts/generate-rpml.md`](prompts/generate-rpml.md) | Producing a new `.rpml` from requirements/screenshots/code | | Review | [`prompts/review-rpml.md`](prompts/review-rpml.md) | Checking an existing `.rpml` for completeness | | Diff impact | [`prompts/rpml-diff-impact.md`](prompts/rpml-diff-impact.md) | Classifying what changed between two `.rpml` versions | | RPML → code | [`prompts/rpml-to-code.md`](prompts/rpml-to-code.md) | Extracting a spec from `.rpml` and generating implementation code | ## Generation best practices (non-negotiable) These are short and load-bearing — follow them even before opening the references. **Output contract (per file).** Each `.rpml` covers exactly one screen/region. Emit a bare `.rpml` file — root element ``, **no HTML wrapper, no doctype**. The document holds: 1. one `` with `title`, `route` (the screen's URL path), and a `description` naming the representative state the snapshot captures — **or** `` for linear reference documents (release notes, specs) with no canvas or route, 2. exactly one `` containing the main snapshot (usually inside a ``), 3. snapshot content built with **RPML primitives only**, 4. `data-pin="N"` on every meaningful region, numbered from 1 with no gaps, 5. a matching top-level `` for every pin (and a matching pin for every numbered annotation — strict 1:1), 6. `` for cross-cutting notes that don't belong to one region (permission matrix, glossary, global policy) — pin-less, rendered at the top of the pane, 7. `` / `` for every conditional branch and state family. To preview, host the `.rpml` (playground `?rpml=`, `npx @21stware/rpui serve .`, or the compiler). Only as a secondary "embed in a page" option do you wrap it in HTML with a single `` — never the primary output. **Pin↔annotation parity.** Every `data-pin="N"` has exactly one top-level ``, and every numbered `` has exactly one `data-pin="N"`. Pins are consecutive from 1. A numbered annotation with no pin is a defect — put other cross-cutting notes in ``. Do not emit `` in the `.rpml`. **Overlay trigger pattern.** Overlays and transient feedback (`modal`, `drawer`, `dropdown`, `popover`, `tooltip`, `toast`) are interaction _results_, not page regions. Never place them in the main snapshot. Instead: pin the **trigger** (the button/row/menu entry that opens it), state the trigger condition + permission gate in the annotation body, and render the overlay **inside the annotation** as an `` of its variants. ```html 触发条件:勾选 ≥1 行后点击「批量关闭」,仅主管/坐席可见。点击弹出二次确认。 ``` A side panel that is a _permanently docked_ structural region may appear open in the snapshot — but document its open/close trigger anyway. When unsure, treat it as an overlay. **Forbidden.** No `div`/`button`/`input`/`table` for product UI (use RPML primitives; plain text in annotations is fine). No `onclick`, event attributes, timers, API calls, runtime focus, or hover behavior. No external CSS, image CDNs, or icon CDNs (the runtime ships inline SVG icons). No `position:absolute`/`fixed` in snapshot content — RPUI owns pin positioning. **Bare tags.** Single-word elements have no suffix (`button`, `table`); compound names keep their hyphen (`list-item`, `table-row`); platform primitives use `ios-*`. Never write the underlying component tags (`page-el`, `main-view`). ## References (single sources of truth) Depth lives in these — do not re-derive it: - **Method** — IA-first design (purpose / priority / regions), visual-weight mapping (sentence / bands / contrast), update restructure rules, recursive decomposition L1–L5, coverage-matrix for combinatorial states, annotation-body dimensions, the what-NOT-to-do list: [`references/practise.md`](references/practise.md). - **Composition** — when to use `list` vs `flex-layout`, overlay pairing, few-shot index (Patterns / Webapp / Gallery): [`references/composition-guide.md`](references/composition-guide.md). Read this before inventing page chrome. - **Compressed spec** — root structure, attributes, rules at a glance: [`references/spec-summary.md`](references/spec-summary.md). Full language rules: `spec/`. - **Component reference** — every element and its attributes: `llms.txt` (authoritative). One-line element index: [`references/element-index.md`](references/element-index.md). - **Worked example (the complexity bar)** — a complete, implementation-depth prototype to study before authoring: [`references/example-reference.rpml`](references/example-reference.rpml) (a service desk: every region pinned and annotated, deep where the domain warrants, every overlay modeled as trigger → result, cross-cutting concerns in ``). Match your depth to the domain — don't over-build a simple page to this level. More graduated examples (entry → complex) live in `examples/`. - **Visual catalog** — `bun run dev` → `/preview/`: **Mobile → Patterns** (full-screen few-shots) and **Mobile → Mobile Widget Gallery** (per-widget iOS cards), **Webapp → Primitives Gallery** (desktop control fragments), **Webapp** product screens (desktop IA). The two galleries are distilled into inline shots in `prompts/generate-rpml.md` (§ Widget composition shots). Do not paste the entire gallery HTML into the model context; pick one few-shot + composition-guide. ## Workflow 1. Gather inputs (requirement → screenshot → conditional code → permission matrix → async states → existing product/page IA). Make every inferred state explicit in an annotation. 2. **Design information architecture first (gate)** — product nav/screen inventory when relevant; always page purpose, priority stack (P0/P1/P2), and region map (chrome / primary / secondary / tertiary / transient). Do **not** invent columns, cards, or tabs until this is fixed. See `references/practise.md` §1b. 3. **Map IA to visual weight (same gate)** — visual sentence, must-see vs must-have, one protagonist + one action, Identity/Proof/Action band order, alignment lock, three-rank contrast budget. Encode with RPML semantics (`gap`, `pane`/`panel`, type rank, one `variant="primary"`), not CSS. See `references/practise.md` §1c. 4. Pick the device preset (`desktop` desktop/admin, `tablet`, `mobile`) — fixed-width, auto-height. Do **not** put a numeric `height` on `view` / `viewport` / `app-shell` (that clips the page). Chrome like `navigator height="52"` is fine. 5. Choose the **most information-dense representative state** for the snapshot that still respects the IA priority stack: loaded data, active selection, an open docked panel, role-specific controls, active validation. Never an empty shell. 6. Build the snapshot inside `` with RPML primitives so layout **expresses** the region map **and** the contrast budget; add `data-pin="N"` in scan/importance order. 7. Create one top-level `` per pin (labels = region roles). 8. Apply recursive decomposition (L1→L5) and the coverage-matrix method to each region — see `references/practise.md`. 9. Write annotation bodies at implementation depth (include IA role + visual intent); expand every hidden interaction result into an ``. 10. On **updates**: re-evaluate IA with the new requirement; restructure / re-home / renumber rather than pure append (`practise.md` §1b.5). Re-rank visual weight when the protagonist or action changes (`practise.md` §1c). 11. Verify no forbidden patterns (HTML product UI, JS, external resources, absolute positioning, `style=`). 12. **Validate:** `bun run validate ` — fix every reported error before delivering; re-check the IA checklist (§1b.4) and visual-weight checklist (§1c.6) yourself. **Multi-screen products.** A prototype is rarely one file. Produce **one `.rpml` per screen or functional region**, named by route, and collect them in a directory the gallery can host (`serve`, the compiler, or playground folder-drop). Never cram multiple screens into one `` — the one-`` contract forbids it. The split signal is conceptual, not numeric: if a `` is covering more than one screen or route, split it into separate files. Link the resulting screens with `` and state the entry/exit routes in each `description` so the set reads as one connected flow. ## Quality bar Before finishing, confirm: - **IA was designed before layout**; a reviewer can restate the primary job and region hierarchy from the snapshot + pins alone, - **visual weight was designed with IA**; a reviewer can name the visual sentence, the protagonist, and the one primary action; quiet must-haves stay muted, - pin numbers continuous and roughly follow importance/scan order; every pin has a matching top-level annotation, - the snapshot shows the most information-dense useful state **without violating the priority stack**, - updates restructured hierarchy when needed (no pure accretion / dual primaries / dump regions), - decomposition reached implementation depth where the domain warranted it (state machines, permission gates, validation, boundaries covered), - combinatorial states (permission × state, role × scale, step × validation) enumerated, not collapsed, - every hidden interaction result expanded into an enum; overlays modeled as trigger → result, - role/permission differences explicit, - runtime limits noted where they affect fidelity (e.g. `table` cell text is sampled from column names — describe exact data in the annotation), - no forbidden product-UI HTML, scripts, event handlers, or external resources. --- ## File: rpml/prompts/generate-rpml.md # System Prompt: Generate RPML from Requirements You are an RPML prototype author. RPML is a static UI specification language rendered by the RPUI Web Components runtime. Your output is a complete `.rpml` file — HTML-like markup (parsed as HTML, not strict XML) with `page` as root, no HTML wrapper, no doctype. Boolean attributes may omit their value (`required`, `has-action`) and bare `&` in text needs no escaping. ## Step 1 — Gather inputs Before writing any markup, collect: 1. Product requirement or user story (route, title, user goal). 2. Screenshot or design draft (regions, layout, density). 3. Existing code with conditionals (`v-if`, `&&`, ternaries, guards) — each is a state to enumerate. 4. Permission matrix (roles and what differs per role). 5. Known async states (loading, empty, error, retry, partial-failure). 6. Existing project IA — README route map, sibling screens, shared chrome, and (on edits) the current page's region hierarchy. If inputs are missing, infer common SaaS/product states and make every assumption explicit in an annotation. ## Step 2 — Design information architecture (mandatory gate) **Do not write layout or content until this step is complete.** Layout is an expression of IA, not a replacement for it. Visual rank is how that IA is seen — lock it in 2.3 before picking cards, type sizes, or buttons. Order of work forever: ```text inputs → retrieve shots → IA (purpose + priority + regions) → visual weight (sentence + bands + contrast) → representative state → layout → content/states ``` ### 2.0 Retrieve layout shots (mandatory before layout) The catalog is a tree (`mobile` / `desktop` → page function → domain). `list_shot_facets` returns the **path array** of nodes that exist, e.g. `["desktop.home_root", "mobile.auth_entry"]`. Every entry has shots. Choose paths from that array on three axes — do not invent strings: 1. **Platform** — `mobile` vs `desktop` prefix 2. **Business** — which product domain the page belongs to (commerce, ai_agent, …) 3. **IA** — the page-function (`home_root`, `primary_list`, `auth_entry`, …) Then call `retrieve_shots({ paths: ["desktop.home_root"] })` (MCP: `search_shots` with the same `paths`). One retrieve per page. A listed path always returns data. Use the hit's `summary`, `primary_action`, `ia_text` (and `get_shot` / `include_rpml` for RPML) as **constraints**. Do not clone brand chrome. If no path fits the screen, skip retrieve and write IA from `../references/composition-guide.md` + skeletons — never from a widget gallery as a page standard. The widget composition shots later in this prompt are **primitive recipes** (how to nest list vs sidebar), not page standards. Page standards come from `retrieve_shots`. ### 2.1 Product-level IA (multi-screen sets / README) Decide before inventing pages: - Screen inventory and routes (what exists, entry/exit). - Primary navigation model (sidebar, top nav, mobile tabs, stack). - What each screen **owns** vs. what is shared chrome. - Which user jobs map to which screens (no two screens fighting for the same P0 job without a reason). Encode product IA in `README.rpml` (and keep chrome consistent across screen files). Also lock one **visual language** for the set: a single material (retail-flat / tool-dense / system-translucent) and one primary-action punctuation rule, so sibling screens don't mix card chrome with tool-dense hairlines. ### 2.2 Page-level IA (every screen, generate or update) Lock these before any `` body: 1. **Purpose** — one sentence primary job for this route. 2. **Priority stack** — P0 / P1 / P2 information and actions (P0 dominates the canvas). 3. **Region map** — named roles, not widgets: chrome · primary · secondary · tertiary · transient (overlays). 4. **Grouping & scan order** — what is read first; what is one decision unit. 5. **Disclosure** — always visible vs. progressive vs. modal; how selection/filter/role changes hierarchy. Do **not** emit IA as RPML tags. Encode it in `page description`, pin order, and region labels. Gallery examples keep a sibling **plain-text** record (`kind: ia-text`, `media: text/plain`) for retrieval — grouping / scan-order / disclosure only. Primitive recipes (`list` vs `panel+flex`, `ios-tabbar` on desktop) belong in `../references/composition-guide.md`. ### 2.3 Map IA to visual weight (mandatory, still before markup) P0/P1/P2 is **meaning**. The snapshot must also rank **attention**. Skip this and you get correct regions with no hierarchy: equal cards, two primary buttons, legal copy as loud as the title. Lock with the IA, then encode in primitives — **not** CSS (`style=` is illegal): 1. **Visual sentence** — one sentence the screen communicates. 2. **Must-see vs must-have** — must-see is loud; must-have (legal, timestamps, hints) stays `muted` / smaller / tertiary. 3. **One protagonist + one action** — one dominant surface; **exactly one** `button variant="primary"` (or one filled `ios-button`) in the snapshot. Everything else `secondary` / `ghost`. 4. **Band order** — Identity · Proof · Action. Tool/triage: Identity → Proof → Action. Exhibit/commerce: Proof → Identity → Action. Auth/checkout: Identity → Action, Proof quiet. 5. **Alignment lock** — scan = start; ceremony = center; data = grid. One system per screen. 6. **Contrast budget** — three ranks only: hero (area + isolation) · emphasis (heading / semibold / the one primary) · quiet (`text size="sm|xs" variant="muted"`, `heading level="6"`). **Distribution:** weight ≈ position × area × contrast × isolation. Spacing **groups** — tight `gap` (4–8) inside a decision, `12–16` inside a band, `24–32` when the band changes. Prefer `4 8 12 16 24 32`. **Surface jobs (one each):** `pane` = group with no chrome; `panel elevation="1"` / `card` = one lifted container, not every block; `bg="muted"` = rails/headers, not the P0 surface; `highlight` = the selected row; brand color = the one primary button, not headings. Motion, gradient, and material that RPML cannot paint go in the region's annotation as **Visual intent** (one or two sentences, no CSS). **Hard fail:** two primaries in the snapshot; equal-weight card/stat walls; title/price/legal/CTA at the same type rank; elevation or bordered+muted+highlight on every region. Full method: `../references/practise.md` §1c. ### 2.4 Encode IA and visual weight in the artifact | Decision | RPML encoding | | -------- | ------------- | | Purpose + hierarchy emphasis | `page description` restates the job and what the snapshot privileges | | Visual sentence + band order | Same `description`: name the protagonist and the action, not only the data state | | Retrieval copy (Gallery / RAG only) | Sibling `ia-text` document (`text/plain`) — **not** inside the `.rpml` | | Region map | L1 pins/annotations named by role; pin order ≈ scan/importance order | | Priority | Dominant surface = P0; secondary columns/inspectors = P1; overflow/enums = P2 | | Visual rank | Hero = area/`flex="1"`/isolation; emphasis = type + one `variant="primary"`; quiet = `muted` / smaller | | Shared chrome | Same `app-shell` / nav / tabbar pattern as siblings; correct `active` | | Motion / material (not snapshot CSS) | Annotation **Visual intent** — not `style=` | | Cross-cutting policy (not IA) | `` — not a fake numbered pin | **Hard fail:** equal-weight card grids with no primary; random side panels; new feature appended without re-ranking priority; overlays treated as permanent peer regions; two `variant="primary"` buttons competing in the snapshot. Full method depth: `../references/practise.md` §1b (IA first + update restructure rules) and §1c (visual weight). ## Step 3 — Choose representative state The main snapshot shows the **most information-dense representative state** of the IA you designed: loaded data, an active selection that reveals secondary hierarchy, an open docked panel when that panel is part of the default job, role-specific controls, active validation. Never show an empty shell. The representative state must still respect the priority stack (don't hide P0 to show a flashy secondary). ## Step 4 — Build the document Only after Steps 2–3, output a valid RPML file following this structure: ```html IA role and visual intent (protagonist / emphasis / quiet; band; any motion or material). Trigger condition, data source, permission gate, validation rules, error handling, boundary values. Detail about sub-region. ``` ## Rules **Use only RPML elements for product UI.** Never use `div`, `button`, `input`, `table`, `script`, or `style`. **No inline styles.** The `style="..."` attribute is illegal on every RPML element — styling is determined by element semantics, not CSS. The validator rejects any `style=` attribute. Pick the right RPML element/variant instead of styling your way around it. **Height (do not clip).** `view` / `viewport` / `app-shell` with a `device` preset are fixed-width and **auto-height**. Omit `height`, or write `height="auto"`. A numeric `height` on those frames **clips** overflowing content — that is the usual "new page is cut off" bug. Chrome pieces (`navigator height="52"`) may stay numeric. Never copy a gallery `height="560"` onto a real page. **Overlay pattern:** Do not place `modal`, `drawer`, `dropdown`, `popover`, `tooltip`, or `toast` in the main snapshot. Pin the trigger; render the overlay inside its annotation ``. **Pin parity (strict, 1:1):** Every `data-pin="N"` has exactly one matching numbered ``, and every numbered `` has exactly one matching `data-pin="N"` in the view. Pins are consecutive from 1. **Never write a numbered annotation with no pin** — that is the most common defect. Page-level IA does **not** go in the `.rpml` (no `` tags). Other cross-cutting notes (permission matrix, glossary, global policy) go in ``. Neither belongs inside the snapshot. **Cross-page links (required whenever navigation exists):** Whenever the UI or an annotation describes a transition to another screen (CTA, list row drill-down, tab, back stack, "see also", empty-state action, success next-step), you **must** wire a real jump — do not only describe it in prose. Use one of: 1. **``** inside the annotation body (preferred for "go to X" notes and flow steps). `section` deep-links a target annotation. 2. **`link="other.rpml"`** (optional `link-section="N"`) on a snapshot control/region that is the real click target (button, list-item, row, tab, card). The runtime shows a small path chip and supports ⌘/Ctrl+click to jump in the workbench/viewer. Hard rules: - Never leave "navigates to settings / detail / login" as plain text only — always attach `anchor` or `link=`. - `to` / `link` values are **sibling `.rpml` filenames** (or relative paths in the project set), not arbitrary URLs. - Prefer `link=` on the visible control in the main snapshot; use `` in annotation enums/flow notes. - Multi-screen products must form a connected graph: every outbound path mentioned in README/routes should appear as `link`/`anchor` on at least one screen. **Diagrams:** Use `` (inside an annotation, or in `mode="doc"` README flow) with Mermaid text. Put the diagram header on its own line. README / document-mode flowcharts are **horizontal** (`flowchart LR` / `graph LR`) so the process reads left-to-right. Diagrams render at mermaid's intrinsic 1:1 size and are **not** squeezed to the prose column — keep node labels short, and split flows longer than about 6 steps. State machines and trees may use `TD`. Sequence diagrams are already horizontal. ```html flowchart LR A[列表] --> B{有筛选?} B -->|是| C[过滤结果] B -->|否| D[全部数据] ``` **No interactivity:** No `onclick`, event attributes, timers, API calls, external images, or CDN resources. **No `position:absolute` or `position:fixed`** in snapshot content. ## Updates (editing an existing screen) When changing an existing `.rpml`, **do not default to incremental append**. 1. Reconstruct the current IA (purpose, priority stack, region map) from the file + README. 2. Fold the new requirement into that model: does it extend P0, promote a secondary, add a region, split a screen, or demote something? 3. Choose the structural response (re-home, reorder, split, deepen) — see `practise.md` §1b.5 table. 4. Apply the smallest markup change that implements the **new** hierarchy; renumber pins if scan order changed; update `description` when P0 changed. 5. Keep sibling chrome/nav consistent when the product IA shifts. Pure accretion that creates dual primaries, dump regions, or stacked equal cards is a failed update. ## Quality targets - **IA first.** Purpose, priority stack, and region map decided before layout; snapshot visibly expresses them. - **Visual weight second.** Visual sentence, band order, alignment, and contrast budget decided before chrome; one protagonist, one primary action; must-haves that must not compete stay quiet. Not a CSS/skin pass. - **One annotation per pinned region — no target count.** Pin and annotate as many regions as the page actually has; a dense admin screen has many, a simple form has few. Do not pad to hit a number, and do not drop a real region to stay under one. Completeness, not a quota, decides breadth. - Nest as deep as the domain warrants — a simple stat card stays shallow; a data table with a detail drawer goes deep (region → element → state family → per-state rule → boundary). Let depth follow complexity, not a target. - Every conditional branch in `` — states, permission variants, validation outcomes, async results. - Annotation bodies at implementation depth: IA role, visual intent, trigger, data source, state-machine transitions, permission gates, validation rules, error handling, boundary values. - **Updates restructure when hierarchy changes** — not only append content. For the full method — IA-first design, visual-weight mapping, recursive decomposition (L1–L5), the coverage-matrix technique for combinatorial states, update restructure rules, and the annotation-body dimensions — see `../references/practise.md`. The complexity bar (annotation depth) is `../references/example-reference.rpml`. For **widget composition** — which primitives to reach for and how they nest — study the two galleries in the playground (`bun run dev` → `/preview/`): **Webapp → Primitives Gallery** (desktop/Web) and **Mobile → Mobile Widget Gallery** (`device="mobile"` iOS, one widget per card). The two shots below are distilled from them. ## Widget composition (not page standards) These two snippets are **primitive recipes** — which widgets nest how. They are not page IA and not the layout standard. Constrain each new screen from `retrieve_shots` (Step 2.0) first; copy composition idioms from here only. Row stacks use `list`/`list-item` (or `ios-list`/`ios-list-item` on iOS); `flex-layout`/`layout` are geometry only. **Web (`device="desktop"`) — app shell + filter + data table:** ```html
``` **Mobile (`device="mobile"`) — iOS shell + grouped list + tab bar:** ```html ``` Both shots are snapshot bodies only — wrap them in `` / `` and add `data-pin` + matching `` blocks per the structure above. ## Element categories (quick reference) - **Canvas:** `page`, `view`, `viewport`, `annotation`, `annotation-global`, `enum`, `enum-item`, `anchor`, `diagram` - **Layout:** `layout`, `panel`, `pane`, `card`, `app-shell`, `navigator`, `sidebar`, `split-pane`, `divider`, `spacer` - **Controls:** `input`, `search`, `textarea`, `select`, `button`, `button-group`, `checkbox`, `checkbox-group`, `radio`, `radio-group`, `radio-card`, `toggle`, `password-input`, `tag-input`, `form`, `form-item`, `form-field-description`, `date-picker`, `upload`, `slider`, `range`, `number-input`, `rating`, `pin-input`, `color-swatch`, `autocomplete` - **Navigation:** `tabs`, `tab`, `breadcrumb`, `pagination`, `steps`, `segmented`, `menu`, `menu-item`, `context-menu`, `command-palette`, `toc`, `kbd`, `list`, `list-item`, `badge`, `avatar` - **Display:** `table`, `table-row`, `table-list-row`, `bulk-action-bar`, `empty`, `loading`, `skeleton`, `stat-card`, `tag`, `chip`, `tree`, `tree-item`, `timeline`, `timeline-item`, `calendar`, `kanban`, `kanban-column`, `kanban-card`, `code-block`, `diff`, `image-grid`, `key-value`, `kv-row`, `accordion`, `accordion-item`, `image-placeholder`, `progress`, `chart`, `avatar-group`, `comment`, `file-list`, `file-item` - **Feedback/Overlays:** `alert`, `toast`, `banner`, `modal`, `drawer`, `dropdown`, `popover`, `tooltip`, `countdown`, `result`, `permission-gate` - **Display (additional):** `quota-bar`, `api-key`, `audit-row`, `workflow-node` - **iOS** (device="mobile"): wrap screens in `app-shell height="auto"` with `ios-navbar` / body / `ios-tabbar`; also `ios-list`, `ios-list-item`, `ios-action-sheet`, `ios-alert`, `ios-switch`, `ios-segmented`, `ios-button`, `ios-search`, `ios-stepper` - **Agent/Chat:** `chat`, `user-message`, `agent-message`, `system-message`, `tool-call`, `agent-output`, `reasoning`, `message-actions`, `suggestions`, `typing`, `composer`, `citation`, `token-usage`. Both `user-message` and `agent-message` are **full-width** (role title is the first line of the body) — never wrap either in a chat bubble or `variant="bubble"`. - **Document** (`mode="doc"` pages): `doc-heading`, `doc-paragraph`, `doc-list`, `doc-list-item`, `doc-quote` ## List attributes (global convention — all `options` / `items` / `actions` / `columns` / `content` / `steps` / …) Many primitives take a **list attribute** (parsed by the runtime as a list of strings). Wrong separators look like layout bugs. Follow this pattern **everywhere**, not only on iOS: ### Priority (pick the highest that fits) 1. **Structured rows → child elements** (preferred when a row has icon + label + trailing value, or multi-field cells): ```html ``` 2. **Short tokens with no internal comma → comma `,`** (default for enums of short labels / icon ids / pure numbers): ```html ``` 3. **Any item may contain `,` (money thousands, addresses, sentences) → pipe `|` as the list separator** for the **whole** attribute: ```html actions="招商银行 · ¥52,360|微信钱包 · ¥3,870|现金 · ¥1,200" columns="姓名|城市|备注" content="张三|北京,朝阳|紧急, 今晚处理" ``` **Rule of thumb:** `,` = list of short tokens; `|` = list when items can contain commas; **children** = label + detail / multi-field rows. Do **not** use `,` for both thousands grouping and list separation in the same attribute. Runtime keeps `¥52,360` intact when possible, but `|` or children is the reliable authoring rule. **High-risk list attrs** (prefer children or `|`): `actions` (action-sheet, bulk-action-bar), `content` (table-row / table-list-row), free-text `columns`. **Low-risk** (comma OK): `icons`, pure-number `data`, short `options`/`items`/`steps`/`keys`. ## Required attributes & anti-patterns (common generation bugs) These failures look like "bad layout" but are almost always **wrong or missing attributes**: 1. **`ios-action-sheet`** - Prefer child `ios-list-item` with `label` + `detail` (see above). - If using `actions=`, use `|` when values include money/commas. - Optional: `title`, `destructive` (exact action label), `cancel`. 2. **`ios-segmented` / `segmented`** - Prefer **child items** (`ios-segment` / `segmented-item`) so each segment can carry `link=`. - Compact: **required** `options` (or `items`) with real product labels; optional `links` CSV. - **Wrong:** empty `` / `` → defaults to **Day/Week/Month**. - **Right:** ``` ``` or compact: `` 3. **`ios-tabbar` / `ios-navbar`** - **Do not put `link=` on the whole bar** — that marks the chrome group. Use **per-item** children. - Tabbar: prefer `ios-tab` children; compact still accepts `items`+`icons` (+ optional `links`/`pins`). - Navbar: prefer `ios-nav-action slot="trailing"` (or `leading`); compact: `trailing-icon` + `trailing-links` / `trailing-pins` / `back-link`. - **`active` = this page's tab** (index or label). Never hardcode `active="0"` on every screen. - **Right (profile page):** ``` ``` 4. **`select` / `combobox` / `toggle-group` / `tag-input`** - Always set `options` (or `tags`) to real labels; do not leave defaults. - Short labels → `,`; labels that contain commas → `|`. 5. **`table` / `table-row`** - Simple cells → `columns="A,B,C"` + `content="a,b,c"`. - Cells with commas/money → `columns="A|B|C"` + `content="a|¥12,480|c"` or use child structure when available. 6. **Logo / product mark** - Prefer ``. Do not invent a bare document/`file` icon as the product brand. 7. **Form layout quality (critical)** Choose a density pattern by context — never invent a tall single-column tower on desktop: | Context | Pattern | Key attrs | | --- | --- | --- | | Page create/edit (many fields) | Multi-column | `form columns="2"` (+ `span="all"` for bio/actions) inside `layout columns="minmax(0,640~720px)"` | | Modal / sheet form | Compact 2-col dialog | `modal width="440"` + `form columns="2"`; long labels `span="all"` | | Settings / profile prefs | Label-left rows | `form-item layout="row"` (not stacked labels) | | Amount + currency / date range | Compound field | one `form-item` + inner `layout columns="1fr 110px"` | | Dense admin create | 3-col | `form columns="3"` | **Wrong (looks sparse / toy):** ```html
``` **Right:** ```html
``` **Settings density:** ```html
``` Catalog recipes: **Primitives → Layout Patterns** (`form · multi-column`, `form · modal compact`, `form · settings rows`, `form · inline compound fields`, `form · 3-col dense admin`). 8. **Charts** - Prefer real `` over empty placeholders for metrics distribution. - Donut: provide `labels`/`series` for legend; optional `center="68% 完成率"`. 9. **Forms inside annotation modals / enum-items** - Must still read as a **dialog**, not a phone-narrow strip of stacked fields. - Use `modal width="440"` (or `520` with `columns="2"`) and fill width — no nested skinny `panel`. --- ## File: rpml/prompts/review-rpml.md # System Prompt: Review RPML for Completeness You are an RPML reviewer. Given an RPML document, check it against the following criteria and report all issues with element paths and specific remediation steps. ## Checklist ### Structure - [ ] Root element is `` with `title`, `route`, and `description` attributes. - [ ] Exactly one `` inside ``. - [ ] `` has a `device` attribute (`desktop`, `tablet`, or `mobile`). - [ ] Main snapshot is built with RPML primitives only — no `div`, `button`, `input`, `table`, `script`, `style`, or external resources. - [ ] No `style="..."` attribute on any element — it is illegal in RPML (styling is semantic). The validator flags it; remove it and use the correct RPML element/variant instead. ### Information architecture - [ ] Page **purpose** (primary user job) is clear from `description` + snapshot; not a grab-bag of unrelated widgets. - [ ] A **priority stack** is visible: one dominant P0 surface; secondary/tertiary regions support it rather than compete at equal weight. - [ ] **Region map** is coherent: chrome / primary / secondary / tertiary / transient (overlays) are distinguishable; L1 pin labels match region roles. - [ ] Pin order roughly follows **scan/importance order**, not arbitrary paint order. - [ ] Shared chrome (nav/sidebar/tabbar) matches product IA and sibling screens; `active` state is correct for this route. - [ ] Overlays are not permanent peer regions of the primary job. - [ ] If this file was clearly **updated by accretion**, flag dual primaries, dump/misc regions, or new equal-weight cards that should have been re-homed — recommend a hierarchy restructure, not more append-only content. ### Visual weight (IA made visible) - [ ] **Visual sentence** is restatable from `description` + snapshot (one job, not a collage). - [ ] **One protagonist** owns area/isolation; **exactly one** `variant="primary"` (or one filled `ios-button`) in the main snapshot unless an enum makes a second mutually exclusive. - [ ] **Band order** (Identity / Proof / Action) is visible and matches the job (tool vs exhibit vs checkout) — not a default equal stack. - [ ] **Alignment** is one system (start / center / grid), not mixed without a band reason. - [ ] **Contrast budget** is three ranks: hero / emphasis / quiet. Legal, timestamps, hints, and membership copy are `muted` / smaller — present, not loud. - [ ] **Spacing groups** decisions (tighter inside a unit, larger at band changes); not one gap everywhere. - [ ] **Surface jobs do not stack:** `pane` where chrome would compete; at most one elevation language; `bg="muted"` on rails/headers, not on P0; no `style=`; no extra `color="primary"` on headings. - [ ] Annotation bodies that need it include **Visual intent** (protagonist / quiet; motion or material if it affects implementation) — not CSS. ### Cross-page navigation - [ ] Every described transition to another screen uses `` and/or `link="….rpml"` on the real control — not prose-only. - [ ] Outbound targets exist as sibling `.rpml` files (or are clearly planned in README). - [ ] Drill-downs / CTAs / back destinations in annotations are wired, not just narrated. ### Pin/annotation parity - [ ] Every `data-pin="N"` in the main view has a matching top-level ``. - [ ] Every top-level `` has a corresponding `data-pin="N"` in the main view. **A numbered annotation with no pin is a defect** — flag it. If the content is genuinely cross-cutting (permission matrix, glossary, global policy, conventions), it should be moved to ``, not left as an orphan numbered annotation. - [ ] Pin numbers are consecutive from 1 with no gaps. - [ ] `` blocks carry no `id` and no pin (they are page-level, rendered at the top of the pane). ### Annotation count and depth - [ ] Annotation count matches the page's real regions — there is **no target number**. Every meaningful region is pinned and annotated; none is padded in or dropped to hit a count. A dense screen legitimately has many; a simple one has few. - [ ] Depth follows domain complexity (region → element → state family → per-state rule → boundary). Complex regions nest deep; simple ones stay shallow. Neither artificially deep nor too shallow. ### Enum coverage - [ ] Every conditional branch has a corresponding `` with one `` per branch. - [ ] Async states covered: loading, empty, error/retry, partial-failure, timeout. - [ ] Permission variants covered: every role that sees different UI has its own enum item. - [ ] Validation states covered: default, filled, error (with error message), disabled. - [ ] Overlay triggers (modal, drawer, dropdown, popover, toast) have their overlays rendered inside annotations, not in the main snapshot. - [ ] Combinatorial state matrices enumerated (permission × state, role × data-size, step × validation) rather than collapsed. ### Annotation body quality - [ ] Each L1/L2 annotation body covers the relevant subset of: **IA role** (why this region exists for the page job), **visual intent** (protagonist / emphasis / quiet; band; motion/material if relevant), trigger/entry condition, data source & refresh, state enumeration, permission gate, validation rule, error/async handling, boundary values. - [ ] Bodies read as implementation spec, not captions. Engineering can derive conditional-rendering logic; QA can derive test cases. - [ ] Assumptions (inferred states not present in inputs) are explicitly flagged. ### Forbidden patterns - [ ] No `onclick`, event attributes, `addEventListener`, timers, or API call references in markup. - [ ] No `position:absolute` or `position:fixed` in snapshot content. - [ ] No external image URLs; `` used instead. - [ ] All tags are bare RPML names (no prefixed or aliased tags). - [ ] Overlays not present in the main snapshot (only their triggers are pinned there). ## Output format Report issues grouped by category. For each issue: - **Location**: element path or `id`/`label` of the nearest ancestor annotation. - **Issue**: what is wrong or missing. - **Fix**: specific action to resolve it. If the document passes all checks, state that explicitly and note any minor recommendations. --- ## File: rpml/prompts/rpml-diff-impact.md # System Prompt: RPML Change Impact Analysis You are a change analyst. Given a diff between `old.rpml` and `new.rpml`, identify every impacted area and classify it by team and severity. ## Analysis dimensions ### 1. Pin changes For each added or removed `data-pin="N"`: - **Added pin**: new UI region introduced. Check whether a matching `` was also added. If not, flag as broken reference. - **Removed pin**: UI region removed or merged. Check whether the old annotation still exists (orphaned annotation). Note downstream impact: any code referencing this region by pin number should be updated. ### 2. Annotation changes (spec drift) For each modified ``: - **Label change**: the region was renamed. Low risk unless code uses the label as a key. - **Body change**: spec was updated. Classify as one of: - *Clarification* — same behavior, clearer wording. No code change required. - *Behavioral change* — trigger condition, data source, or boundary value changed. Engineering review required. - *Removed dimension* — a previously documented concern (permission gate, error handling, boundary) was dropped. Flag as potential regression. ### 3. New states in `enum` (test cases needed) For each `` added: - Name the state and its parent annotation path. - This is a new test case for QA. - If the enum item introduces a new state machine branch, a new conditional-rendering path exists in the implementation. For each `` removed: - The state was explicitly removed from scope. Existing test cases covering it should be deleted or repurposed. ### 4. Permission changes (auth changes needed) For each change involving `` or annotation prose mentioning roles: - **New gate added**: a previously accessible element is now role-restricted. Auth guard must be added. - **Gate removed**: a previously restricted element is now accessible. Auth guard must be removed. - **Role list changed**: different roles can now access the element. Auth guard logic must be updated. - Flag all permission changes as requiring security review before deployment. ## Output format Produce a structured report with four sections matching the dimensions above. For each finding: - **Location**: annotation `id` + `label`, or element path in the diff. - **Change type**: added / removed / modified. - **Impact**: what must change (code, tests, auth, or documentation). - **Severity**: `breaking` (behavior removed or restricted), `additive` (new behavior, no regression), or `clarification` (no behavior change). End with a summary table: | Dimension | Added | Removed | Modified | Breaking | |-----------|-------|---------|----------|----------| | Pins | N | N | N | N | | Annotations | N | N | N | N | | Enum items | N | N | — | N | | Permission gates | N | N | N | N | --- ## File: rpml/prompts/rpml-to-code.md # System Prompt: RPML → Code Generation You are a code generator. Given an RPML file, extract its full specification and generate implementation code for the described UI. ## Extraction pass Before generating code, parse the RPML and extract: ### 1. Component hierarchy Walk the `` tree and map each an RPML primitive to its framework equivalent. The `data-pin` attributes identify the top-level named regions. ### 2. State machines Every `` defines a set of mutually exclusive states. For each enum: - Collect all `` labels — these become union type members. - The parent annotation's `label` names the state machine. - The `description` attributes on enum items document transition conditions. Example mapping: ``` with items ["default", "loading", "error", "empty"] → type TableState = "default" | "loading" | "error" | "empty" ``` ### 3. Permission gates Every `` and every annotation mentioning role conditions defines an auth guard. Extract: - The roles that can see/use the gated element. - The fallback (locked UI, hidden element, or redirect). ### 4. Form validation rules For each `
` / ``: - `required` attribute → required field rule. - `error="..."` text → the validation message and its trigger condition (described in the annotation body). - Cross-field constraints described in annotation prose → extract as named validation functions. ### 5. API contract hints Scan annotation bodies for mentions of: - Data sources (API endpoints, table names, service names). - Refresh cadence (polling interval, websocket, on-demand). - Payload shapes implied by column names in `` and ``. - Error codes or HTTP status handling described in error/retry enum items. ## Code generation rules - Generate one component file per top-level annotation region (matching `data-pin` numbers). - State machines become typed enums or union types with a reducer/store slice. - Permission gates become auth guard functions or HOCs wrapping the gated component. - Form validation rules become a validation schema (Zod, Yup, or native — match the project's existing library). - API contract hints become typed interfaces and fetch/query function stubs with TODO comments where the spec is ambiguous. - Generate loading, empty, and error branch components for every region that has those enum items. - Do not invent behavior not present in the RPML. Mark all ambiguous cases with `// TODO: spec unclear — see annotation `. ## Output structure ``` ComponentName/ index.tsx # root component with state machine wiring types.ts # union types from enum, permission role types validation.ts # form validation schema api.ts # API contract interfaces and fetch stubs states/ Loading.tsx # loading state component Empty.tsx # empty state component Error.tsx # error state component ``` Adjust to match the project's existing file conventions. --- ## File: rpml/references/composition-guide.md # RPML Composition Guide **Audience:** agents generating RPML, and humans reviewing structure. **Role:** decision layer between the element catalog and full-page few-shots. **Not a substitute for:** `practise.md` (decomposition), `element-index.md` (API), or a complete annotated `.rpml` (pin/annotation depth). The playground **Primitives Gallery** (Webapp → Gallery, desktop) and **Mobile Widget Gallery** (Mobile → Gallery, `device="mobile"`) show what components look like in fragments. This file says **which structure to choose** and **which full screens to copy**. --- ## 1. Layer model (non-negotiable) ```text page / view / viewport / app-shell → screen chrome & device list / section / panel / form → content blocks (semantic) list-item / form-item / … → rows & fields (semantic) flex-layout / layout (grid) → geometry only (no business meaning) ios-* → iOS HIG chrome when device=mobile (prefer inside app-shell) overlay-stage + modal|drawer|sheet → dimmed stage + dialog (always pair) ``` Playground: full-screen Mobile / Webapp items keep IA off the RPML document. Open the bottom-right **IA** dock (Structure + `text/plain` record). Widget galleries skip it. Coverage lives at Mobile → **P0 coverage matrix** and Webapp → **Web IA coverage matrix** (the desktop development standard). **Split:** the IA text record is page IA (what to understand first, how groups scan, what is chrome vs primary). This file is **which RPML primitives implement that hierarchy**. A Don't like "fake rows with panel + flex" or "ios-tabbar on desktop" belongs here and on `skeleton.dont`, not in the IA text. | Layer | Responsibility | Do not | | --- | --- | --- | | Semantic containers | Express *what* (list of messages, settings group) | Encode ad-hoc spacing as the only structure | | Layout primitives | Express *how spaced* (gap, columns, align) **and visual rank** | Fake lists, cards-as-pages, or navigation | | Platform (`ios-*`) | Match system look on mobile | Use for desktop admin shells | **Visual rank (from `practise.md` §1c):** after IA, pick primitives that spend contrast budget — not that decorate. | Need | Prefer | Avoid | | --- | --- | --- | | Protagonist work surface | `list` / `table` / large `image-placeholder` with `flex="1"` or the wide column | Four equal `card` / `stat-card` tiles as the page | | Group without competing | `pane` | `panel` on every subsection | | One lifted container | `panel elevation="1"` or `card` | `elevation="2"` on every block | | Quiet must-have (legal, time, hint) | `text size="sm\|xs" variant="muted"` | Same size as the title / price | | The page action | **One** `button variant="primary"` (or one filled `ios-button`) | Two primaries; a large ghost block "so it reads as CTA" | | Selected row | `highlight` on that row only | Highlight + bordered + muted + elevation together | | Stage vs content | `bg="muted"` on rails / section headers | `bg="muted"` on the P0 surface | | Band change | larger `gap` / `spacer` (24–32) | One gap value for the whole page | --- ## 2. Decision table (prefer left) | Need | Prefer | Avoid | | --- | --- | --- | | Stack of similar rows (mail, chat, quotes, settings, orders) | `list` + `list-item` | `panel` + `flex-layout` per row | | iOS Settings / system grouped lists | `ios-list` + `ios-list-item` | Generic list with fake chevrons only | | Sidebar app navigation | `nav-item` inside `sidebar` / `app-shell` | Bare `list-item` without shell | | Mobile screen chrome | `app-shell height="auto"` + `ios-navbar` / body / `ios-tabbar` | Outer `flex-layout` only for stacking chrome | | Filter chips above a table/list | `filter-bar` | Hand-rolled chip row only | | Page columns / dashboard tiles | `layout columns="…"` | Nested flex for every grid | | Local alignment inside a card | `flex-layout` | New semantic component for one gap | | Modal / confirm | `overlay-stage` > `modal` | Bare `modal` floating in a column | | Mobile bottom sheet | `overlay-stage side="bottom"` > `sheet` | Absolute CSS | | Desktop side panel | `drawer` (optionally in `overlay-stage side="…"`) | Wide `panel` pretending to be a drawer | | Table of records | `table` + `table-row` (or `data-table`) | `list` of fake columns | | Marketing / docs prose | `mode="doc"` + `doc-*` | Snapshot canvas for pure text | | Agent transcript | `chat` + agent primitives | Full-width rows; never wrap turns in chat bubbles | **List-item composition (generic list):** ```xml ``` - **Leading:** `avatar` / `icon` / `image-placeholder` / `status-dot`, or `icon="…"`. - **Body:** `title`/`label` + `subtitle`. - **Trailing:** `detail`, `badge`, `chevron`, or children (`tag`, `toggle`, `button`, …). --- ## 3. Few-shot index (study these, don’t invent chrome) Open the playground (`bun run dev` → preview). Prefer **whole screens** over gallery fragments when generating a page. ### Mobile — group **Patterns** (primary few-shots) | Screen | Use when generating… | | --- | --- | | iOS Settings (System) | System settings, preference groups, icon rows + switches | | Messages — Conversation List | Chat inbox, notification list with avatar + preview | | Mail — Inbox | Email / ticket queues (sender, subject, time) | | Contacts — List | Address book, A–Z sections | | Phone — Recents | Call log, activity rows with status | | Watchlist — Quotes | Dense data rows, market / metrics lists | | Stock — Quote Detail | Detail hero + chart + stats + dual CTA | | Commerce — Home Feed | E-commerce browse, product grid | | Profile — User Detail | User/profile header + stats + tabs | | Moments — Social Feed | Social timeline, image posts | | Music — Library & Now Playing Mini | Media library + mini player | | Wallet — Cards & Activity | Balance card + transaction list | | Home — Dashboard | App home, quick actions, recent activity | | Search — Results | Search + filters + ranked hits | | Notifications | Notification center / activity inbox | | Empty State | Canonical empty + CTA | | Form — Edit Profile | Settings-style edit form | | Success — Order Placed | Post-action success / receipt | | Error / Offline | Load failure + retry | | Calendar · month grid | Month/week calendar as the primary surface (not a settings list of events) | | Canvas · spatial board | Infinite canvas + tools + inspector (structure DNA, not a brand clone) | | Desktop · issue master-detail | Desktop triage: list left, selected entity right | ### Mobile — group **App Flows** | Screen | Use when… | | --- | --- | | Login / Sign-up | Auth | | Settings | App settings (not full iOS Settings) | | Checkout / Cart | Cart + summary + pay bar | ### Web — playground **Webapp** product screens | Screen | Use when… | | --- | --- | | Linear — Issue List / Inbox | Desktop issue queues | | Stripe — Dashboard / Payments | Admin metrics + tables | | Shopify Admin — Orders | Commerce back-office | | Gmail / Superhuman — Inbox | Desktop mail | | GitHub — PR / Diff | Dev workflows | | Notion — Editor / Database | Docs + tables | ### Web — **Gallery / Primitives Gallery** Use for **local composition** (how a filter+table card looks, how plan-cards sit together). Do **not** treat the multi-column gallery page as a product IA template. ### Mobile — **Gallery / Mobile Widget Gallery** The mobile counterpart of the Primitives Gallery, in the **Mobile** tab. Each card shows **one bare widget or mobile-specific molecule** at `pane` level (no phone shell — the card is not a full `device="mobile"` page), so you can recall a control's shape and attributes. Coverage: - **Chrome / nav** — `ios-navbar` (large / back), `ios-tabbar`, `toolbar` (bottom actions), large-title + `ios-segmented`. - **Controls** — `ios-search`, `ios-segmented`, `ios-switch`, `ios-stepper`, `ios-button` (filled/tinted/plain/block), `slider` row, `chip` filter row, `rating`, `pin-input` OTP, and a composed **amount keypad** (`heading` + 3-col `button` grid + `ios-button`). - **Rows** — `ios-list` (grouped), rich `ios-list-item`, notification rows, `chat` transcript (`user-message`/`agent-message` full-width), story/avatar rail. - **Content molecules** — balance hero + quick actions, `stat-card` KPIs, mini `chart`, product tile, media rail, `progress`/`quota-bar`, `carousel`. - **Overlays / feedback** — `ios-action-sheet`, `ios-alert`, `sheet`, `context-menu`, `sonner`/`toast`, `banner`/`header-notification`, permission prompt, `result`, `empty`. - **AI** — mobile `chat` + `composer`. Molecules with no dedicated primitive (amount keypad, balance hero, quick-action grid, story rail) are **composed** from `layout`/`flex-layout`/`button`/`avatar`/`panel`. One widget per card — it is a widget catalog, not a product IA template. Prefer **Mobile → Patterns** full screens for IA, and wrap chrome in `app-shell` when building an actual screen. ### Annotated depth bar | Artifact | Use when… | | --- | --- | | `references/example-reference.rpml` | Full pin/annotation/enum depth (service desk) | | `examples/03-list-with-filter.rpml` | List + filters | | `examples/05-dashboard.rpml` | Dashboard | | `examples/02-form-page.rpml` | Form-heavy screen | | **Preview → Primitives → Layout Patterns** | Copy-paste recipes: multi-column form, modal form, inline field row, bento, dashboard grid, info header, master-detail | When authoring complex forms or dashboards, **open Layout Patterns first** and adapt a recipe rather than inventing a single-column stack. --- ## 4. Screen skeletons (copy structure, replace content) ### A. Desktop app shell + list ```xml …nav-item… …list-item… ``` ### A2. Large form (multi-column — do not stack every field) **Problem:** 8+ fields as a single-column `` looks tall and sparse. **Pattern:** put the form in a constrained panel, use `columns="2"` (desktop) or keep 1 column on mobile, and mark full-width fields with `span="all"`. ```xml ``` Rules: - Desktop / tablet wide forms → `form columns="2"` (or `3` for very dense admin). - Mobile → single column (omit `columns` or use default). - Textarea, address, submit row, section headers → `form-item span="all"`. - Prefer a **max-width column** (~640–800px) centered in the viewport; full-bleed single-column forms on desktop look stretched and “long”. - Group with `ios-list` / cards only when the product is settings-style; otherwise multi-column `form` is enough. ### B. Mobile app shell + list + tab bar Prefer `` so `ios-navbar` / body / `ios-tabbar` are first-class chrome (same role as desktop sidebar + navigator). Do not hand-roll an outer column flex just for chrome stacking. ```xml ``` ### B2. Action sheet with money (children, not comma-CSV) ```xml ``` List-attr rule (also in generate-rpml): short tokens → `,`; items that may contain commas/money → `|` or **children**. Never `actions="招商 · ¥52,360,微信 · ¥3,870"`. ### C. Overlay as trigger → result (static) In the **main snapshot**, show the **trigger** (button, row). In the **annotation enum**, show the **result** (`overlay-stage` + `modal` / `sheet`). Do not rely on click handlers. --- ## 5. Anti-patterns (reject in review) 1. **Pseudo-list:** repeating `…` instead of `list`/`list-item`. 2. **Layout as product UI:** `div`-like nesting of flex only, no list/form/table semantics. 3. **Orphan overlays:** `modal`/`sheet` without `overlay-stage` in fragments meant to show dimmed UI. 4. **Comma-CSV with money/long text:** `actions`/`content` using `,` while items contain thousands separators — use children or `|`. 5. **Empty segmented / hard-coded tab `active="0"`** on every mobile page. 4. **Absolute / fixed** positioning for chrome RPUI already owns. 5. **Empty shell snapshot** as the only state (no data, no selection, no error enum). 6. **Brand cosplay** (copying IG/TikTok chrome) when the product is a tool/SaaS — prefer **Patterns**. 7. **Hard min-widths** / desktop tables forced into mobile viewports without `density="compact"`. 8. **HTML product controls** (`
固定吸顶。包含品牌、面包屑、全局搜索、新建入口、通知、当前用户。搜索作用域为「工单号 / 标题 / 客户名」三字段联合模糊匹配,回车触发。 触发条件:输入 ≥ 2 字符且回车,或点击搜索图标,防抖 300ms。结果以下拉浮层呈现(点击搜索框触发),不替换主列表,选中项跳转详情。 「新建工单」点击打开新建工单浮层(坐席/主管可见,只读审计隐藏)。通知角标汇总「分配给我 + @我 + SLA 升级」未读数,>99 显示 99+,点击打开通知抽屉。
固定 220px。分类项角标为该视图下「未读/待处理」实时计数,非总数。当前选中「全部工单」。 「待我审批」仅审批人可见;「服务目录」仅管理员可见。普通坐席看不到这两项。 Tab 按工单状态机切分,角标为各状态实时数量,切换仅过滤当前列表不跳路由。右侧为看板/导出入口。 流转:待处理 → 处理中 → 待客户确认 →(确认)已解决 /(驳回)回到处理中。「全部」聚合所有未归档状态。 触发:点击「导出」,导出当前筛选结果 CSV,单次上限 5000 行,超限提示分批。只读审计可导出不可批量操作。 四个关键运营指标,实时聚合(30s 刷新)。趋势箭头对比上一周期,颜色语义独立于箭头方向——「即将超时」高值是负面信号。 多维筛选 + 批量操作栏。筛选项 AND 关系,变更即时生效。批量栏仅在勾选 ≥1 行时出现。 优先级、处理人为下拉单选;时间为范围选择;「仅看我负责」为快捷开关。主快照中下拉保持收起,展开态在此枚举(下拉浮层非常驻)。 触发:点击「高级筛选」,打开浮层补充条件(来源渠道、标签、自定义字段)。
触发:勾选 ≥1 行后出现,显示已选数与可执行动作。动作受权限与状态约束:跨状态工单不可「合并」。各动作再点击会触发对应确认浮层与结果反馈,见下方子注释。
触发:点击批量栏中对应动作。确认框列出影响范围与可逆性。浮层不画进主快照,在此按触发展开。 触发:确认后执行完成,顶部 Toast 反馈,3s 自动消失。 触发:切换 Tab/筛选或加载失败时替换表格区。
表格为页面主体。SLA 列按剩余时间染色。行尾操作列、行点击均为浮层触发点——详情抽屉不画在主快照里。 行的视觉状态由「读取状态 + SLA + 选中」三维叠加决定。 剩余 >2h 正常色;≤2h 橙色;已超时红色并显示「超 Xh」。 触发:点击任意行或行尾「查看」,从右滑入 400px 抽屉,不遮挡选中行。抽屉切片上的 SLA 字段与底部操作组带 pin,分别对应下方两条子注释。
对应抽屉切片上的 pin 1。剩余 ≤2h 显示橙色并标注「即将超时」,已过期红色。修改截止时间需审批人权限。 对应抽屉切片上的 pin 2。按钮可用性随工单状态变化。 点击「标记解决」需填写解决方案,未填报错。
本页跨 4 类角色,能力差异需显式说明,供研发实现 RBAC、QA 设计权限用例。这是跨区域的横切关注点,不绑定单一 pin,因此用 annotation-global 承载。 完备原型必须覆盖极值与边界。横切关注点,不绑定单一 pin。 --- ## File: rpml/references/practise.md # RPML Generation Practices The single reference for _how_ to decompose a page into a complete RPML prototype. `SKILL.md` routes here for method depth; the runnable system prompt is `prompts/generate-rpml.md`. **Governing order (non-negotiable):** ```text inputs → information architecture (IA) → visual weight (sentence + bands + contrast) → representative state → layout chrome → content & states ``` Never invent layout or fill controls before the IA of the screen (or product) is explicit. Layout is how IA is expressed, not a substitute for it. Visual rank is how that IA is *seen* — decide it before picking cards, type sizes, or buttons. ## 1. Inputs to gather before generating Collect in priority order: 1. **Product requirement / user story** — the feature, route, and user goal. 2. **Screenshot or design draft** — identifies regions, layout, and density. 3. **Existing code with conditionals** — read every `v-if`, `&&`, ternary, and guard; each is a state to enumerate. 4. **Permission matrix / role notes** — which roles exist and what differs per role. 5. **Known async states** — loading, empty, error, retry, partial-failure, timeout. 6. **Existing IA in this project** — README route map, sibling screens, shared chrome (sidebar/tabs), and the current page's region map if editing. If any input is missing, infer common SaaS/product states and make every assumption explicit in an annotation. Never silently omit a plausible state. ## 1b. Information architecture first (before any layout) IA answers: **what must the user understand and do here, in what order of importance, and how is that hierarchy expressed as regions?** Layout answers: **which RPML primitives and columns implement that hierarchy.** Content answers: **what labels, values, and states fill those regions.** If you skip IA, you get pretty but incoherent screens: equal-weight cards, random side panels, tabs that don't match jobs-to-be-done, and incremental edits that bolt features onto the wrong place. ### 1b.1 What "IA" means at two scales | Scale | Design object | Must decide before markup | | ----- | ------------- | ------------------------- | | **Product / set** | Screen inventory + nav model | Which screens exist, entry routes, primary nav (sidebar / tabs / stack), what each screen owns vs. shares | | **Single page** | Region hierarchy | Primary job of this view, ordered regions (primary → secondary → tertiary), what is chrome vs. content, what is always visible vs. progressive disclosure | Product-level IA usually lives in `README.rpml` (route map, modules, flows). Page-level IA is decided **every time** you generate or materially update a screen — even when the README already exists. ### 1b.2 Page IA model (required mental model) Before writing `` content, lock these five layers: 1. **Purpose** — one sentence: the user's primary job on this screen (e.g. "triage open tickets and open one for action"). 2. **Priority stack** — ordered list of information/actions by importance (P0 must be visible without scroll on the main canvas; P1 visible in the default state; P2 progressive / secondary / overlay). 3. **Region map** — named structural areas and their roles, not widgets. Example: - Chrome: app nav / page header / contextual toolbar - Primary: main work surface (list, canvas, feed, form) - Secondary: filters, inspectors, summaries that support the primary job - Tertiary: metadata, audit, help, overflow - Transient: overlays triggered from regions (not co-equal regions) 4. **Grouping & sequencing** — what is scanned first (F/Z patterns, reading order), what is grouped together because it is one decision, what must not compete for attention. 5. **Disclosure model** — always-on vs. collapsed vs. docked vs. modal; which states change the hierarchy (empty vs. loaded vs. selection-active). **Anti-pattern:** jumping from a feature request to "add a card / column / tab" without re-ranking the priority stack. New content must earn a place in the hierarchy or force a deliberate restructure of it. ### 1b.3 How IA shows up in RPML (so it actually shapes output) IA is not a private thought — encode it so layout and annotations cannot drift: | IA decision | Where it appears in the `.rpml` | | ----------- | ------------------------------- | | Page IA (retrieval, not RPML) | Sibling `ia-text` record (`text/plain`) — purpose, priority, regions, grouping. Gallery dock only. **Never** `` tags | | Grouping / disclosure rules | Same `ia-text` Do / Don't — scan order, sectioning, chrome vs primary. **Not** primitive recipes (`list` vs `flex`, `ios-tabbar`) | | Screen purpose + representative hierarchy | `` — name the job and the hierarchy emphasis, not only the data state | | Cross-page nav model | README route map + each screen's chrome (sidebar active item / tabbar active / breadcrumb) | | Region map | `data-pin` order follows **importance / reading order**, not arbitrary paint order; L1 annotation labels match region roles ("Primary list", "Context inspector") | | Priority (P0/P1/P2) | Snapshot composition: P0 fills the dominant surface; P1 sits adjacent; P2 in overflow, accordion, or annotation-only | | Shared chrome vs. page body | `app-shell` / `navigator` / `ios-tabbar` for shared; body for page-owned content — never reinvent nav per file without reason | | Hierarchy change under selection / filter / role | Documented in annotation bodies + ``; snapshot shows the **selected hierarchy** if that is the densest real use | Pin numbers should roughly track scan order (1 = most critical region users must understand first). That makes the annotation pane read as a guided IA walkthrough, not a random inventory. ### 1b.4 IA checklist (pass before building markup) - [ ] I can state the page's primary job in one sentence. - [ ] I have an ordered priority stack (P0/P1/P2) for information and actions. - [ ] Every major region has a role (chrome / primary / secondary / tertiary / transient). - [ ] Shared product chrome matches sibling screens (same nav model and active state). - [ ] Nothing of equal visual weight competes with the primary job without a reason. - [ ] Overlays are not treated as permanent peers of the primary region. - [ ] Visual-weight gate (§1c) is locked: sentence, bands, one protagonist, one action, contrast budget. - [ ] If this is an **update**, I have decided whether the change **extends**, **reorders**, or **restructures** the existing IA (see §1b.5). ### 1b.5 Updates: restructure IA — do not only append Edits that add capability almost always change hierarchy. **Default is wrong:** "find a gap and insert another block." **Default should be:** re-evaluate the page IA with the new requirement as a first-class input, then choose the smallest structural move that preserves a clear hierarchy. | Change type | IA response | Typical RPML action | | ----------- | ----------- | ------------------- | | **Reinforces existing P0** | Keep region map; deepen primary region | Edit primary pin/annotation; add enums | | **Promotes a secondary concern to frequent use** | Re-rank priority stack; may swap primary/secondary surfaces | Move content between regions; retitle pins; renumber if scan order changes | | **New job that doesn't fit any region** | Add a region **or** split a new screen — decide by whether the job shares context with this route | New L1 pin **or** new `.rpml` + anchors; update README routes | | **Cross-cutting policy / permission** | Not a new visual peer | `` or shared chrome change across files | | **Deprecates old primary** | Demote or remove; do not leave zombie equal-weight UI | Remove/repurpose pins; rewrite description; fix active nav | | **Density overflow** | Progressive disclosure or split screen — never endless equal cards | Collapse to filters/tabs/inspector; or split file | **Hard rules for updates:** 1. **Read the current page (and README) first** — reconstruct the existing region map and priority stack before editing. 2. **Name the IA delta** in your reasoning (and briefly in `description` or a global note when the hierarchy changed): what was P0 before, what is P0 after. 3. **Prefer re-homing over stacking** — if a new filter, metric, or action is added, place it where the hierarchy says it belongs; do not append a fifth equal card under four existing ones. 4. **Renumber pins when scan order changes** — pin order is part of the IA narrative. 5. **Keep sibling screens consistent** — if nav, IA module boundaries, or shared chrome change, update related files in the same pass when the user is editing the product set. 6. **Reject pure accretion** when it creates two primaries, duplicate entry points, or a "misc" dumping ground region. Worked intuition: user asks to "add AI summary to the ticket list." - Bad: another full-width card above the list (steals P0, breaks triage job). - Better IA: summary as **selection-dependent secondary** in an inspector, or a one-line insight in the list toolbar, with full summary in annotation enums — primary remains the list. ## 1c. Visual expression of IA (mandatory gate, before markup) IA ranks **meaning**. Visual expression ranks **attention**. A correct P0/P1/P2 stack rendered as equal cards, equal type, and two primary buttons is a failed encoding — the snapshot does not say what the IA decided. This is **not** a skin pass and **not** CSS. RPML styling is semantic (`variant`, `size`, `gap`, `elevation`, `pane` vs `panel`). Do **not** invent `style=`, palettes, or decorative gradients. Do encode weight with the primitives the runtime already has. Motion, light, and material that RPML cannot paint belong in annotation **Visual intent**, not in fake chrome. ### 1c.1 Three weights — never collapse them | Weight | Question | Typical trap | | ------ | -------- | ------------ | | **Business** | Must this exist (price, legal, CTA, permission)? | Legal / helper copy set at title size | | **Cognitive (must-see)** | What must the user grasp in one second? | Hiding P0 to show a flashy P1 | | **Visual** | How much attention may this consume? | Making everything large, filled, or `primary` | **Mapping rule:** business-high does **not** imply visual-high. Disclaimers, timestamps, membership notes, and field hints must exist (must-have) and stay **quiet**. The protagonist (must-see) owns area and isolation. The primary action owns **contrast**, not acreage — a small `variant="primary"` button beats a huge ghost block. ### 1c.2 Gate (lock with §1b.2, before any `` body) 1. **Visual sentence** — one sentence the screen must communicate. Example: "This is the waiting queue; open a ticket and reply." If a region does not serve the sentence, demote or delete it. 2. **Must-see vs must-have** — must-see gets visual rank; must-have may be muted, smaller, tertiary, or annotation-only. 3. **One protagonist + one action** — one region owns the canvas; the snapshot contains **exactly one** `button variant="primary"` (or one filled `ios-button`) for the page job. All other actions are `secondary` / `ghost` / `link`. Danger is reserved for destructive jobs. 4. **Three bands and their order** — Identity (what is this) · Proof (why trust / what to inspect) · Action (what next). Order is a product choice, not a default stack: - **Tool / triage / admin:** Identity → Proof → Action (name the queue, then the work, then act). - **Commerce / marketing exhibit:** Proof → Identity → Action (hero first, name and terms after). - **Auth / checkout / system:** Identity → Action, with Proof as quiet trust copy. 5. **Alignment lock** — one system per screen. Scan/compare = start (left). Brand/ceremony = center. Efficiency/data = grid. Do not mix centered titles with a split footer, or left-aligned tools with a ceremonial hero, unless the bands are deliberately different surfaces. 6. **Contrast budget — three ranks only:** | Rank | Use for | RPML levers | | ---- | ------- | ----------- | | **Hero** | Protagonist surface | Dominant area (`flex="1"`, wide column, large `image-placeholder`, primary `list`/`table`); isolation via a **larger** `gap`/`spacer` around it | | **Emphasis** | Title, key value, the one action | `heading` level 1–3; `text weight="semibold"`; **one** `button variant="primary"` | | **Quiet** | Meta, legal, timestamps, secondary nav, helper | `text size="sm\|xs" variant="muted"`; `heading level="6"`; `button variant="ghost\|secondary"` | Spending the hero rank twice (huge title **and** huge image on a compact transactional tile; four equal `stat-card`s as the page) is a hard fail. ### 1c.3 Distribution — how weight is placed Visual weight ≈ **position × area × contrast × isolation**. Raise one factor; do not raise all four on every element. - **Position** — first in scan order (top, pin 1) is heavier. Pin order must still match this. - **Area** — the work / proof surface is the largest region. The CTA may be small. - **Contrast** — `variant="primary"`, `highlight` on the selected row, and `tag` color outrank extra `heading` size. Brand color is punctuation, not fill — do not set `color="primary"` on headings "for emphasis". - **Isolation** — spacing **groups**. Tight `gap` (4–8) inside one decision; `12–16` inside a band; `24–32` when the band changes. Prefer the scale `4 8 12 16 24 32` — do not invent 13 / 17 / 22. **Band → structure (common mappings):** | Intent | Prefer | | ------ | ------ | | Identity in a tool | `navigator` / `ios-navbar` title + muted meta — not a hero `card` | | Proof as work | `list` / `table` as the primary region; selected row `highlight` | | Proof as exhibit | Large `image-placeholder` (or hero media) **above** copy | | Action as checkout | Footer `flex-layout justify="between"`: quiet price/meta start, one primary end | | Action as ritual | Centered primary under stacked, centered copy | | Grouping without competing | `pane` (no border / fill / radius) | | One lifted container | `panel elevation="1"` or `card` — not on every block | | Floating result | `elevation="2"` only on that overlay/surface | A reviewer who greys out color should still see: one big region, one emphasized action, quiet meta. If they cannot, distribution failed. ### 1c.4 Surface means — each has one job Do not stack border + elevation + `bg="muted"` + `highlight` + `bordered` on the same block. | Surface | Job | Snapshot lever | Do not | | ------- | --- | -------------- | ------ | | **Spacing** | Group decisions | `gap`, `spacer`, `padding` / `px` / `py` | Equal gaps everywhere; spacer as decoration | | **Background** | Stage vs content | Rails / section headers `bg="muted"`; work surface stays default | Painting the P0 surface `muted` so it recedes | | **Chrome / border** | Define a container only when contrast is missing | `panel` / `card` / `bordered` **or** a sibling `divider` | Panel-in-panel; hairline on every group | | **Elevation** | Altitude | Default or `1` for a card; `2` only if it must float above the page | Elevation on every region | | **Type** | Rank | `heading` level + `text` size / weight / `variant` / `align` | Title, price, legal, and CTA at the same size | | **Color** | Punctuation | One primary button; `tag color` for status; `color="danger"` for errors | Extra primary-colored headings; colored panels | | **Motion / gradient / material** | Explain a state change, or light — never snapshot CSS | Annotation **Visual intent** (see below) | `style=`; fake gradient panels; animating titles | **Annotation-only surface** (RPML cannot render these; still specify when they affect implementation): - **Motion** — only when state changes (press, overlay enter/exit, list replace). Name the trigger and a duration class: press ~120ms, hover/small ~200ms, overlay ~200–320ms, entrance ~320–480ms. No motion on titles, legal copy, or keyboard-repeat actions. - **Gradient** — light on a hero or CTA, never a second brand wash across the page. - **Material** — pick one for the product and keep sibling screens on it: retail-flat (quiet cards, one filled CTA), tool-dense (flat, hairlines, compact), or system-translucent (platform chrome on mobile). Mixing two materials on one route looks like a template collage. ### 1c.5 Encode in the artifact | Decision | Where it appears | | -------- | ---------------- | | Visual sentence + band order | `page description` — job **and** what the snapshot privileges ("Waiting queue; list is P0, inspector is P1, Reply is the action") | | Protagonist | Dominant surface + pin 1 (or the pin on that surface); L1 label names the role, not the widget | | Action | Exactly one `variant="primary"` (or one filled `ios-button`) in the main snapshot | | Quiet must-haves | `muted` / smaller type / tertiary region / annotation enum — **present**, not loud | | Motion, gradient, material | L1/L2 annotation body, **Visual intent** — one or two sentences, never CSS | | Product material | README design notes + the same chrome language on sibling screens | ### 1c.6 Checklist (pass with §1b.4 before markup) - [ ] Visual sentence is one job. - [ ] One protagonist region; one primary action in the snapshot. - [ ] Band order (Identity / Proof / Action) is chosen and visible. - [ ] Alignment is one system. - [ ] Contrast budget is three ranks; must-haves that must not compete are quiet. - [ ] Spacing groups decisions; band changes use a larger gap. - [ ] At most one elevation language; `pane` where chrome would compete. - [ ] No `style=`. No extra `color="primary"` on text. No equal-weight card wall. - [ ] Implementation motion / material, if any, lives in annotations. ### 1c.7 Hard fail - Two or more `variant="primary"` in the main snapshot without a mutually exclusive `` reason. - Four equal `stat-card` / `card` tiles as the page (no protagonist). - Title, price, disclaimer, and CTA at the same `text` size / weight. - `panel elevation="2"` (or bordered + muted + highlight) on every region. - Hero media **and** a display-size title both claiming first place on a compact transactional tile. - "Visual polish" as extra badges, extra buttons, or extra color instead of re-ranking. ## 2. Recursive decomposition (L1–L5) Apply this top-down to every pinned region **after** the page IA region map is fixed. L1 pins should map 1:1 onto IA regions (chrome / primary / secondary…), not onto random widgets. Stop nesting when further splitting adds no implementation value. | Level | Element | Purpose | | ----- | -------------------------------------------- | ---------------------------------------------------------------------------------------------- | | L1 | `` (pinned) | Structural area of the page: navbar, sidebar, filter bar, table, drawer | | L2 | Nested `` | Distinct responsibility inside the region: one column, a form field group, the bulk-action bar | | L3 | `` or nested annotation containing one | Mutually exclusive states for that element: default/focus/filled/error; collapsed/expanded | | L4 | `` + `description` | What each state means: trigger, threshold, transition, permission gate | | L5 | Deepest annotation/enum | Extremes and failure modes: 0/empty/overflow values, race conditions, permission denials | A simple stat card may stop at L3. A data table with a detail drawer routinely reaches L5. Let the domain decide depth; let completeness decide breadth. **IA decides which L1 regions exist; decomposition decides how deep each goes.** ## 3. Coverage-matrix method Completeness in complex apps is combinatorial, not a flat list. When two or more axes interact, enumerate the **product**, not each axis alone: - **permission × state** — detail-drawer buttons differ by role _and_ by ticket status. - **role × data-size** — admin view of 5000 rows vs agent view of 7 rows. - **flow-step × validation** — each wizard step × (valid / invalid / pending). - **read-state × SLA × selection** — a table row's appearance is the product of all three. Build the matrix mentally, drop impossible cells, and create one `` per surviving combination. If a cell is intentionally out of scope, say so in an annotation rather than leaving it blank. ## 4. Annotation body structure L1/L2 bodies must read like a spec, not a caption. For a non-trivial region, cover the relevant subset in plain prose — one or two precise sentences each: - **IA role** — primary / secondary / chrome / transient; why this region exists for the page job. - **Visual intent** — protagonist / emphasis / quiet; band (Identity / Proof / Action); alignment; and any motion, light, or material that RPML cannot paint. One or two sentences — not CSS. - **Trigger / entry condition** — what causes this to appear or activate. - **Data source & refresh** — where values come from, polling/refresh cadence. - **State enumeration** — which states exist (then expand them in ``). - **Permission gate** — which roles see/use it, what changes per role. - **Validation rule** — required fields, formats, cross-field constraints. - **Error / async handling** — loading, empty, partial-failure, retry behavior. - **Boundary values** — limits, overflow, truncation, zero/critical states. "Compact" means no padding — it does **not** mean omitting a dimension that matters. Completeness wins over brevity; precision wins over length. ### 4.1 Cross-cutting concerns → `` Some notes don't belong to any single pinned region: a role/permission matrix that spans the whole page, a global empty/error/loading policy, a glossary of domain terms, page-wide conventions, **or the page-level IA summary** (purpose + priority stack) when it helps implementers. **Do not** invent a numbered annotation for these — a numbered annotation must always have a matching pin. Put them in ``, which is pin-less by design and renders at the top of the annotation pane (the "0th" annotation): ```html 三类角色能力差异,供研发实现 RBAC、QA 设计权限用例。 ``` ### 4.2 Cross-page links and diagrams - **``** — explicit jump control in annotation bodies / flow notes; `section` deep-links a target annotation. - **`link="other.rpml"`** (+ optional `link-section`) on snapshot elements — marks the real UI control as a cross-page jump (chip + ⌘/Ctrl+click in workbench). **Required** when the annotation describes navigation: never prose-only "goes to X". - **``** — render a Mermaid flow / state / sequence / ER diagram inside an annotation (or in a `mode="doc"` README) to specify a state machine or flow precisely. Put the diagram header (`flowchart LR`, `stateDiagram-v2`, …) on its own line. README process flows default to **LR** and render at 1:1 (not scaled to the prose column). For product-level IA, a site-map or nav diagram in README is preferred over inventing ad-hoc nav on every screen. ## 5. Quality bar A prototype meets the bar when a reviewer reading it has no remaining "but what happens when…" questions — **and** can restate the page's primary job, region hierarchy, and visual sentence without guessing. Concrete targets: - **IA before layout.** Purpose, priority stack, and region map were decided before markup; the snapshot visibly expresses that hierarchy. - **Visual weight before chrome.** Visual sentence, band order, alignment, and contrast budget were decided with IA; the snapshot has one protagonist and one primary action; quiet must-haves stay muted. See §1c. - **One annotation per pinned region — no target count.** Pin and annotate every meaningful region the page actually has. A dense admin page has many; a simple form has few. Never pad to a number, never drop a real region to stay under one. _Completeness decides breadth; the page decides the count._ - **Depth follows complexity.** Nest as deep as the region warrants — a stat card stays shallow, a data table with a detail drawer goes deep. Don't force uniform depth. - **Strict pin↔annotation parity.** Every `data-pin="N"` ↔ exactly one numbered ``, both directions. A numbered annotation with no pin is a defect. Cross-cutting notes go in `` (see §4.1), not an orphan numbered annotation. - **Every conditional branch** in `` — states, permission variants, validation outcomes, async results. - **Implementation-depth annotation bodies**: IA role, trigger conditions, data source, state-machine transitions, permission gates, validation rules, error handling, boundary values. - **Updates restructure when needed.** No pure accretion that creates dual primaries or orphan dump regions. Reference: [`example-reference.rpml`](example-reference.rpml) (bundled with this skill) — implementation-level bodies, every overlay modeled as trigger → result, with cross-cutting concerns in ``. Study it before authoring; it is the complexity bar. ## 6. What NOT to do - Do not use `div`, `button`, `input`, or `table` for product UI. Use RPML primitives only. - Do not add `onclick`, hover behavior, runtime focus, timers, API calls, or framework state. - Do not import external CSS, image CDNs, or icon CDNs. The runtime provides inline SVG icons. - Do not use `position:absolute` or `position:fixed` in snapshot content. RPUI owns pin positioning. - Do not place overlays (`modal`, `drawer`, `dropdown`, `popover`, `tooltip`, `toast`) in the main snapshot. Pin the trigger; render the overlay inside its annotation enum. - Do not stack mutually exclusive states (empty + loading + modal) side by side in the snapshot. - Use bare RPML tags. Single-word elements have no suffix (`button`, `table`); compound names keep their hyphen (`list-item`, `table-row`); platform primitives use `ios-*`. - Do not omit a plausible state because the input didn't mention it; infer and annotate. - **Do not lay out before IA** — no columns, cards, or tabs until purpose, priority stack, and region map are fixed. - **Do not style before visual weight** — no equal-gap card walls, extra `primary` buttons, or elevation-on-everything instead of ranking attention. See §1c.7. - **Do not write `style=`** or invent palettes / gradients in markup. Surface that RPML cannot paint goes in **Visual intent**, not CSS. - **Do not update by pure append** — re-rank hierarchy; restructure regions when new content changes the primary job. - **Do not create two visual primaries** or a catch-all "other" region to avoid IA decisions. ## 7. Validation Run the validator after generating: ``` bun run validate ``` The validator checks: - Every `data-pin="N"` has a matching top-level ``. - Pin numbers are continuous from 1 with no gaps. - Structural constraints (page root, exactly one view, etc.). Fix all reported errors before delivering the file. After structural validation, re-check the IA checklist in §1b.4 and the visual-weight checklist in §1c.6 yourself — the machine validator does not know your hierarchy or contrast budget. --- ## File: rpml/references/spec-summary.md # RPML Spec Summary (Context Pack) ## File format An RPML file is HTML-like markup, parsed as HTML (not strict XML). The root element is ``. No HTML wrapper, no doctype required. Because it parses as HTML, boolean attributes may omit their value (`required`, `has-action`) and bare `&` in text needs no escaping. Import the renderer once: ```html ``` Or load a standalone `.rpml` file at runtime via the playground (`?rpml=`), `npx @21stware/rpui serve .`, or the compiler. ## Root structure Snapshot mode (default) — one screen with a scaled canvas and annotation pane: ```html Spec prose. Nested spec. ``` Document mode (`mode="doc"`) — linear prose, no canvas, no route: ```html Title Body text with bold and code. Item one. Quoted text. ``` ## Two-layer model **Canvas layer** — document structure and specification: - `page` — root; `title`, `route` (snapshot mode), `description`, optional `mode` (`snapshot` default | `doc` for linear documents with no canvas/route/pins). - `view` — scaled snapshot frame; `device`, `scale`, optional `width`/`height`. - `viewport` — snapshot viewport; same `device` as view. - `annotation` — specification block; top-level has `id` matching a pin, nested has no `id`. - `annotation-global` — page-level, pin-less note for cross-cutting concerns; renders at the top of the pane. No `id`, no pin. - `enum` — horizontal container for mutually exclusive states. - `enum-item` — one state card; `label` required, `description` optional. - `anchor` — cross-page link (`to`, optional `section`) to another screen in the file set. - `diagram` — Mermaid text → inline SVG at 1:1; README flows use `flowchart LR`. Place in an annotation or in `mode="doc"`. **Primitive layer** — static UI building blocks used inside `view` and inside annotation `enum-item` bodies. A broad library across layout, controls, navigation, data display, feedback, iOS, and agent families. The full registered set is enumerated in `element-index.md`. ## Pin system - Add `data-pin="N"` to any element inside ``. Pins number from 1 with no gaps. Pin as many regions as the page has — no target count. - Strict bidirectional parity: every `data-pin="N"` ↔ exactly one top-level ``. A numbered annotation with no pin is a defect — put cross-cutting notes in `` instead. - The runtime renders water-drop pin markers automatically. Never write pin DOM manually. ## Annotation nesting and section addressing Annotations nest arbitrarily. The runtime auto-assigns `data-rp-section` paths (authors do not write them): | Depth | Example path | Marker | | -------------------- | ------------ | ------------------------------------- | | Top-level (has `id`) | `3` | Blue water-drop, shows id | | Nested depth 1 | `3-2` | Purple circle, shows local index `2` | | Nested depth ≥2 | `3-2-1` | Green triangle, shows local index `1` | Local index = 1-based position among annotation siblings under the same parent. Sibling order is significant. Clicking a pin or annotation title sets `?section=` in the URL. Loading a URL with `?section=3-2-1` focuses that annotation. ## Decomposition levels (L1–L5) | Level | What it describes | | ----- | ------------------------------------------------------------------------- | | L1 | Page region (annotation with id) | | L2 | Element or concern inside the region (nested annotation) | | L3 | State family — mutually exclusive states (enum) | | L4 | Per-state rule — trigger, threshold, transition (enum-item + description) | | L5 | Boundary/exception — edge cases, overflow, permission denial | Not every region reaches L5. Let domain complexity decide depth. ## enum usage Use `` for: state families (loaded/loading/empty/error), permission variants, validation branches, overlay results (open/closed, success/failure), and any conditional branch in code. Each `enum-item` gets an auto-numbered black square badge. Combinatorial states (permission × state) must be enumerated as products, not as separate flat lists. ## Overlay pattern `modal`, `drawer`, `dropdown`, `popover`, `tooltip`, `toast` are **never placed in the main snapshot**. Pin the trigger element; render the overlay inside the trigger's annotation (usually inside ``). Exception: a permanently docked side panel may appear open in the snapshot as the representative state, but its trigger and conditions must still be documented. ## Forbidden in RPML - Raw `div`, `button`, `input`, `table`, `script`, `style` for product UI. - `style="..."` attribute on any element — RPML's look is determined by element semantics, not inline CSS. The validator rejects it. - `onclick`, event attributes, timers, API calls, framework state. - External images (use `image-placeholder`), external CSS, CDN icons. - `position:absolute` or `position:fixed` in snapshot content. - Prefixed or aliased tags — use bare RPML tag names only. - Interactive JS of any kind. ## Validation ``` bun run validate ``` Checks structural constraints (root is `page`, exactly one `view`, `page` has a `title`, `annotation-global` carries no `id`), pin↔annotation parity, and consecutive pin numbering from 1. ---