Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ experiments/*/results/*/transcript.jsonl
**/.uxcli/mockups/index.html
**/.uxcli/map/
**/.uxcli/studio/
**/.uxcli/dashboard/
**/.uxcli/experience/

# experiment results stay on the machine that ran them
Expand Down
31 changes: 31 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,37 @@ Entries are written per release. The log is the source material, not the text.

## [Unreleased]

### Changed — one visual surface, picked from drawings

- `uxcli studio` and `uxcli dashboard` now write the same page, in a design system of its own
(`src/core/ui.js`, picked by the owner from four drawings, `docs/design/system/`): the project as a
canvas of the screens a person sees, each journey a filmstrip of device frames with the real pictures,
the action and its keystroke-level seconds on the arrow between; what is wrong pinned on the picture,
on the screen it was measured on, in words a person reads ("Call action is 2 scrolls down", not the
selector); "fix first" and a to-do bar ordered fail, finding, waiting on a person, not yet done.
Views: Journeys (the canvas; layers Built, Drawn, Versions with compare), Screens, Runs, People,
Library. The studio's decisions (pick, redraw, note) and live refresh stay; the dashboard keeps its
folder switch. The coverage-matrix page is retired; `uxcli dashboard` still prints the matrix as a card.
- The mockups, map and experience pages take the same tokens. The mockups page painted text at 2.9:1;
it now passes, and `page-pairs` runs the page probes on it as well as on the map.
- The studio's first visit fitted nothing: it saved the view before checking whether one was saved.

### Added — the dashboard

- `uxcli dashboard [dir …] [--serve [--port=N]] [--json]`: the management view of one or more projects,
read-only, in the look the owner picked from three ("control room"). Overview is a coverage matrix —
every screen the journeys pass through against understood, drawn, picked, reviewed, walked, verdict,
estimate and walkthrough; a gap is never a pass and names the command that fills it; a journey whose
packet keeps no steps reads as ran-with-its-verdict, the start of an unwalked journey as nothing to
measure. Runs, Design and Understanding views. Served, it switches folders, takes another one from the
page, and serves pictures only from under a folder's `.uxcli/`. Gate: `dashboard-pairs`, which also
runs the page probes on every view.

### Fixed

- `uxcli template apply <id> --src=DIR` wrote into the current directory; it now writes into DIR's
project root.

### Added — the designer's habits: a library to cite, templates to start from, the walk measured

- Policy `project.design: off | drawn | picked`: a project can hold the work to a design step. A journey
Expand Down
29 changes: 17 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,20 +50,24 @@ from a cited source, or measured by the browser.
## The studio

```bash
uxcli studio --serve # http://127.0.0.1:4317 — the project as a board, refreshed as the agent works
uxcli studio --serve # http://127.0.0.1:4317 — the project as a canvas, refreshed as the agent works
```

A canvas laid out like a design file, built from what is under `.uxcli/` and nothing else: each journey
a band, each workflow a lane, each step a column; in each column the screen as drawn (the variants, A/B,
the pick), as built (the last walk's screenshot with its findings pinned where the browser found the
element) and as each named version left it. Drag to pan, scroll or pinch to zoom, click a frame for what
the files and the walk say about it. Context (actors, unknowns, insights) and the library (lenses,
templates) are a tab away. Served, it is also where a person decides — choose a drawing, or ask for a
redraw — written as `pick.json` or `revise.json` through the same parser, creating only — and leaves a
note on a built frame, written as a `redesign` proposal citing the walk. Two versions can be compared on
the board, each frame of the later one saying what moved. The agent
works through the CLI and the skill; `uxcli studio --shot=FILE` gives it the same board as a picture.
Free, local, no account: the page is one HTML file.
The project as the screens a person sees. Each journey is a filmstrip on a canvas: device frames with the
pictures the last walk took, the action and its seconds on the arrow between them, what is wrong pinned
on the picture of the screen it was measured on and said in a sentence. "Fix first" and a to-do bar say
what to do next and take you to it. Layers switch the frames between what was built, what was drawn,
and named versions side by side. Screens, Runs, People (who it is for, what is still a question) and
Library are a tab away. Served, it is also where a person decides — choose a drawing, ask for a redraw,
or leave a note on a screen that becomes a `redesign` proposal — through the same parsers the files are
read with. One HTML file, free, local, no account; `uxcli studio --shot=FILE` gives an agent the picture.

