Record the browser floor and file what breaks below it
CI / full-gate (push) Successful in 4s
Mirror / github-mirror (push) Successful in 4s

This commit was merged in pull request #176.
This commit is contained in:
2026-10-30 21:21:35 +01:00
parent e8dbb2a809
commit 935674bc49
2 changed files with 40 additions and 11 deletions
+11 -2
View File
@@ -142,7 +142,8 @@ From here, render real pages against the app shell and fetch upstream data — s
3. **Maintain:** Plainpages solves only the parts every project shares: auth, the GUI 3. **Maintain:** Plainpages solves only the parts every project shares: auth, the GUI
building blocks (menu, tables, filters, forms) and the plugin structure. Everything building blocks (menu, tables, filters, forms) and the plugin structure. Everything
else is a plugin. else is a plugin.
4. **Maintain:** Every page is a server-rendered document that works with zero JavaScript. 4. **Maintain:** Every page is a server-rendered document that works with zero JavaScript,
on old and embedded browsers too. A newer browser only makes it nicer.
5. **Maintain:** State lives in the URL. Only state the URL can't carry lives in the 5. **Maintain:** State lives in the URL. Only state the URL can't carry lives in the
database, never in local storage or JavaScript. A cookie carries only what must be bound database, never in local storage or JavaScript. A cookie carries only what must be bound
to the browser: the session and its CSRF token. to the browser: the session and its CSRF token.
@@ -176,7 +177,8 @@ From here, render real pages against the app shell and fetch upstream data — s
configuration through environment variables and the drop-in mounts (`plugins/`, configuration through environment variables and the drop-in mounts (`plugins/`,
`config/`, `locales/`), and on upgrades within a minor version that need no change on `config/`, `locales/`), and on upgrades within a minor version that need no change on
their side. their side.
- **Visitors** to a site built on Plainpages. They may rely on pages that load fast and - **Visitors** to a site built on Plainpages, on any browser, including the older engines in
TVs, kiosks, thin clients and embedded panels. They may rely on pages that load fast and
work without JavaScript, with a keyboard and a screen reader. work without JavaScript, with a keyboard and a screen reader.
## Overview ## Overview
@@ -1017,6 +1019,13 @@ underneath it — cramped, but nothing is unreachable. List state
(`?q=…&status=…&sort=…&page=…`) lives **in the URL**, so a view is bookmarkable and shareable; the (`?q=…&status=…&sort=…&page=…`) lives **in the URL**, so a view is bookmarkable and shareable; the
URL is the only state the UI keeps. URL is the only state the UI keeps.
**Two browser floors** serve goal 4. Every page's content and actions stay reachable on
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
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
decorate a page but never decide whether something shows or can be read.
**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.
Once the query names a value, that field renders read-only (`field` with `readonly`, and a `link` back Once the query names a value, that field renders read-only (`field` with `readonly`, and a `link` back
+29 -9
View File
@@ -6,15 +6,17 @@
`Bar = 1` `Bar = 1`
`Next ID = 29` `Next ID = 33`
## 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 |
|---|---|---|---|---|---|---|---|---|---|---|---| |---|---|---|---|---|---|---|---|---|---|---|---|
| 11 | 0.4.21 | | question | **Record the browser floor Plainpages supports, and whether the fallback below it is a contract or a courtesy.** | 1 | 1 | 1 | 1 | 2 | 2 by 2, 1 by 1 | 4.3 |
| 19 | 0.4.21 | | question | **Run the documented Docker commands on macOS and fix whatever misbehaves.** | 2 | 2 | 1 | 3 | 3 | 1 by 3 | 3.3 | | 19 | 0.4.21 | | question | **Run the documented Docker commands on macOS and fix whatever misbehaves.** | 2 | 2 | 1 | 3 | 3 | 1 by 3 | 3.3 |
| 15 | 0.4.21 | | security | **Set `Cache-Control` on every rendered page from the host: `private, no-store` on a gated or signed-in page, a cacheable value on a public one.** | 2 | 2 | 2 | 4 | 3 | 8 by 3 | 3.0 | | 15 | 0.4.21 | | security | **Set `Cache-Control` on every rendered page from the host: `private, no-store` on a gated or signed-in page, a cacheable value on a public one.** | 2 | 2 | 2 | 4 | 3 | 8 by 3 | 3.0 |
| 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 |
| 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 |
| 9 | 0.4.21 | | | **Typecheck `e2e-tests/` in the gate.** | 1 | 1 | 1 | 1 | 3 | code health by 2 | 9.0 | | 9 | 0.4.21 | | | **Typecheck `e2e-tests/` in the gate.** | 1 | 1 | 1 | 1 | 3 | code health by 2 | 9.0 |
@@ -23,6 +25,7 @@
| 10 | 0.4.21 | | | **Pin Playwright's `workers` to a fixed number.** | 1 | 1 | 1 | 1 | 1 | code health by 1 | 7.2 | | 10 | 0.4.21 | | | **Pin Playwright's `workers` to a fixed number.** | 1 | 1 | 1 | 1 | 1 | code health by 1 | 7.2 |
| 18 | 0.4.21 | | | **Record in AGENTS.md and README → Security model that the CSRF token is deliberately unbound to the session, and why.** | 1 | 1 | 1 | 1 | 1 | code health by 1 | 7.2 | | 18 | 0.4.21 | | | **Record in AGENTS.md and README → Security model that the CSRF token is deliberately unbound to the session, and why.** | 1 | 1 | 1 | 1 | 1 | code health by 1 | 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 | | 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 |
| 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 |
| 25 | 0.4.21 | | | **Point the views' references to `docs/plugin-contract.md` at the README.** | 1 | 2 | 1 | 2 | 1 | 9 by 3 | 4.6 | | 25 | 0.4.21 | | | **Point the views' references to `docs/plugin-contract.md` at the README.** | 1 | 2 | 1 | 2 | 1 | 9 by 3 | 4.6 |
| 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 |
@@ -42,13 +45,6 @@
## Details ## Details
### 11. Record the browser floor Plainpages supports, and whether the fallback below it is a contract or a courtesy.
The stylesheet needs `:has()` (Dec 2023); the menus need the popover API (Safari 17) and CSS anchor
positioning for placement, which is newer and unguarded, since the `@supports` test covers popover
only. An iPadOS 16 tablet gets panels flowing inline rather than working menus. Nobody has rendered
that path on real hardware.
### 19. Run the documented Docker commands on macOS and fix whatever misbehaves. ### 19. Run the documented Docker commands on macOS and fix whatever misbehaves.
Needs a Mac. Two suspects, both from `--user "$(id -u):$(id -g)"`: a macOS `id -g` is `20`, which is Needs a Mac. Two suspects, both from `--user "$(id -u):$(id -g)"`: a macOS `id -g` is `20`, which is
@@ -60,6 +56,24 @@ The host owns it, not the reverse proxy. Today responses carry `Vary: Accept-Lan
`Cache-Control`, so a shared cache in front of `web` may store a signed-in page, with that user's `Cache-Control`, so a shared cache in front of `web` may store a signed-in page, with that user's
data, and serve it to the next visitor. data, and serve it to the next visitor.
### 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.
README → "Two browser floors": Chromium 87 must reach every page. `public/css/styles.css` hides
`.nav-children` and only a `:has()` rule reveals them, so below Chromium 105 (webOS TV 22 and 23)
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.
### 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
forgot/register alerts match nothing in `views/` or `examples/`.
### 26. Add a domain glossary and the project's intended horizon to the README. ### 26. Add a domain glossary and the project's intended horizon to the README.
Glossary: host, manifest, chrome, nav fragment, permission token, system plugin, denylist. Also the Glossary: host, manifest, chrome, nav fragment, permission token, system plugin, denylist. Also the
@@ -87,6 +101,12 @@ signed double-submit, and `SameSite=Lax` plus HSTS cover it. Valid while the ses
Only `tsc --strict` runs today. A new tool is a new dependency, so justify it. Only `tsc --strict` runs today. A new tool is a new dependency, so justify it.
### 32. Fail the gate on a stylesheet feature the browser floor lacks, unless it is guarded.
Nothing checks README → "Two browser floors", and no E2E engine is old enough to show a break.
Lint every stylesheet, the examples' included, against Chromium 87, Safari 15 and Firefox 115, and
allow a feature only inside an `@supports` that guards it.
### 27. Inset the scheduling example's lead text, shift count, alerts and call-to-action by `--pad-x`, like the bars around them. ### 27. Inset the scheduling example's lead text, shift count, alerts and call-to-action by `--pad-x`, like the bars around them.
They sit flush against the content pane's edge while the filter bar, table and pager sit 12px in. They sit flush against the content pane's edge while the filter bar, table and pager sit 12px in.