agents.md holds the instructions, claude.md points at it

opencode discovers only AGENTS.md, Claude Code only CLAUDE.md — so with the
content living in CLAUDE.md, every doc here was invisible to opencode. Swaps
all five pairs (root, src/apps, src/databases, themes, plugins): AGENTS.md now
holds the full text verbatim, CLAUDE.md is a short pointer. docs-check's
permission-count regex now reads root AGENTS.md instead of CLAUDE.md; the
workspace-level officer-dev/CLAUDE.md check is untouched, out of scope.
This commit is contained in:
2026-08-27 06:35:31 +00:00
parent 185aa3168e
commit 2360a3ef60
11 changed files with 1280 additions and 1291 deletions
+118
View File
@@ -0,0 +1,118 @@
# Themes
Drop a theme in here and it appears in the picker on the next page load. No install step, no rebuild, no
restart — a theme is a block of CSS custom properties, and none of that needs compiling.
**This directory is untracked**, exactly like `plugins/`. Only this file is in the repository. Your themes
are yours; the platform's own live in `src/workspaces/theme/css/themes/`.
---
## The shape
```
themes/
my-theme/
theme.css required — the values
theme.json required — the name a person reads
assets/ optional — a wallpaper, a logo
```
The directory name **is** the theme's id. It is not repeated in the manifest, because an id that can
disagree with where its files sit eventually will.
### `theme.json`
```json
{
"name": "My Theme",
"family": "My Theme",
"dark": true
}
```
| | |
|---|---|
| `name` | required — what the picker shows |
| `family` | optional — groups variants together, like `Duck Light` and `Duck Dark`. Defaults to `name` |
| `dark` | optional — adds the `.dark` class. Set it for a dark theme; several hundred `dark:` utilities still resolve against it |
### `theme.css`
Must target its own directory name, or nothing will happen and it will be baffling:
```css
[data-theme='my-theme'] {
--background: #1e1e2e;
--foreground: #cdd6f4;
/* …the rest */
}
```
The server checks this on discovery and reports it rather than letting you wonder.
---
## What to put in it
**Twenty tokens are required.** They are the standard shadcn set, which is what every published palette
already ships — Tokyo Night, Catppuccin, Gruvbox, Nord all come in this vocabulary, so porting one is
mostly copy-paste.
```
background foreground · card card-foreground · popover popover-foreground
primary primary-foreground · secondary secondary-foreground
muted muted-foreground · accent accent-foreground
destructive destructive-foreground · border input ring · radius
```
**Everything else is derived from those.** Panels, the header, the dock, glass, grid lines, scrollbars —
all computed from `card`, `border`, `background` and `foreground`. A palette theme never mentions them.
**Four more are worth setting**, because the standard set has no notion of them and the defaults are
generic: `--success`, `--warning`, `--info`, `--busy`, each with a `-foreground`. `busy` means *running*,
not *warning* — it is what a live job wears.
**Anything else you want to change, you can.** Declaring a variable in your theme beats the derived
default, because both land on `<html>` and yours is more specific. That is how a theme gets a *texture*
rather than just a palette:
```css
[data-theme='my-theme'] {
--panel-bg: rgba(255, 255, 255, 0.08); /* glass instead of a flat card */
--panel-blur: 20px;
--panel-radius: 0; /* square panels */
--page-bg-src: '/themes/my-theme/assets/wallpaper.webp';
--logo-src: '/themes/my-theme/assets/logo.svg';
}
```
The full list of what you can override is in `src/workspaces/theme/README.md`, and
`src/workspaces/theme/css/themes/duck-dark.css` is a worked example of a textured theme.
**Assets are quoted paths, not `url(...)`.** The bundler resolves `url()` at build time and your files are
served at runtime, so a `url()` token would break. The app wraps the path for you.
---
## Writing one
Start from `src/workspaces/theme/css/themes/plain.css` rather than from a Duck theme. Plain is white,
black and grey throughout, so every value you change is one you decided about, with nothing inherited from
someone else's brand.
Then: reload the page, open the picker or press `ctrl+shift+space`, and use `j`/`k` to browse. `l` applies
the focused theme to the app behind the list, so you can watch your own theme against real screens.
Editing `theme.css` and reloading is the whole loop. The stylesheet is cached for a minute.
---
## Checking your work
Render the app under **Plain** first if something looks wrong under yours. Anything still coloured under
Plain is a component painting itself rather than reading a token — that is a platform bug, not yours, and
worth reporting.
`bun run check:theme` counts hardcoded colours in the platform. It does not check themes, but it is why
your theme has a chance of working: the number can only go down.
+3 -115
View File
@@ -1,118 +1,6 @@
# Themes
Drop a theme in here and it appears in the picker on the next page load. No install step, no rebuild, no
restart — a theme is a block of CSS custom properties, and none of that needs compiling.
**The instructions live in [`AGENTS.md`](AGENTS.md). Read it first — this file only points there.**
**This directory is untracked**, exactly like `plugins/`. Only this file is in the repository. Your themes
are yours; the platform's own live in `src/workspaces/theme/css/themes/`.
---
## The shape
```
themes/
my-theme/
theme.css required — the values
theme.json required — the name a person reads
assets/ optional — a wallpaper, a logo
```
The directory name **is** the theme's id. It is not repeated in the manifest, because an id that can
disagree with where its files sit eventually will.
### `theme.json`
```json
{
"name": "My Theme",
"family": "My Theme",
"dark": true
}
```
| | |
|---|---|
| `name` | required — what the picker shows |
| `family` | optional — groups variants together, like `Duck Light` and `Duck Dark`. Defaults to `name` |
| `dark` | optional — adds the `.dark` class. Set it for a dark theme; several hundred `dark:` utilities still resolve against it |
### `theme.css`
Must target its own directory name, or nothing will happen and it will be baffling:
```css
[data-theme='my-theme'] {
--background: #1e1e2e;
--foreground: #cdd6f4;
/* …the rest */
}
```
The server checks this on discovery and reports it rather than letting you wonder.
---
## What to put in it
**Twenty tokens are required.** They are the standard shadcn set, which is what every published palette
already ships — Tokyo Night, Catppuccin, Gruvbox, Nord all come in this vocabulary, so porting one is
mostly copy-paste.
```
background foreground · card card-foreground · popover popover-foreground
primary primary-foreground · secondary secondary-foreground
muted muted-foreground · accent accent-foreground
destructive destructive-foreground · border input ring · radius
```
**Everything else is derived from those.** Panels, the header, the dock, glass, grid lines, scrollbars —
all computed from `card`, `border`, `background` and `foreground`. A palette theme never mentions them.
**Four more are worth setting**, because the standard set has no notion of them and the defaults are
generic: `--success`, `--warning`, `--info`, `--busy`, each with a `-foreground`. `busy` means *running*,
not *warning* — it is what a live job wears.
**Anything else you want to change, you can.** Declaring a variable in your theme beats the derived
default, because both land on `<html>` and yours is more specific. That is how a theme gets a *texture*
rather than just a palette:
```css
[data-theme='my-theme'] {
--panel-bg: rgba(255, 255, 255, 0.08); /* glass instead of a flat card */
--panel-blur: 20px;
--panel-radius: 0; /* square panels */
--page-bg-src: '/themes/my-theme/assets/wallpaper.webp';
--logo-src: '/themes/my-theme/assets/logo.svg';
}
```
The full list of what you can override is in `src/workspaces/theme/README.md`, and
`src/workspaces/theme/css/themes/duck-dark.css` is a worked example of a textured theme.
**Assets are quoted paths, not `url(...)`.** The bundler resolves `url()` at build time and your files are
served at runtime, so a `url()` token would break. The app wraps the path for you.
---
## Writing one
Start from `src/workspaces/theme/css/themes/plain.css` rather than from a Duck theme. Plain is white,
black and grey throughout, so every value you change is one you decided about, with nothing inherited from
someone else's brand.
Then: reload the page, open the picker or press `ctrl+shift+space`, and use `j`/`k` to browse. `l` applies
the focused theme to the app behind the list, so you can watch your own theme against real screens.
Editing `theme.css` and reloading is the whole loop. The stylesheet is cached for a minute.
---
## Checking your work
Render the app under **Plain** first if something looks wrong under yours. Anything still coloured under
Plain is a component painting itself rather than reading a token — that is a platform bug, not yours, and
worth reporting.
`bun run check:theme` counts hardcoded colours in the platform. It does not check themes, but it is why
your theme has a chance of working: the number can only go down.
Kept separate so agents that look for `AGENTS.md` by convention find the same guidance as those that
look for `CLAUDE.md`, without the two drifting apart.