@cs4alhaider/screenbook 0.1.0
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.
- package/CLAUDE.md +185 -0
- package/LICENSE +21 -0
- package/README.md +208 -0
- package/TESTING.md +54 -0
- package/app-bridge.js +131 -0
- package/bridge.js +598 -0
- package/cli/screenbook.js +290 -0
- package/cli/templates.js +139 -0
- package/core/browser.js +108 -0
- package/core/fsx.js +71 -0
- package/core/meta.js +94 -0
- package/core/project.js +482 -0
- package/core/serve.js +224 -0
- package/docs/README.md +21 -0
- package/docs/app-mode-guide.md +79 -0
- package/docs/authoring-guide.md +129 -0
- package/docs/cli.md +78 -0
- package/docs/getting-started.md +88 -0
- package/docs/mcp-agents.md +60 -0
- package/docs/shots/android-dark.png +0 -0
- package/docs/shots/app-mode.png +0 -0
- package/docs/shots/hero-studio.png +0 -0
- package/docs/shots/rtl-arabic.png +0 -0
- package/docs/shots/theme-lab.png +0 -0
- package/docs/shots/theming-noir.png +0 -0
- package/docs/shots/web-dashboard.png +0 -0
- package/docs/studio-guide.md +64 -0
- package/docs/troubleshooting.md +55 -0
- package/examples/qahwa/app.config.json +9 -0
- package/examples/qahwa/features/order/data/scenarios.json +25 -0
- package/examples/qahwa/features/order/data/seed.json +21 -0
- package/examples/qahwa/features/order/feature.json +7 -0
- package/examples/qahwa/features/order/flows.json +16 -0
- package/examples/qahwa/features/order/manifest.json +78 -0
- package/examples/qahwa/features/order/screens/010-menu.html +121 -0
- package/examples/qahwa/features/order/screens/020-drink.html +120 -0
- package/examples/qahwa/features/order/screens/030-cart.html +99 -0
- package/examples/qahwa/features/order/screens/040-status.html +125 -0
- package/examples/qahwa/features/order/screens/050-status-android.html +95 -0
- package/examples/qahwa/features/order/screens/shared.css +146 -0
- package/examples/qahwa/features/roastery/data/scenarios.json +35 -0
- package/examples/qahwa/features/roastery/data/seed.json +17 -0
- package/examples/qahwa/features/roastery/feature.json +7 -0
- package/examples/qahwa/features/roastery/flows.json +14 -0
- package/examples/qahwa/features/roastery/manifest.json +32 -0
- package/examples/qahwa/features/roastery/screens/010-orders.html +103 -0
- package/examples/qahwa/features/roastery/screens/020-menu-editor.html +88 -0
- package/examples/qahwa/features/roastery/screens/shared.css +115 -0
- package/examples/qahwa/review/comments.json +3 -0
- package/examples/qahwa/themes/qahwa.json +43 -0
- package/examples/spa-demo/app/index.html +100 -0
- package/examples/spa-demo/app.config.json +9 -0
- package/examples/spa-demo/features/notes-app/feature.json +11 -0
- package/examples/spa-demo/review/comments.json +3 -0
- package/examples/spa-demo/themes/default.json +15 -0
- package/examples/tokens-lab/app.config.json +9 -0
- package/examples/tokens-lab/features/lab/data/scenarios.json +5 -0
- package/examples/tokens-lab/features/lab/data/seed.json +3 -0
- package/examples/tokens-lab/features/lab/feature.json +7 -0
- package/examples/tokens-lab/features/lab/flows.json +14 -0
- package/examples/tokens-lab/features/lab/manifest.json +33 -0
- package/examples/tokens-lab/features/lab/screens/010-swatches.html +112 -0
- package/examples/tokens-lab/features/lab/screens/020-typography.html +115 -0
- package/examples/tokens-lab/review/comments.json +3 -0
- package/examples/tokens-lab/themes/noir.json +32 -0
- package/examples/tokens-lab/themes/paper.json +32 -0
- package/index.html +50 -0
- package/mcp/server.js +366 -0
- package/package.json +57 -0
- package/shell/flowplayer.js +85 -0
- package/shell/frames.js +108 -0
- package/shell/main.js +397 -0
- package/shell/net.js +126 -0
- package/shell/review.js +213 -0
- package/shell/screenhost.js +380 -0
- package/shell/selftest.js +275 -0
- package/shell/shell.css +952 -0
- package/shell/sidebar.js +112 -0
- package/shell/store.js +119 -0
- package/shell/themelab.js +186 -0
- package/shell/toast.js +15 -0
- package/shell/toolbar.js +128 -0
- package/skill/SKILL.md +52 -0
- package/skill/references/app-mode.md +77 -0
- package/skill/references/bridge-api.md +55 -0
- package/skill/references/cli-mcp.md +53 -0
- package/skill/references/flows.md +44 -0
- package/skill/references/i18n.md +52 -0
- package/skill/references/scenarios.md +45 -0
- package/skill/references/screen-contract.md +83 -0
- package/skill/references/tokens.md +56 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# ScreenBook β authoring contract
|
|
2
|
+
|
|
3
|
+
> This file ships with ScreenBook and is scaffolded into consumer projects by `init` (it is the
|
|
4
|
+
> contract from EN-22/EN-32). You are authoring **screens, data, themes and flows** for a
|
|
5
|
+
> ScreenBook prototype project. You never need to read or edit anything under `engine/` β
|
|
6
|
+
> everything you can do is defined here. Engine developers: see `engine/ENGINE-BRIEF.md` instead.
|
|
7
|
+
|
|
8
|
+
## What this is
|
|
9
|
+
|
|
10
|
+
ScreenBook is a static, no-build prototype studio. A project is just folders of standalone HTML
|
|
11
|
+
screens plus JSON files. The shell (`engine/index.html`) turns them into a navigable,
|
|
12
|
+
device-framed gallery with data scenarios, themes, dark/RTL toggles, guided flows, review
|
|
13
|
+
comments and a self-test.
|
|
14
|
+
|
|
15
|
+
## Project layout (convention over configuration)
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
app.config.json β name, features order, devices, themes, languages
|
|
19
|
+
themes/default.json β design tokens (light + dark), flattened to CSS vars
|
|
20
|
+
features/<feature-id>/
|
|
21
|
+
feature.json β { id, title, icon, description, order }
|
|
22
|
+
manifest.json β DERIVED index β never edit; CLI/MCP regenerates it
|
|
23
|
+
flows.json β named walkthroughs: { list: [{id, title, icon, steps: [β¦]}] }
|
|
24
|
+
data/seed.json β mock data for the feature
|
|
25
|
+
data/scenarios.json β named overlays over the seed (empty states, mid-trip, β¦)
|
|
26
|
+
screens/
|
|
27
|
+
shared.css / shared.js β optional feature-wide look/helpers (screens opt in per file)
|
|
28
|
+
010-home.html β one file = one screen; numeric prefix = sidebar order fallback
|
|
29
|
+
review/comments.json β Review-Mode pins from humans (read them, fix, resolve)
|
|
30
|
+
engine/ β the studio itself β NEVER edit
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## The screen contract (the whole truth in one file)
|
|
34
|
+
|
|
35
|
+
```html
|
|
36
|
+
<!DOCTYPE html>
|
|
37
|
+
<html lang="en">
|
|
38
|
+
<head>
|
|
39
|
+
<meta charset="UTF-8">
|
|
40
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
41
|
+
<title>Home</title>
|
|
42
|
+
<script type="application/json" id="screen-meta">
|
|
43
|
+
{
|
|
44
|
+
"title": "Trips home",
|
|
45
|
+
"description": "Entry point β active trip hero, drafts, memories.",
|
|
46
|
+
"order": 10,
|
|
47
|
+
"device": "ios",
|
|
48
|
+
"status": "draft",
|
|
49
|
+
"tags": ["core"]
|
|
50
|
+
}
|
|
51
|
+
</script>
|
|
52
|
+
<script src="../../../engine/bridge.js"></script>
|
|
53
|
+
</head>
|
|
54
|
+
<body>
|
|
55
|
+
<button data-go="020-wizard">Plan a trip</button>
|
|
56
|
+
<script>
|
|
57
|
+
Engine.ready(function (data) { /* seed β active scenario, dates resolved */ });
|
|
58
|
+
</script>
|
|
59
|
+
</body>
|
|
60
|
+
</html>
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Rules:
|
|
64
|
+
- The meta block is **required** (`title` at minimum) β the self-test fails without it.
|
|
65
|
+
`device`: `ios` (393Γ852) Β· `android` (412Γ915) Β· `web` (1280Γ760) Β· `none` (bare).
|
|
66
|
+
Optional: `devices` (variant array, e.g. `["ios","android"]` β one file rendered in each
|
|
67
|
+
listed frame; self-test covers all of them), `status` (draft/wip/ready/review), `tags`,
|
|
68
|
+
`group` (sidebar section), `statusBar` (`light`|`dark` β overrides status-bar/home-bar
|
|
69
|
+
ink on full-bleed art), `updated` (stamped automatically on save; manual override kept).
|
|
70
|
+
- Exactly one bridge include: `<script src="../../../engine/bridge.js"></script>`.
|
|
71
|
+
- Screens must render sensibly with **empty** `Engine.data` and `Engine.state`.
|
|
72
|
+
- Static screens with zero JS are perfectly legal.
|
|
73
|
+
- Sheets/modals live **inside** the screen that opens them (hidden markup until triggered).
|
|
74
|
+
- Markup duplication across screens is normal β there is no templating system. Shared look
|
|
75
|
+
goes in `screens/shared.css` + `shared.js`, included explicitly per screen.
|
|
76
|
+
- Screens may pull any library via CDN (`<script src="https://β¦">`) β iframe isolation makes
|
|
77
|
+
that safe. Keep it inside the screen.
|
|
78
|
+
- Never draw device hardware (status bar, notch, home bar) β the shell's frame owns it.
|
|
79
|
+
Pad with `var(--safe-top)` / `var(--safe-bottom)` instead.
|
|
80
|
+
|
|
81
|
+
## Bridge API (everything a screen can use)
|
|
82
|
+
|
|
83
|
+
| API | Behavior |
|
|
84
|
+
|-----|----------|
|
|
85
|
+
| `Engine.ready(cb)` | `cb(data)` after theme + data are injected. Late registration still fires. |
|
|
86
|
+
| `Engine.data` | Frozen merged mock data: seed β active scenario, `{{todayΒ±N}}` resolved. |
|
|
87
|
+
| `Engine.state` | Shared KV across screens: `get(k, fallback)` / `set(k, v)` / `subscribe(k, cb)` / `keys()`. Survives screen switches; shell has a reset button. |
|
|
88
|
+
| `Engine.go(id)` / `data-go="id"` | Navigate to a sibling screen. Standalone fallback: plain `<id>.html` link. |
|
|
89
|
+
| `Engine.toast(msg)` | Transient toast inside the frame. |
|
|
90
|
+
| `Engine.env` | `{ mode, dir, lang, device, scenario, theme, embedded }`. |
|
|
91
|
+
| `Engine.meta` | This screen's parsed meta block. |
|
|
92
|
+
| CSS vars | Active theme flattened to `--color-*`, `--font-*`, `--radius-*`, `--space-*` on `:root`, plus `--safe-top` / `--safe-bottom`. The bridge also sets `data-mode="light|dark"`, `data-device`, `dir`, `lang` on `<html>`. |
|
|
93
|
+
|
|
94
|
+
Dark mode: style with tokens and they flip automatically; for bespoke dark rules use
|
|
95
|
+
`html[data-mode="dark"] .thing { β¦ }`. RTL: layout flips via `dir` β use logical CSS
|
|
96
|
+
(`margin-inline-start`, `inset-inline-end`) instead of left/right when possible.
|
|
97
|
+
|
|
98
|
+
Opened standalone (no shell), a screen self-loads the default theme + seed and accepts
|
|
99
|
+
`?scenario=β¦&mode=dark&dir=rtl&lang=ar&theme=β¦` query params.
|
|
100
|
+
|
|
101
|
+
## Data scenarios (`data/scenarios.json`)
|
|
102
|
+
|
|
103
|
+
```json
|
|
104
|
+
{ "list": [
|
|
105
|
+
{ "id": "default", "icon": "π§ͺ", "title": "Full demo", "description": "Baseline seed." },
|
|
106
|
+
{ "id": "new-user", "icon": "π", "title": "Brand-new user",
|
|
107
|
+
"remove": ["trips"], "patch": { "user": { "isNew": true } } } ] }
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`patch` deep-merges over the seed (arrays replace), `remove` deletes dot-paths first.
|
|
111
|
+
Date tokens `{{today}}`, `{{today+3}}`, `{{today-2}}` resolve to real ISO dates at injection β
|
|
112
|
+
"mid-trip today" is literally today. Always keep a `default` entry.
|
|
113
|
+
|
|
114
|
+
## Themes (`themes/<id>.json`)
|
|
115
|
+
|
|
116
|
+
```json
|
|
117
|
+
{ "id": "default", "title": "Warm",
|
|
118
|
+
"modes": {
|
|
119
|
+
"light": { "color": { "accent": "#BF5B24", "bg": "#F7F2EA", "ink": "#241C13", "ink-2": "#6E6152" },
|
|
120
|
+
"font": { "sans": "-apple-system, sans-serif" }, "radius": { "card": "20px" } },
|
|
121
|
+
"dark": { "color": { "accent": "#E1793B", "bg": "#171108", "ink": "#F4EBDD" } } } }
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Flattening: `color.ink-2` β `--color-ink-2` (nested paths join with `-`). `dark` deep-merges
|
|
125
|
+
over `light`, so only list what changes. Style screens **with tokens**, not hard-coded colors,
|
|
126
|
+
and the whole project can be re-themed from JSON β humans do exactly that in the shell's
|
|
127
|
+
π¨ **Theme Lab** (live token editing; Save writes `themes/<id>.json` under `engine serve`),
|
|
128
|
+
so a theme file may change without an agent touching it. Re-read before editing.
|
|
129
|
+
|
|
130
|
+
## Strings (`i18n.json` + `Engine.t`) β optional per feature
|
|
131
|
+
|
|
132
|
+
Key-first: `{ "slide1.title": { "en": "β¦", "ar": "β¦" } }`. Screens call
|
|
133
|
+
`Engine.t('slide1.title')` (placeholders: `Engine.t('greet', {name})`). Fallback: active
|
|
134
|
+
language β `defaultLanguage` β the key. Language switches reload the screen, so ready-time
|
|
135
|
+
binding is always right. Missing translations are validate WARNINGS, never blockers.
|
|
136
|
+
Features without i18n.json simply own their text inline β also fine.
|
|
137
|
+
|
|
138
|
+
## Flows (`flows.json`)
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{ "list": [ { "id": "create-trip", "title": "Create a trip", "icon": "β¨",
|
|
142
|
+
"description": "From empty home to a planned trip.",
|
|
143
|
+
"steps": [
|
|
144
|
+
{ "screen": "010-home", "note": "Start here β empty state invites planning." },
|
|
145
|
+
{ "screen": "020-wizard", "note": "Seven questions.", "scenario": "new-user" } ] } ] }
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The shell plays these as guided stakeholder walkthroughs (prev/next + note captions).
|
|
149
|
+
A step may pin a `scenario` (and `mode`). Steps must reference existing screens.
|
|
150
|
+
|
|
151
|
+
## Embedded apps (app mode)
|
|
152
|
+
|
|
153
|
+
A feature with `"type": "app"` embeds a WHOLE single-page app: `src` (project-relative entry
|
|
154
|
+
HTML), `device`, `chrome: "self"|"engine"` (self = the app draws its own status bars), and
|
|
155
|
+
optionally `driver: "driver.js"` β a project-owned script injected into the app that calls
|
|
156
|
+
`AppBridge.register({screens, go, current?, set?, scenarios?})` so sealed apps stay untouched.
|
|
157
|
+
Cooperative apps include `engine/app-bridge.js` themselves. Register via CLI
|
|
158
|
+
(`screenbook app add <src>`) or MCP (`add_app`). Full contract: the skill's
|
|
159
|
+
`references/app-mode.md`. Validation boots the app and visits every reported screen.
|
|
160
|
+
|
|
161
|
+
## Review comments
|
|
162
|
+
|
|
163
|
+
Humans pin comments on screens in Review Mode; they land in `review/comments.json` with full
|
|
164
|
+
context (screen, scenario, theme, mode, dir, CSS path of the clicked element). Workflow:
|
|
165
|
+
read them (`get_review_comments`), fix the screens, then `resolve_comment` β don't delete.
|
|
166
|
+
|
|
167
|
+
## Doing the work
|
|
168
|
+
|
|
169
|
+
Prefer the MCP tools (`add_screen`, `update_screen`, `set_data`, `validate`, `screenshot`, β¦) β
|
|
170
|
+
they validate the contract, stamp `updated`, regenerate manifests and write atomically.
|
|
171
|
+
Editing screen files directly is fine too; afterwards run:
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
node engine/cli/screenbook.js manifest # rescan β manifest.json
|
|
175
|
+
node engine/cli/screenbook.js validate # static checks + headless render of every screen
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
While `node engine/cli/screenbook.js serve` runs (http://127.0.0.1:4600), every save
|
|
179
|
+
live-reloads the browser and manifests regenerate automatically β no cache-busting, ever.
|
|
180
|
+
|
|
181
|
+
**Definition of done: `validate` is green** β zero static errors, every screen renders in
|
|
182
|
+
every theme Γ light/dark Γ ltr/rtl with zero console errors. Check review comments too.
|
|
183
|
+
|
|
184
|
+
Hard rules: never edit `manifest.json` (derived) Β· never edit `engine/` Β· never rename a
|
|
185
|
+
screen that other screens `data-go` to (or fix the references + flows in the same change).
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Abdullah Alhaider
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
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
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# β§ ScreenBook
|
|
2
|
+
|
|
3
|
+
**A static, no-build prototype studio β built for AI agents to author, humans to review.**
|
|
4
|
+
|
|
5
|
+
Drop folders of plain HTML screens plus small JSON files into a project and get a navigable,
|
|
6
|
+
device-framed design gallery: iPhone / Android / browser frames, mock-data scenarios, design-token
|
|
7
|
+
themes, dark/light and LTR/RTL toggles, guided demo flows, pinned review comments, and a
|
|
8
|
+
machine-verified self-test β plus a CLI and an MCP server so agents can build screens
|
|
9
|
+
**without ever holding the engine's implementation in context**.
|
|
10
|
+
|
|
11
|
+

|
|
12
|
+
|
|
13
|
+
## Why this exists
|
|
14
|
+
|
|
15
|
+
Prototyping with AI agents has a failure mode: the prototype starts as "just an HTML file,"
|
|
16
|
+
grows into a hand-rolled single-page app, and after a month you have a 20k-line runtime where
|
|
17
|
+
**an agent can't touch one screen without reading the whole world** β and neither can you.
|
|
18
|
+
|
|
19
|
+
ScreenBook inverts every cause of that rot:
|
|
20
|
+
|
|
21
|
+
| Instead of⦠| ScreenBook does⦠|
|
|
22
|
+
|---|---|
|
|
23
|
+
| One growing SPA runtime | **One file = one screen.** Full standalone HTML documents; each opens alone in a browser. |
|
|
24
|
+
| Metadata trapped in code | **Metadata as data.** A JSON block inside each screen; indexes are derived, never hand-kept. |
|
|
25
|
+
| App code and viewer code tangled | **A hard engine/app boundary.** Screens know one include (`bridge.js`) and a tiny API. |
|
|
26
|
+
| Hard-coded demo data | **Seed + scenario overlays** with `{{todayΒ±N}}` date tokens β "mid-trip today" is literally today. |
|
|
27
|
+
| CSS forks per restyle | **Design tokens as JSON themes**, flattened to CSS variables, dark mode as a delta. |
|
|
28
|
+
| "Trust me, it works" | **A self-test gate**: every screen Γ theme Γ light/dark Γ LTR/RTL must render with zero console errors. |
|
|
29
|
+
|
|
30
|
+
| | | |
|
|
31
|
+
|---|---|---|
|
|
32
|
+
|  |  |  |
|
|
33
|
+
| Browser-chrome frame, scenario-driven data | Android frame, dark mode | Same screens, different theme JSON |
|
|
34
|
+
|
|
35
|
+
## Setup
|
|
36
|
+
|
|
37
|
+
Requirements: **Node β₯ 18**. For headless validation/screenshots: Google Chrome (or see below).
|
|
38
|
+
|
|
39
|
+
Full guides live in **[docs/](docs/README.md)** β getting started & prerequisites, the
|
|
40
|
+
studio tour, the authoring guide, CLI/MCP references, app mode, troubleshooting.
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1. Put the engine into your project (this repo IS the engine β folder name matters):
|
|
44
|
+
git clone https://github.com/cs4alhaider/screenbook my-prototypes/engine
|
|
45
|
+
# (npm: @cs4alhaider/screenbook β after first publish)
|
|
46
|
+
|
|
47
|
+
# 2. Scaffold the project around it:
|
|
48
|
+
cd my-prototypes
|
|
49
|
+
node engine/cli/screenbook.js init .
|
|
50
|
+
|
|
51
|
+
# 3. Run the studio:
|
|
52
|
+
node engine/cli/screenbook.js serve .
|
|
53
|
+
# β http://127.0.0.1:4600 Β· live reload Β· manifests auto-regenerate Β· no cache, ever
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`init` creates `app.config.json`, a starter theme, a `welcome` feature with one screen β and
|
|
57
|
+
drops **`CLAUDE.md`** (the authoring contract) plus a **packaged agent skill** into the project,
|
|
58
|
+
so any AI agent that opens it immediately knows the rules.
|
|
59
|
+
|
|
60
|
+
For headless `validate` / `screenshot`, install the one dev dependency once:
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
cd engine && npm i # playwright-core β drives your installed Chrome
|
|
64
|
+
# no Chrome? use: npm i -D playwright && npx playwright install chromium
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Try the shipped examples first:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node engine/cli/screenbook.js serve engine/examples/qahwa # β coffee app: iOS + Android + web
|
|
71
|
+
node engine/cli/screenbook.js serve engine/examples/tokens-lab # π¨ two themes, live token lab
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## How it helps β a five-minute story
|
|
75
|
+
|
|
76
|
+
**1. A screen is one honest file.** No framework, no build, one conventional include:
|
|
77
|
+
|
|
78
|
+
```html
|
|
79
|
+
<!DOCTYPE html>
|
|
80
|
+
<html lang="en">
|
|
81
|
+
<head>
|
|
82
|
+
<script type="application/json" id="screen-meta">
|
|
83
|
+
{ "title": "Cart", "description": "Lines, totals, checkout.",
|
|
84
|
+
"order": 30, "device": "ios", "status": "wip" }
|
|
85
|
+
</script>
|
|
86
|
+
<script src="../../../engine/bridge.js"></script>
|
|
87
|
+
</head>
|
|
88
|
+
<body>
|
|
89
|
+
<button data-go="040-checkout">Checkout</button>
|
|
90
|
+
<script>
|
|
91
|
+
Engine.ready(function (data) { /* seed β active scenario, dates resolved */ });
|
|
92
|
+
</script>
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Save it under `features/shop/screens/030-cart.html` β it's already in the sidebar, framed as an
|
|
98
|
+
iPhone, styled by your tokens (`var(--color-accent)`), live-reloading on every save.
|
|
99
|
+
|
|
100
|
+
**2. Colors are a panel, not a hunt.** The π¨ **Theme Lab** shows every token the active theme
|
|
101
|
+
defines and edits them **live against the hosted screens** β color pickers for hex values,
|
|
102
|
+
plain fields for fonts/radii/spacing, a theme switcher, Export, and one-click **Save** straight
|
|
103
|
+
back to `themes/<id>.json` while `serve` runs:
|
|
104
|
+
|
|
105
|
+

