@cs4alhaider/screenbook 0.1.0 → 0.2.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/AGENTS.md +147 -0
  2. package/CLAUDE.md +4 -184
  3. package/README.md +146 -163
  4. package/bridge.js +16 -2
  5. package/cli/postinstall.js +34 -0
  6. package/cli/screenbook.js +84 -34
  7. package/cli/templates.js +54 -1
  8. package/core/browser.js +22 -7
  9. package/core/project.js +28 -7
  10. package/core/serve.js +22 -7
  11. package/docs/README.md +25 -14
  12. package/docs/brand/logo-mark.png +0 -0
  13. package/docs/brand/logo-transparent.png +0 -0
  14. package/docs/brand/logo.png +0 -0
  15. package/docs/brand/readme-header.png +0 -0
  16. package/docs/contributing/architecture.md +153 -0
  17. package/{TESTING.md → docs/contributing/testing.md} +13 -8
  18. package/docs/{app-mode-guide.md → guides/app-mode.md} +17 -10
  19. package/docs/{authoring-guide.md → guides/authoring.md} +43 -17
  20. package/docs/guides/getting-started.md +84 -0
  21. package/docs/{studio-guide.md → guides/studio.md} +3 -2
  22. package/docs/{troubleshooting.md → guides/troubleshooting.md} +11 -8
  23. package/docs/{cli.md → reference/cli.md} +15 -10
  24. package/docs/{mcp-agents.md → reference/mcp-agents.md} +3 -3
  25. package/docs/shots/android-dark.png +0 -0
  26. package/docs/shots/android-light.png +0 -0
  27. package/docs/shots/app-mode-dark.png +0 -0
  28. package/docs/shots/app-mode-light.png +0 -0
  29. package/docs/shots/hero-studio-dark.png +0 -0
  30. package/docs/shots/hero-studio-light.png +0 -0
  31. package/docs/shots/review-mode-dark.png +0 -0
  32. package/docs/shots/review-mode-light.png +0 -0
  33. package/docs/shots/rtl-arabic-dark.png +0 -0
  34. package/docs/shots/rtl-arabic-light.png +0 -0
  35. package/docs/shots/theme-lab-dark.png +0 -0
  36. package/docs/shots/theme-lab-light.png +0 -0
  37. package/docs/shots/theming-noir-dark.png +0 -0
  38. package/docs/shots/theming-noir-light.png +0 -0
  39. package/docs/shots/web-dashboard-dark.png +0 -0
  40. package/docs/shots/web-dashboard-light.png +0 -0
  41. package/examples/qahwa/features/order/screens/010-menu.html +1 -1
  42. package/examples/qahwa/features/order/screens/020-drink.html +1 -1
  43. package/examples/qahwa/features/order/screens/030-cart.html +1 -1
  44. package/examples/qahwa/features/order/screens/040-status.html +1 -1
  45. package/examples/qahwa/features/order/screens/050-status-android.html +1 -1
  46. package/examples/qahwa/features/roastery/screens/010-orders.html +1 -1
  47. package/examples/qahwa/features/roastery/screens/020-menu-editor.html +1 -1
  48. package/examples/spa-demo/app/index.html +1 -1
  49. package/examples/tokens-lab/features/lab/screens/010-swatches.html +1 -1
  50. package/examples/tokens-lab/features/lab/screens/020-typography.html +1 -1
  51. package/mcp/server.js +4 -2
  52. package/package.json +7 -6
  53. package/shell/screenhost.js +4 -1
  54. package/shell/selftest.js +18 -2
  55. package/shell/shell.css +17 -9
  56. package/shell/store.js +2 -0
  57. package/skill/SKILL.md +1 -1
  58. package/skill/references/cli-mcp.md +1 -1
  59. package/skill/references/screen-contract.md +1 -1
  60. package/docs/getting-started.md +0 -88
  61. package/docs/shots/app-mode.png +0 -0
  62. package/docs/shots/hero-studio.png +0 -0
  63. package/docs/shots/rtl-arabic.png +0 -0
  64. package/docs/shots/theme-lab.png +0 -0
  65. package/docs/shots/theming-noir.png +0 -0
  66. package/docs/shots/web-dashboard.png +0 -0
@@ -16,7 +16,7 @@
16
16
  "updated": "2026-07-18"
17
17
  }
18
18
  </script>
19
- <script src="../../../engine/bridge.js"></script>
19
+ <script src="../../../screenbook/bridge.js"></script>
20
20
  <link rel="stylesheet" href="shared.css">
21
21
  <style>
22
22
  /* Material-ish accents for the Android take */
