diff --git a/README.md b/README.md index e7452c24..515a9c47 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,8 @@ If you build with AI agents, this repo is made for you: Studio: look at a plugin, say what you see, and hand the text to your agent. Nothing leaves your computer (Linux today). - **One plugin, several outputs.** The same plugin ships as a web embed or a mobile app, and a plugin - can target the desktop: Rudder is the desktop example, running in the Studio today with its - desktop packaging next. + can target the desktop: Rudder is the desktop example, a corner widget in the Studio and its own + desktop app (`bun run desktop:build rudder-voice`, see [HOWTO-build-the-desktop-app](./documentation/HOWTO-build-the-desktop-app.md)). - **Build together.** [Run it in the cloud](#run-it-in-the-cloud) gives a whole team one shared dev server. diff --git a/documentation/HOWTO-which-gates-need-a-browser.md b/documentation/HOWTO-which-gates-need-a-browser.md new file mode 100644 index 00000000..48420256 --- /dev/null +++ b/documentation/HOWTO-which-gates-need-a-browser.md @@ -0,0 +1,139 @@ +# Which gates need a browser + +There are **238** checks in this repo. **97 need a real browser**; the rest run anywhere. If you are not +the person who owns the browser seat, that second group is not yours to run — and knowing which is +which saves you from a whole class of wrong answer. + +> **The short version.** A gate that reads *files* is headless. A gate that asks *what the page +> actually painted* needs a browser. Run the first kind freely; hand the second kind to whoever owns +> the browser. + +--- + +## Telling them apart + +**Ask the repo, don't grep:** + +```bash +bun run browser-gates # lists every script that launches a browser +``` + +That is the authoritative answer — the same detector `check:browser-gates-guarded` uses, so it +cannot drift from what is enforced. A grep for `playwright|chromium` also works today and is fine +for a quick look, but it is a heuristic: it finds files that *mention* the word, and it would miss a +file that reached the package by path. The name tells you nothing either. `check:packet-render` sounds like a file check and drives a browser; +`check:composites-mount` sounds like it needs one and does not (it mounts every composite through +Vite's SSR loader, no browser at all). + +### Measured on `main` (2026-09-28) + +| | Count | +|---|---:| +| headless — run these yourself | the remainder | +| **browser — not yours unless you own the seat** | **97** | +| neither (shells out to `bun`/`php`) | 3 — `check:mobile`, `check:wp-embed`, `smoke:playground` | + +The browser group is, broadly: everything that renders a map (`check:map-paints`, +`check:map-data-painted`, `check:auto-engine`, `check:maplibre-drops`), everything about layout +(`check:fits-screen`, `check:shell-scroll`, `check:filter-width`, `check:scrim-viewport`), +everything that prints (`check:poster-prints`, `check:print-geometry`, `check:packet-render`), +everything about right-to-left shaping (`check:rtl-shaping`, `check:embedded-rtl`), the English-leak +family (`check:english-leaks`, `check:leaf-english-leaks`, `check:host-locale-contract`), and every +`smoke:*`. + +## Why the split exists, and why it is not negotiable + +One machine, one browser seat. Two agents running Playwright at once fight over it, and the loser +does not fail — **it reports that it could not look**, which is a third outcome most people's mental +model does not have (see below). So the rule is: **one person runs the browser gates.** In this +repo that is the QA agent; ask them rather than starting a second browser. + +This is not a limitation to work around. A source read and a render are *different measurements*, +and confusing them is the most common way to report something false: + +- `check:leaf-english-leaks` can only find a leak by comparing what the catalogue says with **what + the page shows**. Four catalogue gates can all pass — keys exist, differ per locale, are reachable, + are in scope — while a page still paints English. Catalogue-green is not render-green. +- Equally, a headless check often settles a question people assume needs pixels. Of 16 cards parked + as "needs a browser", **7 turned out to be about markup, a path, a manifest field or a gate** — + all readable without one. + +**Ask what property the claim is about**, then pick the tool. Not the other way round. + +--- + +## The guard: you cannot launch a browser by accident any more + +The rule above used to be remembered. It is now **enforced**. Every browser gate reaches Playwright +through `internal/lib/playwright-guarded.mjs`, which refuses to launch unless +`internal/browser-slot.sh` has exported `DOXA_BROWSER_SLOT`: + +```bash +internal/browser-slot.sh bun run check:packet-render # the only way to run one +``` + +Run a browser gate outside the slot and it does not fail — **it exits 75 and tells you the command**: + +``` +this gate launches a browser. Browsers run only on the browser seat, inside the slot: + internal/browser-slot.sh bun run diff --git a/plugin-designs/gospel-resources/resources-map.vue b/plugin-designs/gospel-resources/resources-map.vue index a86cb910..1f2ff425 100644 --- a/plugin-designs/gospel-resources/resources-map.vue +++ b/plugin-designs/gospel-resources/resources-map.vue @@ -1956,7 +1956,7 @@ useShadowStyles( '.gm-key{display:flex;align-items:center;gap:5px;font-size:14px;white-space:nowrap}' + // This one label is a phrase, not a legend word, so it is allowed to wrap rather than // be clipped by the nowrap above. - '.gm-key.wide{white-space:normal;text-align:left;line-height:1.25}' + + '.gm-key.wide{white-space:normal;text-align:start;line-height:1.25}' + '.gm-sw{width:12px;height:12px;border-radius:3px;flex:none}' + '.gm-dot{width:10px;height:10px;border-radius:50%;flex:none;display:inline-block;' + 'border:1.4px solid #fff;box-shadow:0 0 0 1px rgba(0,0,0,.18);vertical-align:-1px}' + @@ -1971,7 +1971,7 @@ useShadowStyles( '.gm-filtered{margin-top:5px;font-size:13.5px;line-height:1.45;color:#3b463d}' + '.is-dark .gm-filtered{color:#e5e7eb}' + '.gm-kclear{border:1px solid #d1d4cd;background:#fff;color:#3b463d;font:600 10.5px inherit;' + - 'padding:3px 8px;border-radius:6px;cursor:pointer;margin-left:4px;min-height:24px}' + + 'padding:3px 8px;border-radius:6px;cursor:pointer;margin-inline-start:4px;min-height:24px}' + '.gm-pinkey-h{font-size:14px;font-weight:600;margin-bottom:4px}' + '.gm-caveat{margin-top:5px;font-size:13.5px;opacity:.72;line-height:1.4}' + '.gm-dot-hole{border:2.5px solid #7d3b30;box-sizing:border-box}' + @@ -2028,7 +2028,7 @@ useShadowStyles( '.gm-q:focus{outline:2px solid #92b195;outline-offset:1px}' + '.gm-qx{position:absolute;inset-inline-end:4px;top:50%;transform:translateY(-50%);border:0;background:transparent;color:#6d766e;font-size:19px;line-height:1;cursor:pointer;padding:4px 6px;min-height:24px}' + '.gm-qres{margin:-2px 0 7px;border:1px solid #d1d4cd;border-radius:8px;background:#fff;overflow:hidden}' + - '.gm-qrow{display:flex;align-items:baseline;gap:6px;width:100%;text-align:left;border:0;background:transparent;font:inherit;font-size:14px;color:#3b463d;padding:6px 8px;cursor:pointer;min-height:30px;border-bottom:1px solid #eceee9}' + + '.gm-qrow{display:flex;align-items:baseline;gap:6px;width:100%;text-align:start;border:0;background:transparent;font:inherit;font-size:14px;color:#3b463d;padding:6px 8px;cursor:pointer;min-height:30px;border-bottom:1px solid #eceee9}' + '.gm-qrow:last-child{border-bottom:0}' + '.gm-qrow:hover{background:#f3f3f1}' + '.gm-qn{font-weight:700}' + @@ -2081,19 +2081,19 @@ useShadowStyles( // must look identical, so the shell carries the box and .gm-pg-btn/.gm-pg-open carry // the interactive bits. cursor:pointer is NOT on the shell — on a resource row the // gaps between the name and the chips are not clickable, and saying otherwise lies. - '.gm-pg{width:100%;box-sizing:border-box;text-align:left;display:block;padding:8px 9px;' + + '.gm-pg{width:100%;box-sizing:border-box;text-align:start;display:block;padding:8px 9px;' + 'margin-bottom:6px;border-radius:8px;' + 'border:1px solid rgba(0,0,0,.10);background:rgba(0,0,0,.02);color:inherit;font:inherit}' + '.is-dark .gm-pg{border-color:rgba(255,255,255,.10);background:rgba(255,255,255,.03)}' + '.gm-pg-btn{cursor:pointer;text-decoration:none;color:inherit}' + // The drill-in control on a resource row: a bare button that inherits the shell's // look, so the row still reads as one tap-target for the name + language line. - '.gm-pg-open{display:block;width:100%;text-align:left;padding:0;margin:0;border:0;' + + '.gm-pg-open{display:block;width:100%;text-align:start;padding:0;margin:0;border:0;' + 'background:transparent;color:inherit;font:inherit;cursor:pointer}' + '.gm-pg-n{display:block;font-weight:700;font-size:15.5px}' + '.gm-pg-l{display:block;font-size:14px;opacity:.75;margin-top:1px}' + '.gm-code{display:inline-block;font:600 10px ui-monospace,SFMono-Regular,Menlo,monospace;' + - 'padding:1px 5px;border-radius:4px;background:rgba(0,0,0,.09);margin-left:5px;vertical-align:1px}' + + 'padding:1px 5px;border-radius:4px;background:rgba(0,0,0,.09);margin-inline-start:5px;vertical-align:1px}' + '.is-dark .gm-code{background:rgba(255,255,255,.12)}' + '.gm-chips{display:flex;flex-wrap:wrap;gap:5px;margin-top:5px}' + // nowrap keeps the short status chips ("Film - link checked") on one line, but a Door43 @@ -3099,7 +3099,7 @@ useShadowStyles(
Loading resources…
-
{{ pgErr }}
+
{{ pgErr }}