|
|
106
|
+
|
|
107
|
+
**3. States are data, not branches.** Add a scenario and the π dropdown demos your empty state:
|
|
108
|
+
|
|
109
|
+
```json
|
|
110
|
+
{ "id": "first-visit", "icon": "π", "title": "First visit",
|
|
111
|
+
"remove": ["order"], "patch": { "user": { "isNew": true } } }
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**4. Demos tell themselves.** A `flows.json` walkthrough gives stakeholders a guided,
|
|
115
|
+
captioned story β each step can pin its own scenario, and every state is a shareable URL:
|
|
116
|
+
`β¦#shop/030-cart?scenario=first-visit&mode=dark&dir=rtl`.
|
|
117
|
+
|
|
118
|
+
**5. Review closes the loop.** Reviewers pin comments directly on screens; each pin captures
|
|
119
|
+
screen, scenario, theme, mode, direction and the exact element clicked, and syncs to
|
|
120
|
+
`review/comments.json` β where agents read and resolve them.
|
|
121
|
+
|
|
122
|
+
**6. The machine decides "done".**
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
node engine/cli/screenbook.js validate .
|
|
126
|
+
# β static contract checks
|
|
127
|
+
# β 28/28 render checks green across 7 screens (Γ themes Γ dark/light Γ ltr/rtl, zero console errors)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## The agent workflow (the actual point)
|
|
131
|
+
|
|
132
|
+
Register the MCP server with your agent β for Claude Code:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
claude mcp add screenbook -- npx -y @cs4alhaider/screenbook mcp --root .
|
|
136
|
+
# or from a checkout: claude mcp add screenbook -- node engine/cli/screenbook.js mcp --root .
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Then ask for work in plain language:
|
|
140
|
+
|
|
141
|
+
> *"Add a checkout screen to the shop feature: summary card, payment method list from the seed,
|
|
142
|
+
> a pay button that toasts. Match the existing screens. Validate and show me a screenshot."*
|
|
143
|
+
|
|
144
|
+
The agent never opens `engine/` β it calls `list_screens` β `get_screen` (steals an existing
|
|
145
|
+
screen's idioms) β `add_screen` (which **lints the contract, stamps the date, regenerates the
|
|
146
|
+
manifest, writes atomically**) β `validate` (full headless render matrix) β `screenshot`
|
|
147
|
+
(a PNG inside the real device frame). Human taste, machine discipline.
|
|
148
|
+
|
|
149
|
+
18 tools: `list_features Β· list_screens Β· get_screen Β· add_screen Β· update_screen Β·
|
|
150
|
+
remove_screen Β· add_feature Β· get_data Β· set_data Β· get_theme Β· set_theme Β· get_flows Β·
|
|
151
|
+
set_flows Β· get_review_comments Β· resolve_comment Β· validate Β· screenshot Β· add_app`.
|
|
152
|
+
|
|
153
|
+
## CLI
|
|
154
|
+
|
|
155
|
+
| Command | Does |
|
|
156
|
+
|---|---|
|
|
157
|
+
| `init [dir] [--name X]` | Scaffold a project + authoring contract + agent skill |
|
|
158
|
+
| `serve [dir] [--port]` | Studio server: live reload, auto-manifests, review sync, `/engine/*` mounted from this package, no-cache always |
|
|
159
|
+
| `manifest [dir]` | Rescan screens β regenerate derived `manifest.json` per feature |
|
|
160
|
+
| `validate [dir] [--static] [--json]` | Contract checks + headless render of every screen Γ theme Γ mode Γ direction |
|
|
161
|
+
| `screenshot [dir] --screen f/s [--mode light,dark] [--dir ltr,rtl] [--scenario id] [--theme id]` | Device-framed PNGs (comma lists = matrix) |
|
|
162
|
+
|
|
163
|
+
## What's in this repo
|
|
164
|
+
|
|
165
|
+
```
|
|
166
|
+
bridge.js the ONLY file screens include (dual-mode: shell-driven / standalone)
|
|
167
|
+
index.html + shell/ the studio: frames, dropdowns, flows, review mode, selftest
|
|
168
|
+
core/ shared project API (scan, manifests, atomic writes, validate, serve, browser)
|
|
169
|
+
cli/ Β· mcp/ two thin faces over the same core
|
|
170
|
+
CLAUDE.md the authoring contract agents follow (scaffolded into projects)
|
|
171
|
+
skill/ packaged agent skill + reference docs (scaffolded into projects)
|
|
172
|
+
examples/ qahwa (β 3-device demo app) Β· tokens-lab (π¨ multi-theme lab)
|
|
173
|
+
test/ 42-test suite: unit + serve E2E + CLI E2E β npm test
|
|
174
|
+
TESTING.md the full test map and gaps
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
RTL and Arabic are first-class (`dir` flipping, per-screen language dictionaries):
|
|
178
|
+
|
|
179
|
+
<img src="docs/shots/rtl-arabic.png" width="320" alt="RTL Arabic screen in dark mode">
|
|
180
|
+
|
|
181
|
+
## App mode β embed entire existing apps
|
|
182
|
+
|
|
183
|
+
Already have a single-page prototype? Don't rewrite it β **embed it**:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
node engine/cli/screenbook.js app add legacy/index.html --device ios --chrome self
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The app's views appear in the sidebar as screens, deep links work, dark/RTL/scenario toggles
|
|
190
|
+
drive it live. Cooperative apps include `engine/app-bridge.js` and register themselves
|
|
191
|
+
(`examples/spa-demo`); sealed apps get a small project-owned **driver** injected at runtime β
|
|
192
|
+
**zero changes to the app's own files**. `validate` boots each app and visits every screen it
|
|
193
|
+
reports, still demanding zero console errors.
|
|
194
|
+
|
|
195
|
+

|
|
196
|
+
|
|
197
|
+
## Status
|
|
198
|
+
|
|
199
|
+
**v1 β complete and machine-verified** (studio, 4 frames, scenarios, flows, Theme Lab,
|
|
200
|
+
review mode, selftest, CLI, MCP, agent onboarding, example projects, 44-test suite).
|
|
201
|
+
**v2 β in progress**: app mode β shipped (above) Β· theme write-back β (Theme Lab Save) Β·
|
|
202
|
+
i18n strings β (`Engine.t` + key-first per-feature `i18n.json`, fallback chain, validate
|
|
203
|
+
warnings on gaps) Β· npm packaging next.
|
|
204
|
+
|
|
205
|
+
Born inside [Rahhal](https://github.com/cs4alhaider)'s product lab, where the predecessor
|
|
206
|
+
prototypes grew to ~22k lines and taught every lesson this engine inverts.
|
|
207
|
+
|
|
208
|
+
License: MIT.
|
package/TESTING.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# ScreenBook β testing map
|
|
2
|
+
|
|
3
|
+
> The "don't forget" file: every test layer, what it covers, how to run it, and where the
|
|
4
|
+
> gaps are. Update this whenever a layer is added or a gap closes. Definition of done for
|
|
5
|
+
> any engine change: **`npm test` green AND `validate` green on all four projects below.**
|
|
6
|
+
|
|
7
|
+
## How to run everything
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd engine
|
|
11
|
+
npm i # once β playwright-core (drives your installed Chrome)
|
|
12
|
+
npm test # unit + E2E suites (node:test, ~58 tests, ~60s incl. browser)
|
|
13
|
+
|
|
14
|
+
# full browser matrix on every project that ships in this repo:
|
|
15
|
+
node cli/screenbook.js validate .. # Rahhal (repo root)
|
|
16
|
+
node cli/screenbook.js validate examples/qahwa
|
|
17
|
+
node cli/screenbook.js validate examples/tokens-lab
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Layers
|
|
21
|
+
|
|
22
|
+
| Layer | File(s) | Covers |
|
|
23
|
+
|-------|---------|--------|
|
|
24
|
+
| **EngineKit unit** | `test/kit.test.js` | The REAL `bridge.js` loaded in a Node VM exactly as the shell loads it: deepMerge (arrays replace), scenario overlay (removeβpatch order, seed immutability), `{{todayΒ±N}}` resolution incl. embedded strings, token flattening, dark-over-light theme merge, fallback safety. |
|
|
25
|
+
| **Meta contract unit** | `test/meta.test.js` | Meta block parse errors, `withMeta` round-trip, `updated` stamping incl. the manual-override rule, every `lintScreen` violation, `data-go` extraction, filename-prefix order fallback. |
|
|
26
|
+
| **Atomic fs unit** | `test/fsx.test.js` | Atomic write (no temp leftovers, parent dirs), change-detection writes, JSON fallback/error paths. |
|
|
27
|
+
| **Project API unit** | `test/project.test.js` | Scan/sort, manifest regen stability (no churn), screen CRUD guards (lint reject, dup, missing feature), data/theme/flows shape validation + config registration, review comment filters/resolve, `staticValidate` catching dangling `data-go`, missing/stale manifests, broken flow refs. |
|
|
28
|
+
| **Serve E2E** | `test/serve.test.js` | Real HTTP on an ephemeral port: health, `/` redirect, `/engine/*` package mount, path-traversal refusal, no-cache headers, review GET/POST β disk, **Theme-Lab write-back** (`POST /__sb/theme/:id` β file, bad-shape/bad-id 400s), and the **live-reload pipeline**: raw file write β SSE event β debounced manifest auto-regen. |
|
|
29
|
+
| **CLI E2E** | `test/cli.test.js` | Spawns the real binary per command: `init` (full scaffold incl. CLAUDE.md + skill, `--force` guard), `manifest` (regenerated β unchanged), `validate --static --json` (green β red on a broken screen, exit codes), `serve` (parses printed URL, fetches studio/health/screen), `validate` **full browser** on the init-scaffolded project, `screenshot` (PNG on disk), error paths (no project β exit 2, bad `--screen` spec β exit 2). |
|
|
30
|
+
| **Browser matrix** | shell `?selftest=1` via `validate` | Every screen Γ theme Γ light/dark Γ ltr/rtl: bridge handshake, `Engine.ready` fires, meta present, **zero console errors** (screen AND shell). This is the machine-verified fixed point gate. |
|
|
31
|
+
| **App-mode gate** | same `?selftest=1` pass | Every `type:"app"` feature: app must register (hello) within 20s, report β₯1 screen, and **every reported screen is visited once** β zero console errors (driver failures surface as `driver <fn>:` rows; adapters may declare narrow `ignoreErrors` for third-party noise, e.g. MapLibre teardown). |
|
|
32
|
+
| **Shell interaction E2E** | `test/shell.test.js` | Real Chrome driving the studio against a purpose-built fixture: boot + data binding, live dark/RTL env, `data-go` β deep link, scenario overlay (`remove`) on reload, flow deep links + stepping with pinned scenarios, `Engine.state` across screens + reset chip, **review pinning β disk β resolve**, Theme-Lab live edit reaching the screen, **app mode** (embedded cooperative app: screen list, navigation, env follow). |
|
|
33
|
+
| **MCP session E2E** | `test/mcp.test.js` | Spawns the real stdio server against an init-scaffolded project: initialize (protocol echo + instructions), 18-tool surface, contract enforcement (lint reject, dup reject), full CRUD incl. `add_app`, static validate through the tool, JSON-RPC error shapes (-32601/-32602). |
|
|
34
|
+
|
|
35
|
+
## Projects that double as fixtures
|
|
36
|
+
|
|
37
|
+
| Project | Exercises |
|
|
38
|
+
|---------|-----------|
|
|
39
|
+
| repo root (Rahhal) | iOS frame, AR/RTL/i18n screens, scenarios with `remove`, flows, `statusBar` override, shared.css/js β **plus both certified prototypes embedded as driven apps** (`features/rahhal-ios` 31 screens Β· `features/rahhal-web` 11 screens, drivers injected, `docs/mockups/` byte-untouched) |
|
|
40
|
+
| `examples/qahwa` | iOS + **Android** + **web** frames in one project, in-screen sheet (EN-24), `Engine.state` cart, feature groups, device filter |
|
|
41
|
+
| `examples/tokens-lab` | **Multi-theme** (Theme βΎ + theme axis in the matrix), **bare `none` frame**, live token re-skin, MutationObserver pattern |
|
|
42
|
+
| `examples/spa-demo` | **Cooperative app mode** β a one-page notes app including `app-bridge.js` itself; engine chrome on an app |
|
|
43
|
+
| `screenbook init` output | The scaffold itself (validated headlessly inside `test/cli.test.js` on every run); `app add` covered too |
|
|
44
|
+
|
|
45
|
+
## Known gaps (candidates for the next hardening pass)
|
|
46
|
+
|
|
47
|
+
- ~~Shell interaction suite~~ β **promoted to `test/shell.test.js`** (2026-07-18).
|
|
48
|
+
- ~~MCP server E2E~~ β **promoted to `test/mcp.test.js`** (2026-07-18).
|
|
49
|
+
- Keyboard navigation (ββ/jk, `/`, flow arrows) not yet asserted in the shell suite.
|
|
50
|
+
- RTL screenshot diffing / visual regression (screenshots exist; no baseline comparison).
|
|
51
|
+
- Windows: paths are POSIX-tested only; `fs.watch` recursive + rename semantics unverified.
|
|
52
|
+
- `file://` standalone mode has no automated check (manual only; fallback tokens by design).
|
|
53
|
+
- Driven-app injection path in the shell suite uses the cooperative mini-app only; the two
|
|
54
|
+
Rahhal drivers are covered by root `validate` (64/64), not by `npm test`.
|
package/app-bridge.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* ScreenBook app-bridge β app mode (EN-20). Runs INSIDE an embedded
|
|
3
|
+
* single-page app and speaks the sba:* postMessage protocol with the shell.
|
|
4
|
+
*
|
|
5
|
+
* Two ways it gets into the app page:
|
|
6
|
+
* cooperative β the SPA includes this file itself and calls
|
|
7
|
+
* AppBridge.register(adapter) from its own code
|
|
8
|
+
* driven β the shell injects this file, then injects a project-owned
|
|
9
|
+
* driver script that calls register(); the app's own files
|
|
10
|
+
* stay byte-untouched (how the certified prototypes plug in)
|
|
11
|
+
*
|
|
12
|
+
* adapter = {
|
|
13
|
+
* title?: string
|
|
14
|
+
* screens(): [{ id, title, description?, group? }] (required)
|
|
15
|
+
* go(id): navigate the app to a screen (required)
|
|
16
|
+
* current?(): current screen id
|
|
17
|
+
* set?(env): { mode?, dir?, lang?, scenario?, theme?, vars? } applied live
|
|
18
|
+
* scenarios?(): [{ id, icon?, title, description? }] (maps the app's own π)
|
|
19
|
+
* }
|
|
20
|
+
* Call AppBridge.navigated(id) whenever the app navigates on its own.
|
|
21
|
+
*/
|
|
22
|
+
(function () {
|
|
23
|
+
'use strict';
|
|
24
|
+
|
|
25
|
+
var shell = window.parent !== window ? window.parent : null;
|
|
26
|
+
var adapter = null;
|
|
27
|
+
var ignoreErrors = [];
|
|
28
|
+
|
|
29
|
+
function send(type, payload) {
|
|
30
|
+
if (!shell) return;
|
|
31
|
+
var msg = { type: type };
|
|
32
|
+
if (payload) Object.keys(payload).forEach(function (k) { msg[k] = payload[k]; });
|
|
33
|
+
try { shell.postMessage(msg, '*'); } catch (e) { /* shell gone */ }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function reportError(message) {
|
|
37
|
+
var text = String(message);
|
|
38
|
+
/* adapters may declare known-benign noise from a sealed app's own libraries
|
|
39
|
+
(adapter.ignoreErrors: [RegExp|string]) β declared, visible, reviewable */
|
|
40
|
+
for (var i = 0; i < ignoreErrors.length; i++) {
|
|
41
|
+
if (ignoreErrors[i].test(text)) return;
|
|
42
|
+
}
|
|
43
|
+
send('sba:console-error', { message: text });
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function guarded(fn, label) {
|
|
47
|
+
try { return fn(); }
|
|
48
|
+
catch (err) {
|
|
49
|
+
reportError('driver ' + label + ': ' + (err && err.message ? err.message : err));
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeScreens(list) {
|
|
55
|
+
return (list || []).map(function (s) {
|
|
56
|
+
if (typeof s === 'string') return { id: s, title: s };
|
|
57
|
+
return { id: s.id, title: s.title || s.id, description: s.description, group: s.group };
|
|
58
|
+
}).filter(function (s) { return s.id; });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function pushScreens() {
|
|
62
|
+
if (!adapter) return;
|
|
63
|
+
send('sba:screens', { screens: normalizeScreens(guarded(function () { return adapter.screens(); }, 'screens()')) });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
window.AppBridge = {
|
|
67
|
+
register: function (a) {
|
|
68
|
+
adapter = a || {};
|
|
69
|
+
ignoreErrors = (adapter.ignoreErrors || []).map(function (p) {
|
|
70
|
+
return p instanceof RegExp ? p : new RegExp(String(p));
|
|
71
|
+
});
|
|
72
|
+
var capabilities = ['go'];
|
|
73
|
+
if (adapter.set) capabilities.push('env');
|
|
74
|
+
if (adapter.scenarios) capabilities.push('scenarios');
|
|
75
|
+
var hello = { title: adapter.title || document.title || 'App', capabilities: capabilities };
|
|
76
|
+
if (adapter.scenarios) {
|
|
77
|
+
hello.scenarios = guarded(function () { return adapter.scenarios(); }, 'scenarios()') || [];
|
|
78
|
+
}
|
|
79
|
+
send('sba:hello', hello);
|
|
80
|
+
pushScreens();
|
|
81
|
+
var cur = adapter.current && guarded(function () { return adapter.current(); }, 'current()');
|
|
82
|
+
if (cur) send('sba:current', { id: cur });
|
|
83
|
+
},
|
|
84
|
+
navigated: function (id) { send('sba:current', { id: id }); },
|
|
85
|
+
refreshScreens: pushScreens
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
window.addEventListener('message', function (e) {
|
|
89
|
+
var msg = e.data;
|
|
90
|
+
if (!msg || typeof msg.type !== 'string' || !adapter) return;
|
|
91
|
+
switch (msg.type) {
|
|
92
|
+
case 'sba:go':
|
|
93
|
+
guarded(function () { adapter.go(msg.id); }, 'go(' + msg.id + ')');
|
|
94
|
+
send('sba:current', { id: msg.id });
|
|
95
|
+
break;
|
|
96
|
+
case 'sba:env':
|
|
97
|
+
if (adapter.set) {
|
|
98
|
+
guarded(function () {
|
|
99
|
+
adapter.set({ mode: msg.mode, dir: msg.dir, lang: msg.lang, scenario: msg.scenario, theme: msg.theme, vars: msg.vars });
|
|
100
|
+
}, 'set(env)');
|
|
101
|
+
}
|
|
102
|
+
break;
|
|
103
|
+
case 'sba:ping':
|
|
104
|
+
send('sba:pong', {});
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
/* console/error forwarding so `validate` can hold apps to zero errors */
|
|
110
|
+
if (shell) {
|
|
111
|
+
window.addEventListener('error', function (e) {
|
|
112
|
+
if (e.target && e.target !== window && e.target.tagName) {
|
|
113
|
+
reportError('Resource failed: <' + e.target.tagName.toLowerCase() + '> ' + (e.target.src || e.target.href || ''));
|
|
114
|
+
} else {
|
|
115
|
+
reportError((e.message || 'Script error') + (e.filename ? ' (' + e.filename.split('/').pop() + ':' + e.lineno + ')' : ''));
|
|
116
|
+
}
|
|
117
|
+
}, true);
|
|
118
|
+
window.addEventListener('unhandledrejection', function (e) {
|
|
119
|
+
reportError('Unhandled rejection: ' + (e.reason && e.reason.message ? e.reason.message : e.reason));
|
|
120
|
+
});
|
|
121
|
+
var origConsoleError = console.error;
|
|
122
|
+
console.error = function () {
|
|
123
|
+
reportError(Array.prototype.map.call(arguments, function (a) {
|
|
124
|
+
if (typeof a === 'string') return a;
|
|
125
|
+
if (a && a.stack) return String(a.stack);
|
|
126
|
+
try { return JSON.stringify(a); } catch (e) { return String(a); }
|
|
127
|
+
}).join(' '));
|
|
128
|
+
return origConsoleError.apply(console, arguments);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
})();
|