Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions theme-lab/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 TD

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
73 changes: 73 additions & 0 deletions theme-lab/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# theme-lab

**Turn any image into a full Hermes desktop theme.** A desktop plugin for the
[Hermes Desktop](https://hermes-agent.nousresearch.com/docs/user-guide/desktop)
that demonstrates the **`THEMES_AREA`** surface end to end: drop an image, get a
color-matched light + dark theme with a complete 16-color terminal palette,
installed live into the app.

> Plugin id: `theme-lab` (folder name == id). This example is the standalone
> distribution of the same plugin; full history lives in the
> [`theme-lab`](https://github.com/0-CYBERDYNE-SYSTEMS-0/theme-lab) repo.

## What it demonstrates

- **`THEMES_AREA`** — register a `DesktopTheme` (light + dark palettes, ANSI
terminal palette) so it appears instantly in Settings → Appearance, ⌘K, and
`/skin`.
- **`PANES_AREA`** + **`PALETTE_AREA`** — the forge pane and a ⌘K command.
- **`ctx.storage`** — persist themes across restarts, with a v1 → v2 schema
migration.
- **Live apply without Settings** — the app has no SDK API to programmatically
set the active skin, but the gateway `config.set display.skin=<name>` RPC
broadcasts `skin.changed`, which the desktop drains into a live repaint. This
example writes the forged theme as a backend skin (`~/.hermes/skins/<slug>.yaml`)
so `config.set` can resolve it, then applies it live — no Settings visit.
- **Boot persistence** — the desktop hydrates the saved skin before plugin/backend
themes exist (so it snaps to default); this example re-applies the saved
appearance via `config.set` once the gateway is ready.

## Install

Requires the **Hermes desktop app** (`hermes desktop`).

```bash
cp -r theme-lab ~/.hermes/desktop-plugins/
```

The app watches `desktop-plugins/` and hot-loads within seconds. If it doesn't
appear: ⌘K → **Reload desktop plugins**. Then open the **Theme Lab** pane and
drop an image on it.

To uninstall: delete `~/.hermes/desktop-plugins/theme-lab/`.

## Usage

1. Open the **Theme Lab** pane (drag it wherever you like).
2. Drop / paste / browse for an image.
3. Pick **dark** or **light** forge mode, reorder swatches if you want a color to
dominate, then hit **Apply** — it applies live, no Settings visit.

## Notes for plugin authors

Desktop plugins are single ESM files importing only `@hermes/plugin-sdk`, `react`,
and `react/jsx-runtime` — UI is `jsx()` calls, not JSX syntax. The official docs:
[Desktop Plugin SDK](https://hermes-agent.nousresearch.com/docs/developer-guide/desktop-plugin-sdk).

This is the **first `THEMES_AREA` example** in this repo — the other examples here
are Python gateway/dashboard plugins that install to `~/.hermes/plugins/`. A
desktop plugin lives in `~/.hermes/desktop-plugins/` (a different SDK surface).

## Development

The color math is validated by a standalone Node harness (no build, zero deps):

```bash
node theme-lab/forge-math-test.cjs # palette math, verbatim swatch→theme, ANSI, migration
```

`RESIZE-NOTES.md` documents the frozen-CSS layout constraints of the packaged app.

## License

MIT — see [LICENSE](LICENSE).
50 changes: 50 additions & 0 deletions theme-lab/RESIZE-NOTES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Theme Forge — resize behavior notes

How the pane adapts when resized, and the intentional constraints.

## The hard constraint: frozen build CSS

The desktop app ships a Tailwind stylesheet compiled ONLY from the app's own
source tree at build time. Plugins load at runtime from `~/.hermes`, so any
class used ONLY by a plugin generates NO CSS rules. Before touching layout
here, verify every class against the shipped stylesheet:

app.asar.unpacked/dist/assets/index-*.css (inside the release build)

Rules this pane follows:
- No `sm:` / breakpoint variants (the build contains zero `@media (min-width)`).
- No arbitrary values used only here (`shadow-[…]`, `w-[min(…)]`, `accent-(…)`,
`scale-*`, `flex-nowrap`…). Those live as inline `style` instead.
- Anything size-critical (the color wheel) is sized inline in px.

## What resizes fluidly

- Pane zone: `width: 280px` default, `minWidth: 220px`, `maxWidth: 520px`
(CSS STRINGS — the PaneSizing contract rejects bare numbers).
- Card header row: `flex-wrap`, so at narrow widths the icon-button cluster
wraps to a second line instead of clipping the name.
- Theme name: `min-w-0 truncate` — long names ellipsize, never push buttons out.
- Swatch tray + strip-view swatches: `overflow-x-auto` horizontal scroll with a
fade hint; wheel-scroll is translated to horizontal scroll.
- Theme list: `ScrollArea` with `min-h-0 flex-1` — vertical scroll, never clips.
- Terminal preview: `overflow-x-auto` — long mono lines scroll instead of
hard-clipping.
- Drop zone / header / segmented controls: `min-w-0`, wrap when needed.

## Intentional non-fluid cases (graceful degradation)

1. **Color wheel = fixed 128px square.** A fluid wheel needs ResizeObserver
plumbing for pointer math (radius from live rect); not worth the complexity
for a 220–520px pane. Instead the wheel+controls container is `flex-wrap`:
below ~260px content width the controls column wraps UNDER the wheel.
2. **Swatch slots = fixed 36px.** They scroll horizontally; never shrink.
Shrinking below ~28px makes them untappable.
3. **Segmented controls** keep their natural width; at extreme narrow widths
the header row wraps rather than compressing the controls.

## Pointer interaction note

The color wheel's hue/sat picking uses `setPointerCapture` on the wheel div —
handlers (`onPointerDown/Move/Up/Cancel`) MUST stay attached to that same div.
A refactor that lifts them off silently kills the drag (this happened once;
regression-fixed in commit 5404738).
Loading