modelroom 0.0.0-stage → 0.0.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.
Files changed (66) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/LICENSE +21 -0
  3. package/README.md +149 -2
  4. package/dist/bin/modelroom.d.ts +3 -0
  5. package/dist/bin/modelroom.d.ts.map +1 -0
  6. package/dist/bin/modelroom.js +242 -0
  7. package/dist/bin/modelroom.js.map +1 -0
  8. package/dist/lib/agents/index.d.ts +22 -0
  9. package/dist/lib/agents/index.d.ts.map +1 -0
  10. package/dist/lib/agents/index.js +68 -0
  11. package/dist/lib/agents/index.js.map +1 -0
  12. package/dist/lib/artifact.d.ts +22 -0
  13. package/dist/lib/artifact.d.ts.map +1 -0
  14. package/dist/lib/artifact.js +62 -0
  15. package/dist/lib/artifact.js.map +1 -0
  16. package/dist/lib/config.d.ts +53 -0
  17. package/dist/lib/config.d.ts.map +1 -0
  18. package/dist/lib/config.js +92 -0
  19. package/dist/lib/config.js.map +1 -0
  20. package/dist/lib/facets.d.ts +62 -0
  21. package/dist/lib/facets.d.ts.map +1 -0
  22. package/dist/lib/facets.js +181 -0
  23. package/dist/lib/facets.js.map +1 -0
  24. package/dist/lib/frontmatter.d.ts +41 -0
  25. package/dist/lib/frontmatter.d.ts.map +1 -0
  26. package/dist/lib/frontmatter.js +219 -0
  27. package/dist/lib/frontmatter.js.map +1 -0
  28. package/dist/lib/lint.d.ts +26 -0
  29. package/dist/lib/lint.d.ts.map +1 -0
  30. package/dist/lib/lint.js +139 -0
  31. package/dist/lib/lint.js.map +1 -0
  32. package/dist/lib/opencode.d.ts +42 -0
  33. package/dist/lib/opencode.d.ts.map +1 -0
  34. package/dist/lib/opencode.js +229 -0
  35. package/dist/lib/opencode.js.map +1 -0
  36. package/dist/lib/pkg.d.ts +2 -0
  37. package/dist/lib/pkg.d.ts.map +1 -0
  38. package/dist/lib/pkg.js +19 -0
  39. package/dist/lib/pkg.js.map +1 -0
  40. package/dist/lib/rooms.d.ts +63 -0
  41. package/dist/lib/rooms.d.ts.map +1 -0
  42. package/dist/lib/rooms.js +178 -0
  43. package/dist/lib/rooms.js.map +1 -0
  44. package/dist/lib/server.d.ts +53 -0
  45. package/dist/lib/server.d.ts.map +1 -0
  46. package/dist/lib/server.js +538 -0
  47. package/dist/lib/server.js.map +1 -0
  48. package/docs/01_concept.md +69 -0
  49. package/docs/02-convention.md +109 -0
  50. package/docs/03-cli.md +103 -0
  51. package/docs/04-agents.md +88 -0
  52. package/examples/rooms/sign-in/BEHAVIOUR.md +23 -0
  53. package/examples/rooms/sign-in/BRIEF.md +15 -0
  54. package/examples/rooms/sign-in/SPEC.md +14 -0
  55. package/examples/rooms/sign-in/index.html +56 -0
  56. package/package.json +81 -4
  57. package/templates/AGENTS.md +31 -0
  58. package/templates/modelroom.json +14 -0
  59. package/templates/room/BEHAVIOUR.md +19 -0
  60. package/templates/room/BRIEF.md +16 -0
  61. package/templates/room/SPEC.md +9 -0
  62. package/templates/room/index.html +53 -0
  63. package/ui/dashboard.html +332 -0
  64. package/ui/modelroom.css +2 -0
  65. package/ui/room.html +466 -0
  66. package/ui/settings.html +125 -0
