Correct the stale and false comments and docs the comprehension seats flagged #232
@@ -347,8 +347,8 @@ curl -X PUT http://keto:4467/admin/relation-tuples -H 'content-type: application
|
|||||||
**A permission's name is authored in plugin code; only its *grants* live in Keto.** A plugin
|
**A permission's name is authored in plugin code; only its *grants* live in Keto.** A plugin
|
||||||
declares the permissions it gates on (`permissions:` in the manifest) and the host collects them
|
declares the permissions it gates on (`permissions:` in the manifest) and the host collects them
|
||||||
into one catalog — `ctx.declaredPermissions` — which is the fixed list the admin screens offer.
|
into one catalog — `ctx.declaredPermissions` — which is the fixed list the admin screens offer.
|
||||||
Nothing in a GUI invents a name, and a tuple naming something no installed plugin declares gates
|
Nothing in a GUI invents a name. A grant of a name that no installed plugin declares still reaches
|
||||||
nothing.
|
the user's JWT, so a plugin may gate on that name without declaring it.
|
||||||
|
|
||||||
A change takes effect on the user's **next login or JWT re-mint** (~10 min) — see [Instant
|
A change takes effect on the user's **next login or JWT re-mint** (~10 min) — see [Instant
|
||||||
revoke](#instant-revoke-the-optional-denylist) when you need it sooner.
|
revoke](#instant-revoke-the-optional-denylist) when you need it sooner.
|
||||||
@@ -1363,8 +1363,8 @@ be `metadata_public`, not `metadata_admin` — the session Kratos hands the toke
|
|||||||
public metadata, and the user can already read these permissions in their own JWT. The projection is
|
public metadata, and the user can already read these permissions in their own JWT. The projection is
|
||||||
a per-login cache, authoritative nowhere; a stale one self-heals on the next login.
|
a per-login cache, authoritative nowhere; a stale one self-heals on the next login.
|
||||||
|
|
||||||
Login resolves both direct grants and group membership, so the JWT `permissions` match what the
|
Login resolves both direct grants and group membership, so the JWT `permissions` match what Keto
|
||||||
admin **Effective access** view shows. Cost: **a handful of Keto reads + one identity refresh per
|
says the user holds. Cost: **a handful of Keto reads + one identity refresh per
|
||||||
login**, never per request. JWKS is cached, so signature verification hits the network only on key
|
login**, never per request. JWKS is cached, so signature verification hits the network only on key
|
||||||
rotation.
|
rotation.
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
# bash ci.sh
|
# bash ci.sh
|
||||||
#
|
#
|
||||||
# Exits non-zero on the first failure. Each E2E suite OWNS a clean stack — never point two suites at
|
# Exits non-zero on the first failure. Each E2E suite OWNS a clean stack — never point two suites at
|
||||||
# one backend (auth-refresh revokes the admin's sessions; full-flow writes users/groups/roles to Keto).
|
# one backend (auth-refresh revokes the admin's sessions; full-flow writes users, groups and grants).
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
cd "$(dirname "$0")"
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
|||||||
+3
-2
@@ -165,8 +165,9 @@ services:
|
|||||||
|
|
||||||
# Ory Hydra — OAuth2/OIDC provider (other apps log in *through* plainpages; README).
|
# Ory Hydra — OAuth2/OIDC provider (other apps log in *through* plainpages; README).
|
||||||
# DSN is its own `hydra` DB (init.sql); config in ory/hydra/hydra.yml. web implements the
|
# DSN is its own `hydra` DB (init.sql); config in ory/hydra/hydra.yml. web implements the
|
||||||
# login challenge at /oauth2/login (consent next). Dev permits the http issuer via --dev
|
# login challenge at /oauth2/login and the consent challenge at /oauth2/consent. Dev permits the
|
||||||
# (compose.override.yml); prod sets an https issuer via env (URLS_SELF_ISSUER).
|
# http issuer via --dev (compose.override.yml); prod sets an https issuer via env
|
||||||
|
# (URLS_SELF_ISSUER).
|
||||||
hydra-migrate:
|
hydra-migrate:
|
||||||
image: oryd/hydra:v26.2.0
|
image: oryd/hydra:v26.2.0
|
||||||
depends_on:
|
depends_on:
|
||||||
|
|||||||
@@ -513,3 +513,20 @@ Read most (opens and searches):
|
|||||||
- `AGENTS.md` 1
|
- `AGENTS.md` 1
|
||||||
- `README.md` 1
|
- `README.md` 1
|
||||||
- `docs/decisions.md` 1
|
- `docs/decisions.md` 1
|
||||||
|
|
||||||
|
## Reading cost, chunk f0194a598d2a, finished 2026-11-08T23:10:24Z
|
||||||
|
|
||||||
|
81419 new input tokens before the first commit.
|
||||||
|
|
||||||
|
Read most (opens and searches):
|
||||||
|
|
||||||
|
- `src/http/app-admin.test.ts` 11
|
||||||
|
- `examples/plugins/auth-admin/admin-users.ts` 7
|
||||||
|
- `todo.md` 6
|
||||||
|
- `examples/plugins/auth-admin/admin-groups.test.ts` 4
|
||||||
|
- `README.md` 3
|
||||||
|
- `examples/plugins/auth-admin/admin-groups.ts` 3
|
||||||
|
- `CHANGELOG.md` 2
|
||||||
|
- `docs/reading-cost.md` 2
|
||||||
|
- `examples/plugins/auth-admin/admin-grants.ts` 2
|
||||||
|
- `examples/plugins/auth-admin/i18n/en-US.ts` 2
|
||||||
|
|||||||
@@ -35,9 +35,9 @@ screen degrades to a themed 503 rather than crashing. Everything else is an ordi
|
|||||||
folder-discovered, gated per route by its screen's `<resource>:<action>` permission, rendering the
|
folder-discovered, gated per route by its screen's `<resource>:<action>` permission, rendering the
|
||||||
core building blocks in `views/`.
|
core building blocks in `views/`.
|
||||||
|
|
||||||
Each screen is its own resource — `users`, `groups`, `oauth2-clients` — split into `:read` and
|
Each screen is its own resource — `users`, `groups`, `oauth2-clients`, `plugin-settings` — split
|
||||||
`:write`, so a helpdesk account can be given `users:read` alone. Holding none of the six hides the
|
into `:read` and `:write` (`plugin-settings` has only `:read`), so a helpdesk account can be given
|
||||||
Admin section entirely.
|
`users:read` alone. Holding none of the seven hides the Admin section entirely.
|
||||||
|
|
||||||
There is **no Permissions screen**. Permission names are declared in plugin code, not created in a
|
There is **no Permissions screen**. Permission names are declared in plugin code, not created in a
|
||||||
GUI, so the host's catalog (`ctx.declaredPermissions`) is the fixed list — and holding one is a
|
GUI, so the host's catalog (`ctx.declaredPermissions`) is the fixed list — and holding one is a
|
||||||
@@ -46,7 +46,7 @@ A grant of a name no installed plugin declares is listed apart on the same page,
|
|||||||
|
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
- `plugin.ts` — the manifest: the Admin nav fragment, the six permissions the plugin declares, and
|
- `plugin.ts` — the manifest: the Admin nav fragment, the seven permissions the plugin declares, and
|
||||||
the route table — one thin handler per method+path, gated via `permissionName(resource, actionForMethod(method))`
|
the route table — one thin handler per method+path, gated via `permissionName(resource, actionForMethod(method))`
|
||||||
so a GET needs `:read` and a POST `:write`.
|
so a GET needs `:read` and a POST `:write`.
|
||||||
- `admin-grants.ts` — the permission picker and the grant diff, shared by the Users and Groups
|
- `admin-grants.ts` — the permission picker and the grant diff, shared by the Users and Groups
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ const GROUP_NS = "Group";
|
|||||||
const MEMBERS = "members";
|
const MEMBERS = "members";
|
||||||
const DEFAULT_PAGE_SIZE = 25;
|
const DEFAULT_PAGE_SIZE = 25;
|
||||||
const PAGE_SIZES = [25, 50, 100];
|
const PAGE_SIZES = [25, 50, 100];
|
||||||
// One Keto page of candidate users is fetched for the member pickers (mirrors admin-users).
|
// One Kratos `listIdentities` page of candidate users is fetched for the member pickers (mirrors
|
||||||
|
// admin-users).
|
||||||
const LIST_FETCH_SIZE = 250;
|
const LIST_FETCH_SIZE = 250;
|
||||||
const GROUP_NAME = /^[a-z0-9][a-z0-9_-]*$/; // URL-safe; doubles as the path segment
|
const GROUP_NAME = /^[a-z0-9][a-z0-9_-]*$/; // URL-safe; doubles as the path segment
|
||||||
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; // a Kratos identity id
|
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; // a Kratos identity id
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<%#
|
<%#
|
||||||
Admin group membership body, captured into the shell content slot. Config:
|
Admin group membership body, captured into the shell content slot. Config:
|
||||||
group { name }
|
group { name }
|
||||||
members { action, rows: { kind:"group"|"identity", label, subject }[] } action = remove-member endpoint
|
members { action, rows: { kind:"group"|"user", label, subject }[] } action = remove-member endpoint
|
||||||
add { action, options: {label,value}[] } action = add-member endpoint
|
add { action, options: {label,value}[] } action = add-member endpoint
|
||||||
del { action } delete the whole group
|
del { action } delete the whole group
|
||||||
|
canWrite? false (a `groups:read` holder) drops the add, remove and delete forms
|
||||||
|
permissions? PermissionPicker; when set, the group's permission picker is rendered
|
||||||
csrfToken, error?
|
csrfToken, error?
|
||||||
%><%
|
%><%
|
||||||
const group = locals.group;
|
const group = locals.group;
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export default definePlugin({
|
|||||||
public: true, // the header gates nothing; each child names its own gate, and an empty header is dropped
|
public: true, // the header gates nothing; each child names its own gate, and an empty header is dropped
|
||||||
}],
|
}],
|
||||||
|
|
||||||
// Roles this plugin introduces (docs + Keto seeding). Namespaced `<id>:<action>`.
|
// Permissions this plugin declares: the admin picker offers them and bootstrap grants them to the demo admin.
|
||||||
permissions: [
|
permissions: [
|
||||||
{ description: "View shifts", name: READ },
|
{ description: "View shifts", name: READ },
|
||||||
{ description: "Create and edit shifts", name: WRITE },
|
{ description: "Create and edit shifts", name: WRITE },
|
||||||
|
|||||||
@@ -20,6 +20,11 @@
|
|||||||
resolution is unreliable across engines. color-scheme is still
|
resolution is unreliable across engines. color-scheme is still
|
||||||
set as a hint for native widgets (date picker, scrollbars,
|
set as a hint for native widgets (date picker, scrollbars,
|
||||||
selects).
|
selects).
|
||||||
|
Footgun: a token value an engine of the oldest browser floor can't parse (a
|
||||||
|
function or unit it lacks) is invalid at computed-value time, so every
|
||||||
|
declaration reading it through var() computes as `unset`; a fallback declared
|
||||||
|
before it doesn't help. The test in src/test-tooling/stylesheet-floor.ts
|
||||||
|
fails on such a value.
|
||||||
------------------------------------------------------------- */
|
------------------------------------------------------------- */
|
||||||
:root {
|
:root {
|
||||||
/* ---- raw LIGHT palette ---- */
|
/* ---- raw LIGHT palette ---- */
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
// One-command bootstrap (the MVP bar). One-shot compose service: runs after
|
// One-command bootstrap (the MVP bar). One-shot compose service: runs after
|
||||||
// kratos+keto are healthy (web waits on it), idempotent on every `docker compose up`:
|
// kratos+keto are healthy (web waits on it), idempotent on every `docker compose up`:
|
||||||
// 1. generate the JWKS signing key if absent (committed dev key makes this a safety net);
|
// 1. generate the JWKS signing key if absent (committed dev key makes this a safety net);
|
||||||
// 2. seed a demo admin (admin@plainpages.local / admin) in Kratos;
|
// 2. create a database and login role for each discovered plugin that declares storage;
|
||||||
// 3. grant it its permissions in Keto so menu/permission checks resolve out of the box — every
|
// 3. seed a demo admin (admin@plainpages.local / admin) in Kratos;
|
||||||
|
// 4. grant it its permissions in Keto so menu/permission checks resolve out of the box — every
|
||||||
// discovered plugin's declared permission names (plus any ADMIN_PERMISSIONS), so a dropped-in
|
// discovered plugin's declared permission names (plus any ADMIN_PERMISSIONS), so a dropped-in
|
||||||
// plugin is usable by the demo admin with no host config edit (the host stays plugin-agnostic).
|
// plugin is usable by the demo admin with no host config edit (the host stays plugin-agnostic).
|
||||||
// Then prints a first-run banner; fails loud on any unexpected upstream error.
|
// Then prints a first-run banner; fails loud on any unexpected upstream error.
|
||||||
|
|||||||
+2
-2
@@ -4,8 +4,8 @@ import type { JsonWebKey } from "./jwks.ts";
|
|||||||
|
|
||||||
// JWS signature verification with the Node stdlib — no `jose`/JWT dep:
|
// JWS signature verification with the Node stdlib — no `jose`/JWT dep:
|
||||||
// `createPublicKey({format:"jwk"})` imports a JWK and verifies the RS*/ES* signatures
|
// `createPublicKey({format:"jwk"})` imports a JWK and verifies the RS*/ES* signatures
|
||||||
// the Kratos tokenizer produces (see AGENTS.md). Signature only — adds claim checks
|
// the Kratos tokenizer produces. Signature only: `jwt-middleware.ts`
|
||||||
// (exp/iss/aud, clock skew), JWKS-by-`kid` fetch/cache/rotation, and `token` bounds.
|
// picks the key by `kid` and checks the claims.
|
||||||
|
|
||||||
// JOSE `alg` → Node verify parameters. ES* signatures are raw r‖s (IEEE P1363), not DER.
|
// JOSE `alg` → Node verify parameters. ES* signatures are raw r‖s (IEEE P1363), not DER.
|
||||||
// Extend this map to widen support. Security invariant: never add `HS*`/`none` — this map
|
// Extend this map to widen support. Security invariant: never add `HS*`/`none` — this map
|
||||||
|
|||||||
+2
-2
@@ -40,8 +40,8 @@ export interface CompletedLogin {
|
|||||||
// The coarse permissions a user holds — directly (`Permission:<name>#granted@user:<id>`) or transitively via a
|
// The coarse permissions a user holds — directly (`Permission:<name>#granted@user:<id>`) or transitively via a
|
||||||
// group granted the permission. Enumerates the defined permissions (the distinct objects in the Permission
|
// group granted the permission. Enumerates the defined permissions (the distinct objects in the Permission
|
||||||
// namespace) and asks Keto to check each grant, so a permission granted to a group reaches the JWT —
|
// namespace) and asks Keto to check each grant, so a permission granted to a group reaches the JWT —
|
||||||
// matching the OPL model and the admin "Effective access" view. At login/refresh only, never per
|
// matching the OPL model. At login/refresh only, never per request; permission count is small, so the
|
||||||
// request; permission count is small, so the per-permission checks are cheap and run in parallel.
|
// per-permission checks are cheap and run in parallel.
|
||||||
export async function readPermissions(keto: KetoClient, userId: string): Promise<string[]> {
|
export async function readPermissions(keto: KetoClient, userId: string): Promise<string[]> {
|
||||||
const subject_id = `user:${userId}`;
|
const subject_id = `user:${userId}`;
|
||||||
const names = new Set<string>();
|
const names = new Set<string>();
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
// Router: pure core mapping method + pathname → a discovered plugin route. I/O-free;
|
// Router: pure core mapping method + pathname → a discovered plugin route. I/O-free;
|
||||||
// app.ts is the shell (build context, gate, call handler, render RouteResult). A route mounts at
|
// app.ts is the shell (build context, gate, call handler, render RouteResult). A route mounts at
|
||||||
// `/<id>` + its path (fullPath, shared with conflict detection); `:name` segments → path params.
|
// `/<id>` + its path (fullPath, shared with conflict detection); `:name` segments → path params.
|
||||||
// Specificity: a literal segment beats a `:param` (/users/new wins /users/:id), order-independent.
|
|
||||||
|
|
||||||
import { fullPath, type Plugin, type Route } from "./plugin.ts";
|
import { fullPath, type Plugin, type Route } from "./plugin.ts";
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// System capabilities: privileged host services a first-party/system plugin (the built-in admin
|
// System capabilities: privileged host services a first-party/system plugin (the `auth-admin`
|
||||||
// screens are the reference consumer) needs but an ordinary domain plugin does not — the Ory admin
|
// example plugin is the reference consumer) needs but an ordinary domain plugin does not — the Ory admin
|
||||||
// clients and the instant-revoke hook. Exposed on ctx.system and re-exported via @plainpages/plugin-api.
|
// clients and the instant-revoke hook. Exposed on ctx.system and re-exported via @plainpages/plugin-api.
|
||||||
//
|
//
|
||||||
// Every field is optional: it is present only when the host wired that dependency (Ory configured,
|
// Every field is optional: it is present only when the host wired that dependency (Ory configured,
|
||||||
|
|||||||
+2
-2
@@ -66,7 +66,7 @@ export function buildPluginChrome(opts: ChromeOptions): PageChrome {
|
|||||||
const nav = composeNav(fragments, opts.menu.override, opts.user ?? null, t);
|
const nav = composeNav(fragments, opts.menu.override, opts.user ?? null, t);
|
||||||
if (opts.currentPath) {
|
if (opts.currentPath) {
|
||||||
// Mark by the *best* (longest) href that is the path or a parent of it, so a sub-path like
|
// Mark by the *best* (longest) href that is the path or a parent of it, so a sub-path like
|
||||||
// /admin/users/new marks the Users base leaf (/admin/users) and the dashboard marks Dashboard.
|
// /auth-admin/users/new marks the Users base leaf (/auth-admin/users) and the dashboard marks Dashboard.
|
||||||
// Marked before the locale rides along, so an href still matches the plain request path.
|
// Marked before the locale rides along, so an href still matches the plain request path.
|
||||||
const target = bestHref(nav, opts.currentPath);
|
const target = bestHref(nav, opts.currentPath);
|
||||||
if (target) markCurrent(nav, target);
|
if (target) markCurrent(nav, target);
|
||||||
@@ -140,7 +140,7 @@ function carryLocaleInto(nodes: NavNode[], carryLocale: (href: string) => string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// The href of the leaf that owns `path`: an exact match, else the longest href that is a parent of
|
// The href of the leaf that owns `path`: an exact match, else the longest href that is a parent of
|
||||||
// it (href + "/" prefixes path), so /admin/users/123 resolves to the /admin/users leaf. "/" never
|
// it (href + "/" prefixes path), so /auth-admin/users/123 resolves to the /auth-admin/users leaf. "/" never
|
||||||
// counts as a parent (it would own everything). Returns undefined when nothing matches.
|
// counts as a parent (it would own everything). Returns undefined when nothing matches.
|
||||||
function bestHref(nodes: NavNode[], path: string): string | undefined {
|
function bestHref(nodes: NavNode[], path: string): string | undefined {
|
||||||
let best: string | undefined;
|
let best: string | undefined;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
| ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score |
|
| ID | Release | Blocked by | Exempt | Item | L | E | I | R | S | Moves | Score |
|
||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
| 63 | 0.5.0 | | | **Correct the stale and false comments and docs the comprehension seats flagged, so each states what the code does today.** | 2 | 2 | 1 | 3 | 3 | code health by 3, 9 by 2 | 15.4 |
|
|
||||||
| 68 | 0.5.0 | | | **Make `serveHome` and `serveDashboard` return their result to the request pipeline, as every other stage does, instead of sending it themselves.** | 2 | 1 | 3 | 4 | 3 | code health by 3 | 13.5 |
|
| 68 | 0.5.0 | | | **Make `serveHome` and `serveDashboard` return their result to the request pipeline, as every other stage does, instead of sending it themselves.** | 2 | 1 | 3 | 4 | 3 | code health by 3 | 13.5 |
|
||||||
| 66 | 0.5.0 | | | **Make `src/auth/bootstrap.ts` read its settings through `src/config.ts` and call Kratos and Keto through their client modules.** | 2 | 1 | 3 | 4 | 4 | code health by 3 | 9.0 |
|
| 66 | 0.5.0 | | | **Make `src/auth/bootstrap.ts` read its settings through `src/config.ts` and call Kratos and Keto through their client modules.** | 2 | 1 | 3 | 4 | 4 | code health by 3 | 9.0 |
|
||||||
| 70 | 0.5.0 | | | **Give the duplicated paging loop, Link-header parser and `FieldConfig` type one home each, and rename `buildPluginChrome` to `buildPageChrome`.** | 2 | 1 | 1 | 2 | 3 | code health by 2 | 9.0 |
|
| 70 | 0.5.0 | | | **Give the duplicated paging loop, Link-header parser and `FieldConfig` type one home each, and rename `buildPluginChrome` to `buildPageChrome`.** | 2 | 1 | 1 | 2 | 3 | code health by 2 | 9.0 |
|
||||||
@@ -30,37 +29,6 @@
|
|||||||
|
|
||||||
## Details
|
## Details
|
||||||
|
|
||||||
### 63. Correct the stale and false comments and docs the comprehension seats flagged, so each states what the code does today.
|
|
||||||
|
|
||||||
Prose only; no behaviour changes. Make each listed place easier to read, since the seats named
|
|
||||||
most of them.
|
|
||||||
|
|
||||||
- `src/auth/login.ts:43` and `README.md` ~L1367 name an admin "Effective access" view that doesn't
|
|
||||||
exist.
|
|
||||||
- `src/plugin-host/router.ts:4` header says specificity is order-independent; `matchRoute` sorts by
|
|
||||||
`:param` count only, so a tie (`/a/:x/b` against `/a/b/:y`) falls to discovery order.
|
|
||||||
- `ci.sh:9` ("users/groups/roles") and `examples/plugins/scheduling/plugin.ts:37` ("Roles", and
|
|
||||||
`<id>:<action>` where the rule is `<resource>:<action>`) use words the project doesn't have.
|
|
||||||
- `src/plugin-host/system.ts:1-2` calls "the built-in admin screens" the reference consumer; they
|
|
||||||
are the `auth-admin` example plugin.
|
|
||||||
- `examples/plugins/auth-admin/admin-groups.ts:18` says a Keto page; the call is Kratos
|
|
||||||
`listIdentities`.
|
|
||||||
- `compose.yml` ~L166 says consent comes next; `src/auth/oauth-consent.ts` implements it.
|
|
||||||
- `src/auth/jwt.ts:6-7`: "Signature only — adds claim checks…" lacks its subject (the caller,
|
|
||||||
`jwt-middleware.ts`, adds them).
|
|
||||||
- `src/ui/chrome.ts` ~L69 and ~L143 use `/admin/users` as example paths; admin lives at
|
|
||||||
`/auth-admin/users`.
|
|
||||||
- `README.md` ~L350 says a tuple naming an undeclared permission "gates nothing"; declaring stays
|
|
||||||
optional, so a plugin may gate on it. `examples/plugins/auth-admin/README.md:49` says six
|
|
||||||
permissions; `plugin.ts` declares seven.
|
|
||||||
- `examples/plugins/auth-admin/views/partials/group-detail-body.ejs:2-7` documents member
|
|
||||||
`kind:"identity"` where the model says `"user"`, and omits the `canWrite` and `permissions`
|
|
||||||
locals.
|
|
||||||
- `src/auth/bootstrap.ts:1-8` header lists the JWKS, admin and Keto steps, not the plugin-storage
|
|
||||||
step.
|
|
||||||
- The custom-property `unset` footgun is stated only in `src/test-tooling/stylesheet-floor.ts:10-12`;
|
|
||||||
say it beside the tokens at `public/css/styles.css:9-23`, where an author writes one.
|
|
||||||
|
|
||||||
### 68. Make `serveHome` and `serveDashboard` return their result to the request pipeline, as every other stage does, instead of sending it themselves.
|
### 68. Make `serveHome` and `serveDashboard` return their result to the request pipeline, as every other stage does, instead of sending it themselves.
|
||||||
|
|
||||||
In `src/http/app.ts`, both (~L143-169) call `sendResult` with the owning plugin's renderer and
|
In `src/http/app.ts`, both (~L143-169) call `sendResult` with the owning plugin's renderer and
|
||||||
|
|||||||
Reference in New Issue
Block a user