todo: one file per item, grouped by theme, with a generated index

TODO.md had reached 1050 lines and 72 open items across 28 sections, read mostly
on a phone. It is now a directory: todo/<theme>/<item>.md, one card per file, with
frontmatter carrying id, status, created date and order, and the prose body copied
across verbatim. TODO.md becomes a generated index (bun run todo:index).

Dates: where an item's prose stated one it is used; otherwise its old section's;
otherwise 2026-08-20, floored at the day before the split rather than invented.
date_source records which.

Shaped for a two-way Kanban board, filed as todo/tooling — a card is a file, id is
the identity, frontmatter is the machine's half and the body is the owner's.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 05:42:39 +00:00
co-authored by Claude Fable 5
parent 8fa560df1b
commit 6071ded175
124 changed files with 2240 additions and 1043 deletions
@@ -0,0 +1,13 @@
---
id: plugin-repos/12-stale-headscale-route-paths-in-comments
title: "~12 stale `/headscale` route paths in comments across `web/`, plus `App.tsx:78` in the platform"
theme: plugin-repos
status: done
created: 2026-08-21
date_source: stated
order: 4
---
~12 stale `/headscale` route paths in comments across `web/`, plus `App.tsx:78` in the platform.
**Done.** Verified 2026-08-21: none left in either. The one remaining mention is a form
placeholder, `https://headscale.example.com`, which is correct — that IS a Headscale URL.
+20
View File
@@ -0,0 +1,20 @@
---
theme: plugin-repos
heading: "Plugin repos — what writing their READMEs turned up"
kind: theme
---
Three READMEs were written on 2026-08-16 (`music`, `offscale`, `example`) and pushed to their own repos.
Writing them meant reading each plugin against its own documentation, which is the cheapest audit there
is. Everything below was found that way and none of it is fixed.
**Offscale is still branded headscale in shipped, user-visible places.**
**Documentation that actively lies.**
**Done.** Verified 2026-08-21: zero occurrences of either name.
**Conventions a plugin author has to guess.** All verified in `discover.ts`/`mount.ts`, none documented
outside the example's new README:
**Genuinely missing.**
@@ -0,0 +1,17 @@
---
id: plugin-repos/a-manifest-is-module-cached-for-the-process
title: "A manifest is module-cached for the process lifetime: editing it does nothing until `pm2 restart officer`, and `outdated` cannot see a version bump either"
theme: plugin-repos
status: done
created: 2026-08-21
date_source: stated
order: 14
---
A manifest is module-cached for the process lifetime: editing it does nothing until
`pm2 restart officer`, and `outdated` cannot see a version bump either.
**Fixed 2026-08-21 (`da30d1a6`).** `importFresh` keys the import on the file's mtime — a changed
manifest is re-read, an unchanged one is still a cache hit. Found the expensive way: a reinstalled
jellyfin was judged against the DELETED plugin's manifest, so it was ruled ineligible for the user
list and provisioning silently did nothing. Note the limit: only the named file is re-read, so a
plugin's own imports still need a restart.
@@ -0,0 +1,12 @@
---
id: plugin-repos/a-plugin-s-assets-icon-png-is-only-published
title: "A plugin's `assets/icon.png` is only published while installed, so its real icon is invisible in the store before install"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 19
---
A plugin's `assets/icon.png` is only published while installed, so its real icon is invisible in the
store before install. The catalogue's `iconUrl` works around it, and an author has to know to add one.
@@ -0,0 +1,12 @@
---
id: plugin-repos/panel-keys-and-layout-apptypes-must-agree-and
title: "Panel `key`s and layout `appType`s must agree, and a mismatch fails SILENTLY — `PluginScreen.tsx` pins `appTypes.allowed` with `fallback: allowed[0]`, so a typo renders the first panel"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 12
---
Panel `key`s and layout `appType`s must agree, and a mismatch fails SILENTLY — `PluginScreen.tsx`
pins `appTypes.allowed` with `fallback: allowed[0]`, so a typo renders the first panel.
@@ -0,0 +1,12 @@
---
id: plugin-repos/permission-key-prefixing-is-convention-not
title: "Permission-key prefixing is convention, not validation"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 15
---
Permission-key prefixing is convention, not validation. Only a collision with a CORE key is refused,
silently, at mount, with a console error.
@@ -0,0 +1,12 @@
---
id: plugin-repos/plugin-md-has-11-stale-self-references
title: "`PLUGIN.md` has ~11 stale self-references (`headscale_servers`, `listHeadscaleServers`, permission key `headscale`, `/api/headscale`, panel keys, `useHeadscaleSection()`)"
theme: plugin-repos
status: done
created: 2026-08-20
date_source: assumed
order: 5
---
`PLUGIN.md` has ~11 stale self-references (`headscale_servers`, `listHeadscaleServers`, permission
key `headscale`, `/api/headscale`, panel keys, `useHeadscaleSection()`).
@@ -0,0 +1,13 @@
---
id: plugin-repos/plugins-example-manifest-ts-12-lists-web
title: "`plugins/example/manifest.ts:12` lists `web/Router.tsx` as the frontend convention; discovery requires `web/panels.ts` AND `web/layout.ts`"
theme: plugin-repos
status: done
created: 2026-08-20
date_source: assumed
order: 10
---
`plugins/example/manifest.ts:12` lists `web/Router.tsx` as the frontend convention; discovery
requires `web/panels.ts` AND `web/layout.ts`. The same file documents an `ownerOnly` field that does
not exist and never did.
@@ -0,0 +1,13 @@
---
id: plugin-repos/plugins-music-musicapi-md-documents-now
title: "`plugins/music/MUSIC_API.md` documents now-playing as one snapshot per user with no mention of `?device=`, which the schema makes part of the primary key"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 9
---
`plugins/music/MUSIC_API.md` documents now-playing as one snapshot per user with no mention of
`?device=`, which the schema makes part of the primary key. A mobile client written to that doc is
correct only by accident.
@@ -0,0 +1,14 @@
---
id: plugin-repos/plugins-music-musicapi-md-says-favourites-and
title: "`plugins/music/MUSIC_API.md` says favourites and now-playing are served by the platform from Postgres; the sidecar serves them itself, scoped by `X-Officer-User`"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 8
---
`plugins/music/MUSIC_API.md` says favourites and now-playing are served by the platform from
Postgres; the sidecar serves them itself, scoped by `X-Officer-User`. It also documents the playlist
item key as `<albumRel>/<file>` while the schema and favourites use the home path — the same file
contradicts itself.
@@ -0,0 +1,13 @@
---
id: plugin-repos/plugins-music-web-musicplayerhost-tsx-header
title: "`plugins/music/web/MusicPlayerHost.tsx` header says it is mounted once in the persistent DashboardLayout; it moved into `MusicDetail`"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 18
---
`plugins/music/web/MusicPlayerHost.tsx` header says it is mounted once in the persistent
DashboardLayout; it moved into `MusicDetail`. That is exactly the comment that would mislead
someone about why playback stops off-route.
@@ -0,0 +1,12 @@
---
id: plugin-repos/plugins-offscale-offscaleapi-md-claims-to
title: "`plugins/offscale/OFFSCALE_API.md` claims to publish the contract \"in full\" and omits six live surfaces, including all of `/_officer/companion/*` and `/_officer/enroll/invites`"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 7
---
`plugins/offscale/OFFSCALE_API.md` claims to publish the contract "in full" and omits six live
surfaces, including all of `/_officer/companion/*` and `/_officer/enroll/invites`.
@@ -0,0 +1,13 @@
---
id: plugin-repos/plugins-offscale-plugin-md-445-449-says-the
title: "`plugins/offscale/PLUGIN.md:445-449` says the ACL assistant is deliberately unwired and must not be wired up"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 6
---
`plugins/offscale/PLUGIN.md:445-449` says the ACL assistant is deliberately unwired and must not be
wired up. It IS wired — `sidecar/assist.ts`, `claude-proxy.ts`, `web/PolicyAssistant.tsx` and the
manifest's `readOnlyWrites` all ship.
@@ -0,0 +1,15 @@
---
id: plugin-repos/plugins-offscale-spawns-the-ssh-binary
title: "`plugins/offscale` spawns the `ssh` binary (`sidecar/ssh.ts`) but declares no `osDependencies`, so install never checks for it and the Console fails at runtime instead of refusing at install"
theme: plugin-repos
status: done
created: 2026-08-16
date_source: stated
order: 16
---
`plugins/offscale` spawns the `ssh` binary (`sidecar/ssh.ts`) but declares no `osDependencies`, so
install never checks for it and the Console fails at runtime instead of refusing at install.
**Already fixed** — the manifest declares `ssh` with per-manager packages, and its own comment
records the 2026-08-16 change. Verified 2026-08-21: the probe reports `offscale ssh=ok`. The entry
outlived the fix.
@@ -0,0 +1,13 @@
---
id: plugin-repos/scripts-reindex-music-ts-resolves-its-port
title: "`scripts/reindex-music.ts` resolves its port file from `process.env.DATA_PATH ??"
theme: plugin-repos
status: done
created: 2026-08-13
date_source: stated
order: 17
---
`scripts/reindex-music.ts` resolves its port file from `process.env.DATA_PATH ?? cwd/data`, and
`DATA_PATH` stopped being an env var on 2026-08-13. Run from the repo it looks in
`platform/data/music/.server` and never finds it. One line.
@@ -0,0 +1,12 @@
---
id: plugin-repos/the-appname-table-prefix-is-unenforced
title: "The `<appName>_` table prefix is unenforced"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 13
---
The `<appName>_` table prefix is unenforced. The barrel is `export *` across every plugin into one
`public` schema, so two plugins choosing the same unprefixed name collide with no error until push.
@@ -0,0 +1,13 @@
---
id: plugin-repos/the-companion-contract-docs-the-offscale-code
title: "The Companion contract docs the offscale code cites (`COMMS/OFFSCALE_COMPANION_API.md`, `COMMS/OFFSCALE_INVITE_ENROLLMENT.md`) exist nowhere — `COMMS/` was deleted"
theme: plugin-repos
status: open
created: 2026-08-20
date_source: assumed
order: 11
---
The Companion contract docs the offscale code cites (`COMMS/OFFSCALE_COMPANION_API.md`,
`COMMS/OFFSCALE_INVITE_ENROLLMENT.md`) exist nowhere — `COMMS/` was deleted. Two of the plugin's
eight sections depend on a service with no published spec.
@@ -0,0 +1,13 @@
---
id: plugin-repos/web-consoleview-tsx-22-mints-terminal-session
title: "`web/ConsoleView.tsx:22` mints terminal session ids as `headscale-console-<id>`"
theme: plugin-repos
status: done
created: 2026-08-16
date_source: stated
order: 3
---
`web/ConsoleView.tsx:22` mints terminal session ids as `headscale-console-<id>`. That is persisted
state rather than a comment, so renaming it orphans live console sessions — cheap now, less so later.
**Done 2026-08-16**, and the file carries a comment recording it — a pty session KEY, not a label.
@@ -0,0 +1,12 @@
---
id: plugin-repos/web-offscaleserverpicker-tsx-26-renders-the
title: "`web/OffscaleServerPicker.tsx:26` renders the literal heading `Headscale`"
theme: plugin-repos
status: done
created: 2026-08-21
date_source: stated
order: 2
---
`web/OffscaleServerPicker.tsx:26` renders the literal heading `Headscale`.
**Done.** Verified 2026-08-21: it renders `Offscale`.
@@ -0,0 +1,13 @@
---
id: plugin-repos/web-panels-ts-registers-three-panels-named
title: "`web/panels.ts` registers three panels named \"Headscale servers\", \"Headscale\", \"Headscale\" — these are what a user reads in the panel picker"
theme: plugin-repos
status: done
created: 2026-08-21
date_source: stated
order: 1
---
`web/panels.ts` registers three panels named "Headscale servers", "Headscale", "Headscale" — these
are what a user reads in the panel picker.
**Done.** Verified 2026-08-21: the three read "Offscale servers", "Offscale", "Offscale".