@@ -15,7 +15,7 @@
15
15
  "updated": "2026-07-18"
16
16
  }
17
17
  </script>
18
- <script src="../../../engine/bridge.js"></script>
18
+ <script src="../../../screenbook/bridge.js"></script>
19
19
  <link rel="stylesheet" href="shared.css">
20
20
  </head>
21
21
  <body>
@@ -15,7 +15,7 @@
15
15
  "updated": "2026-07-18"
16
16
  }
17
17
  </script>
18
- <script src="../../../engine/bridge.js"></script>
18
+ <script src="../../../screenbook/bridge.js"></script>
19
19
  <link rel="stylesheet" href="shared.css">
20
20
  </head>
21
21
  <body>
@@ -62,7 +62,7 @@
62
62
  </div>
63
63
 
64
64
  <!-- the ONE line that makes this a cooperative ScreenBook app -->
65
- <script src="../engine/app-bridge.js"></script>
65
+ <script src="../screenbook/app-bridge.js"></script>
66
66
  <script>
67
67
  var VIEWS = [
68
68
  { id: 'inbox', title: 'Inbox', description: 'The notes list — tap one to open it.' },
@@ -15,7 +15,7 @@
15
15
  "updated": "2026-07-18"
16
16
  }
17
17
  </script>
18
- <script src="../../../engine/bridge.js"></script>
18
+ <script src="../../../screenbook/bridge.js"></script>
19
19
  <style>
20
20
  * { box-sizing: border-box; }
21
21
  body {
@@ -15,7 +15,7 @@
15
15
  "updated": "2026-07-18"
16
16
  }
17
17
  </script>
18
- <script src="../../../engine/bridge.js"></script>
18
+ <script src="../../../screenbook/bridge.js"></script>
19
19
  <style>
20
20
  * { box-sizing: border-box; }
21
21
  body {
package/mcp/server.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  } from '../core/project.js';
17
17
 
18
18
  const ENGINE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
19
- const VERSION = '0.1.0';
19
+ const VERSION = '0.2.1';
20
20
 
21
21
  const rootFlagIx = process.argv.indexOf('--root');
22
22
  const rootArg = rootFlagIx > -1 ? process.argv[rootFlagIx + 1] : undefined;
@@ -32,7 +32,7 @@ const SCREEN_CONTRACT =
32
32
  'A screen is one full standalone HTML document: <!DOCTYPE html>, a required ' +
33
33
  '<script type="application/json" id="screen-meta"> block ({title, description, order, ' +
34
34
  'device: ios|android|web|none, status?, tags?, group?, statusBar?: light|dark, updated?}), and one ' +
35
- '<script src="../../../engine/bridge.js"></script> include. Use CSS vars from the active theme ' +
35
+ '<script src="../../../screenbook/bridge.js"></script> include. Use CSS vars from the active theme ' +
36
36
  '(--color-*, --font-*, --radius-*, --space-*, --safe-top, --safe-bottom), bind mock data with ' +
37
37
  'Engine.ready(cb → cb(data)), navigate with data-go="<screen-id>" attributes, share state via ' +
38
38
  'Engine.state.get/set/subscribe, notify with Engine.toast(msg). Screens must render sensibly ' +
