feat(web): pure sharing-policy mapping, and a CI path for frontend logic #353

Merged
Cordy merged 6 commits from web-guard into main 2026-08-28 01:34:55 +00:00
Owner

Follow-up to #352, which left four bare ! translating between negative storage and positive UI with nothing checking them.

The actual finding

The gap was bigger than four characters. CI never read the frontend at all — no node, no test target, no syntax check. web/ contains only static/. So a syntax error in a 7000-line inline script, or a flipped toggle, reached production unchallenged. The inversion was simply the first place it was going to bite.

What was considered

Option Why not
CI grep asserting the negations survive Tests the text, not the behaviour. Breaks on any harmless refactor, and generalises to nothing.
Move inversion into the backend as positive derived fields Kills this bug, but puts two names for one thing on the wire, and the save path still has to choose. Leaves the vacuum.
jsdom + a test runner package.json and node_modules in a repo that deliberately has neither, plus a network dependency on every CI run.

What was built

index.html gains a marked, DOM-free block:

// --- pure:sharing-policy ---
function sharingPolicyToForm(st) { ... }
function sharingPolicyFromForm(f) { ... }
// --- /pure:sharing-policy ---

Load and save both route through it, and the splice asserted that no bare inversion survives outside the block — otherwise the tested copy and the running copy drift apart, which would be worse than no test.

web/test/sharing-policy.test.js lifts that block out and exercises it. No runner, no dependencies, no package.json: the single-file frontend stays single-file. Six cases, including the two that encode the reasoning rather than the behaviour — absent keys must read as both features enabled (the upgrade-safety claim, now pinned on the frontend too), and turning both switches off must store explicit true rather than undefined.

CI gains a Web checks step: extract the inline scripts, node --check them, run the suite.

Verified by mutation

A guard nobody has watched fail is not a guard. The build flips publicLinks: !st.shareLinksDisabled to !! in a scratch copy and requires the suite to fail:

--- mutation check
mutation caught (exit 1)

Note

node --check now covers the whole inline script, so this also closes a gap that had nothing to do with sharing. web/test/ is the place for the next piece of frontend logic that deserves a test, rather than a one-off guard for this one.

Follow-up to #352, which left four bare `!` translating between negative storage and positive UI with nothing checking them. ## The actual finding The gap was bigger than four characters. **CI never read the frontend at all** — no node, no test target, no syntax check. `web/` contains only `static/`. So a syntax error in a 7000-line inline script, or a flipped toggle, reached production unchallenged. The inversion was simply the first place it was going to bite. ## What was considered | Option | Why not | |---|---| | CI grep asserting the negations survive | Tests the text, not the behaviour. Breaks on any harmless refactor, and generalises to nothing. | | Move inversion into the backend as positive derived fields | Kills this bug, but puts two names for one thing on the wire, and the save path still has to choose. Leaves the vacuum. | | jsdom + a test runner | `package.json` and `node_modules` in a repo that deliberately has neither, plus a network dependency on every CI run. | ## What was built `index.html` gains a marked, DOM-free block: ```js // --- pure:sharing-policy --- function sharingPolicyToForm(st) { ... } function sharingPolicyFromForm(f) { ... } // --- /pure:sharing-policy --- ``` Load and save both route through it, and the splice asserted that **no bare inversion survives outside the block** — otherwise the tested copy and the running copy drift apart, which would be worse than no test. `web/test/sharing-policy.test.js` lifts that block out and exercises it. No runner, no dependencies, no `package.json`: the single-file frontend stays single-file. Six cases, including the two that encode the reasoning rather than the behaviour — absent keys must read as both features enabled (the upgrade-safety claim, now pinned on the frontend too), and turning both switches off must store explicit `true` rather than `undefined`. CI gains a **Web checks** step: extract the inline scripts, `node --check` them, run the suite. ## Verified by mutation A guard nobody has watched fail is not a guard. The build flips `publicLinks: !st.shareLinksDisabled` to `!!` in a scratch copy and requires the suite to fail: ``` --- mutation check mutation caught (exit 1) ``` ## Note `node --check` now covers the whole inline script, so this also closes a gap that had nothing to do with sharing. `web/test/` is the place for the next piece of frontend logic that deserves a test, rather than a one-off guard for this one.
The frontend has had no test path at all -- no node in CI, no test
target, nothing. The two settings fields stored negative for upgrade
safety are read and written through four bare negations, and a flipped
one renders a perfectly convincing wrong answer.

This is the smallest honest harness: no runner, no dependencies, no
package.json, so the single-file frontend stays single-file. It pulls a
marked block of pure functions out of index.html and exercises it.

Fails as written -- the block does not exist yet.
feat(web): pure sharing-policy mapping, and a CI path for frontend logic
All checks were successful
ci / test-and-build (pull_request) Successful in 44s
b3670601ae
The four negations that translate between negative storage and positive
UI now live in one DOM-free block that a test can lift out of the file.
Load and save both route through it, and the splice asserts no bare
inversion survives anywhere else -- otherwise the tested copy and the
running copy could drift apart.

CI gains a Web checks step: node --check over the extracted inline
scripts, then the mapping suite. Until now nothing in the pipeline read
the frontend at all, so a syntax error or a flipped toggle reached
production unchallenged.

Verified by mutation: flipping one negation in a scratch copy makes the
suite fail, so the guard is known to catch the bug it exists for.
Cordy merged commit c68b23905b into main 2026-08-28 01:34:55 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: Cordy/Cairn#353
No description provided.