@@ -0,0 +1,109 @@
1
+ # The convention
2
+
3
+ A room is one screen. Everything in a room is a file with one job, and every file has one
4
+ audience. That is the whole idea; the rest follows.
5
+
6
+ ## Files
7
+
8
+ ```
9
+ rooms/<room>/
10
+ ├── BRIEF.md why this room exists → director, everyone
11
+ ├── SPEC.md what the artifact cannot show → designer, agent, implementer
12
+ ├── BEHAVIOUR.md what it promises → tests, implementer
13
+ └── index.html the artifact (the screen) → director, owner, implementer
14
+ ```
15
+
16
+ The artifact is the visible spec. `SPEC.md` is the invisible one. They must not overlap.
17
+
18
+ ## Rules
19
+
20
+ 1. **The artifact owns what is visible.** If a statement can be shown in a screenshot, it
21
+ belongs to `index.html`, not to `SPEC.md`.
22
+ 2. **A statement lives in one file.** To reach another file, reference it by id
23
+ (`@sign-in-1`) instead of copying it.
24
+ 3. **The design layer never reads the application.** Rooms hold promises the room itself can
25
+ keep. Anything that needs the real system — records, timestamps, rounding, permissions —
26
+ belongs to the application and is handed over, not fetched.
27
+ 4. **Implementation reads the design layer, never the other way round.**
28
+
29
+ This is enforceable rather than aspirational: an agent working in the design layer can be
30
+ denied access outside it (for example, OpenCode `permissions` with
31
+ `external_directory: deny` and write rules limited to `rooms/*`).
32
+
33
+ ## The artifact
34
+
35
+ - Self-contained: no CDN, no web fonts, no framework, no build step. It is going to be handed
36
+ to implementation, so it has to stand on its own (and render with no network).
37
+ - One file per state (`index-error.html` and so on). Steps in a flow are separate rooms.
38
+ - Anything shared (a component, a token) is named in `SPEC.md`, not reinvented per room.
39
+
40
+ ## Frontmatter is the record
41
+
42
+ The body is the content. The frontmatter is the record the dashboard reads: the title,
43
+ the one-line summary, the tags, the state. Nothing else stores it, so nothing has to be
44
+ kept in sync.
45
+
46
+ ```yaml
47
+ ---
48
+ title: Sign in
49
+ status: draft
50
+ version: 1
51
+ summary: A boring front door — the person is signed in, or told plainly why not.
52
+ tags: [auth, form]
53
+ ---
54
+ ```
55
+
56
+ Reserved keys — shape only, never a fixed vocabulary:
57
+
58
+ | Key | Shape | Shown as |
59
+ |---|---|---|
60
+ | `title` | string | the card's heading |
61
+ | `summary` | string | one line under the heading |
62
+ | `key_points` | string[] | up to three bullets |
63
+ | `tags` | string[] | chips, and a checkbox facet |
64
+ | `status` | string | a badge, and a facet |
65
+ | `version` | number or string | a `v3` badge |
66
+ | `updated_at` | date string | sort order (falls back to file mtime) |
67
+ | `deprecated` | boolean | hidden unless archived is on |
68
+ | `superseded_by` | string | a pointer to the room that replaced this one |
69
+
70
+ **Everything else is a facet.** Add `category: kiosk`, `platform: tablet`, or anything
71
+ that helps you narrow the list; a string becomes a radio group or a select (built from
72
+ the values that exist, with counts), a list becomes checkboxes. The dashboard never
73
+ validates values — it shows what is there.
74
+
75
+ `adopted` (or `accepted`, `approved`) is a human decision. An agent must never set it.
76
+
77
+ ## What an agent may do
78
+
79
+ An agent adds. It does not delete.
80
+
81
+ - new room, new key, new tag, new doc → fine
82
+ - merge two spellings → add an alias, leave both values in place
83
+ - retire a room → `deprecated: true` and `superseded_by: <room>`
84
+ - remove a file or a key → a human does it
85
+
86
+ That is what makes "the agent keeps the record tidy" safe: every move is additive and
87
+ reversible in git.
88
+
89
+ ## The check
90
+
91
+ ```
92
+ modelroom check
93
+ ```
94
+
95
+ It reads the frontmatter — head of the file only — and reports what would stop the
96
+ dashboard from deriving the record: malformed or nested frontmatter, a reserved key with
97
+ the wrong shape, two spellings of the same value, a `title` that disagrees with the H1,
98
+ a `superseded_by` that points nowhere. Errors are fixable by an agent; warnings are for a
99
+ human. It never writes.
100
+
101
+ ## Naming
102
+
103
+ - Files are named for **what they are**, not for who writes them and not for the tool:
104
+ `BRIEF`, `SPEC`, `BEHAVIOUR`, `DESIGN`, `principles`, `components`, `tokens`.
105
+ - Avoid words that shift meaning with the reader (`context`, `system`, `ui`, `ux`) and words
106
+ that name a medium (`screen`, `page`) when the thing is not a medium.
107
+ - **Scope is expressed by the directory, not by the filename.** One `BRIEF.md` per level —
108
+ product, flow, room — read top-down, written only at your own level.
109
+ - Prefer one word that survives translation (see the list above) over a compound.
package/docs/03-cli.md ADDED
@@ -0,0 +1,103 @@
1
+ # CLI
2
+
3
+ ```
4
+ modelroom [serve] [--port 4321] [--host 127.0.0.1] [--open]
5
+ modelroom init [<room>] [--dir rooms] [--force]
6
+ modelroom check [--json] [--strict]
7
+ modelroom doctor
8
+ modelroom --version | --help
9
+ ```
10
+
11
+ `modelroom` with no command is `serve`. `serve` needs Node 20+ and nothing else. `init` only
12
+ copies templates. `check` reads the frontmatter of every room and reports convention problems.
13
+ `doctor` checks the setup and exits non-zero when something is broken (optionally missing things
14
+ do not count).
15
+
16
+ ## serve
17
+
18
+ Starts the workspace on `127.0.0.1:4321` by default. `modelroom` alone does the same.
19
+
20
+ - `/` — every room, side by side
21
+ - `/room/<name>` — chat, the room's docs, the artifact
22
+ - `/settings` — which agent to run
23
+ - `/files/<path>` — your repository, served read-only and **unmodified** (no injection)
24
+
25
+ `--open` tries to open a browser; on a headless machine it is ignored.
26
+
27
+ ## init
28
+
29
+ Copies the room templates and, if missing, `modelroom.json`, `AGENTS.md` and a
30
+ `.modelroom/` line in `.gitignore`.
31
+
32
+ ```
33
+ modelroom init checkout # creates rooms/checkout/
34
+ modelroom init checkout --dir design/screens
35
+ modelroom init checkout --force # overwrite existing files
36
+ ```
37
+
38
+ ## check
39
+
40
+ ```
41
+ modelroom check # errors and warnings
42
+ modelroom check --json # machine-readable
43
+ modelroom check --strict # warnings fail too
44
+ ```
45
+
46
+ It only reads the head of each doc (frontmatter lives there) and never writes:
47
+
48
+ ```
49
+ FAIL sign-in/BRIEF.md reserved-type tags must be array, got string
50
+ warn checkout/BRIEF.md h1-title H1 "Checkout" differs from title "check out"
51
+ warn sign-in/BRIEF.md variant status: draft / Draft — same value, different spelling
52
+
53
+ 2 room(s), 1 error(s), 2 warning(s)
54
+ ```
55
+
56
+ Exit code is 1 on errors (or on warnings with `--strict`).
57
+
58
+ ## doctor
59
+
60
+ ```
61
+ modelroom doctor
62
+ ok node 22.14.0 (needs >= 20)
63
+ ok root exists: /path/to/repo
64
+ ok rooms dir exists: rooms
65
+ ok rooms found: checkout, sign-in
66
+ ok config: modelroom.json
67
+ ok agent command on PATH: opencode
68
+ ok png rendering (playwright): not installed (optional)
69
+ ok room checkout: docs ok
70
+ ```
71
+
72
+ ## Config: `modelroom.json`
73
+
74
+ ```jsonc
75
+ {
76
+ "root": ".", // where your repository is
77
+ "rooms": "rooms", // rooms live here
78
+ "artifact": "index.html", // the file that is the screen
79
+ "docs": ["BRIEF.md", "SPEC.md", "BEHAVIOUR.md"], // the only files the UI may write
80
+ "port": 4321,
81
+ "agent": {
82
+ "adapter": "opencode", // "opencode" | "command"
83
+ "command": "opencode",
84
+ "agent": "build",
85
+ "model": null, // set this when your agent's default fails
86
+ "standalone": true,
87
+ "args": [] // adapter "command": e.g. ["-p", "{prompt}"]
88
+ }
89
+ }
90
+ ```
91
+
92
+ Anything you change from `/settings` is kept locally in `.modelroom/state.json`
93
+ (git-ignore it) — committed configuration stays in `modelroom.json`.
94
+
95
+ ## Thumbnails
96
+
97
+ Rendering the artifact to PNG is optional and needs Playwright:
98
+
99
+ ```
100
+ npm i -D playwright && npx playwright install chromium
101
+ ```
102
+
103
+ Without it, rooms simply show no thumbnail and the **make png** button reports why.
@@ -0,0 +1,88 @@
1
+ # Agents
2
+
3
+ modelroom does not bundle a model and does not own a chat product. It runs the agent you
4
+ configure, one session per room, and shows you what changed.
5
+
6
+ ## Built-in adapters
7
+
8
+ | Adapter | What it runs | Notes |
9
+ |---|---|---|
10
+ | `opencode` | [OpenCode](https://opencode.ai) CLI | sessions, model and agent selection |
11
+ | `command` | any CLI | you provide `agent.args`; `{prompt}` and `{session}` are substituted |
12
+
13
+ ### opencode
14
+
15
+ ```jsonc
16
+ {
17
+ "agent": {
18
+ "adapter": "opencode",
19
+ "command": "opencode",
20
+ "agent": "build", // an agent defined in .opencode/agents/
21
+ "model": "openrouter/deepseek/deepseek-v4.1-flash",
22
+ "standalone": true
23
+ }
24
+ }
25
+ ```
26
+
27
+ modelroom runs, roughly:
28
+
29
+ ```
30
+ opencode run --standalone --agent <agent> --model <model> --session <id> --auto "<prompt>"
31
+ ```
32
+
33
+ Two things are worth knowing, both learned the hard way:
34
+
35
+ - **Set `model`.** Many agents resolve to a free tier by default, and the CLI then refuses to
36
+ run outside its own app (`OpenCode's free tier can only be used from within OpenCode`).
37
+ - **`standalone: true`** runs the agent against a private server that inherits this process's
38
+ environment, which is what makes provider keys visible to it. If your shared service already
39
+ has keys, either setting works.
40
+
41
+ Agents that need to *write* only the room's files should be scoped on their side, for example
42
+ with OpenCode `permissions`:
43
+
44
+ ```yaml
45
+ # .opencode/agents/design.md
46
+ permissions:
47
+ - { action: "*", resource: "*", effect: deny }
48
+ - { action: read, resource: "rooms/*", effect: allow }
49
+ - { action: edit, resource: "rooms/*", effect: allow }
50
+ - { action: glob, resource: "*", effect: allow }
51
+ - { action: grep, resource: "*", effect: allow }
52
+ - { action: external_directory, resource: "*", effect: deny }
53
+ ```
54
+
55
+ ### command
56
+
57
+ For anything else — Claude Code, Codex, a local model, a shell script:
58
+
59
+ ```jsonc
60
+ {
61
+ "agent": { "adapter": "command", "command": "claude", "args": ["-p", "{prompt}"] }
62
+ }
63
+ ```
64
+
65
+ `{session}` is substituted with the room's session id. Sessions are stored per room in
66
+ `.modelroom/state.json`; **new session** in the room UI starts a fresh one.
67
+
68
+ ## Writing an adapter
69
+
70
+ An adapter is one object:
71
+
72
+ ```js
73
+ export const mine = {
74
+ name: 'mine',
75
+ newSessionId: () => `ses_${Date.now().toString(36)}${Math.random().toString(36).slice(2, 10)}`,
76
+ run({ cfg, cwd, prompt, sessionId, onChunk, onExit }) {
77
+ // spawn your CLI; stream output with onChunk('stdout'|'stderr', text)
78
+ // call onExit(code, errorMessage?) once it finishes
79
+ },
80
+ };
81
+ ```
82
+
83
+ Add it to `lib/agents/index.mjs`. Two rules keep adapters honest:
84
+
85
+ 1. **Close stdin** (`stdio: ['ignore', 'pipe', 'pipe']`). A CLI that reads stdin will hang
86
+ forever if the pipe stays open.
87
+ 2. **Do not parse the output into meaning.** modelroom shows the agent's words as they are.
88
+ Interpretation belongs to the person reading the room.
@@ -0,0 +1,23 @@
1
+ # sign-in — behaviour
2
+
3
+ What this room promises.
4
+
5
+ ```gherkin
6
+ Feature: sign in
7
+
8
+ @sign-in-1
9
+ Scenario: correct credentials
10
+ Given the sign-in screen is open
11
+ When the person submits a valid email and password
12
+ Then the screen reports success and leaves for the next room
13
+
14
+ @sign-in-2
15
+ Scenario: wrong credentials
16
+ Given the sign-in screen is open
17
+ When the person submits a password that does not match
18
+ Then the screen stays, and explains in one sentence that the details did not match
19
+ ```
20
+
21
+ Notes
22
+ - The room does not decide how credentials are checked — that belongs to the application.
23
+ - `@sign-in-2` must not reveal whether the email exists.
@@ -0,0 +1,15 @@
1
+ ---
2
+ title: Sign in
3
+ status: draft
4
+ version: 1
5
+ summary: A boring front door — the person is signed in, or told plainly why not.
6
+ tags: [auth, form]
7
+ ---
8
+ # Sign in
9
+
10
+ Why does this room exist?
11
+
12
+ - **For:** someone who has an account and needs to get in, once, on a device they may not own
13
+ - **Because:** we want a front door that is boring, quick and impossible to get wrong
14
+ - **Matters most:** the person is signed in, or told plainly why not
15
+ - **Not doing:** sign-up, password reset, "remember me", social login
@@ -0,0 +1,14 @@
1
+ # sign-in — spec
2
+
3
+ Only what the artifact cannot show.
4
+
5
+ - **Where it is used:** desktop and tablet browsers; keyboard and touch; works offline
6
+ (nothing on this screen fetches until submit)
7
+ - **States:**
8
+ - default — `index.html`
9
+ - rejected credentials — `index-error.html` (same layout, one alert above the button)
10
+ - **Constraints:**
11
+ - one purpose per screen: sign in, nothing else
12
+ - the submit button is reachable without scrolling at 1024×768
13
+ - errors are stated in one sentence, without blame
14
+ - **Uses:** `components/field`, `components/button-primary`, tokens `--surface`, `--primary`
@@ -0,0 +1,56 @@
1
+ <!doctype html>
2
+ <!-- sign-in — example room artifact. Self-contained: no CDN, no framework, no build. -->
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="utf-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1">
7
+ <title>sign in</title>
8
+ <style>
9
+ :root {
10
+ --bg: #faf9f7; --surface: #ffffff; --text: #1c1917; --muted: #78716c;
11
+ --line: #e7e5e4; --primary: #1c1917; --primary-text: #fafaf9;
12
+ --danger-bg: #fef2f2; --danger-line: #fecaca; --danger-text: #991b1b;
13
+ --radius: 14px;
14
+ }
15
+ * { box-sizing: border-box; }
16
+ html, body { height: 100%; }
17
+ body {
18
+ margin: 0; display: grid; place-items: center; padding: 32px 20px; background: var(--bg); color: var(--text);
19
+ font-family: "Hiragino Kaku Gothic ProN", "Hiragino Sans", "Noto Sans JP", system-ui, sans-serif; line-height: 1.7;
20
+ }
21
+ .card {
22
+ width: min(440px, 100%); background: var(--surface); border: 1px solid var(--line);
23
+ border-radius: var(--radius); padding: 32px; display: flex; flex-direction: column; gap: 20px;
24
+ }
25
+ .brand { font-size: 13px; letter-spacing: .12em; text-transform: uppercase; color: var(--muted); }
26
+ h1 { font-size: 22px; margin: 0; }
27
+ .field { display: flex; flex-direction: column; gap: 6px; }
28
+ label { font-size: 13px; color: var(--muted); }
29
+ input {
30
+ font: inherit; padding: 12px 14px; border: 1px solid var(--line); border-radius: 10px; background: var(--surface); color: var(--text);
31
+ }
32
+ input:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
33
+ button {
34
+ font: inherit; padding: 14px 20px; border-radius: 10px; border: 1px solid var(--primary);
35
+ background: var(--primary); color: var(--primary-text); cursor: pointer; min-height: 48px;
36
+ }
37
+ .hint { font-size: 13px; color: var(--muted); }
38
+ </style>
39
+ </head>
40
+ <body>
41
+ <main class="card">
42
+ <div class="brand">modelroom · example</div>
43
+ <h1>Sign in</h1>
44
+ <div class="field">
45
+ <label for="email">Email</label>
46
+ <input id="email" type="email" autocomplete="username" placeholder="you@example.com">
47
+ </div>
48
+ <div class="field">
49
+ <label for="password">Password</label>
50
+ <input id="password" type="password" autocomplete="current-password" placeholder="••••••••">
51
+ </div>
52
+ <button>Sign in</button>
53
+ <p class="hint">This is a model room — nothing here is connected to anything.</p>
54
+ </main>
55
+ </body>
56
+ </html>
package/package.json CHANGED
@@ -1,6 +1,83 @@
1
1
  {
2
2
  "name": "modelroom",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.0.1",
4
+ "description": "A full-scale model room for UI. Design screens with any coding agent — BRIEF, SPEC, BEHAVIOUR and the artifact in one place.",
5
+ "keywords": [
6
+ "agent-agnostic",
7
+ "ai-agents",
8
+ "claude-code",
9
+ "coding-agents",
10
+ "design",
11
+ "design-system",
12
+ "design-workflow",
13
+ "live-preview",
14
+ "opencode",
15
+ "spec-driven"
16
+ ],
17
+ "homepage": "https://github.com/modelroom/modelroom#readme",
18
+ "bugs": {
19
+ "url": "https://github.com/modelroom/modelroom/issues"
20
+ },
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/modelroom/modelroom.git"
25
+ },
26
+ "bin": {
27
+ "modelroom": "dist/bin/modelroom.js"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "ui",
32
+ "templates",
33
+ "examples",
34
+ "docs",
35
+ "README.md",
36
+ "CHANGELOG.md",
37
+ "LICENSE"
38
+ ],
39
+ "type": "module",
40
+ "types": "./dist/lib/server.d.ts",
41
+ "exports": {
42
+ ".": {
43
+ "types": "./dist/lib/server.d.ts",
44
+ "default": "./dist/lib/server.js"
45
+ }
46
+ },
47
+ "scripts": {
48
+ "dev": "node --watch bin/modelroom.ts",
49
+ "modelroom": "node bin/modelroom.ts",
50
+ "start": "node bin/modelroom.ts",
51
+ "build": "tsc -p tsconfig.build.json",
52
+ "build:css": "tailwindcss -i src/modelroom.css -o ui/modelroom.css --minify",
53
+ "dev:css": "tailwindcss -i src/modelroom.css -o ui/modelroom.css --watch",
54
+ "lint": "oxlint",
55
+ "lint:types": "oxlint --type-aware",
56
+ "format": "oxfmt",
57
+ "format:check": "oxfmt --check",
58
+ "typecheck": "tsc --noEmit",
59
+ "test": "vitest run --coverage",
60
+ "test:watch": "vitest",
61
+ "verify": "npm run lint && npm run lint:types && npm run format:check && npm run typecheck && npm test && npm run build",
62
+ "prepublishOnly": "npm run build"
63
+ },
64
+ "devDependencies": {
65
+ "@arethetypeswrong/cli": "^0.18.5",
66
+ "@tailwindcss/cli": "^4.3.3",
67
+ "@types/node": "^26.6.5",
68
+ "@vitest/coverage-v8": "^5.0.5",
69
+ "daisyui": "^5.7.50",
70
+ "lefthook": "^2.2.2",
71
+ "oxfmt": "^0.73.0",
72
+ "oxlint": "^1.88.0",
73
+ "oxlint-tsgolint": "^7.0.2004",
74
+ "playwright": "^1.64.1",
75
+ "publint": "^0.3.25",
76
+ "tailwindcss": "^4.3.3",
77
+ "typescript": "^7.0.2",
78
+ "vitest": "^5.0.5"
79
+ },
80
+ "engines": {
81
+ "node": ">=22.18"
82
+ }
83
+ }
@@ -0,0 +1,31 @@
1
+ # AGENTS.md — design layer
2
+
3
+ This repository uses [modelroom](https://github.com/modelroom/modelroom): one room per screen,
4
+ with the reason, the invisible spec, the promises and the artifact kept apart.
5
+
6
+ ## Where you may write
7
+
8
+ - `rooms/<room>/BRIEF.md` — why this room exists
9
+ - `rooms/<room>/SPEC.md` — what the artifact cannot show (devices, states, constraints, what it may use)
10
+ - `rooms/<room>/BEHAVIOUR.md` — what it promises (operations and results)
11
+ - `rooms/<room>/index.html` — the artifact itself
12
+
13
+ Nothing else. Do not touch the application code from here; the design layer never reads it.
14
+
15
+ ## How to work
16
+
17
+ 1. Read the room's `BRIEF.md` first, then `SPEC.md` and `BEHAVIOUR.md`.
18
+ 2. Change the artifact to match them. If they are wrong, say so — do not quietly reword them.
19
+ 3. Keep the artifact self-contained: no CDN, no web fonts, no framework, no build step.
20
+ It is going to be handed to implementation, so it must stand on its own.
21
+ 4. Never restate in `SPEC.md` what can be seen in the artifact. Never copy a promise into
22
+ two files; reference it by id (`@verb-1`) instead.
23
+ 5. Promises that need the real system (records, timestamps, permissions) do not belong in
24
+ this layer.
25
+
26
+ ## Conventions
27
+
28
+ - Add `Status: draft|review|adopted` to `BRIEF.md` when you want the dashboard to show a state.
29
+ Changing it to `adopted` is a human decision, not yours.
30
+
31
+ See `docs/02-convention.md` in the modelroom package for the reasoning.
@@ -0,0 +1,14 @@
1
+ {
2
+ "root": ".",
3
+ "rooms": "rooms",
4
+ "artifact": "index.html",
5
+ "docs": ["BRIEF.md", "SPEC.md", "BEHAVIOUR.md"],
6
+ "port": 4321,
7
+ "agent": {
8
+ "adapter": "opencode",
9
+ "command": "opencode",
10
+ "agent": "build",
11
+ "model": null,
12
+ "standalone": true
13
+ }
14
+ }
@@ -0,0 +1,19 @@
1
+ # {{room}} — behaviour
2
+
3
+ What this room promises: operations and their results. Keep the wording in the language of
4
+ the work, not of the code.
5
+
6
+ ```gherkin
7
+ Feature: {{room}}
8
+
9
+ @first-1
10
+ Scenario: a first promise
11
+ Given some starting point
12
+ When the person does something
13
+ Then something observable happens
14
+ ```
15
+
16
+ Notes
17
+ - One id per promise (`@<verb>-<n>`), and one promise per scenario.
18
+ - Promises that need the real system (records, timestamps, permissions) belong to the
19
+ application, not to this file.
@@ -0,0 +1,16 @@
1
+ ---
2
+ title: {{room}}
3
+ status: draft
4
+ version: 1
5
+ summary: One sentence the next person can decide from.
6
+ tags: []
7
+ ---
8
+ # {{room}}
9
+
10
+ Why does this room exist? Who is it for, and what has to be true for it to be worth
11
+ building? Keep the priorities and the things you are deliberately not doing.
12
+
13
+ - **For:**
14
+ - **Because:**
15
+ - **Matters most:**
16
+ - **Not doing:**
@@ -0,0 +1,9 @@
1
+ # {{room}} — spec
2
+
3
+ Only what the artifact cannot show. If it can be seen in a screenshot, it belongs to the
4
+ artifact, not here.
5
+
6
+ - **Where it is used:** (device, input, environment)
7
+ - **States:** (e.g. default, empty, error — and the file each one lives in)
8
+ - **Constraints:** (what must stay true regardless of the layout)
9
+ - **Uses:** (shared components and tokens this room is allowed to use)
@@ -0,0 +1,53 @@
1
+ <!doctype html>
2
+ <!-- {{room}} — artifact (the thing you hand to implementation)
3
+ Self-contained on purpose: no CDN, no framework, no build. -->
4
+ <html lang="en">
5
+ <head>
6
+ <meta charset="utf-8">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <title>{{room}}</title>
9
+ <style>
10
+ :root {
11
+ --bg: #faf9f7;
12
+ --surface: #ffffff;
13
+ --text: #1c1917;
14
+ --muted: #78716c;
15
+ --line: #e7e5e4;
16
+ --primary: #1c1917;
17
+ --primary-text: #fafaf9;
18
+ --radius: 16px;
19
+ --space: 24px;
20
+ }
21
+ * { box-sizing: border-box; }
22
+ html, body { height: 100%; }
23
+ body {
24
+ margin: 0; display: grid; place-items: center; padding: 40px 20px; background: var(--bg); color: var(--text);
25
+ font-family: "Hiragino Kaku Gothic ProN", "Hiragino Sans", "Noto Sans JP", system-ui, sans-serif;
26
+ line-height: 1.7;
27
+ }
28
+ .card {
29
+ width: min(520px, 100%); background: var(--surface); border: 1px solid var(--line);
30
+ border-radius: var(--radius); padding: var(--space); display: flex; flex-direction: column; gap: var(--space);
31
+ }
32
+ h1 { font-size: 22px; margin: 0; letter-spacing: .01em; }
33
+ p { margin: 0; color: var(--muted); font-size: 15px; }
34
+ .actions { display: flex; gap: 12px; flex-wrap: wrap; }
35
+ button {
36
+ font: inherit; padding: 14px 22px; border-radius: var(--radius); border: 1px solid var(--line);
37
+ background: var(--surface); color: var(--text); cursor: pointer;
38
+ }
39
+ button.primary { background: var(--primary); color: var(--primary-text); border-color: var(--primary); }
40
+ </style>
41
+ </head>
42
+ <body>
43
+ <main class="card">
44
+ <h1>{{room}}</h1>
45
+ <p>This is the artifact for the room. Replace it from the chat, or write it by hand.
46
+ The spec and the promises live next to it in SPEC.md and BEHAVIOUR.md.</p>
47
+ <div class="actions">
48
+ <button class="primary">Primary action</button>
49
+ <button>Secondary</button>
50
+ </div>
51
+ </main>
52
+ </body>
53
+ </html>