0fe02c84af43dd1f4e17d6b5ed59dd450cdc0d5f
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
bd15a186b5 |
themes: rotate to the one you are wearing, star the ones you like
Four changes for 200+ themes, and one mistake worth recording. ORDERING IS A ROTATION, not a sort. The registry order is the list's only structure — families sit together in it — so lifting the selected theme to the top answers "which am I on" by destroying "what is next to it". Rotating keeps every neighbour and moves the starting point. Both lists use the same `ordered` array, which is the point: "the one below the one I have on" has to mean the same thing in the dropdown and in the ctrl+shift+space browser, or one teaches an order the other contradicts. FAVOURITES are a table, not a JSON column. theme_favorites is a row per star, so a toggle is an INSERT or a DELETE and cannot race with itself; an array in user_settings would be read-modify-written and two tabs would lose one. No foreign key to a theme, because themes are not rows — most are compiled in, the rest are directories discovered at request time — so a star can outlive its theme and the reader intersects with the live registry rather than trusting the table. Favourites-first is a second rotation-preserving pass rather than a filter, so the list can still answer "what else is there". Filtering to favourites only is a separate, explicit toggle. THE INSTRUCTIONS NO LONGER SCROLL AWAY. DropdownMenuContent carried overflow-y-auto itself, so the one line telling you the shortcuts exist was visible for half a second and then gone under two hundred entries — which is the same as not having written it. Header and footer are now siblings of a scroll box instead of passengers in it, and the header names alt+space too. THE MISTAKE: the routes went on /themes first, and that prefix is deliberately UNAUTHENTICATED — the sign-in screen paints in the chosen theme, before any account exists to ask. An anonymous GET answered 500, which was the consequence arriving politely. They now live on /theme-favorites, claimed by the `account` permission, which is core and already owns /user, /dock and /api-keys: a starred theme is the same shape as a dock preference. I then mounted it one line off, in the unprotected block beside the thing I was moving it away from, and caught that by reading rather than by testing. Verified after: /api/theme-favorites is 401 unauthenticated on both verbs, and /api/themes still answers 200 so the login screen keeps its colours. 10 tests on the pure ordering — including the property that makes a rotation a rotation: every adjacency in the original survives it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|
|
0d29e242a3 |
themes are installable, like plugins
`platform/themes/<id>/` — untracked, same rule as `plugins/`, only the instructions
tracked. Drop a directory in and the theme appears in the picker on the next page load.
themes/my-theme/theme.css required — `[data-theme='my-theme'] { … }`
themes/my-theme/theme.json required — { name, family, dark }
themes/my-theme/assets/ optional — wallpaper, logo
── Why this is cheaper than installing a plugin ──
A plugin is code: it needs a manifest module, mounting, and a frontend rebuild to reach
the bundler. A theme is a block of CSS custom properties, so it needs none of that. The
server reads the directory at request time and the client adds a `<link>`. No install
step, no rebuild, no restart — which is the whole reason this is eighty lines of
discovery rather than four hundred.
The id is the DIRECTORY NAME, not a manifest field, for the same reason a plugin's
`appName` is not one: an id that can disagree with where its files sit eventually will.
Discovery checks that the CSS actually targets its own directory name, because the
failure is otherwise silent — the theme appears, is selected, and nothing changes.
── Problems are reported, not swallowed ──
A malformed theme returns a reason rather than vanishing:
broken-probe: theme.css does not target [data-theme='broken-probe']
The client logs those, so an author has somewhere to look instead of wondering why
their theme never appeared.
── Boundaries kept ──
The `theme` package still performs no IO. It gained an `extraThemes` prop; Officer
fetches and injects, exactly as it supplies the panel registry to `workspaces`. A
failure to fetch is not an error — no server, no session, no directory all mean "you get
the built-in themes", which is a complete product.
`/api/themes` is a `core` capability: read-only, and not deniable, because an account
that cannot read the theme list renders unstyled. Installing remains a directory on
disk; an upload endpoint would be a way to write arbitrary CSS into the page from a
request.
Verified in-process: listing, the problem report, CSS served as text/css, and a
traversal attempt answered 404.
── Needs a restart ──
Server-side change. Until then the client's fetch 404s and silently keeps the built-ins,
so nothing breaks in the meantime.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|