Ninth plugin out, third resurrection: switched off since 2026-08-13 with the permission reduced to api: [], so the server booted clean and every panel request 404'd. invoiceshelf/schema was also commented out of the drizzle barrel. Named `invoices`, not `invoiceshelf`. The appName drives the API prefix, the frontend route, the table prefix and the install key all at once, and before extraction those had drifted: the API was /api/invoiceshelf while the screen was /invoices. The URL is what a person sees and should name what the plugin shows rather than the daemon behind it — the permission key and label were already `invoices`, so the API prefix was the odd one out. Cost: three call sites, and renaming invoiceshelf_accounts to invoices_accounts to satisfy the table-prefix rule. That rename was free ONLY because the table existed on no server, which was checked before deciding. With rows in it, keeping the vendor name would have been the better trade: db:push sees a rename as a drop plus a create and this project has no migration mechanism. The sidecar keeps its Invoiceshelf-named functions deliberately — they name the upstream API it actually speaks. 91 palette classes converted, 52 of them in STATUS_TONE, which was already the single source of status colour but held raw palette strings. SENT and VIEWED now share `info`: the closed six-tone set has no second shade of "in flight, not yet actioned". Recorded in PLUGIN.md as a real loss.
94 lines
5.3 KiB
Markdown
94 lines
5.3 KiB
Markdown
# Invoices — the extraction record
|
|
|
|
Extracted from the platform on **2026-08-19**, the ninth plugin out, after jellyfin.
|
|
`plugins/EXTRACTING-A-PLUGIN.md` is the runbook.
|
|
|
|
## Dead on arrival, like the two before it
|
|
|
|
`hono.ts` carried the router import and its `PROTECTED_MOUNTS` entry commented out
|
|
(`// plugin — switched off 2026-08-13`) and the permission had been reduced to `api: []`, so
|
|
`assertPermissionTotality` was satisfied, the server booted clean, and every panel request 404'd. Same
|
|
data hazard too: `invoiceshelf/schema` was commented out of the drizzle barrel.
|
|
|
|
## The naming decision, which is the interesting part
|
|
|
|
Before extraction the API was **`/api/invoiceshelf`** and the screen was **`/invoices`**. Those had
|
|
drifted apart harmlessly while they were separate hardcoded strings. A plugin cannot hold both:
|
|
`appName` is THE id — API prefix, frontend route, table prefix, sidecar suffix and install key all
|
|
derive from it.
|
|
|
|
So one of them had to move, and the choice was:
|
|
|
|
| appName | URL | API | Tables |
|
|
| -------------- | -------------- | -------------------- | ------------------------------- |
|
|
| `invoiceshelf` | `/invoiceshelf` ❌ | `/api/invoiceshelf` ✓ | `invoiceshelf_accounts` ✓ |
|
|
| `invoices` | `/invoices` ✓ | `/api/invoices` (3 sites) | rename to `invoices_accounts` |
|
|
|
|
**`invoices` won**, for two reasons. The URL is the part a person sees, and it should name what the
|
|
thing shows rather than the vendor behind it — the permission key and the label were already
|
|
`invoices`, so the API prefix was the odd one out rather than the route. And the cost was small:
|
|
three call sites, all in `useInvoiceShelfData.ts`, plus a table rename.
|
|
|
|
**The rename was free only because the table existed on no server** — checked before deciding, not
|
|
after. `invoiceshelf_accounts` did not exist in the live database, so there were no rows to migrate.
|
|
Had there been any, keeping the vendor name and living with `/invoiceshelf` would have been the better
|
|
trade: `bun db:push` sees a rename as a drop plus a create, so a table with data would have needed a
|
|
hand-written `ALTER TABLE … RENAME TO` before the push, and that is a migration this project has no
|
|
mechanism for.
|
|
|
|
## What moved
|
|
|
|
| Was | Is now |
|
|
| ------------------------------------------------------------ | --------------- |
|
|
| `src/servers/sidecar/invoiceshelf/*` (4 files) | `sidecar/*` |
|
|
| `src/servers/api/invoiceshelf/router.ts` | `api/router.ts` |
|
|
| `src/databases/officer_db/src/invoiceshelf/*` (3 files) | `db/*` |
|
|
| `src/workspaces/officerdev/src/apps/Invoices/*` (25 files) | `web/*` |
|
|
| `src/apps/officer-web/Screens/Dashboard/Invoices/*` | `web/layout.ts` |
|
|
|
|
The `invoiceshelf*` symbols inside `db/` were renamed to `invoices*` with the table, so the schema
|
|
file and the SQL agree. The SIDECAR keeps its `Invoiceshelf`-named functions — those name the upstream
|
|
API it speaks, which really is InvoiceShelf, and renaming them would have made the sidecar lie about
|
|
what it talks to.
|
|
|
|
## Self-contained
|
|
|
|
`invoices_accounts` is a registry this plugin owns outright — label, URL, encrypted credential, one
|
|
active — like photos and jellyfin, and for the same reason: one person really does keep more than one
|
|
company's books. No core table, no platform union to extend.
|
|
|
|
## Fixtures
|
|
|
|
`invoiceshelf` was a fixture in `members.test.ts` — re-anchored an hour earlier when jellyfin left, and
|
|
broken again here. It now names `caldav` and `memos`, both services with **no extraction planned**,
|
|
which is the actual fix for a test that has been re-pointed four times.
|
|
|
|
## Not carried over
|
|
|
|
`composeTemplate` and `modes: ['existing', 'provisioned']` died with the catalogue entry. Like every
|
|
plugin so far, this is **point-at-your-own-instance only** until plugin Docker provisioning exists.
|
|
|
|
## One status distinction the tone set could not keep
|
|
|
|
`STATUS_TONE` in `web/shared.ts` was already the single source for status colour, but as raw palette
|
|
classes — 52 of this plugin's 91 palette occurrences were in that one map. It is now
|
|
`Record<string, Tone>`, read only by `StatusBadge`.
|
|
|
|
**`SENT` and `VIEWED` both resolve to `info`, and upstream had them as distinct blues.** They are real,
|
|
ordered states, and the closed six-tone set has no second shade of "in flight, not yet actioned" —
|
|
`busy` would misrepresent *viewed* as in progress. The badge text still distinguishes them; only the
|
|
colour is shared. Recorded here because it is a genuine loss rather than a tidy-up, and because the
|
|
right fix, if it matters, is a seventh tone rather than a local exception.
|
|
|
|
Two judgement calls in the same map, both reversible one-liners:
|
|
|
|
- `PARTIALLY_PAID` → `busy`, not `warning`, because it renders in the same cell as `UNPAID` and
|
|
flattening both to warning would destroy a live distinction.
|
|
- `EXPIRED` → `warning` while `REJECTED` → `danger`: an estimate that lapsed can be re-issued, one that
|
|
was declined is over.
|
|
|
|
The dashboard chart's three hard-coded hexes moved with the legend dots that label them, to
|
|
`var(--info)` / `var(--success)` / `var(--destructive)`. Those resolve from `:root` in every theme
|
|
(only 3 of 14 themes override `--info`/`--success`; all 14 define `--destructive`). **Not verified in a
|
|
browser.**
|