## The dashboard

`uxcli dashboard . ../other-app --serve` (http://127.0.0.1:4319) is the studio's page for several folders
at once, switched from the header, another one added from the page. Without `--serve` it is written to
`.uxcli/dashboard/index.html`; the card it prints is the coverage matrix — every screen against
understood, drawn, picked, reviewed, walked, verdict, estimate and walkthrough, a gap never a pass.

## Install

Expand Down Expand Up @@ -217,6 +221,7 @@ uxcli review check # every review complete, fresh, and not co
uxcli walkthrough <journey> [--as=<actor>] | check # the four cognitive-walkthrough questions at every step, answered from the screenshots; held to the walk
uxcli version [save <journey> <name>] # name a walk so it is kept; experience --journey=<id> --from=<a> --to=<b> compares two
uxcli experience [dir] [--page] # what the person goes through: steps, typing, scrolls, waits, an estimate in seconds; findings pinned on screenshots
uxcli dashboard [dir …] [--serve] # the management view: coverage matrix, runs, design, understanding; served, several folders
uxcli studio [dir] [--serve] [--shot=FILE] # the board: every journey's screens drawn, built and per version, pan and zoom like a design file; served, a person picks there
uxcli map [dir] # the journey map page: MODEL · RUN · DIFF · IMPACT, step by step, with the evidence
uxcli run <journey.json> # measure a journey --env --origin --viewport --json --out
Expand Down
15 changes: 14 additions & 1 deletion bin/uxcli.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ const usage = `usage:
uxcli prefer make <study> --a=<folder> --b=<folder> [--question=…] | serve <study> --judge=<person> [--port=N] | tally <study> [--json]
blind pairwise preference: two folders of pictures of the same screens, paired by file name and renamed so nothing names a group; each judge answers on a local page in their own order with sides swapped; tally counts wins and a sign test. A judge is a person, never an agent
uxcli walkthrough <journey> [--as=<actor>] [--for=<person>] [--run=R-…] | check a cognitive walkthrough of the last walk: four questions at every step, answered looking at its screenshot, optionally as one of the project's actors; check refuses an incomplete one and one the walk contradicts; "no" and "unsure" are findings
uxcli dashboard [dir …] [--serve [--port=N]] [--json] the management view, read-only: every screen against every kind of evidence (a gap is never a pass), the runs with their steps and pictures, the design, the understanding; --serve on localhost switches between folders and takes another folder from the page
uxcli studio [dir] [--serve [--port=N]] [--shot=FILE] [--json] the board: every journey as a canvas — each step drawn (variants, the pick), built (the last walk, findings pinned) and in each named version; pan, zoom, inspect; --serve on localhost refreshes as files change and lets a person pick or ask for a redraw
uxcli version [save <journey> <name> [--note=…] [--run=DIR] [--proposal=proposals/P-x.json]] name a walk of a journey so it is kept and can be compared: uxcli experience --journey=<id> --from=<name> --to=<name> [--page]
uxcli template [show <id> | apply <id> [dir]] where to start for a kind of product (workspace, shop, landing): screens and their lens, journeys to walk, what to research; apply writes the actor questions as unknowns, creating only
Expand Down Expand Up @@ -185,6 +186,18 @@ try {
if (r.problems) { console.error('uxcli: ' + r.problems.join('\n ')); process.exit(1); }
console.log(W.writtenCard(r, root));
process.exit(0);
} else if (cmd === 'dashboard') {
// The management view of one or more projects. Read-only: decisions stay in the studio and the files.
const DB = await import('../src/dashboard.js'); const dirs = args.length ? args : ['.'];
if (flags.has('--serve')) {
const r = await DB.serveDashboard(dirs, { port: Number(opt('port') || 4319) });
if (!r.server) { console.error('uxcli: ' + r.problems.join('\n ')); process.exit(1); }
console.log(`uxcli dashboard · ${r.roots.length} folder${r.roots.length === 1 ? '' : 's'}${r.problems.length ? ' · skipped: ' + r.problems.join('; ') : ''}\n open ${r.url} (ctrl-c to stop)`); await new Promise(() => {});
}
const { dashboardCard } = await import('../src/core/dashboard.js');
const r = await DB.writeDashboard(dirs[0]); if (!r.matrix) { console.error('uxcli: ' + (r.problems || []).join('\n ')); process.exit(1); }
console.log(flags.has('--json') ? JSON.stringify({ matrix: r.matrix, runs: r.runs, design: r.design, understanding: r.understanding }, null, 1) : dashboardCard(r.matrix, path.relative(process.cwd(), r.page)));
process.exit(0);
} else if (cmd === 'studio') {
// The board: every journey as a canvas of steps — drawn, built, each named version — with findings pinned.
const S = await import('../src/studio.js'); const vpt = opt('viewport') || '390x844';
Expand Down Expand Up @@ -219,7 +232,7 @@ try {
const T = await import('../src/template.js'); const lib = T.templates();
const pick = id => { const t = lib.list.find(x => x.id === id); if (!t) { console.error(`no template ${id}; have: ${lib.list.map(x => x.id).join(', ')}`); process.exit(1); } return t; };
if (args[0] === 'show' && args[1]) { const t = pick(args[1]); console.log(flags.has('--json') ? JSON.stringify(t, null, 1) : T.templateShowCard(t)); }
else if (args[0] === 'apply' && args[1]) { const t = pick(args[1]); const r = T.applyTemplate(args[2] || '.', t); console.log(flags.has('--json') ? JSON.stringify(r, null, 1) : T.applyCard(r, t)); }
else if (args[0] === 'apply' && args[1]) { const t = pick(args[1]); const at = path.resolve(args[2] || opt('src') || '.'); const r = T.applyTemplate((await import('../src/journey.js')).findRoot(at) || at, t); console.log(flags.has('--json') ? JSON.stringify(r, null, 1) : T.applyCard(r, t)); }
else console.log(flags.has('--json') ? JSON.stringify({ templates: lib.list.map(t => ({ id: t.id, name: t.name, when: t.when })), problems: lib.problems }, null, 1) : T.templateListCard(lib));
process.exit(lib.problems.length ? 1 : 0);
} else if (cmd === 'review' && args[0] === 'check') {
Expand Down
14 changes: 14 additions & 0 deletions docs/design/dashboard/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# The dashboard — what was drawn and what was picked

Drawn by an agent (Claude) with the CRM example's real data, picked by the owner on 2026-10-03.

| question | drawings | picked |
|---|---|---|
| What does the page answer first? | `a-attention` (what waits on a person), `b-matrix` (every screen against every kind of evidence), `c-timeline` (everything that happened) | **b-matrix** |
| Which look? | `theme-1-instrument` (light, dense, developer tool), `theme-2-notebook` (warm paper, serif), `theme-3-control` (dark, numbers first) | **theme-3-control** |
| Which views beside the overview, first? | Decisions, Runs, Design, Understanding | **Runs, Design, Understanding** — Decisions stays in the studio and the files, so the dashboard writes nothing |

Each drawing was measured with `uxcli run` before it was shown; `theme-2-notebook` failed 1.4.3 once
(#9b8f7e on #f4efe6, 2.76:1) and was corrected before the pick. Built as `uxcli dashboard`
(`src/dashboard.js`, `src/core/dashboard.js`, `src/core/dashboard-page.js`); `test/dashboard-pairs.mjs`
runs the page probes on every view.
24 changes: 24 additions & 0 deletions docs/design/dashboard/a-attention.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Attention first</title><style>:root{--ink:#15181d;--dim:#5f6774;--bg:#f5f6f8;--surface:#fff;--line:#e3e6ea;--brand:#2457c5;--brand-soft:#e9effb;--fail:#b42318;--fail-soft:#fdecea;--find:#8a5a00;--find-soft:#fff6e0;--pass:#1e7a46;--pass-soft:#e6f4ec;--muted:#eef0f3}
*{box-sizing:border-box}body{margin:0;font:14px/1.45 -apple-system,"Segoe UI",Helvetica,Arial,sans-serif;color:var(--ink);background:var(--bg)}
.top{display:flex;align-items:center;gap:12px;height:52px;padding:0 16px;background:var(--surface);border-bottom:1px solid var(--line)}.logo{font-weight:800}.sp{flex:1}
.proj{display:flex;align-items:center;gap:6px;border:1px solid var(--line);border-radius:8px;padding:5px 10px;font-weight:600}
.btn{border:1px solid var(--line);background:var(--surface);border-radius:8px;padding:6px 11px;font:600 13px inherit;font-family:inherit;color:var(--ink)}.btn.p{background:var(--brand);border-color:var(--brand);color:#fff}
.k{font-size:11px;font-weight:700;color:var(--dim);letter-spacing:.06em;text-transform:uppercase}
.card{background:var(--surface);border:1px solid var(--line);border-radius:12px;padding:14px 16px}
.v{display:inline-block;border-radius:6px;padding:1px 7px;font-size:12px;font-weight:700}.v.fail{background:var(--fail-soft);color:var(--fail)}.v.find{background:var(--find-soft);color:var(--find)}.v.pass{background:var(--pass-soft);color:var(--pass)}.v.blk{background:var(--muted);color:var(--dim)}
table{width:100%;border-collapse:collapse;font-size:13px}td,th{text-align:left;padding:8px 10px;border-top:1px solid var(--line)}th{font-size:11px;color:var(--dim);text-transform:uppercase;letter-spacing:.05em;border-top:0}
.muted{color:var(--dim)}h1{font-size:20px;margin:0}h2{font-size:15px;margin:0 0 10px}</style></head><body><header class="top"><span class="logo">uxcli</span><span class="proj" data-uxcli="folder-switch">examples/crm ▾</span><span class="muted" style="font-size:12px">read from .uxcli/ · 4 runs · updated 2026-09-25 08:41</span><span class="sp"></span><button class="btn">Open studio</button></header><main style="display:grid;grid-template-columns:240px 1fr;min-height:calc(100vh - 52px)">
<nav style="border-right:1px solid var(--line);background:var(--surface);padding:14px" aria-label="Projects"><div class="k">Folders</div>
<p style="margin:8px 0;font-weight:700">examples/crm <span class="v fail">fail</span></p><p style="margin:8px 0">kinetrak <span class="v blk">no walk</span></p><p style="margin:8px 0" class="muted">+ add a folder…</p></nav>
<section style="padding:20px 24px;display:grid;gap:16px;align-content:start">
<div class="card" data-uxcli="attention" data-uxcli-note="the first thing on the page is what only a person can unblock, with the file it lives in"><h2>Waiting for a person · 5</h2>
<table><tr><th>what</th><th>where</th><th></th></tr>
<tr><td><span class="v fail">fail</span> C-001 call action not in view at 390×844 — handle-inbound-lead s2</td><td class="muted">runs/R-…zs8fns</td><td><button class="btn">Evidence</button></td></tr>
<tr><td>Pick a drawing — agent.call_started asked for a redraw</td><td class="muted">mockups/agent.call_started</td><td><button class="btn">Open</button></td></tr>
<tr><td>3 proposals waiting: instrumentation P-0004 · retirement P-0006 · profile P-0007</td><td class="muted">proposals/</td><td><button class="btn">Review</button></td></tr>
<tr><td>3 unknowns about real_estate_agent with no source</td><td class="muted">understanding/actors</td><td><button class="btn">Open</button></td></tr></table></div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px">
<div class="card"><div class="k">handle-inbound-lead</div><p style="margin:6px 0"><span class="v fail">fail</span> last walk 25 Sep · 2 steps</p><p style="margin:0;font-size:24px;font-weight:800">8.4 s <span class="muted" style="font-size:13px;font-weight:500">estimate · v1 → v2 +3.5 s, one step more</span></p><p class="muted" style="margin:4px 0 0">1 finding: call control 2 scrolls down</p></div>
<div class="card"><div class="k">authenticate</div><p style="margin:6px 0"><span class="v fail">fail</span> last walk 25 Sep · 1 step</p><p style="margin:0;font-size:24px;font-weight:800">— <span class="muted" style="font-size:13px;font-weight:500">no experience walk yet</span></p></div></div>
<div class="card"><h2>Recent walks</h2><table><tr><th>when</th><th>journey</th><th>verdict</th><th>exit</th></tr><tr><td>25 Sep 08:41</td><td>handle-inbound-lead</td><td><span class="v fail">fail</span></td><td>2</td></tr><tr><td>25 Sep 08:39</td><td>authenticate</td><td><span class="v fail">fail</span></td><td>2</td></tr><tr><td>25 Sep 08:30</td><td>handle-inbound-lead</td><td><span class="v blk">blocked</span></td><td>1</td></tr><tr><td>24 Sep 09:12</td><td>handle-inbound-lead</td><td><span class="v find">finding</span></td><td>2</td></tr></table></div>
</section></main></body></html>
Loading
Loading