From 53438fb80694e98b8855889f8eb02f3dfc9df0d5 Mon Sep 17 00:00:00 2001 From: "K. O. A." Date: Sun, 5 Jul 2026 22:08:10 -0400 Subject: [PATCH 1/2] best-practices: add "Styling" section (self-host CSS) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a Styling section: the app's compiled stylesheet only contains classes the bundled code uses, so a runtime-installed plugin renders unstyled unless it ships its own CSS. Rules (38-39): - Ship your own compiled stylesheet via `styles` (under assets/), containing every class your screen needs — don't assume a utility class exists just because the app uses a similar one (esp. arbitrary values like w-[37px]). - Build it to coexist: base/preflight reset OFF (don't re-reset the whole app), selectors scoped to your screen, never the Tailwind Play CDN or a runtime CSS engine (slow, offline-hostile, main-thread), and bump `version` to cache-bust. Renumber Shipping to 40-44 and add a checklist block. Docs only. Co-Authored-By: Claude Opus 4.8 (1M context) Signed-off-by: K. O. A. --- CHANGELOG.md | 6 ++++++ spec/best-practices.md | 47 +++++++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a01670..b071dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -91,6 +91,12 @@ for how the document, manifest, and per-plugin versions relate. code via `context["load_sibling"]`** rather than bare imports that collide across plugins in `sys.modules`, and **logging through `context["log"]` (never `print()`)** plus route namespacing. Added a matching checklist block. Docs only. +- Best-practices guide: added a **"Styling"** section. A plugin can't rely on the app's compiled + stylesheet (it only contains classes the bundled code uses), so a runtime-installed plugin renders + unstyled unless it **ships its own compiled stylesheet via `styles`**. Covers building it to + coexist — base/preflight reset **off**, selectors scoped, **no Tailwind Play CDN / runtime CSS + engine** (slow, offline-hostile), and bump `version` to cache-bust the sheet. Added a matching + checklist block. Docs only. ## [0.1.0] - 2026-07-05 diff --git a/spec/best-practices.md b/spec/best-practices.md index 14a165f..8043eeb 100644 --- a/spec/best-practices.md +++ b/spec/best-practices.md @@ -600,9 +600,39 @@ for you, and a collision with the Host or another plugin is silent and, per rule --- +## Styling + +The app's own compiled stylesheet only contains the utility classes the bundled code happens to use. +A plugin installed at runtime (from the plugin manager, a shared folder, the community) cannot rely +on it — any class the app doesn't already use simply won't exist, and the plugin renders unstyled. +So a plugin owns its own styling. + +### 38. Ship your own compiled stylesheet via `styles` + +Declare a `styles` entry pointing to a **compiled** CSS file (under `assets/`, per rule 28) and put +every class your screen needs in it. Don't assume a utility class exists just because the app uses a +similar one — especially arbitrary-value utilities like `w-[37px]` or `bg-slate-800/50`, which are +generated on demand and are almost never in the app's sheet. If you author with a utility framework, +run its build to produce your own sheet; ship the compiled output, not a config. + +### 39. Build it to coexist — no global resets, no CDN, cache-busted + +- **Turn off the global reset.** Build your stylesheet with the framework's base/preflight reset + **disabled** (e.g. Tailwind's `corePlugins.preflight = false`). A plugin sheet that ships a full CSS + reset re-styles the entire app, not just your screen. +- **Keep it scoped.** Namespace selectors under your screen's root (rule 10) so your rules don't leak + outward — the flip side of the reset rule. +- **Never load a runtime CSS engine or CDN.** Don't pull the Tailwind Play CDN or any in-browser + CSS-in-JS/JIT: it's slow, it's unavailable offline (feedBack runs local-first), and it recompiles on + the main thread. Compile ahead of time and ship the result. +- **Bump your `version` when the stylesheet changes.** The Host cache-busts your assets by plugin + version, so a stale sheet keeps serving until you bump (rule 4). + +--- + ## Shipping & good citizenship -### 38. Fail soft, log clearly +### 40. Fail soft, log clearly - Use `context["log"]` (server) so your messages land in the Host log under your plugin's namespace. @@ -611,25 +641,25 @@ for you, and a collision with the Host or another plugin is silent and, per rule - If a surface can't initialise, degrade to a reduced-but-working state rather than taking the whole plugin down. -### 39. Degrade gracefully across Host versions +### 41. Degrade gracefully across Host versions A plugin may run on a Host older than the one you developed against. Don't assume a `context` key or a client runtime API exists without a documented Host version guaranteeing it. If an optional surface isn't supported, your plugin's other surfaces must still work. -### 40. Only declare capabilities you actually implement +### 42. Only declare capabilities you actually implement `capabilities` and `standards` wire you into cross-plugin pipelines (diagnostics, capability inspection). Declaring a capability you don't service registers a phantom participant and breaks the pipeline. If you don't participate, omit both keys entirely. -### 41. Mind the security boundary +### 43. Mind the security boundary Your `routes` run arbitrary Python in the server process and your `script` runs in the app's renderer. Validate every route input, don't shell out on user data, and don't reach outside your plugin directory. Users installing your plugin are trusting it like an app extension — earn it. -### 42. Ship a README and a changelog +### 44. Ship a README and a changelog A plugin folder should carry a short `README.md` (what it does, which Host version it targets) and note changes per version. It costs little and saves every future reader — including you. @@ -660,6 +690,13 @@ note changes per version. It costs little and saves every future reader — incl namespaced under `/api/plugins//`. - [ ] Logging goes through `context["log"]`, never `print()`. +**Styling (if your screen has custom CSS):** + +- [ ] A compiled stylesheet ships via `styles`, containing every class the screen uses (no reliance + on the app's sheet). +- [ ] Built with the base/preflight reset off and selectors scoped to your screen; no Tailwind Play + CDN or runtime CSS engine; `version` bumped when the sheet changes. + **Client-screen performance (if you ship a `script`):** - [ ] No `querySelector`/layout reads/style writes inside `requestAnimationFrame`, `draw()`, short From a7fcc71ace8e336de5b515b864033d2036b736a0 Mon Sep 17 00:00:00 2001 From: "K. O. A." Date: Mon, 6 Jul 2026 00:32:49 -0400 Subject: [PATCH 2/2] =?UTF-8?q?best-practices:=20styling=20=E2=80=94=20fix?= =?UTF-8?q?=20CSS-scoping=20cross-ref=20(rule=2010=20->=20rule=207)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CSS scoping guidance lives in rule 7 (namespacing), not rule 10 (don't mutate the shell). Signed-off-by: K. O. A. --- spec/best-practices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/best-practices.md b/spec/best-practices.md index 8043eeb..601acda 100644 --- a/spec/best-practices.md +++ b/spec/best-practices.md @@ -620,7 +620,7 @@ run its build to produce your own sheet; ship the compiled output, not a config. - **Turn off the global reset.** Build your stylesheet with the framework's base/preflight reset **disabled** (e.g. Tailwind's `corePlugins.preflight = false`). A plugin sheet that ships a full CSS reset re-styles the entire app, not just your screen. -- **Keep it scoped.** Namespace selectors under your screen's root (rule 10) so your rules don't leak +- **Keep it scoped.** Scope selectors under your screen's root (rule 7) so your rules don't leak outward — the flip side of the reset rule. - **Never load a runtime CSS engine or CDN.** Don't pull the Tailwind Play CDN or any in-browser CSS-in-JS/JIT: it's slow, it's unavailable offline (feedBack runs local-first), and it recompiles on