Skip to content

Collie v0.46.0

Choose a tag to compare

@bermudi bermudi released this 16 Sep 23:22
· 4265 commits to main since this release
26ad23f

The upstream 1.9.0 redesign round, ported whole onto Pup: the composer's actions belt, the
dashboard's workspace grouping, and the one-name rule — on a branch first, so the herd on the phone
can try it before it becomes Pup's face. The cache subsystem, config.toml, crew machinery, and the
mux abstraction the redesign upstream rides on are not part of this port; see Declined.

Bridge

  • One name rule, one place rule, mirrored bridge-side. bridge/pane-name.ts is the push's half
    of the rule the screens use: NAME — paneLabel, else sessionName, else a non-stale terminalTitle,
    else the agent word; PLACE — space › tab. Shared fixtures (pane-name.fixtures.json) run on both
    sides, so a rule changed on one side alone turns red. A multi-agent push digest names the panes
    instead of reading "claude, claude, claude", appending the place only when two panes would read
    the same; a single alert's body is the place, not workspace · absolute-path. (upstream 6e8eeaf,
    15f9db6)
  • Panes arrive in the multiplexer's own arrangement. The bridge no longer tie-breaks panes by
    pane id (an opaque string whose alphabetical order nobody can see); space, then tab, then pane
    position all read off Herdr's listing order, and a poll caught mid-create sorts last. (upstream
    6e8eeaf, on Herdr's listing — Pup does not port the mux layer upstream built this on)
  • terminalTitleStale is declared on the wire type for the mirrored rule to read; Herdr reports no
    foreground command, so on this bridge it is never set. Known limit: Claude /rename session
    names are already sniffed and ride the wire as before.
  • Workspaces carry their repo. Herdr's workspace.list already reports worktree.repo_root
    and is_linked_worktree (live-probed on this herd: 11 spaces, 2 with repos); the wire type now
    declares the pair and the snapshot carries it, so the web can nest a worktree under the space
    holding its repo. (upstream 6e8eeaf's worktree half)

Web — the names pass and the dashboard's second axis

  • One name and one place, on every screen. A pane is called the same thing everywhere — your
    label, else the session's /rename name, else the terminal title, else the agent word — and
    space › tab sits beneath it as the second line, never as the first. The fork's project-first
    row titles are superseded by this on the branch. A numbered tab reads tab 2 in a lighter ink
    everywhere it renders alone. lib/pane-name.ts carries the rule once; paneDisplayName is
    gone. (upstream 6e8eeaf, 583e561)
  • Rows keep the multiplexer's arrangement. Triage buckets no longer re-sort by activity — a
    row moves only when it changes bucket, never while you reach for it. The space list keeps
    Herdr's own workspace order (same as the strip), recency sort deleted; the row still shows its
    time. Worktrees nest one step under the space holding their repo, a filtered list stays flat.
    (upstream 6e8eeaf)
  • The dashboard asks two questions, in order. Needs-you and Ready·unseen stay pinned on top
    by urgency; everything else sits under its WORKSPACE, one counted group per space, shells
    joining their tab after its agents. An urgent pane is pulled out of its group, never copied.
    Working/Recent headings, Recent's fold and its sort toggle are gone — no clock is left in the
    order to reverse. Every row is the one 44px two-line form (name + dot and mark inline, place
    beneath), urgent rows in the same framed list shape as workspace rows, and a finished-unseen
    pane carries a small dot after its name. (upstream 64b6f49, 10cd055, 458876b, cd70aa3)
  • Pup-shaped omissions: no host or cache chips on the name line (no crew, no cache subsystem),
    and Pup has no i18n layer, so upstream's locale strings are the literals they rendered to.

Web — the compact strips and the pane header

  • The tab row and the pane row get compact, one continuous band. Tabs draw as 32px plain cells
    (11px text) that still answer a 44px tap through a transparent hit-area extension; the open tab
    is an outlined pill on the row's own ground, there is no horizontal rule and nothing under the
    tab row, and the pane pills beneath shrink to 24px on the same band (--chrome, a new token for
    the ground chrome stands on when it cannot stand on the page). A numbered tab reads tab N in
    the lighter ink on the strip too. (upstream c2a1650, 02ac7a5, 0ded2d2, 583e561)
  • The raw pane id is gone from every pill. p3 is Herdr's coordinate, not a name; a pill is
    numbered only when a neighbour would otherwise read the same, and the number is its 1-based
    place in the row — the thing the reader is actually looking at. (upstream 2eefd38, new
    lib/pane-ordinal.ts)
  • The pane header follows the one name rule. Line 1 is the pane's name with the agent's mark
    on it; line 2 names the workspace alone (the tab strip below already names the open tab), and
    the cwd left the header — it lives on the dashboard's tab-scoped rows and in History. The title
    block keeps its 44px tap to the space overview. (upstream 6e8eeaf, b14ffd4)
  • New ui/labelled-strip.tsx / ui/section-label.tsx primitives (the strip row's shared recipe:
    tap floor, scroller padding, above-label) and ui/list-group.tsx (the framed flat-row region)
    arrive with them, as upstream ships them. Pup drops upstream's per-tab agent tile on the strip.

Web — the actions belt

  • One row of actions above the keyboard, with the running agent's own commands in it. Keys,
    Type, Quick, Agent and the display gear share a single scrolling belt with the harness's
    commands, which sit in a segment tinted with that harness's brand colour (Claude's orange, omp's
    purple; Codex and pi are monochrome by design and take the muted ground) and carry an icon each.
    Every button sends its bare command and the harness paints its own picker in the mirror, so the
    list of models or effort levels never lives in Collie. The old Controls row and the 30px
    swipe-handle band are gone; a bare Layers mark pinned at the belt's right end opens the pane
    switcher, a drag up from anywhere on the band opens the same sheet, and the composer keeps its
    row back. Settings gains a per-device "Harness shortcuts" switch (on by default).
    (upstream fc8d1be, 9b53078, 5e7f626, 63513ae, 931f857, adaa1fb + the belt's follow-ups,
    ported as the end state)
  • Operators add or replace the segment with bar = true in commands.toml, with an optional
    bar_label (twelve characters kept whole). The replacement rule is per surface (ADR 0018's
    posture, applied to the bar alone): bar rows never blank the Agent palette, and a shipped
    dangerous command's two-tap confirm is inherited as a floor. (upstream fc8d1be)
  • Pup-shaped limits, stated: the belt carries no host tag and no cache reading (no crew, no
    cache subsystem); the composer's verified-paste transport, guarded submit, draft previews and
    docks are untouched — only the row that held their toggles changed shape; omp's Tree button
    stays off the bar until this repo holds an omp--tree.txt capture to vouch for it (upstream
    cc38c2d's own rule, followed rather than shortcut).

Web — the keys tray

  • A compact seven-column Keys pad. One fixed grid, two rows — Esc/Tab/⇧/Ctrl/Alt/Up/⏎, then
    Ctrl C / a 3-wide Space / ← ↓ → with Down under Up — replaces the Keys/123 segmented toggle and
    the stacked rows. The digits, the Ctrl presets and F1–F12 fold behind one row of accordion
    chips (123 / Presets / F keys, one panel open at a time). The tray's resting height drops from
    ~275px to ~119px at 390px with every key still ≥36px tall; Enter and Shift draw glyphs with the
    full word as the accessible name. A composed key queue still dies with its dock (ADR 0005) and
    still survives switching to the digit grid mid-chord. (upstream e12b433)

Declined (this round, with reasons)

  • The prompt-cache subsystem (~6,400 lines upstream): countdown chips, cold-cache push
    warnings, dated vendor TTL claims, cache-rules.toml. A feature, not a fix, and still settling
    upstream (six follow-up fixes in the days after 1.9.0). Revisit once quiet.
  • config.toml + collie config show/check/init (~3,000 lines): a config layering machine for a
    single-user install that has .env.
  • The crew/mux substrate: protocol floors and compat removals, host-keyed grouping, the mux
    adapter layer the redesign upstream rides on, tmux auto-rename. Pup is one host on Herdr.
  • The first-launch screen: onboarding for a fresh install, shaped around crews.
  • Screen slide transitions, tour, playground, i18n: polish and scaffolding Pup doesn't carry.
    Upstream's locale strings landed as the literals they rendered to.
  • omp's Tree bar button: waits on an omp--tree.txt capture in this repo's corpus (the same
    rule upstream held it to before their capture existed).

Fixed

  • Devin panes get their mark. A devin pane rendered the generic "DE" initials tile on every
    surface (dashboard row, pane header, switcher) — the brand table had no entry and the resolver
    no rule, so it fell through to the fallback. Devin's own pinwheel mark now rides a black tile
    with a white fill, monochrome like Codex's and pi's (no belt accent — its absence is a real
    answer for a monochrome brand). Source: the SVG Logos collection (CC0), rescaled from its
    256×294 viewBox to 24×24 and centred; verified rasterized at render time — ink box dead-centre,
    53–61% tile coverage, no clipping. Tried from the phone on day one of the redesign trial.

  • Sheets no longer pick up stale terminal paint while sliding in. Both floating panels (the
    switcher and Agent commands) showed bands of terminal output across their surface during the
    entrance, gone the next frame — the animated sheet shared its paint with the filtered mirror
    layers beneath. The panel is now one isolated paint boundary, promoted for the life of the
    sheet (f770a73); and the dim settles before the slide — a 150ms ease-out scrim on its own
    compositor layer, so the panel moves against a stable ground instead of a dim still mid-fade
    (7fbfad8). Found on the phone during the redesign trial.

Fixed (upstream round, ported 2026-09-16)

  • A plugin action resolves the service's state directory (upstream AltanS#226, d07ec4c). Herdr
    injects HERDR_PLUGIN_STATE_DIR into every plugin action; the systemd service never receives
    it. Honouring it split one install in two: push-test, pairing and devices — every verb that
    reads or writes state through an action — read ~/.local/state/herdr/plugins/herdr.collie,
    found no subscriptions, and failed, while the service kept them under ~/.local/state/collie.
    resolveStateDir (now pure and exported, like defaultSocketPath) reads COLLIE_STATE_DIR,
    else the user state dir, and ignores Herdr's variable on purpose.
  • A finished agent that Herdr reports as idle reaches Ready · unseen (upstream AltanS#222,
    769cdaa). Herdr 0.9's API says idle for an agent whose turn ended; only its own client
    projects done. isUnseen now accepts either settled status, gated by our own read receipts
    and excluding bare shells; and only a turn that ends (working/blocked → idle/done) counts
    as new work, so Herdr's own acknowledgement and detection flicker never re-mark a read pane.
    The ledger wiring moved to bridge/activity-tracking.ts so the gate has one home. ADR 0003
    and HERDR_API.md carry the compatibility note.
  • Light fills match by luminance, not by one observed value (upstream AltanS#224, 6fb0f1f +
    ebd63ff). Codex's submitted-message band was matched against the literal rgb(240,240,240),
    and a 0.154.0 pane paints rgb(244,244,244) — four levels apart, so the whole band inverted
    to a black bar again. The rule moved to lib/harness/light-fill.ts (Rec. 709 luma, Codex's
    floor 220, omp's 180), shared by both adapters; a real 0.154.0 capture pins the shape. omp's
    belt of pastel card fills now gets the same marking on Pup for the first time — upstream has
    marked them since before the fork point; Pup's raw-only omp path just never carried it.
  • The belt stands at 40px, and the field's focus ring clears it (upstream a0ae39e). The
    belt's scroller reads py-1 — 40px on the phone instead of a thin 32px strip; the harness
    section grows to h-10 with -my-1 so its tint still runs rule to rule; and the field row's
    pt-1 is the one place that puts the focus ring's 4px reach inside the row rather than on the
    belt's bottom edge.

Update

Update the plugin — advances the checkout, rebuilds, restarts (runs from any directory):

herdr plugin action invoke update --plugin herdr.collie

Or just restart the bridge to pick up an already-built change:

herdr plugin action invoke restart --plugin herdr.collie