diff --git a/AGENTS.md b/AGENTS.md index 6e599ea..f4f7e00 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -370,7 +370,7 @@ docker compose -f compose.yml up --build -d # production stack up and a *minimal* plugin live. Nothing comes before Quick start. Keep its commands copy-pasteable; deeper detail lives in its own section, linked. 2. **Returning developer (rest).** A **Contents** ToC right after Quick start, then sections ordered - by **what an adopter reaches for first**, not by architectural layering: Goals → Audience → Overview → Users, groups + by **what an adopter reaches for first**, not by architectural layering: Goals → Audience → Overview → Glossary → Users, groups & permissions → Building plugins → menu/blocks/interactivity → Configuration → Auth → Email → Architecture → Testing → Production → Observability → JWT-rotation runbook → Project-layout file map → Extending. Place a new section by how early an adopter needs it. **Users, groups & diff --git a/CHANGELOG.md b/CHANGELOG.md index 170f195..d4ee1ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,8 +8,9 @@ at boot. Entries start at 0.3.0. ### The README defines its terms and states its intended scale -A new Glossary defines host, manifest, chrome, nav fragment, permission token, system plugin and -denylist. It also states the scale Plainpages is sized for: a handful of plugins per deployment. +A new Glossary defines host, plugin, manifest, chrome, nav fragment, permission name, system plugin +and denylist. Plugin dependencies also states the scale Plainpages is sized for: a handful of plugins +per deployment. ### The mobile nav stays reachable on engines without `:has()` diff --git a/README.md b/README.md index 1dfc79a..7d64b51 100644 --- a/README.md +++ b/README.md @@ -215,24 +215,19 @@ avoid is shipping a client-side runtime, not using the platform. Markup is seman - **Host** — the `web` container: the Node process that discovers plugins, serves every page and owns no state of its own. Everything outside it (a plugin's upstream, Ory) is reached over the network. -- **Plugin** — a folder under `plugins/` that adds domain screens; the only way to add a feature. -- **Manifest** — the object a plugin's `index.ts` exports through `definePlugin`: its routes, nav, +- **Plugin** — a folder under `plugins/` that adds domain features. +- **Manifest** — the object a plugin's `plugin.ts` exports as its default: its routes, nav, permissions and hooks. The host checks it at boot and refuses a bad one ([The manifest](#the-manifest)). - **Chrome** — everything on a page that isn't the page's own content: brand, menu, user, theme and CSRF token. A handler gets it as `ctx.chrome` ([RequestContext](#requestcontext)). - **Nav fragment** — the `nav` entries one plugin contributes. The host merges every plugin's fragment into the one global menu and filters it per user ([The menu system](#the-menu-system)). -- **Permission token** — a `:` string such as `shifts:write`, granted in Keto and +- **Permission name** — a `:` string such as `scheduling:write`, granted in Keto and carried in the session JWT's `permissions` claim ([Naming a permission](#naming-a-permission)). - **System plugin** — a plugin that administers Plainpages' own identity stack, through the privileged `ctx.system` ([System capabilities](#system-capabilities-the-ctxsystem-surface)). -- **Denylist** — the optional in-memory list of subjects revoked just now, so a permission change - lands before the JWT expires ([Instant revoke](#instant-revoke-the-optional-denylist)). - -**Intended scale.** One deployment serves a handful of plugins, up to about ten, so a plugin's own -dependency tree ([Plugin dependencies](#plugin-dependencies)) costs disk and memory in proportion to -how many are installed. The `web` container scales out because it is stateless ([Stateless -core](#stateless-core)); a catalogue of hundreds of plugins is not a goal. +- **Denylist** — the optional in-memory list, per instance, of subjects revoked just now, so a + deactivation or a user's own permission change lands before the JWT expires ([Instant revoke](#instant-revoke-the-optional-denylist)). ## Users, groups & permissions @@ -817,6 +812,9 @@ included, since the plugin folder *is* the repo. A baked image needs no extra st above every plugin, and a plugin resolves it from there — nothing to declare, just import it. A copy inside your plugin's own `node_modules` would shadow it with a *second* instance of the host's contract, turning a sign-in redirect into a 500, so discovery refuses one there at boot. +- **Plainpages is sized for a handful of plugins per deployment, about ten.** Each plugin carries + its own dependency tree, so every one adds to disk use and to the memory of the `web` process; + hundreds of plugins is not a goal. - **The host never upgrades or dedupes your dependencies.** Two plugins depending on the same package each get their own copy at their own version, so neither can break the other by upgrading — and keeping yours current, and audited, is yours to own. Renovate here watches every manifest in this diff --git a/public/css/styles.css b/public/css/styles.css index 11aaa0f..92add70 100644 --- a/public/css/styles.css +++ b/public/css/styles.css @@ -9,12 +9,12 @@ /* ---------- 1. TOKENS ---------------------------------------- Two raw palettes (--l-* light, --d-* dark) are defined once. A small mapping (--bg: var(--l-bg) ...) picks the active set. - Theme resolution (no JS), every rule below needing :has(): + Theme resolution (no JS); all three rules below need :has(): • default + @media(dark) → follows the OS (Auto) • html:has(#theme-light) → force light • html:has(#theme-dark) → force dark - Engines without :has() (Chromium below 105, Firefox below 121) - ignore all three and stay light; see the @supports not + Engines without :has() (Chromium below 105, Firefox below 121, + Safari below 15.4) ignore all three and stay light; see the @supports not selector(:has(a)) block further down. We avoid light-dark()/color-scheme for app colors because their resolution is unreliable across engines. color-scheme is still