DEPLOY.md covers standing the stack up from nothing and ends with a two-line
"Updating" section, which is the operation actually performed every time and
the one where the irreversible mistakes live. Redeploying has a required
order — back up before pulling, because a backup taken after a bad deploy is
a backup of the damage — and three traps that are invisible until they cost
data:
- The store runs in WAL mode, so copying bookmarks.db alone while the
container is up can silently drop the newest bookmarks. VACUUM INTO folds
the WAL in; the cold-copy fallback has to take the sidecar files.
- That backup needs the source volume mounted read-write, which looks wrong.
Opening a WAL database creates the -shm file, so :ro fails outright.
- A restored file lands root-owned while the container runs as uid 65532.
Reads succeed and writes do not, so the restore looks like it worked.
Backups go to ../mangabm-backups/, a sibling of the checkout rather than a
directory inside it, so no git operation or careless rm in the project dir
can take the backups along with it. Names carry a UTC timestamp so they sort
chronologically as plain text and cannot collide across a DST shift.
Also documents that a rebuild is mandatory for any UI change now that the
templates, CSS and fonts are //go:embed-ed, and ends at the phone smoke test:
no curl can tell you the panel works on the device.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The design lives in a Claude Design doc that an agent working in this repo
cannot see, so every future UI change would otherwise be a re-derivation
of the same rules from the CSS — and the rules that matter here are the
ones a stylesheet cannot state: that heat is reserved for an unread
chapter, that favourites get brass instead of borrowing it, that light mode
is a re-tuning rather than an inversion.
Records the token table for both colour branches, the three type roles,
the component anatomy of a row, the load-bearing details that look like
noise until they break something (the [hidden] override, the action
strip's flex-basis, the icon sprite, rebuilding the binary to see template
changes), and how to add a font weight.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The design depends on three specific families, and loading them from
fonts.googleapis.com means the UI silently loses its character exactly
where it is used most: Bromite is a de-googled browser whose users
routinely block Google's font domains, and the backend is reachable over a
LAN with no internet route. In both cases the page fell back to Georgia
and a system sans.
Five latin-subset woff2 files, ~120 KB total, in a binary already 27 MB —
the cost is noise, and //go:embed static picks them up with no build
change (backend/Dockerfile already copies static/). Latin only because the
UI is English; woff2 only because every browser that can run this app
supports it, so there is no second format to carry. DM Sans ships as one
variable file, which covers the whole 400-700 range the design uses for
the price of a single request.
staticHandler registers the .woff2 MIME type: Go's built-in table has no
entry for it and the scratch image has no /etc/mime.types, so the fonts
would otherwise be served as application/octet-stream. The two faces
above the fold are preloaded, since they are now discovered a stylesheet
late rather than from a preconnect.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The panel and the web UI are the same product on the same phone, and the
old purple-on-charcoal panel read as a different application once the web
UI moved to Cinder. Same tokens, same rule that heat is typographic:
.item.hot mirrors the web UI's is-new, .item.dim mirrors is-dim, chips and
buttons become mono small-caps with hairline borders instead of pills, and
the loading spinner becomes the same burning hairline rather than a
rotating ring.
Tokens are declared on :host so the panel has one palette block to edit,
but the fonts stay system serif and mono — the web UI's webfonts cannot
follow, because an @import inside the shadow root is at the mercy of the
host site's CSP.
Structure, ids and classes are untouched: this is a restyle, and in
particular the edge tab keeps its geometry, touch-action and #hit sizing
(see docs/superpowers/specs/2026-07-28-edge-tab-hitbox-design.md).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replaces the card-and-chip visual language with the Cinder direction from
the design doc: editorial serif, containerless sheets divided by ash
hairlines, one 760px measure, no radii and no shadows.
The organising rule is that heat is typographic. Only a series with an
unread chapter is allowed to be crimson — title, cover foot rule, "Ch N
out", play icon — so the one thing worth acting on is the one thing that
draws the eye. Favourites get brass instead of borrowing the accent, which
is what made the old three-hue palette read as noise. Both states hang off
two classes on the article (is-new, is-dim), so a new sub-element inherits
the state rather than re-deriving it.
The action strip is flex-basis 100% inside the row, which is what lets the
same markup be a full-width six-cell strip under the row on a phone (46px
targets) and a group of 40px squares beside it on a desktop, with no
duplicate template branches. Icons move to a sprite in icons.html: cards
are swapped in by htmx and reference the page's symbols, so a row no
longer carries a screenful of inline SVG paths.
[hidden] gets a display:none override because every disclosure panel is
now a flex container and display beats the attribute. filter.js grows an
.open class alongside hidden so the strip can show which cell the open
panel belongs to.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tab strip needs to say how many series have an unread chapter without
the user opening the tab to find out, so listView carries the number
alongside the filtered items.
The count is taken over the whole reading set rather than the rendered
items, so it means the same thing on every tab instead of collapsing to
len(Items) on Updated and 0 everywhere else. Archived and finished are
excluded, matching what the Updated tab itself shows.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>