{"id":51827683,"url":"https://github.com/phin-tech/herdr-phin-board","last_synced_at":"2026-07-22T12:00:17.773Z","repository":{"id":372544249,"uuid":"1307660053","full_name":"phin-tech/herdr-phin-board","owner":"phin-tech","description":"Herdr plugin: a status board over your spaces — todo, in progress, waiting on someone, done, plus any status you invent. List or kanban.","archived":false,"fork":false,"pushed_at":"2026-07-21T22:22:52.000Z","size":231,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-22T00:13:21.790Z","etag":null,"topics":["bubbletea","herdr","herdr-plugin","tui"],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/phin-tech.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-07-21T12:08:21.000Z","updated_at":"2026-07-21T22:22:25.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/phin-tech/herdr-phin-board","commit_stats":null,"previous_names":["phin-tech/herdr-phin-board"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/phin-tech/herdr-phin-board","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phin-tech%2Fherdr-phin-board","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phin-tech%2Fherdr-phin-board/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phin-tech%2Fherdr-phin-board/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phin-tech%2Fherdr-phin-board/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/phin-tech","download_url":"https://codeload.github.com/phin-tech/herdr-phin-board/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/phin-tech%2Fherdr-phin-board/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35760583,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-07-20T02:08:10.276Z","status":"online","status_checked_at":"2026-07-22T02:00:06.236Z","response_time":124,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bubbletea","herdr","herdr-plugin","tui"],"created_at":"2026-07-22T12:00:16.617Z","updated_at":"2026-07-22T12:00:17.571Z","avatar_url":"https://github.com/phin-tech.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# herdr-phin-board\n\n[![ci](https://github.com/phin-tech/herdr-phin-board/actions/workflows/ci.yml/badge.svg)](https://github.com/phin-tech/herdr-phin-board/actions/workflows/ci.yml)\n\nA [Herdr](https://herdr.dev) plugin: a status board over your spaces, in a popup,\non a key.\n\nHerdr's own space list tells you what Herdr knows — label, panes, agent state.\nThis adds the part only you know: what you've actually started, what's finished,\nand what's parked because you're waiting on a person or something outside the\nmachine.\n\n```\n ▾ List                                                    🔔 1 space · 4 live · archive hidden\n\n ▾ Triage (0)\n ▾ Todo (0)\n ▾ In Progress (2)\n   dev-stream             ~/src/github.com/phin-tech/dev-stream                      ·working\n   herdr-phin-board       ~/src/github.com/phin-tech/herdr-phin-board                   ·idle\n ▾ Waiting (2)\n   🔔 docs-site           vendor SLA response, chased 2026-07-18                     ·blocked\n ❯ api-gateway            waiting on Dave re: API key                                   ·idle\n ▸ Done (0)\n\n 1 Triage  2 Todo  3 In Progress  4 Waiting  5 Done\n K table · d detail · v move · n note · enter jump · ? help\n```\n\nStatuses are yours: rename them, reorder them, invent new ones. The dim right\ncolumn is Herdr's agent state — a hint only. It never groups, sorts, or\noverrides anything you set.\n\n`K` cycles the same board through three views. The **table** is the flat one —\nevery space on a line, in aligned columns, including the fields the list has no\nroom for:\n\n```\n Board                                                                            5 live · archive hidden\n   SPACE                ↓STATUS     NOTE                                              AGENT    CHANGED\n   dev-stream           In Progress —                                                 idle     just now\n   herdr-phin-board     In Progress —                                                 working  2h ago\n ❯ api-gateway          Waiting     waiting on Dave re: API key rotation — he's back  idle     10m ago\n   docs-site            Waiting     vendor SLA response, chased 2026-07-18            blocked  1d ago\n   billing              Done        —                                                 idle     3d ago\n```\n\nNo groups, no collapse, and it's the only view you can re-sort: `o` cycles\nstatus → name → changed, and `↓` marks the column in force. Sorting by status\nlays the rows out exactly as the list groups them, so `v` still works there.\n\nThe **kanban** is columns, one per status:\n\n```\n Board                                                                          5 live · archive hidden\n\n Todo 0                   In Progress 2            Waiting 2                Done 1\n ──────────────────────── ──────────────────────── ──────────────────────── ────────────────────────\n —                          dev-stream             ❯ api-gateway              billing\n                            ·idle                    waiting on Dave re:      ·idle\n                                                     API key rotation\n                            herdr-phin-board         ·idle\n                            ·working\n                                                     docs-site\n                                                     vendor SLA response,\n                                                     chased 2026-07-18\n                                                     ·blocked\n```\n\nA column *is* a status, so `v` then `h`/`l` walks a card sideways to retag it,\nand `j`/`k` reorders within the column. The view you were last in is remembered.\n\nRows can only ever show a truncated note, so the list keeps a detail pane\nalongside. It tracks the cursor with no keypress needed, and shows the note in\nfull along with the path, workspace, and when the status last changed. `d` hides\nit if you want the room back. In kanban the columns already use the width, so\n`d` opens the same detail as a modal instead — and you can keep browsing with\n`j`/`k`, or edit with `n`, without closing it.\n\n## Pull request context\n\nIf a space's directory has a pull request for its current branch, the board\nshows it: number, state, review decision and CI checks. Worktrees are the case\nthis suits best — one branch per space, so one PR per row.\n\n```\n   SPACE                STATUS      NOTE                          PR                   AGENT\n   billing              In Progress —                             #130 ● changes ·     working\n   docs-site            Waiting     —                             #119 ○ ✗             blocked\n ❯ api-gateway          Waiting     waiting on Dave re: API key   #123 ● approved ✓    idle\n```\n\n`●` open · `○` draft · `◆` merged · `✕` closed, then the review decision, then\nchecks `✓` pass `✗` fail `·` running, then `conflict` or `behind` when the\nbranch cannot land as it stands. A mergeable branch says nothing — the column\nis for what needs doing, and GitHub computes mergeability lazily, so an\nun-computed state is left blank rather than guessed. A row is coloured by whatever most needs\nattention: failing checks first, then changes-requested. `gp` opens the\nselected space's PR in a browser.\n\n**PR state is context, never control.** It never sets a status, moves a row, or\nreorders anything — the same rule the agent hint follows. You drive status; this\njust tells you what GitHub thinks while you decide.\n\n## Talking to a space's agent\n\n`m` types a message into the agent running in the selected space and takes you\nthere — **without submitting it**. You read it, add a line, press enter. The\nboard is where you notice that something is blocked; this is how you say\nsomething about it without hunting for the right window.\n\nIt refuses rather than guesses. Panes with no agent are skipped (shells, plugin\nsidebars, this board), and a space running two agents is a refusal, not a coin\nflip: typing a review comment into the wrong agent is worse than not sending it.\n\n## How pull requests are found\n\nThe PR comes from the space's branch, or from a URL an agent printed: the board\nreads recent pane output for a `…/pull/N` link, which catches a pull request the\nmoment it is announced and reaches ones a branch lookup cannot.\n\nIt reads through the `gh` CLI, so it uses your existing login and needs no\ntoken. Results are cached beside the board and refreshed when you open it, so\nthe board paints instantly and fills in as answers arrive.\n\nNo repo and no PR look the same — an empty column. A **missing or logged-out\n`gh` says so once**, because Herdr launches plugins with a minimal PATH: left\nsilent, the whole feature would vanish and look identical to having no PRs.\n\nThe short form is also pushed as a `$pr` token, if you want it in the sidebar:\n\n```toml\n[ui.sidebar.spaces]\nrows = [\n  [\"state_icon\", \"workspace\"],\n  [\"branch\", \"$status\"],\n  [\"$pr\"],\n]\n```\n\n## Notifications and the bell\n\nA watcher polls your spaces' pull requests in the background, every two minutes,\nand raises a Herdr notification when something actually changes:\n\n| | |\n|---|---|\n| checks pass → fail | the thing you were waiting on just broke |\n| a review lands | approved, or changes requested |\n| clean → conflict | needs a rebase before it can land |\n| merged or closed | the work landed, or didn't |\n\n**Only changes, never states.** A failing check is announced once, not on every\npoll — notifying on state trains you to ignore the notifications.\n\nHerdr toasts are transient: fired while you are away, they are gone. So every\nnotification is also recorded, and the space wears a 🔔 until you look at it.\nSelecting the row clears it; the count in the header means a bell inside a\ncollapsed group or an off-screen column is still visible. The detail view spells\nout what happened, and names the checks that are failing rather than just saying\nsome are.\n\nThe watcher starts when Herdr does, through a `[[startup]]` hook, and again\nafter a live handoff — so a pull request going red reaches you whether or not\nyou have opened the board. Opening the board starts one too, if none is\nrunning. It holds an event subscription, which does double duty: Herdr closing\ndrops the connection so the watcher exits at once rather than discovering the\nloss on its next tick, and a workspace appearing or closing nudges it to poll\nthen instead of waiting out the timer. A burst of events still costs one poll.\n\nLookups are both bounded and paced: a few calls run at once, and a token bucket\nmeters how fast they leave. Concurrency alone is not enough — four workers will\nstill fire fifty requests at a fifty-space board as fast as they can cycle.\nForeground and background differ on purpose: with the board open you are waiting\nfor an answer, so it favours latency; the watcher has two minutes and nobody\nlooking, so it trickles.\n\nA lockfile means there is only ever one watcher, however many times you open the\nboard, and it covers every space across every repo. Run it by hand with\n`herdr-phin-board watch` if you would rather.\n\nIt follows the Herdr session that started it. If you run named sessions\nside by side, spaces in the other one are outside its view.\n\n## Install\n\n```sh\nherdr plugin install phin-tech/herdr-phin-board\n```\n\nThat runs the build step, which compiles from source if Go is on your `PATH`\nand otherwise downloads the binary CI publishes, checking it against the\npublished `.sha256`. Either way you end up with `bin/herdr-phin-board`.\n\nFor local development, point Herdr at a working tree instead — no build runs,\nso you compile it yourself:\n\n```sh\ngit clone https://github.com/phin-tech/herdr-phin-board\ncd herdr-phin-board \u0026\u0026 go build -o bin/herdr-phin-board ./cmd/herdr-phin-board\nherdr plugin link .\n```\n\nThen bind a key in `~/.config/herdr/config.toml`:\n\n```toml\n[[keys.command]]\nkey = \"prefix+d\"\ntype = \"plugin_action\"\ncommand = \"phin-board.open\"\ndescription = \"Space board\"\n```\n\nand reload: `herdr server reload-config`.\n\n`prefix+d` is unbound in Herdr's default keymap. A `keys.command` entry silently\nshadows a built-in, so check the config reference before picking another —\n`prefix+b` is `toggle_sidebar` and `prefix+k` is `focus_pane_up`, both easy to\nlose by accident.\n\nRequires Herdr 0.7.5+. Go is optional: without it the install falls back to a\nprebuilt macOS or Linux binary.\n\n## Status in the Spaces sidebar\n\nThe board mirrors each status into the workspace's `status` metadata token, so\nit can show in Herdr's native Spaces sidebar too. Add `$status` to a row:\n\n```toml\n[ui.sidebar.spaces]\nrows = [\n  [\"state_icon\", \"workspace\"],\n  [\"branch\", { token = \"$status\", fg = \"yellow\", bold = true }],\n]\n```\n\nHerdr 0.7.5 added per-token styling, so a row entry can be\n`{ token, fg, bold, dim }` instead of a plain string.\n\nTokens don't survive a server restart, but the board file does — a\n`workspace.created` hook re-applies the stored status whenever a space appears,\nso the badge is correct even if you never open the board.\n\n**The default status gets no badge.** Every space you have never touched sits\nthere, so badging it would put the same word on every sidebar row while telling\nyou nothing — and it could not distinguish \"I filed this as Todo\" from \"I have\nnever looked at this\". That is why the shipped set leads with **Triage**: it\nmeans *not looked at yet*, which leaves Todo free to mean a decision you\nactually made, badge and all.\n\nWhich status is the default is an explicit choice, not a position: press `D` on\none in the `S` manager. It is marked there, and it can sit anywhere in the\norder, so rearranging the board never silently changes which status goes quiet.\nA board that has never named one falls back to the first.\n\n## Settings\n\nEverything works without configuration. To change something, Herdr gives the\nplugin its own config directory, which survives reinstalls:\n\n```sh\nherdr-phin-board config --init   # write a commented template\nherdr-phin-board config          # show what is in force, and from where\n```\n\nThat lands at `~/.config/herdr/plugins/config/phin-board/config.toml`:\n\n```toml\n# How often the background watcher asks GitHub about your pull requests.\n# Minimum 30s, maximum 1h. Opening or closing a workspace polls immediately\n# regardless, so this only governs noticing a review landing or CI going red.\npoll_interval = \"2m\"\n\n# Herdr toasts when a pull request changes. Bells on the board are recorded\n# either way, so turning this off makes the board quiet rather than blind.\nnotifications = true\n```\n\nA value that is out of range is clamped, and one that makes no sense falls back\nto its default — but either way it says so on stderr rather than ignoring your\ntypo. A broken file never stops the board.\n\nThis is deliberately separate from `board.json`: that is state the board writes\nfor itself, this is what you tell the board, and it is never overwritten.\n\n## Keys\n\n| Key | |\n|---|---|\n| `K` | cycle the view: list → table → kanban (or click the title) |\n| `o` | table only: sort by status, name, or when it last changed |\n| `d` | list: show or hide the detail pane · elsewhere: detail modal |\n| `j` / `k` | move |\n| `gg` / `G` | first row · last row |\n| `gp` | open the pull request in a browser |\n| `h` / `l` | kanban: move between columns · list: collapse / expand a group |\n| `v` | grab the row, then move it — leaving its group changes its status |\n| `enter` | jump to the space (reopens archived ones at their old path) |\n| `1`–`9` | send to that status; the numbers are listed along the bottom |\n| `s` | status picker |\n| `n` | edit the note — who or what you're waiting on |\n| `R` | rename the space — renames the Herdr workspace too |\n| `m` | type a message into that space's agent, then go there to send it |\n| `space` | collapse / expand a group |\n| `F` | show only the status under the cursor — `F` or `esc` for all |\n| `O` | reorder Herdr's own Spaces sidebar to match this board |\n| `a` | show or hide archived spaces |\n| `/` | filter by name, path, or note |\n| `S` | manage statuses: add, rename, reorder, delete, set the default |\n| `x` | forget the selected space |\n| `r` | refresh |\n| `q` | quit |\n\n## Filtering and ordering\n\n`F` narrows the board to whichever status the cursor is on — no picker, since\nyou are already standing on the group you want. `F` again, or `esc`, restores\neverything. It applies to all three views, and the header says so, because an\nempty board that does not explain itself just looks broken.\n\n`O` pushes the board's order onto Herdr: the Spaces sidebar is reordered to\nmatch, statuses first and then however you arranged them by hand.\n\nThat is a deliberate keypress rather than something the board does\ncontinuously, because reordering somebody's sidebar every time a status changed\nwould fight anyone who arranges their spaces themselves. Herdr can filter its\n*Agent* panel by a metadata token — the board's `$status` works there — but not\nits Spaces panel, and plenty of spaces have no agent at all. Moving workspaces\nis the only mechanism that reaches Spaces.\n\n## How state works\n\nHerdr workspace ids (`w4`, `w5`) are reassigned every session, so they're\nuseless as a durable key. Statuses are keyed by **canonical directory path**\ninstead: a status set today is still on the project when you reopen it next\nweek, in a new session, with a different workspace id.\n\nTwo axes are deliberately kept apart:\n\n- **Status** is yours, and it groups the board. A live space marked Done sits in\n  the Done group.\n- **Liveness** is Herdr's, and it decides main list vs archive. Close a space in\n  Herdr and it moves behind `a` with its status intact; `enter` reopens it.\n\nEverything lives in one file, `$HERDR_PLUGIN_STATE_DIR/board.json` — status\ndefinitions and per-directory entries together, written atomically. It's\nhand-editable if you'd rather.\n\nRows sort by most-recently-touched until you arrange a column by hand with `v`.\nAfter that the arrangement sticks: hand-ranked rows hold their positions at the\ntop of the group, and anything you haven't touched falls in below them by\nrecency. Rearranging a row doesn't count as working on it, so it won't disturb\nthat fallback ordering.\n\nSeveral workspaces open on the same directory share one row, because a status\nbelongs to the project rather than the window.\n\n```sh\nherdr-phin-board sync            # re-apply stored statuses to workspace tokens\nherdr-phin-board startup         # what Herdr's [[startup]] hook runs\nherdr-phin-board watch           # poll PRs and notify (the board starts this for you)\nherdr-phin-board config          # show the settings in force\nherdr-phin-board config --init   # write a commented settings template\nherdr-phin-board version         # which build this is\nherdr-phin-board prune           # forget entries whose directory no longer exists\n```\n\n## Releasing\n\nA push to `main` republishes the rolling `latest` prerelease. A `v*` tag cuts a\npermanent one, stamps that version into the binary, and commits the matching\n`version` back to `herdr-plugin.toml` — Herdr reads that file to report what is\ninstalled, so a stale one would claim the wrong version for ever.\n\n```sh\ngit tag v0.2.0 \u0026\u0026 git push origin v0.2.0\n```\n\nCI builds through `build.sh` — the same path `herdr plugin install` takes — and\nfails if the built binary disagrees with the manifest, so the two cannot drift\napart unnoticed.\n\n## Development\n\n```sh\ngo test ./...\ngo build -o bin/herdr-phin-board ./cmd/herdr-phin-board\n./bin/herdr-phin-board          # runs against the live session via $HERDR_SOCKET_PATH\n```\n\nRun the binary directly from any pane inside a Herdr session — it doesn't need\nto be installed as a plugin to work, which makes for a fast inner loop.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphin-tech%2Fherdr-phin-board","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fphin-tech%2Fherdr-phin-board","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fphin-tech%2Fherdr-phin-board/lists"}