@21stware/rpui 0.2.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ export {};
package/dist/serve.js ADDED
@@ -0,0 +1,143 @@
1
+ #!/usr/bin/env node
2
+ import { createServer } from "node:http";
3
+ import { existsSync, statSync, readFileSync, readdirSync } from "node:fs";
4
+ import { dirname, join, resolve, sep, relative } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const __dirname$1 = dirname(fileURLToPath(import.meta.url));
7
+ const GALLERY_JS = join(__dirname$1, "gallery.js");
8
+ function collectRpml(dir) {
9
+ const out = [];
10
+ const walk = (d) => {
11
+ for (const name of readdirSync(d)) {
12
+ if (name.startsWith(".") || name === "node_modules") continue;
13
+ const full = join(d, name);
14
+ const st = statSync(full);
15
+ if (st.isDirectory()) walk(full);
16
+ else if (/\.rpml$/i.test(name)) {
17
+ out.push({ path: relative(dir, full).split(sep).join("/"), source: readFileSync(full, "utf8") });
18
+ }
19
+ }
20
+ };
21
+ walk(dir);
22
+ return out.sort((a, b) => a.path.localeCompare(b.path));
23
+ }
24
+ function escapeHtml(s) {
25
+ return s.replace(/[&<>]/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;" })[c]);
26
+ }
27
+ function buildHtml(dir, title, galleryJs) {
28
+ const docs = collectRpml(dir);
29
+ const data = JSON.stringify(docs).replace(/<\/script/gi, "<\\/script").replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
30
+ return `<!doctype html>
31
+ <html lang="zh-CN">
32
+ <head>
33
+ <meta charset="UTF-8" />
34
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
35
+ <title>${escapeHtml(title)}</title>
36
+ <style>html,body{margin:0;height:100%}</style>
37
+ </head>
38
+ <body>
39
+ <script>globalThis.__RPML_DOCS__ = ${data};<\/script>
40
+ <script type="module">
41
+ ${galleryJs}
42
+ <\/script>
43
+ </body>
44
+ </html>
45
+ `;
46
+ }
47
+ function parseArgs(argv) {
48
+ let dir = ".";
49
+ let port = 3e3;
50
+ let host = "localhost";
51
+ let sawPositional = false;
52
+ for (let i = 0; i < argv.length; i++) {
53
+ const a = argv[i];
54
+ if (a === "-p" || a === "--port") port = Number(argv[++i]) || port;
55
+ else if (a === "--host") host = argv[++i] || host;
56
+ else if (a === "-h" || a === "--help") usage();
57
+ else if (!a.startsWith("-") && !sawPositional) {
58
+ dir = a;
59
+ sawPositional = true;
60
+ }
61
+ }
62
+ return { dir, port, host };
63
+ }
64
+ function usage() {
65
+ console.error(`Usage: rpui serve [dir] [--port 3000] [--host localhost]
66
+
67
+ Host a directory of .rpml files as one navigable gallery.
68
+
69
+ dir directory to serve (default: current directory)
70
+ -p, --port port (default 3000; auto-increments if busy)
71
+ --host host (default localhost)`);
72
+ process.exit(1);
73
+ }
74
+ function listen(server, port, host) {
75
+ return new Promise((res, rej) => {
76
+ const onError = (err) => {
77
+ if (err.code === "EADDRINUSE" && port < 65535) {
78
+ server.listen(++port, host);
79
+ } else {
80
+ rej(err);
81
+ }
82
+ };
83
+ server.on("error", onError);
84
+ server.listen(port, host, () => {
85
+ server.off("error", onError);
86
+ res(port);
87
+ });
88
+ });
89
+ }
90
+ async function serve(argv) {
91
+ const { dir, port, host } = parseArgs(argv);
92
+ const root = resolve(dir);
93
+ if (!existsSync(root) || !statSync(root).isDirectory()) {
94
+ console.error(`✗ Not a directory: ${dir}`);
95
+ process.exit(1);
96
+ }
97
+ if (!existsSync(GALLERY_JS)) {
98
+ console.error(`✗ Missing runtime bundle (gallery.js). Reinstall @21stware/rpui or run the build.`);
99
+ process.exit(1);
100
+ }
101
+ const galleryJs = readFileSync(GALLERY_JS, "utf8");
102
+ const title = root.split(sep).pop() || "RPML";
103
+ const server = createServer((req, res) => {
104
+ const url = (req.url || "/").split("?")[0];
105
+ try {
106
+ if (url === "/" || !url.endsWith(".rpml")) {
107
+ res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
108
+ res.end(buildHtml(root, title, galleryJs));
109
+ return;
110
+ }
111
+ const file = join(root, decodeURIComponent(url.replace(/^\/+/, "")));
112
+ if (file.startsWith(root) && existsSync(file)) {
113
+ res.writeHead(200, { "content-type": "text/plain; charset=utf-8" });
114
+ res.end(readFileSync(file));
115
+ } else {
116
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
117
+ res.end("Not found");
118
+ }
119
+ } catch (e) {
120
+ res.writeHead(500, { "content-type": "text/plain; charset=utf-8" });
121
+ res.end("Error: " + e.message);
122
+ }
123
+ });
124
+ const actualPort = await listen(server, port, host);
125
+ const count = collectRpml(root).length;
126
+ const addr = `http://${host}:${actualPort}`;
127
+ console.log(``);
128
+ console.log(` RPUI serving ${count} .rpml file${count === 1 ? "" : "s"} from ${root}`);
129
+ console.log(``);
130
+ console.log(` Local: ${addr}`);
131
+ console.log(``);
132
+ console.log(` Press Ctrl+C to stop`);
133
+ }
134
+ const [sub, ...rest] = process.argv.slice(2);
135
+ if (sub === "serve") {
136
+ serve(rest);
137
+ } else if (sub === "-h" || sub === "--help" || sub === void 0) {
138
+ usage();
139
+ } else {
140
+ console.error(`Unknown command: ${sub}
141
+ `);
142
+ usage();
143
+ }
package/package.json CHANGED
@@ -1,46 +1,38 @@
1
1
  {
2
2
  "name": "@21stware/rpui",
3
- "version": "0.2.1",
4
- "description": "Rapid Prototype UI: static UI prototype canvas and snapshot primitives implemented as Web Components.",
3
+ "version": "0.3.1",
4
+ "description": "RPUI: static UI prototype renderer (RPML Web Components runtime)",
5
5
  "type": "module",
6
6
  "main": "dist/rpui.js",
7
7
  "module": "dist/rpui.js",
8
8
  "types": "dist/rpui.d.ts",
9
+ "bin": {
10
+ "rpui": "dist/serve.js"
11
+ },
9
12
  "exports": {
10
13
  ".": {
11
14
  "types": "./dist/rpui.d.ts",
12
15
  "default": "./dist/rpui.js"
13
16
  },
14
17
  "./dist/rpui.js": "./dist/rpui.js",
15
- "./llms.txt": "./llms.txt",
16
- "./SKILL.md": "./SKILL.md"
18
+ "./llms.txt": "../../llms.txt",
19
+ "./SKILL.md": "../../SKILL.md"
17
20
  },
18
21
  "sideEffects": true,
19
22
  "files": [
20
- "dist",
21
- "llms.txt",
22
- "SKILL.md",
23
- "README.md",
24
- "LICENSE"
23
+ "dist"
25
24
  ],
26
25
  "scripts": {
27
- "dev": "vite --open /preview/",
26
+ "dev": "vite --open /preview/ --config vite.config.ts",
28
27
  "typecheck": "tsc -p tsconfig.json --noEmit",
29
- "build:js": "vite build",
28
+ "build:js": "vite build --config vite.config.ts && vite build --config vite.gallery.config.ts && vite build --config vite.serve.config.ts",
30
29
  "build:types": "tsc -p tsconfig.types.json",
31
- "build": "npm run clean && npm run typecheck && npm run build:js && npm run build:types",
32
- "release": "npm run build",
33
- "clean": "rm -rf dist && mkdir -p dist"
30
+ "build": "bun run clean && bun run typecheck && bun run build:js && bun run build:types",
31
+ "clean": "rm -rf dist && mkdir -p dist",
32
+ "release": "bun run build"
34
33
  },
35
- "keywords": [
36
- "web-components",
37
- "prototype",
38
- "ui",
39
- "snapshot",
40
- "llm"
41
- ],
42
- "license": "UNLICENSED",
43
34
  "devDependencies": {
35
+ "rpml-parser": "workspace:*",
44
36
  "typescript": "^5.8.3",
45
37
  "vite": "^5.4.11"
46
38
  }
package/LICENSE DELETED
@@ -1,20 +0,0 @@
1
- Copyright (c) 2026 21st Ware. All Rights Reserved.
2
-
3
- PROPRIETARY AND CONFIDENTIAL
4
-
5
- This software and its source code are the proprietary and confidential
6
- property of the copyright holder. No part of this software, in source or
7
- compiled form, may be copied, reproduced, modified, published, uploaded,
8
- posted, transmitted, distributed, sublicensed, or otherwise exploited in
9
- any way, in whole or in part, without the prior express written permission
10
- of the copyright holder.
11
-
12
- Unauthorized copying, distribution, or modification of this software, via
13
- any medium, is strictly prohibited.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19
- WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
20
- IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/SKILL.md DELETED
@@ -1,279 +0,0 @@
1
- # RPUI Prototype Implementation Skill
2
-
3
- Use this skill when converting product requirements, screenshots, existing UI code, or design notes into a static RPUI prototype.
4
-
5
- ## Goal
6
-
7
- Produce one readable, static, self-contained HTML prototype that **completely** exposes a product page's UI structure, interaction states, permissions, loading/empty/error/validation states, state machines, and edge cases — at a level of detail engineering can implement from and QA can derive test cases from, without running the real app.
8
-
9
- RPUI does not simulate interaction. It bakes time-based behavior into a spatial document: every state that would normally appear only after a click, a role change, or a server response is laid out at once. The two words that govern quality are **complex** (cover the real information density of a production page) and **complete** (no state, branch, permission, or edge case left implicit).
10
-
11
- > If a reviewer finishes reading your prototype and still has to ask "but what happens when…", the prototype is not done.
12
-
13
- ## Complexity expectation
14
-
15
- A realistic complex application page is **not** a few annotated regions. Calibrate to this baseline:
16
-
17
- - **8–10 top-level annotations** (`<rp-annotation id="N">`), one per meaningful pinned region.
18
- - **3–5 levels of annotation nesting** where the domain warrants it: region → element → state family → per-state rule → boundary/exception.
19
- - **Every conditional branch enumerated** with `<rp-enum>` — states, permission variants, validation outcomes, async results.
20
- - Annotation bodies written at **implementation depth**: trigger conditions, data source, state-machine transitions, permission gates, validation rules, error handling, boundary values.
21
-
22
- `demo/golden.html` is the reference for this bar. Study it before authoring. The minimal template at the bottom of this file shows mechanics only — never treat it as a complexity target.
23
-
24
- ## Required inputs
25
-
26
- Prefer these inputs, in priority order:
27
-
28
- 1. Product requirement or user story.
29
- 2. Screenshot or design draft.
30
- 3. Existing code with conditional rendering (read `v-if`/`&&`/ternaries/guards — each is a state to enumerate).
31
- 4. Permission matrix or role notes.
32
- 5. Known loading, empty, error, validation, and edge cases.
33
-
34
- If inputs are missing, infer common SaaS/product states and make every assumption explicit in an annotation. Never silently omit a plausible state.
35
-
36
- ## Output contract
37
-
38
- Output a complete HTML file that imports exactly one RPUI runtime file:
39
-
40
- ```html
41
- <script type="module" src="./dist/rpui.js"></script>
42
- ```
43
-
44
- The document must contain:
45
-
46
- 1. one `<rp-page>` root with `title`, `route`, and `description` (the description should state which representative state the main snapshot captures),
47
- 2. exactly one `<rp-main-view>` containing the main page snapshot,
48
- 3. snapshot content built with `rp-*` primitives only,
49
- 4. numbered `data-pin="N"` anchors on every meaningful main-view region,
50
- 5. matching top-level `<rp-annotation id="N" label="...">` blocks,
51
- 6. `<rp-enum>` / `<rp-enum-item>` for every conditional branch and state family.
52
-
53
- ## The recursive decomposition method
54
-
55
- This is the core technique for reaching completeness. Decompose every pinned region top-down through up to five semantic levels. Stop at the level where further splitting adds no implementation value.
56
-
57
- - **L1 — Region** (`<rp-annotation id="N">`, pinned): a structural area of the page (navbar, sidebar, filter bar, table, drawer). One per `data-pin`.
58
- - **L2 — Element / concern** (nested `<rp-annotation>`): a distinct responsibility inside the region (search behavior, a single column, the bulk-action bar, one form field group).
59
- - **L3 — State family** (`<rp-enum>` or a nested annotation containing one): the set of mutually exclusive states for that element (default/focus/filled/error; collapsed/expanded; the rows' read×SLA×selected matrix).
60
- - **L4 — Per-state rule** (`<rp-enum-item>` + `description`, or a deeper annotation): what each state _means_ and the rule behind it — trigger, threshold, transition, gate.
61
- - **L5 — Boundary / exception** (deepest annotation/enum): extremes and failure modes — 0/empty/overflow values, race conditions, permission denials, irreversible actions.
62
-
63
- Not every region needs all five levels. A simple stat card may stop at L3. A data table with a detail drawer will routinely reach L5. Let the domain decide depth; let completeness decide breadth.
64
-
65
- ## Annotation content structure
66
-
67
- L1/L2 annotation bodies must read like a spec, not a caption. For a non-trivial region, cover the relevant subset of these dimensions in plain prose:
68
-
69
- - **Trigger / entry condition** — what causes this to appear or activate.
70
- - **Data source & refresh** — where values come from, polling/refresh cadence.
71
- - **State enumeration** — which states exist (then expand them in `<rp-enum>`).
72
- - **Permission gate** — which roles see/use it, what changes per role.
73
- - **Validation rule** — required fields, formats, cross-field constraints.
74
- - **Error / async handling** — loading, empty, partial-failure, retry behavior.
75
- - **Boundary values** — limits, overflow, truncation, zero/critical states.
76
-
77
- Keep each dimension to one or two precise sentences. "Compact" means no padding and no waffle — it does **not** mean omitting a dimension that matters. Completeness wins over brevity; precision wins over length.
78
-
79
- ## Coverage matrix method
80
-
81
- Completeness in complex apps is combinatorial, not a flat list. When two or more axes interact, enumerate the product, not each axis alone:
82
-
83
- - **permission × state** — e.g. detail-drawer buttons differ by role _and_ by ticket status.
84
- - **role × data-size** — admin view of 5000 rows vs agent view of 7 rows.
85
- - **flow-step × validation** — each wizard step × (valid / invalid / pending).
86
- - **read-state × SLA × selection** — a table row's appearance is the product of all three.
87
-
88
- Build the matrix mentally, drop impossible cells, and create one `<rp-enum-item>` for each surviving combination. If a cell is intentionally out of scope, say so in an annotation rather than leaving it blank.
89
-
90
- ## Implementation steps
91
-
92
- 1. Identify route, title, and a one-sentence description naming the representative state the snapshot captures.
93
- 2. Choose a device preset: `web` desktop/admin, `ipad` tablet, `mobile` phone. Prefer fixed-width, auto-height.
94
- 3. Choose the **most information-dense representative state** for the main snapshot: loaded data, an active selection, an open drawer/expanded panel central to the page, role-specific controls, active validation. The snapshot should look like the page on a busy day, not an empty shell.
95
- 4. Build the snapshot inside `<rp-main-view device="…">` using only `rp-*` primitives, usually inside `<rp-viewport device="…">`.
96
- 5. Add `data-pin="N"` to every meaningful region. Number from 1, no gaps.
97
- 6. For every pin create one top-level `<rp-annotation id="N" label="…">`.
98
- 7. Apply the recursive decomposition method to each region (L1→L5 as warranted).
99
- 8. For every conditional branch, build a coverage matrix and expand it with `<rp-enum>` / `<rp-enum-item label="…" description="…">`.
100
- 9. Write annotation bodies at implementation depth using the content-structure dimensions.
101
- 10. Verify no interactive JS, event attributes, external images, external CSS, or CDN icons are used.
102
-
103
- ## Section addressing, markers & deep links
104
-
105
- The runtime auto-assigns a `data-rp-section` path to every annotation: top-level = its `id` (e.g. `3`); nested = parent path + 1-based sibling index (e.g. `3-2`, `3-2-1`). Authors do not write these.
106
-
107
- Every annotation also gets a **marker showing its local index** (the last segment of its section path):
108
-
109
- - Top-level (has `id`): blue water-drop showing the id (`1`, `2`, …), matching its pin.
110
- - Nested depth 1: purple circle showing its local index (`3-2` shows **2**).
111
- - Nested depth ≥2: green triangle showing its local index (`3-2-1` shows **1**).
112
-
113
- So a nested annotation at `1-1` is marked simply **1** — the number is local to its parent, not the full path. This lets you annotate a UI slice one level deeper and still reference it unambiguously ("see ① under 区域 3").
114
-
115
- - Clicking a pin, or any annotation title, updates the URL `?section=<path>` and scrolls/focuses that annotation (dashed outline).
116
- - Opening a URL with `?section=3-2-1` focuses that nested annotation on load.
117
- - `<rp-enum-item>` cards are auto-numbered with a black square index badge (1, 2, 3…) so prose can refer to "state 2".
118
-
119
- Use stable, intentional nesting order — index-based addresses and markers depend on sibling order.
120
-
121
- ### Annotating a UI slice further
122
-
123
- When a UI slice rendered inside an annotation needs its own explanation, do not flatten it into prose. Nest another `<rp-annotation>` around or after the slice: it receives its own numbered marker (circle/triangle) and its body indents one level. The chain reads:
124
-
125
- ```
126
- main view → ⬤ annotation 1 (pinned) → UI slice → ① nested annotation (marked "1") → detail
127
- ```
128
-
129
- Example — a slice that itself has sub-rules:
130
-
131
- ```html
132
- <rp-annotation id="3" label="筛选区">
133
- 触发条件、数据来源……
134
- <rp-enum>
135
- <rp-enum-item label="展开浮层"><rp-select state="expanded" options="全部,P1,P2"></rp-select></rp-enum-item>
136
- </rp-enum>
137
- <rp-annotation label="排序规则"> <!-- marked ① (purple circle), indented -->
138
- 点击列头切换升/降序,默认按 SLA 剩余时间升序。
139
- <rp-annotation label="多列排序"> <!-- marked ① (green triangle), indented again -->
140
- 按住 Shift 点击追加次级排序键。
141
- </rp-annotation>
142
- </rp-annotation>
143
- </rp-annotation>
144
- ```
145
-
146
- ## Mid-level composition patterns
147
-
148
- Complex pages are built from recurring composite modules. Assemble these from primitives rather than reinventing per page:
149
-
150
- - **Data table module**: `rp-tabs` (status filter) + filter row (`rp-select`/`rp-date-picker`/`rp-toggle`) + `rp-bulk-action-bar` + `rp-table` (`has-checkbox has-action`) + `rp-pagination`.
151
- - **Master–detail**: `rp-layout columns="minmax(0,1fr) <w>"` with the list on the left and a detail panel on the right. If the detail panel opens on row click (transient), apply the overlay trigger pattern; only keep it open in the snapshot when it is a permanently docked region.
152
- - **Dashboard header**: `rp-layout columns="repeat(N,1fr)"` of `rp-stat-card`.
153
- - **Multi-step flow**: `rp-steps` + per-step `rp-form` / `rp-form-item`, each step's validation enumerated.
154
- - **Form with validation**: `rp-form` + `rp-form-item label required error` + control in `error`/`filled` states enumerated side by side.
155
-
156
- ## Component families
157
-
158
- RPUI ships a broad primitive set. Pick the smallest primitive that conveys the intent. See `llms.txt` for the full attribute reference; for a visual catalog of every component and its states, run `npm run dev` and open the source-mode preview at `/preview/`.
159
-
160
- - **Layout**: viewport, layout, panel, card, split-pane, divider, spacer, sidebar, navbar.
161
- - **Data input**: input, search, textarea, select, date-picker, checkbox, radio, toggle, slider, range, number-input, rating, pin-input, color-swatch, autocomplete, upload, button, button-group, form, form-item.
162
- - **Data display**: table, table-row, list, list-item, tree, timeline, calendar, kanban, code-block, diff, image-grid, key-value, accordion, stat-card, tag, chip, badge, avatar, image-placeholder, progress.
163
- - **Navigation**: tabs, breadcrumb, pagination, steps, segmented, menu, context-menu, command-palette, toc, kbd.
164
- - **Feedback / overlays**: alert, toast, banner, empty, loading, skeleton, countdown, result, progress, tooltip, dropdown, popover, modal, drawer.
165
- - **Enterprise / SaaS**: permission-gate, quota-bar, api-key, audit-row, workflow-node.
166
-
167
- ### Platform primitives (Apple HIG)
168
-
169
- For native-feeling iOS / macOS prototypes, prefer the platform-prefixed primitives over the generic web ones:
170
-
171
- - **iOS** (`rp-ios-*`): ios-navbar, ios-tabbar, ios-list / ios-list-item, ios-action-sheet, ios-alert, ios-switch, ios-segmented, ios-button, ios-search, ios-stepper. Use with `device="mobile"`.
172
- - **macOS** (`rp-macos-*`): macos-window, macos-toolbar, macos-menubar, macos-sidebar / macos-source-item, macos-segmented, macos-popover, macos-sheet, macos-stepper, macos-disclosure, macos-table. Use with `device="web"`.
173
-
174
- Choose the platform that matches the product: a generic web SaaS page uses the plain `rp-*` set; a native iOS app uses `rp-ios-*` inside a `device="mobile"` viewport; a native macOS app wraps content in `rp-macos-window`. Do not mix platform styles within one snapshot.
175
-
176
- > ARIA note: component states (checked/expanded/selected/disabled/current) mirror the structure ARIA APG expects, so annotations can describe accessibility intent. RPUI stays static and does **not** emit runtime `role`/`aria-*` — treat ARIA as a design reference for *which states to document*, not as something the runtime manages.
177
-
178
- ## Overlay trigger pattern (critical)
179
-
180
- Overlays and transient feedback are **interaction results, not page regions**. This includes `rp-modal`, `rp-drawer`, `rp-dropdown`, `rp-popover`, `rp-tooltip`, and `rp-toast`. Do **not** place them in the main snapshot — a snapshot showing an open modal or a live toast is a frozen mid-interaction frame, which contradicts RPUI's "space replaces time" model. Worse, never stack mutually exclusive overlays (empty + loading + modal + toast) side by side in the snapshot as if they coexist.
181
-
182
- Instead, model each overlay as a **trigger → result** pair:
183
-
184
- 1. **Pin the trigger** in the main snapshot — the button, row, field, or menu entry that opens the overlay (e.g. an action button, a table row, a `…` menu). The snapshot shows only this resting trigger.
185
- 2. **State the trigger condition** in the annotation body: what action or system event opens it, any precondition, and the permission gate.
186
- 3. **Render the overlay inside the annotation**, normally as an `<rp-enum>` showing the closed→open transition or the overlay's own variants (e.g. confirm vs. irreversible, success vs. partial-failure vs. error toast).
187
-
188
- ```html
189
- <!-- main snapshot: only the trigger is pinned -->
190
- <rp-button label="批量关闭" variant="danger" data-pin="5"></rp-button>
191
-
192
- <!-- annotation: trigger condition + overlay rendered here -->
193
- <rp-annotation id="5" label="批量关闭">
194
- 触发条件:勾选 ≥1 行后点击「批量关闭」,仅主管/坐席可见。点击弹出二次确认。
195
- <rp-enum>
196
- <rp-enum-item label="确认弹窗" description="列出影响范围与可逆性。">
197
- <rp-modal title="批量关闭确认" has-footer>
198
- <rp-alert type="warning" title="将关闭 3 条工单" message="客户 7 天内可重开。"></rp-alert>
199
- </rp-modal>
200
- </rp-enum-item>
201
- <rp-enum-item label="关闭成功" description="3s 自动消失,列表刷新。">
202
- <rp-toast type="success" title="已关闭 3 条工单"></rp-toast>
203
- </rp-enum-item>
204
- </rp-enum>
205
- </rp-annotation>
206
- ```
207
-
208
- Narrow exception: when a side panel is a **permanently docked structural region** of the page (not a transiently opened overlay), it may appear open in the snapshot as the representative state — but its open/close trigger and conditions must still be documented in its annotation. When unsure, treat it as an overlay and use the trigger pattern.
209
-
210
- ## Authoring rules
211
-
212
- - Use `rp-*` tags for new work. `proto-*` and `snap-*` are compatibility aliases only.
213
- - Use `<rp-page>` as root; exactly one `<rp-main-view>` per page.
214
- - Use `rp-*` primitives for both the snapshot and UI slices inside annotations. Never use raw `div`/`button`/`input`/`table` for product UI; plain text and simple inline markup in annotations is fine.
215
- - No CSS or JS in the prototype; no `position:absolute`/`fixed` in snapshot content (RPUI owns pin positioning).
216
- - Do not hide important content behind interactions — expand it into `<rp-enum>`.
217
- - Overlays and transient feedback (`rp-modal`, `rp-drawer`, `rp-dropdown`, `rp-popover`, `rp-tooltip`, `rp-toast`) are interaction results: do not place them in the main snapshot. Pin the trigger and render the overlay inside its annotation (see Overlay trigger pattern). Plain collapsed `rp-select` in the snapshot is fine; its expanded list belongs in an annotation enum.
218
- - Note runtime limits honestly: `rp-table` cell text is sampled by the runtime from column names — for exact data, describe it in the annotation rather than expecting the table to render it.
219
-
220
- ## Multi-page applications
221
-
222
- One `<rp-page>` = one screen. For an application with several screens:
223
-
224
- - Produce one RPUI HTML file per screen; name files by route.
225
- - If a single page exceeds ~12 pins, it is too dense — split a sub-area into its own page and note in the description that it details a region of the parent.
226
- - For cross-screen flows (wizard, drill-down), state the entry/exit routes in each page's description so the set reads as a connected flow.
227
-
228
- ## Minimal template (mechanics only — not a complexity target)
229
-
230
- ```html
231
- <!doctype html>
232
- <html lang="zh-CN">
233
- <head>
234
- <meta charset="UTF-8" />
235
- <script type="module" src="./dist/rpui.js"></script>
236
- </head>
237
- <body>
238
- <rp-page title="页面标题" route="/route" description="主快照取『…』代表态">
239
- <rp-main-view device="web" scale="0.65">
240
- <rp-viewport device="web">
241
- <!-- main snapshot -->
242
- </rp-viewport>
243
- </rp-main-view>
244
-
245
- <rp-annotation id="1" label="区域说明">
246
- 触发条件、数据来源、权限与校验在此用一两句说清。
247
- <rp-enum>
248
- <rp-enum-item label="默认" description="正常数据态。"
249
- ><rp-empty label="示例"></rp-empty
250
- ></rp-enum-item>
251
- <rp-enum-item label="加载中" description="首次进入或刷新。"
252
- ><rp-loading rows="3"></rp-loading
253
- ></rp-enum-item>
254
- <rp-enum-item label="错误" description="服务端或网络异常。"
255
- ><rp-alert type="error" title="加载失败" message="请重试"></rp-alert
256
- ></rp-enum-item>
257
- </rp-enum>
258
- </rp-annotation>
259
- </rp-page>
260
- </body>
261
- </html>
262
- ```
263
-
264
- For a realistic complex page, see `demo/golden.html` (9 top-level annotations, 3–5 levels deep, implementation-level bodies).
265
-
266
- ## Quality bar
267
-
268
- A good RPUI prototype is reviewable by engineering, product, design, and QA without running the app. QA derives test cases from annotations; engineering derives conditional-rendering and state-machine logic from enum items; design sees whether any hidden state was missed.
269
-
270
- Before finishing, check:
271
-
272
- - pin numbers continuous; every pin has a matching top-level annotation,
273
- - the snapshot shows the most information-dense useful state,
274
- - decomposition reached implementation depth where the domain warranted it (state machines, permission gates, validation, boundaries all covered),
275
- - combinatorial states (permission × state, role × scale, step × validation) are enumerated, not collapsed,
276
- - every hidden interaction result is expanded into an enum,
277
- - role/permission differences are explicit,
278
- - runtime limits (e.g. table sampling) are noted where they affect fidelity,
279
- - no forbidden product-UI HTML, scripts, event handlers, or external resources.