@@ -332,6 +332,8 @@ async function handle(msg) {
332
332
  const tool = TOOLS.find((t) => t.name === params?.name);
333
333
  if (!tool) return replyError(id, -32602, `unknown tool: ${params?.name}`);
334
334
  try {
335
+ /* `extra` is a side channel: a tool can set extra.imagePath and the
336
+ file is attached to the reply as inline MCP image content. */
335
337
  const extra = {};
336
338
  const result = await tool.run(params.arguments || {}, extra);
337
339
  const content = [{ type: 'text', text: JSON.stringify(result, null, 2) }];
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@cs4alhaider/screenbook",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "description": "ScreenBook — a static, no-build prototype studio. Drop folders of HTML screens + JSON (data, scenarios, tokens) and get a device-framed gallery with review mode, flows, self-tests, a CLI and an MCP server for AI agents.",
7
+ "description": "ScreenBook — a design workspace for AI agents to present to humans. HTML screens + JSON data in real device frames, with review mode, flows, themes, self-tests, a CLI and an MCP server. Fully offline, no build step, and any MCP agent can drive it.",
8
8
  "type": "module",
9
9
  "bin": {
10
10
  "screenbook": "cli/screenbook.js"
@@ -35,13 +35,14 @@
35
35
  "skill/",
36
36
  "examples/",
37
37
  "docs/",
38
- "CLAUDE.md",
39
- "TESTING.md"
38
+ "AGENTS.md",
39
+ "CLAUDE.md"
40
40
  ],
41
- "devDependencies": {
41
+ "dependencies": {
42
42
  "playwright-core": "^1.45.0"
43
43
  },
44
44
  "scripts": {
45
+ "postinstall": "node cli/postinstall.js",
45
46
  "test": "node --test",
46
47
  "serve": "node cli/screenbook.js serve ..",
47
48
  "validate": "node cli/screenbook.js validate .."
@@ -50,7 +51,7 @@
50
51
  "type": "git",
51
52
  "url": "git+https://github.com/cs4alhaider/screenbook.git"
52
53
  },
53
- "homepage": "https://github.com/cs4alhaider/screenbook#readme",
54
+ "homepage": "https://cs4alhaider.github.io/screenbook/",
54
55
  "bugs": {
55
56
  "url": "https://github.com/cs4alhaider/screenbook/issues"
56
57
  }
@@ -30,7 +30,7 @@ export function injectAppRuntime(iframe, featureId, driverFile, onFail) {
30
30
  }
31
31
  const host = doc.head || doc.documentElement;
32
32
  const runtime = doc.createElement('script');
33
- runtime.src = url('engine/app-bridge.js');
33
+ runtime.src = url('screenbook/app-bridge.js');
34
34
  runtime.onload = () => {
35
35
  const driver = doc.createElement('script');
36
36
  driver.src = url(`features/${featureId}/${driverFile}`);
@@ -218,6 +218,9 @@ export function showScreen({ bust = false } = {}) {
218
218
  return;
219
219
  }
220
220
 
221
+ /* Same feature + same device chrome → keep the mounted frame and only swap
222
+ the iframe's document below; rebuilding the hardware around the screen on
223
+ every navigation would flash and drop the fit scale. */
221
224
  const device = effectiveDevice(entry);
222
225
  const sameFrame = current &&
223
226
  current.featureId === store.ui.feature &&
package/shell/selftest.js CHANGED
@@ -36,6 +36,17 @@ function combos(project) {
36
36
  return list;
37
37
  }
38
38
 
39
+ /*
40
+ * Render ONE (screen × device × theme × mode × dir) combination in a hidden
41
+ * iframe and judge it. The choreography mirrors what the real shell does:
42
+ * 1. Mount the screen at true device dimensions, parked far off-screen.
43
+ * 2. The bridge inside sends sb:hello → answer with a full sb:init payload
44
+ * (theme, mode, dir, default-scenario data) exactly like screenhost would.
45
+ * 3. Collect every sb:console-error the screen reports along the way.
46
+ * 4. On sb:ready, wait SETTLE_MS for late errors, then judge: a meta block
47
+ * with a title + zero console errors = green.
48
+ * 5. A screen that never reaches ready fails via READY_TIMEOUT.
49
+ */
39
50
  function runCombo(project, combo) {
40
51
  return new Promise((resolve) => {
41
52
  const started = performance.now();
@@ -200,11 +211,12 @@ export async function runSelftest() {
200
211
  overlay.hidden = false;
201
212
  overlay.innerHTML =
202
213
  `<div class="st-card">
203
- <div class="st-head"><h2>ScreenBook self-test</h2><span class="st-summary" id="st-summary">0 / ${all.length}</span></div>
214
+ <div class="st-head"><h2>Self-test</h2><span class="st-summary" id="st-summary">0 / ${all.length}</span></div>
204
215
  <div class="st-progress"><i id="st-bar"></i></div>
216
+ <div class="st-now" id="st-now">warming up…</div>
205
217
  <div class="st-rows" id="st-rows"></div>
206
218
  <div class="st-foot">
207
- <span class="st-note">screens × themes × light/dark × ltr/rtl · zero console errors · meta present</span>
219
+ <span class="st-note">every screen × theme × mode × direction · zero console errors</span>
208
220
  <button class="btn-ghost" onclick="location.search=''">Close</button>
209
221
  </div>
210
222
  </div>`;
@@ -217,11 +229,13 @@ export async function runSelftest() {
217
229
  const results = [];
218
230
  let done = 0;
219
231
 
232
+ const nowLine = document.getElementById('st-now');
220
233
  function report(r) {
221
234
  results.push(r);
222
235
  done++;
223
236
  summary.textContent = `${done}${done <= all.length ? ` / ${all.length}` : ' checks'}`;
224
237
  bar.style.width = `${Math.min(100, (done / Math.max(1, all.length)) * 100)}%`;
238
+ nowLine.textContent = `${r.ok ? '✓' : '✕'} ${r.feature}/${r.screen} · ${r.theme}·${r.mode}·${r.dir}${r.device && r.device !== 'app' ? '·' + r.device : ''}`;
225
239
  if (!r.ok) {
226
240
  const row = document.createElement('div');
227
241
  row.className = 'st-row is-fail';
@@ -233,6 +247,7 @@ export async function runSelftest() {
233
247
  }
234
248
  }
235
249
 
250
+ /* N workers drain one shared queue — at most CONCURRENCY iframes live at once. */
236
251
  const queue = all.slice();
237
252
  await Promise.all(Array.from({ length: CONCURRENCY }, async () => {
238
253
  while (queue.length) {
@@ -245,6 +260,7 @@ export async function runSelftest() {
245
260
  for (const f of project.features.values()) {
246
261
  if (f.type !== 'app') continue;
247
262
  summary.textContent = `app: ${f.id}…`;
263
+ nowLine.textContent = `booting embedded app "${f.id}" — visiting every screen it reports…`;
248
264
  await runAppFeature(f, report);
249
265
  }
250
266
 
package/shell/shell.css CHANGED
@@ -868,19 +868,18 @@ body.solo .canvas { padding: 0; }
868
868
 
869
869
  /* ---------------------------------------------------------------- selftest */
870
870
 
871
+ /* Self-test runs in a floating card — the studio stays fully visible. */
871
872
  .selftest-overlay {
872
873
  position: fixed;
873
- inset: 0;
874
+ right: 18px;
875
+ bottom: 18px;
874
876
  z-index: 400;
875
- background: color-mix(in srgb, var(--sb-bg) 88%, transparent);
876
- backdrop-filter: blur(6px);
877
- display: grid;
878
- place-items: center;
879
- padding: 30px;
877
+ max-width: min(520px, calc(100vw - 36px));
880
878
  }
881
879
  .st-card {
882
- width: min(760px, 100%);
883
- max-height: 100%;
880
+ width: 480px;
881
+ max-width: 100%;
882
+ max-height: min(70vh, 640px);
884
883
  display: flex;
885
884
  flex-direction: column;
886
885
  background: var(--sb-panel-2);
@@ -915,7 +914,16 @@ body.solo .canvas { padding: 0; }
915
914
  border-radius: 2px;
916
915
  transition: width .15s ease;
917
916
  }
918
- .st-rows { flex: 1; overflow-y: auto; padding: 10px 12px 14px; min-height: 120px; }
917
+ .st-now {
918
+ padding: 8px 18px 2px;
919
+ font-size: 11px;
920
+ font-family: var(--sb-mono);
921
+ color: var(--sb-ink-2);
922
+ white-space: nowrap;
923
+ overflow: hidden;
924
+ text-overflow: ellipsis;
925
+ }
926
+ .st-rows { flex: 1; overflow-y: auto; padding: 8px 12px 12px; min-height: 60px; }
919
927
  .st-row {
920
928
  display: flex;
921
929
  align-items: baseline;
package/shell/store.js CHANGED
@@ -68,6 +68,8 @@ export function activeScenario(featureId = store.ui.feature) {
68
68
  /* ---------------- hash routing ---------------- */
69
69
 
70
70
  const HASH_KEYS = ['scenario', 'mode', 'theme', 'dir', 'lang', 'flow'];
71
+ /* Re-entrancy guard: applyHash() → set() → syncHash() must not rewrite the
72
+ URL with half-applied state while we're still parsing it. */
71
73
  let applyingHash = false;
72
74
 
73
75
  export function parseHash() {
package/skill/SKILL.md CHANGED
@@ -13,7 +13,7 @@ read or modify `engine/` internals.
13
13
 
14
14
  - One screen = one full HTML document in `features/<f>/screens/<id>.html`, with a required
15
15
  `<script type="application/json" id="screen-meta">` block and one bridge include
16
- (`../../../engine/bridge.js`). It works opened alone AND inside the shell.
16
+ (`../../../screenbook/bridge.js`). It works opened alone AND inside the shell.
17
17
  - Mock data = `features/<f>/data/seed.json`; named **scenarios** overlay it
18
18
  (`patch` deep-merge, `remove` dot-paths, `{{today±N}}` date tokens).
19
19
  - Look = design tokens in `themes/<id>.json`, flattened to CSS vars (`--color-accent`,
@@ -37,7 +37,7 @@ scenario?, theme?, lang?, return_image?}`
37
37
  Register with Claude Code:
38
38
  ```
39
39
  claude mcp add screenbook -- npx -y @cs4alhaider/screenbook mcp --root .
40
- # or from a checkout: claude mcp add screenbook -- node engine/cli/screenbook.js mcp --root .
40
+ # or from a checkout: node <path-to-package>/cli/screenbook.js mcp --root . # rarely needed
41
41
  ```
42
42
 
43
43
  ## Serve endpoints (for tooling)
@@ -29,7 +29,7 @@ fallback and keeps directory listings readable. Prefer ids like `010-home`, `020
29
29
  </script>
30
30
 
31
31
  <!-- 2. REQUIRED bridge — the ONLY engine include, exact relative path -->
32
- <script src="../../../engine/bridge.js"></script>
32
+ <script src="../../../screenbook/bridge.js"></script>
33
33
 
34
34
  <!-- 3. Optional shared feature look/helpers (explicit opt-in per screen) -->
35
35
  <link rel="stylesheet" href="shared.css">
@@ -1,88 +0,0 @@
1
- # Getting started
2
-
3
- ## Prerequisites
4
-
5
- | You need | For | Notes |
6
- |----------|-----|-------|
7
- | **Node.js ≥ 18** | the CLI, the dev server, the MCP server | the only hard requirement — check with `node --version` |
8
- | **A modern browser** | the studio itself | Chrome, Safari, Edge, Firefox — the viewer is plain ES modules, no build |
9
- | **Google Chrome installed** | `validate` and `screenshot` (headless checks) | used through `playwright-core`; see below if you don't have Chrome |
10
- | **Git** | recommended | ScreenBook writes plain files — git is your undo and your review tool |
11
-
12
- Nothing else. No bundler, no framework, no global installs. The viewer has **zero**
13
- dependencies; the CLI has exactly one *dev* dependency (`playwright-core`) used only for
14
- headless validation and screenshots.
15
-
16
- One-time setup for the headless checks:
17
-
18
- ```bash
19
- cd engine
20
- npm i # installs playwright-core (~3s, no browser download)
21
- # no Chrome on this machine? use a bundled browser instead:
22
- npm i -D playwright && npx playwright install chromium
23
- ```
24
-
25
- ## Install
26
-
27
- ScreenBook lives INSIDE your project as a folder named `engine/`:
28
-
29
- ```bash
30
- # today (from the repo):
31
- git clone https://github.com/cs4alhaider/screenbook my-prototypes/engine
32
-
33
- # after npm publish:
34
- # npm i @cs4alhaider/screenbook (then copy/link node_modules/@cs4alhaider/screenbook → engine/)
35
- ```
36
-
37
- The folder name matters: screens reference `../../../engine/bridge.js` by convention.
38
-
39
- ## Your first project — five minutes
40
-
41
- ```bash
42
- cd my-prototypes
43
- node engine/cli/screenbook.js init . --name "My product"
44
- node engine/cli/screenbook.js serve .
45
- # → open http://127.0.0.1:4600
46
- ```
47
-
48
- You now have:
49
-
50
- ```
51
- app.config.json project name · feature order · devices · themes · languages
52
- themes/default.json design tokens (light + dark) — edit live in the 🎨 Theme Lab
53
- features/welcome/ one starter feature with one screen
54
- feature.json · flows.json · data/seed.json · data/scenarios.json
55
- screens/010-hello.html ← copy this file to make your second screen
56
- review/comments.json review pins land here
57
- CLAUDE.md the authoring contract (for you AND your AI agents)
58
- .claude/skills/screenbook/ packaged skill so Claude knows the rules instantly
59
- ```
60
-
61
- Edit `features/welcome/screens/010-hello.html`, save — the browser reloads itself.
62
- Copy it to `020-something.html`, change the meta block's `title`/`order`, save — it's in
63
- the sidebar. That's the whole loop.
64
-
65
- Already have a single-page prototype? Embed it whole instead of rewriting:
66
-
67
- ```bash
68
- node engine/cli/screenbook.js init . --app path/to/index.html
69
- ```
70
-
71
- (see [App mode](app-mode-guide.md)).
72
-
73
- ## The definition of done
74
-
75
- ```bash
76
- node engine/cli/screenbook.js validate .
77
- ```
78
-
79
- Green means: every screen renders in every theme × light/dark × LTR/RTL with **zero console
80
- errors**, all metadata is present, every navigation target exists. Make it your habit —
81
- it's the same gate AI agents are held to.
82
-
83
- ## Serving without the CLI
84
-
85
- Any static server works once `engine/` is copied into the project
86
- (`python3 -m http.server`, nginx, GitHub Pages…). You lose the live extras — auto-reload,
87
- manifest regeneration, review-comment disk sync, Theme-Lab save — but the studio, screens,
88
- scenarios and flows all work. `/engine/index.html` is the entry point.
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file