Give every palette colour a hex value an engine without oklch can read #181
@@ -463,3 +463,13 @@ Default to **no comment**. Delete one that restates the adjacent code, repeats a
|
|||||||
elsewhere, justifies self-evident code, or records history. Write one only for what a competent
|
elsewhere, justifies self-evident code, or records history. Write one only for what a competent
|
||||||
reader of *this* codebase could not infer: a surprising why, a footgun, an invariant, an external
|
reader of *this* codebase could not infer: a surprising why, a footgun, an invariant, an external
|
||||||
constraint. See [Prose discipline](#prose-discipline).
|
constraint. See [Prose discipline](#prose-discipline).
|
||||||
|
|
||||||
|
## Scoring run
|
||||||
|
|
||||||
|
The comprehension panel's seats fill their prompts from these fields.
|
||||||
|
|
||||||
|
- **Language:** strict TypeScript on Node 24, with EJS views and plain CSS
|
||||||
|
- **Kind:** a server-rendered web GUI host with a plugin system
|
||||||
|
- **Domain:** web authentication, authorization and server-rendered admin GUIs
|
||||||
|
- **Domain docs:** the Ory Kratos, Keto and Hydra documentation
|
||||||
|
- **3am question:** a visitor on an old TV browser reports that the sign-in button is blank. Which file and rule?
|
||||||
|
|||||||
@@ -6,6 +6,13 @@ at boot. Entries start at 0.3.0.
|
|||||||
|
|
||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
|
### Colours render on engines without `oklch()`
|
||||||
|
|
||||||
|
The theme palette in `public/css/styles.css` is hex, not `oklch()`. On an engine older than
|
||||||
|
Chromium 111 (Tizen 2024, webOS TV 24) `var(--accent)` was invalid, so the primary button was white
|
||||||
|
text on no background. A plugin that sets a colour custom property, such as `--accent` or `--l-accent`,
|
||||||
|
should use hex, `rgb()` or `hsl()` too.
|
||||||
|
|
||||||
### One horizontal inset across the content pane
|
### One horizontal inset across the content pane
|
||||||
|
|
||||||
The topbar, filter bar, pager, form page and empty-table message inset their content by `--pad-x`
|
The topbar, filter bar, pager, form page and empty-table message inset their content by `--pad-x`
|
||||||
|
|||||||
@@ -1023,8 +1023,10 @@ URL is the only state the UI keeps.
|
|||||||
Chromium 87, Safari 15 and Firefox 115, the engines in years-old TVs, kiosks and Qt panels. Only
|
Chromium 87, Safari 15 and Firefox 115, the engines in years-old TVs, kiosks and Qt panels. Only
|
||||||
features [Baseline Widely available](https://web-platform-dx.github.io/baseline/) (in every
|
features [Baseline Widely available](https://web-platform-dx.github.io/baseline/) (in every
|
||||||
major engine for 30 months) may shape how a page looks; anything newer, like popovers or anchor
|
major engine for 30 months) may shape how a page looks; anything newer, like popovers or anchor
|
||||||
positioning, only enhances. A feature the floor engines lack, such as `:has()` or `oklch()`, may
|
positioning, only enhances. A feature the floor engines lack, such as `:has()`, may
|
||||||
decorate a page but never decide whether something shows or can be read.
|
decorate a page but never decide whether something shows or can be read. A colour is hex, `rgb()` or
|
||||||
|
`hsl()`, never `oklch()`: a custom property holding it passes unchecked, and the `var()` reading it
|
||||||
|
then renders transparent.
|
||||||
|
|
||||||
**A field whose options depend on another field is asked for in steps, one GET form each**, so every
|
**A field whose options depend on another field is asked for in steps, one GET form each**, so every
|
||||||
choice is in the URL, and nothing typed is lost to one because nothing is typed until they are made.
|
choice is in the URL, and nothing typed is lost to one because nothing is typed until they are made.
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Comprehension history
|
||||||
|
|
||||||
|
## 2026-10-31T00:16:02Z, PR #181 at 56ff987, against eed1050
|
||||||
|
|
||||||
|
Ruling: better
|
||||||
|
|
||||||
|
| Seat | Navigation | Locality | Shape | Self-sufficiency | Overall |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| Mid A | same | better | same | better | better |
|
||||||
|
| Inherited architect | same | better | same | better | better |
|
||||||
|
|
||||||
|
Mid A, decided by:
|
||||||
|
|
||||||
|
- Self-sufficiency, Locality: `public/css/styles.css:24` `:root`: the comment above the hex palette says why a colour must not be oklch(), where a colour is edited
|
||||||
|
- Locality: `src/ui/stylesheet-floor.test.ts:11` `declares no oklch() colour`: a colour edit that breaks the browser floor fails a test instead of an old engine
|
||||||
|
|
||||||
|
Inherited architect, decided by:
|
||||||
|
|
||||||
|
- Self-sufficiency, Locality: `public/css/styles.css:24` `:root`: the comment above the hex palette says why a colour must not be oklch(), where a colour is edited
|
||||||
|
- Locality: `src/ui/stylesheet-floor.test.ts:11` `declares no oklch() colour`: a red gate explains the symptom in the file it lands on
|
||||||
|
- Self-sufficiency: `README.md:1027` "Two browser floors": the colour rule now sits beside the floor rule
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Reading cost
|
||||||
|
|
||||||
|
## Reading cost, chunk 57e864bce02a, finished 2026-10-30T23:59:00Z
|
||||||
|
|
||||||
|
100624 new input tokens before the first commit.
|
||||||
|
|
||||||
|
Read most (opens and searches):
|
||||||
|
|
||||||
|
- `src/http/app.test.ts` 9
|
||||||
|
- `README.md` 7
|
||||||
|
- `src/http/app.ts` 4
|
||||||
|
- `todo.md` 3
|
||||||
|
- `e2e-tests/playwright.config.ts` 2
|
||||||
|
- `AGENTS.md` 1
|
||||||
|
- `CHANGELOG.md` 1
|
||||||
|
- `ci.sh` 1
|
||||||
|
- `examples/plugins/scheduling/views/shifts.ejs` 1
|
||||||
|
- `src/auth/csrf.ts` 1
|
||||||
+14
-12
@@ -21,27 +21,29 @@
|
|||||||
widgets (date picker, scrollbars, selects).
|
widgets (date picker, scrollbars, selects).
|
||||||
------------------------------------------------------------- */
|
------------------------------------------------------------- */
|
||||||
:root {
|
:root {
|
||||||
|
/* Hex, never oklch: below Chromium 111 `var(--accent)` is invalid at computed-value time and the
|
||||||
|
primary button loses its background. A fallback declaration cannot catch that. */
|
||||||
/* ---- raw LIGHT palette ---- */
|
/* ---- raw LIGHT palette ---- */
|
||||||
--l-bg:#f3f3f4; --l-surface:#ffffff; --l-surface-2:#f6f6f7; --l-surface-3:#fbfbfc;
|
--l-bg:#f3f3f4; --l-surface:#ffffff; --l-surface-2:#f6f6f7; --l-surface-3:#fbfbfc;
|
||||||
--l-border:#e5e5e8; --l-border-2:#d6d6da;
|
--l-border:#e5e5e8; --l-border-2:#d6d6da;
|
||||||
--l-text:#18181b; --l-text-muted:#6b6b73; --l-text-faint:#9a9aa2;
|
--l-text:#18181b; --l-text-muted:#6b6b73; --l-text-faint:#9a9aa2;
|
||||||
--l-accent:oklch(0.52 0.12 256); --l-accent-bg:oklch(0.96 0.03 256);
|
--l-accent:#3669ac; --l-accent-bg:#e5f3ff;
|
||||||
--l-accent-bd:oklch(0.82 0.07 256); --l-focus:oklch(0.52 0.14 256);
|
--l-accent-bd:#a7c7f2; --l-focus:#2a68b7;
|
||||||
--l-pos:oklch(0.54 0.13 150); --l-pos-bg:oklch(0.95 0.03 150); --l-pos-bd:oklch(0.83 0.07 150);
|
--l-pos:#258343; --l-pos-bg:#e1f5e4; --l-pos-bd:#a8d5b0;
|
||||||
--l-neg:oklch(0.55 0.18 25); --l-neg-bg:oklch(0.96 0.03 25); --l-neg-bd:oklch(0.85 0.08 25);
|
--l-neg:#c53637; --l-neg-bg:#ffebe8; --l-neg-bd:#febab4;
|
||||||
--l-warn:oklch(0.55 0.12 75); --l-warn-bg:oklch(0.96 0.04 85); --l-warn-bd:oklch(0.84 0.08 85);
|
--l-warn:#9a6500; --l-warn-bg:#fef0d4; --l-warn-bd:#e3c78e;
|
||||||
--l-info:oklch(0.54 0.12 245);--l-info-bg:oklch(0.96 0.03 245);--l-info-bd:oklch(0.83 0.07 245);
|
--l-info:#2274af;--l-info-bg:#e2f5ff;--l-info-bd:#a2cdf2;
|
||||||
|
|
||||||
/* ---- raw DARK palette ---- */
|
/* ---- raw DARK palette ---- */
|
||||||
--d-bg:#0d0d0f; --d-surface:#161619; --d-surface-2:#1d1d22; --d-surface-3:#1a1a1e;
|
--d-bg:#0d0d0f; --d-surface:#161619; --d-surface-2:#1d1d22; --d-surface-3:#1a1a1e;
|
||||||
--d-border:#2a2a31; --d-border-2:#3a3a43;
|
--d-border:#2a2a31; --d-border-2:#3a3a43;
|
||||||
--d-text:#ededf0; --d-text-muted:#9b9ba6; --d-text-faint:#6a6a74;
|
--d-text:#ededf0; --d-text-muted:#9b9ba6; --d-text-faint:#6a6a74;
|
||||||
--d-accent:oklch(0.70 0.12 256); --d-accent-bg:oklch(0.32 0.06 256);
|
--d-accent:#6ba0e8; --d-accent-bg:#1d3351;
|
||||||
--d-accent-bd:oklch(0.48 0.08 256); --d-focus:oklch(0.74 0.13 256);
|
--d-accent-bd:#3f5f8a; --d-focus:#72adfb;
|
||||||
--d-pos:oklch(0.74 0.14 150); --d-pos-bg:oklch(0.30 0.05 150); --d-pos-bd:oklch(0.45 0.07 150);
|
--d-pos:#62c37a; --d-pos-bg:#1a3520; --d-pos-bd:#376040;
|
||||||
--d-neg:oklch(0.74 0.16 25); --d-neg-bg:oklch(0.30 0.07 25); --d-neg-bd:oklch(0.47 0.10 25);
|
--d-neg:#ff7e76; --d-neg-bg:#4b1d1b; --d-neg-bd:#8a423d;
|
||||||
--d-warn:oklch(0.80 0.13 85); --d-warn-bg:oklch(0.31 0.05 80); --d-warn-bd:oklch(0.48 0.07 80);
|
--d-warn:#e4b750; --d-warn-bg:#3e2d10; --d-warn-bd:#72592c;
|
||||||
--d-info:oklch(0.74 0.12 245);--d-info-bg:oklch(0.31 0.06 245);--d-info-bd:oklch(0.47 0.08 245);
|
--d-info:#65b2f1;--d-info-bg:#12334c;--d-info-bd:#305f85;
|
||||||
|
|
||||||
/* ---- active mapping: default = LIGHT ---- */
|
/* ---- active mapping: default = LIGHT ---- */
|
||||||
--bg:var(--l-bg); --surface:var(--l-surface); --surface-2:var(--l-surface-2); --surface-3:var(--l-surface-3);
|
--bg:var(--l-bg); --surface:var(--l-surface); --surface-2:var(--l-surface-2); --surface-3:var(--l-surface-3);
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import assert from "node:assert/strict";
|
||||||
|
import { readdirSync, readFileSync } from "node:fs";
|
||||||
|
import { dirname, join } from "node:path";
|
||||||
|
import { test } from "node:test";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
|
||||||
|
const cssDir = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "public", "css");
|
||||||
|
|
||||||
|
// A custom property holding oklch() passes unchecked on an engine below Chromium 111, so the
|
||||||
|
// var() that reads it is invalid at computed-value time: white text on no background.
|
||||||
|
for (const file of readdirSync(cssDir).filter((f) => f.endsWith(".css"))) {
|
||||||
|
test(`${file} declares no oklch() colour`, () => {
|
||||||
|
assert.ok(!/oklch\(/i.test(readFileSync(join(cssDir, file), "utf8")));
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -6,14 +6,14 @@
|
|||||||
|
|
||||||
`Bar = 1`
|
`Bar = 1`
|
||||||
|
|
||||||
`Next ID = 35`
|
`Next ID = 36`
|
||||||
|
|
||||||
## Items
|
## Items
|
||||||
|
|
||||||
| 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 |
|
||||||
|---|---|---|---|---|---|---|---|---|---|---|---|
|
|---|---|---|---|---|---|---|---|---|---|---|---|
|
||||||
| 30 | 0.4.21 | | defect | **Give every `oklch()` colour a fallback, so primary buttons stay readable on an engine without it.** | 1 | 2 | 2 | 3 | 2 | 4 by 5 | 29.9 |
|
|
||||||
| 29 | 0.4.21 | | defect | **Show the whole nav tree on an engine without `:has()`, and fold it only where `:has()` works.** | 2 | 2 | 2 | 4 | 3 | 4 by 6 | 26.9 |
|
| 29 | 0.4.21 | | defect | **Show the whole nav tree on an engine without `:has()`, and fold it only where `:has()` works.** | 2 | 2 | 2 | 4 | 3 | 4 by 6 | 26.9 |
|
||||||
|
| 35 | 0.4.21 | | defect | **Raise the contrast of the dark-mode primary button's white text and of the light-mode status badges' text to 4.5:1.** | 1 | 2 | 1 | 2 | 2 | 6 by 3 | 18.0 |
|
||||||
| 31 | 0.4.21 | | | **Delete the design-preview rules in `public/css/auth.css` that no view uses.** | 1 | 1 | 1 | 1 | 2 | code health by 2, 8 by 1 | 13.3 |
|
| 31 | 0.4.21 | | | **Delete the design-preview rules in `public/css/auth.css` that no view uses.** | 1 | 1 | 1 | 1 | 2 | code health by 2, 8 by 1 | 13.3 |
|
||||||
| 26 | 0.4.21 | | | **Add a domain glossary and the project's intended horizon to the README.** | 1 | 2 | 1 | 2 | 3 | 9 by 3, code health by 2 | 11.9 |
|
| 26 | 0.4.21 | | | **Add a domain glossary and the project's intended horizon to the README.** | 1 | 2 | 1 | 2 | 3 | 9 by 3, code health by 2 | 11.9 |
|
||||||
| 8 | 0.4.21 | | | **Run the visual E2E specs at viewports from a large desktop down to a small phone.** | 2 | 1 | 1 | 2 | 4 | code health by 3 | 9.0 |
|
| 8 | 0.4.21 | | | **Run the visual E2E specs at viewports from a large desktop down to a small phone.** | 2 | 1 | 1 | 2 | 4 | code health by 3 | 9.0 |
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
| 24 | 0.4.21 | | | **Retire `src/ui/shell-context.ts` and render the dashboard from `ctx.chrome`.** | 2 | 1 | 2 | 3 | 4 | code health by 3 | 9.0 |
|
| 24 | 0.4.21 | | | **Retire `src/ui/shell-context.ts` and render the dashboard from `ctx.chrome`.** | 2 | 1 | 2 | 3 | 4 | code health by 3 | 9.0 |
|
||||||
| 17 | 0.4.21 | | | **Remove the icons no caller uses from `ICON_NAMES` and the sprite.** | 1 | 1 | 1 | 1 | 1 | 8 by 1, code health by 1 | 8.8 |
|
| 17 | 0.4.21 | | | **Remove the icons no caller uses from `ICON_NAMES` and the sprite.** | 1 | 1 | 1 | 1 | 1 | 8 by 1, code health by 1 | 8.8 |
|
||||||
| 21 | 0.4.21 | | | **Fail the gate on a function that exceeds a size or complexity limit.** | 3 | 1 | 1 | 3 | 5 | code health by 4 | 7.2 |
|
| 21 | 0.4.21 | | | **Fail the gate on a function that exceeds a size or complexity limit.** | 3 | 1 | 1 | 3 | 5 | code health by 4 | 7.2 |
|
||||||
| 32 | 0.4.21 | 29, 30 | | **Fail the gate on a stylesheet feature the browser floor lacks, unless it is guarded.** | 3 | 1 | 1 | 3 | 4 | code health by 2 | 6.0 |
|
| 32 | 0.4.21 | 29 | | **Fail the gate on a stylesheet feature the browser floor lacks, unless it is guarded.** | 3 | 1 | 1 | 3 | 4 | code health by 2 | 6.0 |
|
||||||
| 27 | 0.4.21 | | | **Inset the scheduling example's lead text, shift count, alerts and call-to-action by `--pad-x`, like the bars around them.** | 1 | 2 | 1 | 2 | 1 | 9 by 2, 2 by 1 | 4.8 |
|
| 27 | 0.4.21 | | | **Inset the scheduling example's lead text, shift count, alerts and call-to-action by `--pad-x`, like the bars around them.** | 1 | 2 | 1 | 2 | 1 | 9 by 2, 2 by 1 | 4.8 |
|
||||||
| 20 | 0.4.21 | | | **Redirect an anonymous hit on a session-needing flow page to sign-in, instead of a 500.** | 2 | 2 | 2 | 4 | 3 | 2 by 4 | 4.2 |
|
| 20 | 0.4.21 | | | **Redirect an anonymous hit on a session-needing flow page to sign-in, instead of a 500.** | 2 | 2 | 2 | 4 | 3 | 2 by 4 | 4.2 |
|
||||||
| 4 | 0.4.21 | | | **Refuse a permission save whose baseline no longer matches Keto, so concurrent edits are not silently lost.** | 2 | 2 | 2 | 4 | 4 | 2 by 4 | 2.8 |
|
| 4 | 0.4.21 | | | **Refuse a permission save whose baseline no longer matches Keto, so concurrent edits are not silently lost.** | 2 | 2 | 2 | 4 | 4 | 2 by 4 | 2.8 |
|
||||||
@@ -42,12 +42,6 @@
|
|||||||
|
|
||||||
## Details
|
## Details
|
||||||
|
|
||||||
### 30. Give every `oklch()` colour a fallback, so primary buttons stay readable on an engine without it.
|
|
||||||
|
|
||||||
The accent, focus and status tokens in `public/css/styles.css` are `oklch()`, which needs Chromium
|
|
||||||
111. Below it (Tizen 2024, webOS TV 24) `--accent` is invalid, so `.btn-primary` is white text on
|
|
||||||
no background: the sign-in button reads as blank.
|
|
||||||
|
|
||||||
### 29. Show the whole nav tree on an engine without `:has()`, and fold it only where `:has()` works.
|
### 29. Show the whole nav tree on an engine without `:has()`, and fold it only where `:has()` works.
|
||||||
|
|
||||||
README → "Two browser floors": Chromium 87 must reach every page. `public/css/styles.css` hides
|
README → "Two browser floors": Chromium 87 must reach every page. `public/css/styles.css` hides
|
||||||
@@ -55,6 +49,12 @@ README → "Two browser floors": Chromium 87 must reach every page. `public/css/
|
|||||||
no nested entry can be reached. Hide the children only inside `@supports selector(:has(a))`. The
|
no nested entry can be reached. Hide the children only inside `@supports selector(:has(a))`. The
|
||||||
theme switch is inert there too, since every theme rule is a `:has()`: hide it under the same guard.
|
theme switch is inert there too, since every theme rule is a `:has()`: hide it under the same guard.
|
||||||
|
|
||||||
|
### 35. Raise the contrast of the dark-mode primary button's white text and of the light-mode status badges' text to 4.5:1.
|
||||||
|
|
||||||
|
Measured in `public/css/styles.css`: white text on the dark-mode primary button (`--d-accent`
|
||||||
|
`#6ba0e8`) is 2.69:1, and the text of the light-mode pos, warn and info badges on their backgrounds 4.17, 4.40 and 4.48:1. WCAG AA
|
||||||
|
needs 4.5:1 for normal text.
|
||||||
|
|
||||||
### 31. Delete the design-preview rules in `public/css/auth.css` that no view uses.
|
### 31. Delete the design-preview rules in `public/css/auth.css` that no view uses.
|
||||||
|
|
||||||
`.auth-view`, the `:target` screen switch, `.tpl-*`, the `fstate`/`rstate` radios and the
|
`.auth-view`, the `:target` screen switch, `.tpl-*`, the `fstate`/`rstate` radios and the
|
||||||
|
|||||||
Reference in New Issue
Block a user