# M12 W2 — doyle design ruling (picker)

> doyle 2026-06-14, gating todlando's W2 JIT plan (M12-W2-PLAN.md). Claims verified vs source
> before ruling. **Headline correction: Resume-from-history is NOT deferred — its data surface
> already exists.**

## Verified against source
- **Session ledger EXISTS** — `spt-store/src/sessions.rs`: `last_k(perch, k) -> Vec<SessionEntry{ ts: RFC3339-UTC, session_id, trigger(Boot|Clear|Compact) }>`, oldest→newest, bounded (MAX_LEDGER=64), garbage-tolerant. Landed M10 (ADR-0019 / REQ-TERM-6) for digest thread-spanning. This IS the per-endpoint session-id log WITH timestamps Q3 assumed might not exist. (Investigation TODO#3 wasn't run — it does exist.)
- **`--resume <session>` flag EXISTS** on `Run{}` (plan line 32) → the terminal path for resume is already there.
- **No attach-presence query** — `presence.rs` has snapshots / most_recently_active / resolve, no "which node is attached to endpoint E's PTY." Q1's gap is real.

## Rulings

**Q1 — attached-state (blue ■ + "Kick <node> and attach"): DEFER to a QUEUED follow-up slice (W2.5), not indefinitely.**
W2 ships **online/offline only**. The blue tri-state + Kick need a new broker attach-presence query AND a force-detach/kick wire op — a distinct broker mutation surface in the PTY-ownership hazard zone (KNOWN-HAZARDS). **Operator decision (2026-06-14): build that surface as a dedicated slice once W2 lands, then come back and wire the blue-attached status + Kick-and-attach into the picker.** So this is a SCHEDULED W2.5, sequenced after W2 completes — not dropped. W2.5 = (1) broker attach-presence query (who is attached to endpoint E's PTY, by which node/surface — derive from PresenceLog conn connect/disconnect if sound, else a new query op), (2) force-detach/kick wire op (its own KH treatment: PTY-ownership invariant, who-may-kick auth), (3) picker: blue ■ tri-state + the "Kick <node> and attach" confirm option. Own JIT plan + own gate.

**Q2 — project-history description pane: best-effort, no new store.**
Derive newest→oldest from the existing contextstore (`p-<project>` branches); render empty if none. Do NOT block W2 on a new history store. It's an informational pane — degrade-to-empty is fine.

**Q3 — Resume-from-history: BUILD IT (correction — do NOT defer).**
The session ledger exists and `--resume` exists. The picker enumerates `sessions::last_k` for the offline LOCAL endpoint, renders titles `<project> @ <ts> (…id5)`, and feeds the chosen `session_id` to `cmd_endpoint_run --resume`. **No new daemon surface.** This is a pure front-end over surfaces that exist — it meets your own "stays in W2" bar. Notes:
- Offline-only + LOCAL-only (the ledger is at the local perch; you resume on a node you own). A remote offline endpoint = Instantiate-locally, not Resume.
- Whether a given adapter actually resumes is its manifest `resume` declaration's job — surfaced at run time, not a picker-scope gap. The picker offers Resume when ledger rows exist; an adapter that can't resume fails at the terminal action, same as any capability mismatch.
- Empty ledger ⇒ no Resume option (offline still gets Attach/Start/View/Fork/Change-adapter).

**Q4 — shortcut name + format: harness-agnostic `spt-<id>` basename (parameterized), per-OS native, sentinel-guarded overwrite. REVISED 2026-06-14 (operator): NOT `cc`, NOT bare `spt`; Windows = `.cmd` not `.ps1`.**

**Naming (operator-raised):** `cc` bakes "Claude Code" into harness-agnostic spt-core — wrong layer. And a bare `spt` shortcut is a TRAP: a cwd `spt.cmd` shadows the PATH `spt.exe` ONLY in cmd.exe (cwd-first search), NOT in PowerShell (no cwd exec) or Unix (`.` off PATH) → inconsistent + a self-recursion footgun (`spt.cmd` calling `spt` re-invokes itself). **Reject bare `spt`.**
- **spt-core W2 default basename = `spt-<id>`** (agnostic, distinct name → no shadow/recursion, bare-invocable). E.g. `spt-doyle.cmd` (Win) / `spt-doyle` (Unix).
- **Basename is a PARAMETER, not hardcoded** — an adapter/flow overrides it; spt-claude-code sets it to `cc-<id>` (CC-ness belongs in the adapter). spt-core never emits `cc`.
- The bare "launch this project" entry already exists: `spt endpoint run` (no args) → picker. No shadowing wrapper needed.

Generate the **current-OS** script: **`.cmd`** on Windows, extension-less script (+`chmod +x`) on Unix — a single portable form can't be both. Bake the non-interactive flags.
- **Why `.cmd` not `.ps1` (operator-raised):** default `PATHEXT` excludes `.ps1`, so a bare/extension-less command name never resolves a `.ps1`, and `.ps1` doesn't run on double-click without `powershell -File`. `.cmd`/`.bat` ARE in PATHEXT → invocable by name. Use `.cmd` (cleaner errorlevel than `.bat`). The `.cmd` body just calls `spt endpoint run …` with the baked flags.
- **Invocation reality (document in `--help` / the generated header):** truly-bare `cc-<id>` (no `.\`, no ext) works only from **cmd.exe in the project dir** (cmd searches cwd + PATHEXT). **PowerShell needs `.\cc-<id>`** (it refuses cwd execution but honors PATHEXT, so `.\cc-<id>` finds the `.cmd`). **Unix needs `./cc-<id>`** (name it `cc-<id>` no extension + exec bit; `.` not on PATH). Truly-bare `cc` everywhere = a launcher installed into a PATH dir — that's `/spt:setup`'s job (SCOPE #1), not the project-root shortcut.
- **Overwrite semantics:** the shortcut is a derived artifact — regenerate freely, BUT only overwrite a file carrying a generated-by **sentinel** (a header comment marker the generator writes + checks). If a same-named file lacks the sentinel, refuse + warn (don't clobber a user file). "Update if exists" = overwrite-our-own-only.

**Q5 — non-interactive flags: approve additive `--create`.**
Add `--create` to `Run{}` (explicit fresh, the default-fresh made explicit; N-1-safe additive). Shortcut covers only **terminal actions reachable non-interactively**: adapter[:profile] + id + (create|resume) + (start|attach|view). Resume IS in this set (ledger + `--resume` exist). Interactive-only branches (Kick, Instantiate-locally, Change-adapter, Fork) stay picker-only — not bakeable. Confirm.

## Bare `spt` → picker (operator idea, 2026-06-14) — ACCEPT into W2

Bare `spt` (no subcommand) becomes an **alias for `spt endpoint run`** (the picker), replacing today's print-help-on-no-args. This removes any need for a cwd `spt.cmd` for the bare-launch case — `spt` is muscle-memory in every project, picker resolves the cwd project (W1.5/HR2 cwd derivation). Routes through the same `cmd_endpoint_run` bringup core → no second path.

**Mandatory guard (load-bearing): TTY detection.**
- Interactive TTY (stdin+stdout a terminal) → launch the picker.
- **Non-TTY (pipe / CI / redirected / no terminal) → fall back to today's help, NEVER launch the ratatui TUI.** A bare `spt` in a script must not hang waiting on a terminal. Unit-test this (non-TTY bare → help, no picker).
- `spt --help` / `-h` / `spt help` **always** print help regardless of TTY (clap intercepts the flags; the `help` subcommand stays).

**Shortcut stays complementary:** bare `spt` = open the picker for this cwd project; `spt-<id>` = jump straight to one endpoint (skip the picker). Both kept.

Folds into **REQ-RUN-PICKER** (an additional entry point + the TTY guard). Additive top-level CLI default change; N-1-safe (explicit subcommands + help unchanged).

## Structure / REQ
- **REQs:** REQ-RUN-PICKER (interactive TUI) + REQ-RUN-SHORTCUT (`cc-<id>` gen + additive flags) — two REQs is the right granularity (separable surfaces). Approve.
- **Stages: doc + impl + unit. NO `int` on the TUI.** ratatui renders to a testable `Buffer` — assert the rendered buffer for screen-state + state-model transitions (category nav, filter narrowing, cursor windowing, status→square, adapter→profile tree, ledger→resume-titles, shortcut content + create-vs-update, flag→action). The live key loop is a **manual-verify leg** (like REQ-PAIR-6's OS probe — note it in the plan). The integrating bringup path is ALREADY int-covered (W1/W1.5 `cmd_endpoint_run`); the picker rides it.
- **Q6/Q7 UX defaults: approved.** In-process `spt/src/picker/`; bare `spt endpoint run` (no `--adapter`/`--id`) = interactive, flags-present = today's non-interactive path untouched; action enum = single source of truth (tap-mode layers on later); nucleo `/` filter; ratatui two-column layout; pinned legend.

## Binding invariant (gate condition)
**No second bringup path.** Every terminal action routes through `cmd_endpoint_run` / existing CLI fns (`cmd_fork`, wake, rc pump). One bringup core — same discipline as W1.5's one-pump. I gate on this.

## Gate posture
Build uncommitted on m12-w1-bringup-rc. I gate (reproduce traceable + read the load-bearing state-model/buffer tests + confirm single-bringup-path) before any commit. Suite + clippy -D + traceable EXIT=0. Operator calls the commit.
