diff --git a/docs/cockpit.md b/docs/cockpit.md
index afeffc4..a45d40f 100644
--- a/docs/cockpit.md
+++ b/docs/cockpit.md
@@ -91,6 +91,38 @@ never-list/handoffs on all six), `test_stations_are_distinct_roles`
(each role's signature phrase appears on its own station and on no
other — no melting), `test_operator_probe_is_explicit_and_live`.
+## The deck (`/deck`) — all roles live, in parallel, with the wizard
+
+The **deck** is the crew law made physical: a tmux-style grid of six
+panes, one per role, all live at the same time — because a real crew
+works in parallel, roles do not take turns existing. Each pane is an
+independent viewport (an iframe onto that role's station in
+chrome-stripped **pane mode**, `?pane=1`): it scrolls, reloads (⟳), and
+zooms (⤢, tmux-style single-pane zoom) independently, and a tiny shim
+keeps every link and form inside the pane (`pane=1` is re-carried), so
+pressing «Probe now» in the operator pane runs the probe *in that pane*.
+Pane mode strips the page chrome but keeps the READ-ONLY label and the
+full station content — one source of truth, two shells.
+
+On the right rides the **wizard**: a ten-step guided first watch that
+takes a newcomer by the hand through every role's real actions on the
+live demo wallet — probe as the operator, read the queue and a refusal
+as the proposer, find the dissenting seat as the bench, verify (and then
+deliberately break) real sample evidence as the cryptographer
+(`/inspect?sample=1` pre-fills `examples/wallet-evidence/`), check the
+drift tripwire as the architect, then run the handoff lap. Each step
+card is **camouflaged in the color of the role being lived** ("YOU ARE
+THE OPERATOR"), and the matching pane **glows** — instruction and
+instrument are bound by hue. Every step states what success looks like
+and what was just learned. Step position is remembered per browser
+session.
+
+Deck contract tests: `test_deck_serves_all_panes_and_wizard` (six live
+panes + all six roles visited by the wizard + success criteria),
+`test_pane_mode_is_chromeless_but_labeled` (no chrome, READ-ONLY label,
+stay-in-pane shim), `test_inspect_sample_prefill`; the read-only byte
+sweep covers `/deck` and pane routes.
+
## The read-only guarantee
The cockpit cannot approve, sign, unlatch, or modify custody state. It
diff --git a/src/pacta/cli.py b/src/pacta/cli.py
index aa29a64..dbb9a63 100644
--- a/src/pacta/cli.py
+++ b/src/pacta/cli.py
@@ -693,7 +693,8 @@ def cmd_wallet_cockpit(args: argparse.Namespace) -> int:
server = serve(wallet_dir, host=args.host, port=args.port)
host, port = server.server_address[0], server.server_address[1]
print(f"warden cockpit (READ-ONLY) on http://{host}:{port} - Ctrl-C to stop")
- print(f" first time? start at http://{host}:{port}/guide - every term explained")
+ print(f" the deck: http://{host}:{port}/deck - all six roles live, guided by the wizard")
+ print(f" the guide: http://{host}:{port}/guide - every term explained")
try:
server.serve_forever()
except KeyboardInterrupt:
diff --git a/src/pacta/deck.py b/src/pacta/deck.py
new file mode 100644
index 0000000..b63d8bb
--- /dev/null
+++ b/src/pacta/deck.py
@@ -0,0 +1,281 @@
+"""deck - the ops deck: every role station live in one tmux-style grid,
+with the guided wizard rail.
+
+The deck is the crew law made physical: six panes, one per role, all
+live at the same time - because in real life the roles act in parallel,
+they do not take turns existing. Each pane is an independent viewport
+(iframe) onto that role's station in chrome-stripped "pane mode"; panes
+reload and zoom independently, tmux-style.
+
+On the right rides the WIZARD: a guided first watch that takes a
+newcomer by the hand through every role's real actions on the live DEMO
+wallet. Each step card is camouflaged in the color of the role being
+lived, and the matching pane glows - instruction and instrument are
+bound by hue, so the learner always knows where to act.
+
+Pure presentation: this module renders strings from the station model;
+all live evidence stays inside the panes, which are ordinary cockpit
+routes and therefore inherit the read-only guarantee wholesale. The
+~60 lines of vanilla JS here do layout only (zoom, reload, step
+navigation, glow) - they never touch wallet data.
+"""
+from __future__ import annotations
+
+from .stations import STATIONS
+from .uikit import STYLE, esc
+
+_DECK_EXTRA_STYLE = """
+ body.deckbody{max-width:none;margin:0;padding:0;height:100vh;display:flex;
+ flex-direction:column;overflow:hidden}
+ .deckbar{display:flex;align-items:center;gap:.7rem;flex-wrap:wrap;
+ padding:.45rem .9rem;background:#1c2430;color:#e8ecf2;font-size:.82rem}
+ .deckbar a{color:#aebcf0;text-decoration:none}
+ .deckbar .mono{opacity:.75}
+ .deckgrid{flex:1;display:grid;grid-template-columns:minmax(0,1fr) 21.5rem;
+ min-height:0}
+ .deckmain{display:grid;grid-template-columns:1fr 1fr;grid-auto-rows:1fr;
+ gap:6px;padding:6px;min-height:0;overflow:auto}
+ @media(min-width:1500px){.deckmain{grid-template-columns:1fr 1fr 1fr}}
+ .pane{display:flex;flex-direction:column;border:1px solid var(--line);
+ border-left:4px solid var(--role);border-radius:6px;background:#fff;
+ min-height:13rem;min-width:0}
+ .pane header{display:flex;align-items:center;gap:.45rem;padding:.22rem .5rem;
+ font-size:.78rem;background:var(--roletint);border-radius:0 5px 0 0}
+ .pane header .q{color:var(--ink2);font-size:.7rem;font-style:italic;
+ overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
+ .pane header .monogram{min-width:1.5rem;height:1.5rem;line-height:1.5rem;
+ font-size:.68rem}
+ .paneacts{display:flex;gap:.25rem}
+ .paneacts button,.paneacts a{border:1px solid var(--line);background:#fff;
+ color:var(--ink);border-radius:4px;font-size:.72rem;line-height:1.25;
+ padding:.05rem .4rem;cursor:pointer;text-decoration:none}
+ .pane iframe{flex:1;border:0;width:100%;min-height:0;border-radius:0 0 5px 5px}
+ .pane.focus{outline:3px solid var(--role);outline-offset:-1px}
+ .deckmain.zoom .pane{display:none}
+ .deckmain.zoom .pane.zoomed{display:flex;grid-column:1/-1;grid-row:1/-1}
+ .wizard{border-left:1px solid var(--line);background:#fff;overflow-y:auto;
+ padding:.8rem .9rem;min-height:0}
+ .wizard h2{margin:.1rem 0 .3rem;font-size:1rem}
+ .wizlegend{display:flex;gap:.3rem;flex-wrap:wrap;margin:.4rem 0 .7rem}
+ .wizlegend span{font-size:.66rem;font-weight:700;border-radius:5px;
+ padding:.1rem .4rem;background:var(--roletint);color:var(--role)}
+ .wizstep{display:none;border-left:4px solid var(--role);
+ background:var(--roletint);border-radius:6px;padding:.65rem .8rem;
+ font-size:.86rem}
+ .wizstep.on{display:block}
+ .wizstep .rolechip{display:inline-block;font-weight:800;font-size:.7rem;
+ letter-spacing:.04em;color:var(--role);margin-bottom:.25rem}
+ .wizstep h3{margin:.1rem 0 .35rem;font-size:.92rem}
+ .wizstep .succ{margin:.45rem 0 0;font-size:.8rem}
+ .wizstep .succ b{color:var(--ok)}
+ .wizstep .learn{margin:.35rem 0 0;font-size:.8rem;color:var(--ink2);
+ font-style:italic}
+ .wizctl{display:flex;align-items:center;gap:.6rem;margin:.7rem 0 0}
+ .wizctl button{padding:.35rem .8rem;font-size:.84rem}
+ .wizctl button:disabled{opacity:.4;cursor:default}
+ #wizprog{font-size:.78rem;color:var(--ink2)}
+ @media(max-width:999px){
+ body.deckbody{overflow:auto;height:auto}
+ .deckgrid{display:flex;flex-direction:column-reverse}
+ .deckmain{grid-template-columns:1fr}
+ .pane{min-height:22rem}
+ .wizard{border-left:0;border-bottom:2px solid var(--line)}
+ }
+"""
+
+# The guided first watch. Each step: (role_id, title, do_html, success_html,
+# learn_html). The step card wears the role's colors; the matching pane glows.
+WIZARD_STEPS: list[tuple[str, str, str, str, str]] = [
+ ("newcomer", "Welcome to the deck",
+ "Six panes, six roles, all live at the same time — a real crew works in "
+ "parallel, and so does this deck. The colors ARE the roles; this wizard "
+ "wears the color of whoever you are acting as, and that role's pane "
+ "glows. Nothing you do here can sign or change anything — every pane is "
+ "read-only evidence.",
+ "You can point at each pane and say what its role is for (the italic "
+ "line in each pane header helps).",
+ "One system, six distinct jobs — never one blurred job."),
+ ("operator", "Morning watch: probe everything",
+ "Act as the Operator (green pane): press "
+ "«Probe now» on the liveness board. Watch the rows fill "
+ "with live facts. Find the log head row and read its "
+ "tree_size — that number is the public transparency log "
+ "answering you, right now.",
+ "Every service row says alive with observed facts and latency; "
+ "the repo rows show HEAD commits.",
+ "Liveness is checked on demand, never assumed — and never silently in "
+ "the background."),
+ ("operator", "Read the wallet's own pulse",
+ "Still in green: scroll to Custody latch — it says "
+ "unlatched, and the panel explains what the latch would do. "
+ "Then Recorded history: this demo wallet carries "
+ "1 incident and 1 refusal receipt on file. You will "
+ "meet both in the next steps.",
+ "You found the latch state and the two counters without leaving the "
+ "green pane.",
+ "The operator reads state from evidence panels, not from memory."),
+ ("proposer", "Live the proposer: find your request",
+ "Switch hats — amber pane. One signing request sits in "
+ "the queue, awaiting device. Read its payload "
+ "fingerprint: that is the SHA-256 of the exact bytes the "
+ "offline signer would sign — and nothing else.",
+ "You can quote the first characters of the fingerprint of what would be "
+ "signed.",
+ "A proposal is precise, or it is nothing."),
+ ("proposer", "Read a refusal like a to-do list",
+ "Still amber: open Incidents (refusals) from the "
+ "station's instrument links. The demo refusal says "
+ "POLICY_DENIED, missing allowlisted destination, "
+ "with a remediation naming exactly what would make the "
+ "same request succeed.",
+ "You can say, in one sentence, what the proposer would fix before "
+ "retrying.",
+ "warden never says a bare «no» — every refusal is machine-readable "
+ "instructions."),
+ ("quorum", "Sit on the bench: find the dissenter",
+ "Now the indigo pane. Count the seats: four, each "
+ "built from a different verified codebase. The demo incident (you saw "
+ "its counter in step 3) records that risc0 answered "
+ "INVALID while the other three said OK — in a real wallet, that single "
+ "dissent freezes custody on the spot.",
+ "You found all four seats and can name the dissenting member.",
+ "One honest dissenter beats three comfortable agreements — that is the "
+ "whole bench."),
+ ("cryptographer", "Recompute — never trust",
+ "The purple pane: click «load the sample "
+ "evidence», then Verify (read-only). The "
+ "deployed verifier re-checks the signatures and the log inclusion in "
+ "front of you. Then delete one character from the receipt box and "
+ "verify again — watch it refuse, loudly.",
+ "First run: ACCEPTED with a diagnostics list. Broken run: "
+ "REJECTED, and the diagnostics name the exact failing check.",
+ "You never trusted this page — you recomputed it. That is the "
+ "cryptographer's entire posture."),
+ ("architect", "Zoom out: map versus territory",
+ "The slate pane: read the drift "
+ "tripwire — it just compared the two renderings of the estate "
+ "map, name by name, and they agree. Then open the Estate "
+ "map link and find this wallet's place among the repos, "
+ "services, and the two self-referential loops.",
+ "You can say where warden sits on the map, and what the tripwire would "
+ "catch.",
+ "The map is recomputed, never remembered — or it lies."),
+ ("operator", "The handoff lap — the team in one incident",
+ "Watch the whole crew move once, in your head, across the panes: the "
+ "proposer escalates a refusal → the operator "
+ "investigates and probes → the bench defends its dissent → "
+ "the cryptographer re-verifies the evidence → the "
+ "architect records what changed. Five roles touched one "
+ "incident — and not one of them did another's job.",
+ "You can retell the lap naming who hands what to whom.",
+ "Teamwork here is handoffs between distinct roles — never a blur."),
+ ("newcomer", "Graduation",
+ "Pick the station that felt most like you and open it full-size (the "
+ "↗ in its pane header). Read its Mission, its Duties — every one a "
+ "real command — and its «This station never…» list; the never-list is "
+ "the fastest way to understand a role. When you are ready for a real "
+ "wallet: pacta wallet init.",
+ "You have a station — and you know what it never does.",
+ "If any page confused you on this watch, that is a bug in the page, "
+ "not in you. Report it — that is the newcomer's superpower."),
+]
+
+
+def render_deck(wallet_dir: str) -> str:
+ """The full deck document: bar, pane grid, wizard rail, layout JS."""
+ demo_badge = (' DEMO WALLET — custody-inert'
+ if "DEMO" in wallet_dir else "")
+ panes = "".join(
+ f'
Success looks like: {success}
' + f'{learn}
' + f'No sample evidence found on this "
+ "machine (expected under examples/wallet-evidence/). Fetch "
+ "real evidence from the live log instead: pacta log-fetch --url "
+ "https://ltl.zkdefi.org --component dalek-ed25519-verified, then "
+ "paste the two files and the log's log.pub.
"
+ "sample loaded evidence for "
+ f"{_esc(stem)} is pre-filled below — press «Verify "
+ "(read-only)» to watch the deployed verifier run. Then delete one "
+ "character from the receipt box and verify again: watch it refuse, "
+ "and read which check failed.