diff --git a/AGENTS.md b/AGENTS.md index 43ddade..7abfabb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -246,7 +246,8 @@ Revisit only if the stated reason stops holding. - **`locale` is a host-owned query param** — in `parseListQuery`'s reserved set, so a localized list page doesn't hand a plugin a phantom `locale` filter. The i18n view locals (`t`, `locale`, `locales`, `localeHref`, `localeParam`, `localeSwitch`, `dir`) are likewise reserved, merged after a handler's - `data` so a collision loses the key instead of breaking the shell. + `data` so a collision loses the key instead of breaking the shell — as is `assetHref` + (`src/http/static.ts`), which stays undocumented for plugins because calling it is not yet contract. - **Catalogs are checked at boot, not at render.** Every locale is compared against its set's `en-US` — keys, string-vs-plural kind, and the plural categories `Intl.PluralRules` requires — and a mismatch stops startup. A plugin may ship fewer locales than the host (its strings fall back to diff --git a/README.md b/README.md index 42cc5a3..fe4dca5 100644 --- a/README.md +++ b/README.md @@ -511,8 +511,8 @@ export async function listThings(ctx: RequestContext) { `include()` the core building-block partials and its own. To load the plugin's own CSS, pass its `/public//x.css` href in the shell's `styles` slot — see the reference's `views/shifts.ejs`. The host adds the file's content hash to every `/public/` href the shell renders, so browsers and - any cache in front of Plainpages keep it for a year; a `/public/` URL your own markup writes is - kept for five minutes, then revalidated. + any cache in front of Plainpages keep the file for a year; a `/public/` URL your own markup writes + is kept for five minutes, then revalidated. - **Finer authorization than the route `permission`** uses the guards from `@plainpages/plugin-api`: `requireSession(ctx)`, `can(ctx, permission)` (coarse JWT-claim check, zero I/O), and `check(keto, ctx, {namespace, object, relation})` (a live Keto check; anonymous ⇒ denied). Throw @@ -1175,7 +1175,7 @@ Rules worth knowing: edit/new/remove/save`, `filter.*`, `pagination.*`, `table.*` — and a plugin's lookup falls through to them. Keep your catalog for your domain words. - **Reserved view locals:** `t`, `locale`, `locales`, `localeHref`, `localeParam`, `localeSwitch`, - `dir`. They are merged after your `data`, so a colliding key of yours is ignored rather than + `dir`, and the host-internal `assetHref`. They are merged after your `data`, so a colliding key of yours is ignored rather than breaking the shell. `locale` is likewise reserved in `parseListQuery`, never returned as a filter. **Kratos writes the auth flow's own text** (field labels, validation errors), tagging each string diff --git a/todo.md b/todo.md index 1d1c18c..a36891e 100644 --- a/todo.md +++ b/todo.md @@ -6,7 +6,7 @@ `Bar = 1` -`Next ID = 43` +`Next ID = 44` ## Items @@ -22,6 +22,7 @@ | 16 | 0.5.0 | | question | **Decide whether the single Keto `Resource` namespace splits into per-domain namespaces.** | 3 | 2 | 3 | 6 | 6 | 7 by 2 | 0.5 | | 6 | 0.5.0 | | | **Warn at discovery when a route or nav node gates on a permission no plugin declares.** | 2 | 1 | 1 | 2 | 2 | 7 by 3, 9 by 3 | 8.0 | | 23 | 0.5.0 | | | **Give list pages one view-model helper from query to pagination, instead of ~40 hand-written lines each.** | 3 | 1 | 2 | 4 | 5 | 1 by 4, 9 by 2, code health by 3 | 7.9 | +| 43 | 0.5.0 | | | **Let a plugin version the scripts and other `/public/` files its own markup links, so they are cached as immutable too.** | 2 | 2 | 1 | 3 | 3 | 8 by 3, 7 by 2 | 5.0 | | 28 | 0.5.0 | | | **Let a plugin add entries to the profile menu through the plugin API.** | 2 | 2 | 2 | 4 | 4 | 7 by 4, 1 by 2 | 4.2 | | 2 | 0.5.0 | | | **Rename the admin example plugin to a name that says what it administers.** | 3 | 1 | 2 | 4 | 6 | 9 by 3, code health by 2 | 2.6 | | 5 | 0.5.0 | | | **List grants that no installed plugin declares, with a remove action.** | 2 | 2 | 2 | 4 | 4 | 2 by 3 | 2.1 | @@ -104,6 +105,15 @@ is ungrantable from the GUI and answers a permanent 403. A warning, not an error Every list screen bridges `parseListQuery`/`paginate` to the partials by hand. At minimum a `buildPaginationModel(page, hrefFor)`. Exported through the barrel, so it is contract surface. +### 43. Let a plugin version the scripts and other `/public/` files its own markup links, so they are cached as immutable too. + +The host versions every `/public/` href it renders (`src/http/static.ts`): the shell's stylesheets, +favicon and logo, and a plugin's `styles`. A file a plugin's own markup links, such as fastjira's +`board-drag.js`, keeps an unversioned URL and is revalidated every five minutes. Offering a way — +`assetHref` documented as a view local, a `ctx.assetHref`, or a `scripts` slot in the shell — adds to +the plugin API, hence a minor. Pick one with the plugin authors' panel, then document it in README → +Reserved view locals. + ### 28. Let a plugin add entries to the profile menu through the plugin API. The profile dropdown stays a dropdown: Sign out is its only entry today, and it will gain more.