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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,6 +573,10 @@ Built with Ihsan · Maintained by [RECTOR](https://github.com/rz1989s) · [getpi

The redesign unifies the three fleet surfaces under one visual language: your pi theme's tokens only (`accent`/`text`/`muted`/`dim`/`warning`/`success`/`error`), a single glyph vocabulary (▶ ⏸ ✓ ✗ ⏳, braille spinners, box-drawing cards), and the `usage —` honesty rule (missing data renders `—`, never an estimate).

**Glyph presets.** Set `ARMORY_FLEET_GLYPHS=ascii` for a fully glyph-free fallback (dumb terminals — `+---+` cards, `-` spinner, `|` footer separators), or `=nerd` for FontAwesome PUA icons (Nerd Font required). Default is `unicode`. Invalid values fall back to unicode with a one-time stderr warning.

**Fleet-tab preview row.** With a running run highlighted in the `/fleet` Fleet tab, a line under the list mirrors the transcript card's state line exactly (same segments, unthemed); it blanks when nothing running is selected.

**In-transcript run cards.** When a `subagent` dispatch fires, the tool row becomes a live card — spinner, agent, model, task, and a state line (last event · turn · elapsed · tok · ctx%) driven by the child run's events. On settle it collapses to one honest line:

```
Expand Down
636 changes: 636 additions & 0 deletions docs/superpowers/plans/2026-09-05-fleet-presentation-p3.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -134,10 +134,11 @@ One line per run (status glyph, duration, tok, $, one-clause outcome), degradati
- **Scroll-state separator (live-only):** while a run streams AND `LiveTimelineState.pinned === false`, the timeline footer line becomes warning-themed `↑ scrolled · live paused · ↓ end to re-follow` (the timeline has no box frame — footer line, not border). Re-follow gesture = the existing scroll-to-bottom re-pin in `LiveTimelineState.onKey`; **Enter keeps Full-message** (the original "enter to re-follow" collided with the bound Full-message action — rejected per §8 additive-only). Replay browsing stays unannotated.
- **#108 card fixes (folded in):** `renderCall` stops hand-rolling its frame and delegates to `liveCardLines` with a provisional `RunCardState` (unifies geometry — kills the missing-bar + handoff-fragment defects at wide terminals); named `CARD_WIDTH = 72` clamp replaces both hardcoded `80`s (subagent.ts renderCall/renderResult); regression test locks the null-segment separator join; the fallback+card double-render gets a time-boxed real-pi smoke investigation (pi-core cause → file upstream, park).

### P3 · Polish
- **Symbol presets** (`unicode` default / `nerd` / `ascii`) as glyph-map variants in glyphs.ts.
- **Segmented footer separators** (thin-powerline `│` default, `|` ascii fallback).
- **Live run-card preview row** in the Fleet tab (the selected run's state line rendered as it appears in-transcript).
### P3 · Polish (ratified 2026-09-05 — env-var selection, nerd ships, live-only preview)
- **Symbol presets** (`unicode` default / `nerd` / `ascii`) as glyph-map variants in glyphs.ts. **Selection = `ARMORY_FLEET_GLYPHS` env var read once at module load** — static resolve: `export const GLYPHS: GlyphMap = pickPreset(process.env.ARMORY_FLEET_GLYPHS)`; invalid value (after trim + lowercase) → unicode + exactly one stderr warning; no runtime switching (rejected: accessor+setter mutates ~40 call sites for a rejected capability; ESM `export let` live binding silently degrades to per-module snapshots under CJS interop). Consumers keep `GLYPHS.x` unchanged (8 files). `GlyphMap` interface; three factory presets, per-preset key-parity tests. `nerd` = FontAwesome PUA icons (F04B play · F017 clock · F04C pause · F00C check · F00D times · F021 refresh · F071 warning · F05A info · F186 moon · F111 circle · F040 pencil · F141 ellipsis · F08E external-link · F046/F096 todo squares); tree/card box-drawing + continuation + todoStruck **stay unicode** (connectors render fine in nerd fonts — nerd's value-add is icons, not connectors); nerd spinner frames are smoke-verified candidates (F110/F021/F1CE/F013) — visual pick by RECTOR in the real-pi smoke, data-only swaps after.
- **Segmented footer separators**: new `footerSep` glyph (`│` U+2502 in unicode+nerd, `|` in ascii) + one `hint(...parts)` join helper. Scope = the footer hint bar (all 5 `footerFor` states) and the `timelineFooter` warning line **only**. Totals header, card state line, row segments, findings, orchestration segments keep `·` (intra-row value separators; totals strip is normative in the Appendix mockup).
- **Live run-card preview row** in the Fleet tab (the selected run's state line rendered as it appears in-transcript). State-line join extracted from `liveCardLines` into pure `stateLine(s, now, frame)` — byte-identical, existing card pins untouched. `previewLine(selectedId, {registry, bgRuns}, now, frame)` resolves the selected fleet item via `cardSnapshot` (registry) or the `bgToCard` adapter (bg pool, lifted to an importable spot). Renders **only for `status === "running"`** (live preview); reserved blank line otherwise (stable height, no flicker); unthemed — literally as in-transcript, zero `theme.fg` exposure; no width clamp (all segments bounded, ~55 cols < `CARD_WIDTH`); not a selectable row — rendered between list and footer. Runs-view preview parked.
- Acceptance (§7): ascii preset renders glyph-free fallbacks everywhere (no mojibake on dumb terminals) — key-parity tests are the structural guarantee, the real-pi ascii smoke is the visual one; default (no-env) rendering stays byte-identical to v1.3.0 except the footer separators.
- Dropped as over-promise: replacing pi's own footer/statusline (that canvas is pi's, not an extension's).

## 6 · armory-todo intersection
Expand All @@ -160,20 +161,21 @@ One line per run (status glyph, duration, tok, $, one-clause outcome), degradati
6. `t` lineage tree in Runs + Fleet views.
- **Accept:** replay a multi-run journal: tree groups correctly by lineage; live timeline scroll detaches with marker and re-follows on scroll-to-bottom; run card renders identical geometry at any terminal width (clamped 72).

**P3 — polish**
7. Symbol presets (unicode/nerd/ascii), segmented separators, live run-card preview row.
- **Accept:** ascii preset renders glyph-free fallbacks everywhere (no mojibake on dumb terminals).
**P3 — polish (ratified 2026-09-05)**
7. Symbol presets (unicode/nerd/ascii, `ARMORY_FLEET_GLYPHS` selection), segmented separators, live-only preview row.
- **Accept:** ascii preset renders glyph-free fallbacks everywhere (no mojibake on dumb terminals); default rendering byte-identical to v1.3.0 except footer separators; nerd preset icons eyeballed by RECTOR in the smoke; preview row ticks on a live run and blanks otherwise.

## 8 · Architecture

- **New:** `src/present/` — `tokens.ts` (status→token map), `glyphs.ts` (vocabulary + presets), `width.ts` (ANSI-aware measure/wrap/truncate), `tree.ts` (P2: `layoutTree()` DFS connector prefixes) · `src/transcript/` — `run-card.ts`, `orchestration-entry.ts`, `findings.ts` (**pure functions** returning components/strings; unit-testable, no TUI imports beyond pi-tui primitives).
- **Changed:** `src/todo-sync/port.ts` (+`listFleetTodos`), `src/todo-sync/adapter.ts` (impl), `src/index.ts` (renderer/entry registrations), `src/panel/fleet-panel.ts` (header/footer/rows wiring, P2: `lastWidth` capture + `t` toggle + separator), `src/panel/rows.ts`+`runs-rows.ts`+`fleet-items.ts` (segment styling, P2: tree prefix param), `src/panel/widget-rows.ts` → component widget controller, `src/tools/subagent.ts` (P2/#108: renderCall delegates to `liveCardLines`, `CARD_WIDTH` clamp).
- **Untouched:** engine, journal, RPC, scheduler, lifecycle runtime, tiers, workflows runtime. All existing keybindings keep their meanings; new keys (`t`, expand is native) only.
- Convention compliance: raw `.ts` via tsx (no build step); tests in `test/*.test.mts` only (repo test-discovery rule); interactive-first (panel/view first, tool action second).
- P3 additions: `present.ts` gains `hint()` + `footerSep` glyph; `run-card.ts` gains extracted `stateLine`; `rows.ts` exports `bgCardSnapshot` (lifted from the index.ts closure — `BgRunStatus` lives there; avoids a transcript→panel import); `fleet-panel.ts` renders the preview row between list and footer.

## 9 · Testing

- Unit (pure renderers): run-card collapsed/expanded/failed paths (`—` honesty), orchestration tree (empty store, mixed statuses, long-task truncation with `↳`), findings block (degradation lines), widget totals/segments, glyph preset completeness (every glyph referenced exists in the active preset), ANSI-width helper (strip correctness incl. wide-CJK conservative case).
- Unit (pure renderers): run-card collapsed/expanded/failed paths (`—` honesty), orchestration tree (empty store, mixed statuses, long-task truncation with `↳`), findings block (degradation lines), widget totals/segments, glyph preset completeness (every glyph referenced exists in the active preset; per-preset key parity; `resolvePresetName` table), `hint()` separator join per preset, `stateLine` extraction parity, `previewLine` truth table (live→line · otherwise blank · stale id→blank), ANSI-width helper (strip correctness incl. wide-CJK conservative case).
- Port: `listFleetTodos` round-trip via adapter with a temp fleet dir.
- Integration: real-pi smoke per getpipher rule — run card appears/animates/finalizes in a live term (mock-vs-real trap, dogfood gotcha #9, applies doubly to renderers); timer-leak check (second burst after first completes).
- Gates: `pnpm typecheck` + `pnpm test:run` standalone before every commit (gotcha #10 — never piped).
Expand All @@ -195,6 +197,7 @@ One line per run (status glyph, duration, tok, $, one-clause outcome), degradati

- SPEC-6-4 deferred NITs bundle (RunJournal tests, envelope spread hardening, `MetaLike`, `fleetMode` double-default) — separate approval, unchanged.
- Replacing pi's footer/statusline; theme *authoring* (presets of glyphs only, not colors); panel key remaps; engine-visible changes; `resultLanguage` pref (spec N2 of #88, separately parked).
- P3-parked: runtime preset switching (env var is process-scoped by ratification); Runs-view preview row; nerd spinner frame tuning beyond the smoke (data-only swaps).

## Appendix · Before/after mockups (normative visual targets)

Expand Down
133 changes: 133 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 3 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { cardSnapshot, type RunCardState } from "./transcript/card-state.ts";
import { orchestrationLines } from "./transcript/orchestration.ts";
import { findingsFromRuns } from "./transcript/findings.ts";
import type { FleetTodoRow } from "./todo-sync/port.ts";
import type { BgRunStatus } from "./panel/rows.ts";
import { bgCardSnapshot, type BgRunStatus } from "./panel/rows.ts";
import { ArmoryMemoryAdapter } from "./memory-hydrate/adapter.ts";
import { ArmoryVisionAdapter } from "./vision/adapter.ts";
import { buildChildLoader } from "./engine/child-loader.ts";
Expand Down Expand Up @@ -622,11 +622,6 @@ export default async function (pi: ExtensionAPI): Promise<void> {
const stopTodosTimer = (): void => {
if (orchestrationTodosTimer) { clearInterval(orchestrationTodosTimer); orchestrationTodosTimer = null; }
};
const bgToCard = (b: BgRunStatus, nowMs: number): RunCardState => ({
runId: b.runId, agent: b.lifecycle, model: b.backend, task: b.task,
status: b.status as RunCardState["status"],
startedAt: b.elapsedMs != null ? nowMs - b.elapsedMs : nowMs,
});
const activeGate = (): string | undefined => {
for (const rec of deps.lifecycleRuns.values()) {
if (rec.status === "checkpoint") {
Expand Down Expand Up @@ -667,7 +662,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
if (baselineRunIds.has(r.runId)) continue; // pre-burst terminal records never re-report
burstRuns.set(r.runId, cardSnapshot(r));
}
for (const b of bg) burstRuns.set(b.runId, bgToCard(b, nowMs));
for (const b of bg) burstRuns.set(b.runId, bgCardSnapshot(b, nowMs));
if (activeCount === 0 && burstOpen) endBurst();
});
pi.registerEntryRenderer("fleet-orchestration", (_entry, _options, theme) => {
Expand All @@ -676,7 +671,7 @@ export default async function (pi: ExtensionAPI): Promise<void> {
const nowMs = Date.now();
const runs = [
...deps.runRegistry.list().map((r) => cardSnapshot(r)),
...[...bgRuns.values()].map((b) => bgToCard(b, nowMs)),
...[...bgRuns.values()].map((b) => bgCardSnapshot(b, nowMs)),
];
return new Text(theme.fg("dim", orchestrationLines(runs, cachedTodos, activeGate(), nowMs).join("\n")), 0, 0);
} catch { return undefined; }
Expand Down
10 changes: 9 additions & 1 deletion src/panel/fleet-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import type { AgentDef, ThinkingLevel } from "../registry/frontmatter.ts";
import { agentsRow, agentInfo, backendsRow, backendInfo, lifecycleRow, lifecyclePhaseTimeline, scheduleRow } from "./rows.ts";
import { fleetRow, renderBgRow } from "./rows.ts";
import { totalsLine, footerFor, actionsForRun, totalsHeader, timelineFooter, type FooterState } from "./present.ts";
import { totalsLine, footerFor, actionsForRun, totalsHeader, timelineFooter, previewLine, type FooterState } from "./present.ts";
import { buildFleetItems } from "./fleet-items.ts";
import { runsRow, runTimelineRow } from "./runs-rows.ts";
import { layoutTree } from "../present/tree.ts";
Expand Down Expand Up @@ -466,6 +466,14 @@ export class FleetPanel extends Container {
this.addChild(new Text(this.theme.fg("dim", " enter submit • esc cancel"), 0, 0));
} else {
this.addChild(this.list);
// P3: live run-card preview row — the selected fleet run's state line, exactly as it
// appears in-transcript (unthemed). Reserved blank line when the selection isn't a
// running run — stable panel height, no flicker as selection moves.
if (this.view === "fleet") {
const sel = this.list.getSelectedItem();
const line = previewLine(sel?.value || null, { registry: this.deps.runRegistry, bgRuns: this.deps.bgRuns }, Date.now(), this.frame);
this.addChild(new Text(line, 0, 0));
}
}

this.addChild(new Spacer(1));
Expand Down
Loading
Loading