fix(forgeblock): render the five instrument parameter blocks on both static surfaces #214
No reviewers
Labels
No labels
build-dist
priority
P1
priority
P2
priority
P3
priority
P4
bug
duplicate
enhancement
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Toilville/toilville-design!214
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/forgeblock-param-coverage"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Greens
Test Generator / test, red on main since 2026-10-14.#210 ("instrument parameter blocks") and #211 ("handheld (clap) destination tab") added five blocks to the ForgeBlock vocabulary —
page_strip,param_page,param_knob,param_enum,param_toggle— with no WordPress and no email renderer branch. Each fell through to its surface's placeholder, and the vocabulary count the coverage test pins was left at 33 against a vocabulary of 38. Eleven tests:test_wp_renders×5,test_email_renders×5,test_full_vocab_covered.Why it landed red, because the obvious explanation is wrong. The
pathsfilter intest-generator.ymlsits underpush:only;pull_request:carries none. The lane did run on both PRs and reportedfailureon both heads —ee73ffe2at 00:36Z,f2d350bat 01:10Z — and both were merged within minutes. The gate was not skipped and not filtered out. It was visible and merged over, which meansTest Generator / testis not a blocking context onmain. See the Impact Statement.Scope
Five renderer branches on each static surface, in
forgeblock_pattern.py(WordPress FSE pattern) andforgeblock_email.py(HTML email). These are separate code paths —if t == ...chains, not a shared mapping table — so both were edited. Every branch reuses the file's existing helpers (_heading,_facts/_fact_set,_bullets,_p,render_sequence) and takes every colour from the palette, never a literal hex.A parameter control renders as an inert specimen, not a live control. This is not a style preference; it follows from two constraints already in the tree. The vocabulary's own group note says range, default, unit and value names "come from the client's own parameter table, which each plugin build already checks against its OPSL device… UDG never restates a number the DSP owns."
forgeblock_email.pyadds that email cannot host live inputs. So a specimen names the label, the OPSL binding, the control kind and the role, and states no value:param_pageis a composite (of: [param_knob, param_enum, param_toggle]), so its controls go through the same renderer at depth, the waycolumn_setalready recurses into its children; its title ish3and the controlsh4beneath it.page_striprenders the ordered page list with the entry point marked, since static output cannot switch pages.The count bump stays a literal.
33→38, not a derivedlen(). That assertion is the only thing that caught this drift; deriving it would remove the tripwire and let the next block land unrendered in silence.Out of scope, deliberately:
forgeblock.ymlis untouched — the five blocks are already defined and valid, and flippingused: falsewould driftdist/*/artifact.htmland pull a large generated diff into a fix that does not need one._PROGRAMMATICis untouched; explicit branches supersede it for these five. The unknown-type fallthrough is untouched, sincetest_forgeblock_pattern.py:56asserts an unknown type still renders the placeholder. The ASCII renderer already covers all 38 via its[{t}]fallback.Testing
Measured in the CI container (
python:3.12-slim,Toilville_Ritualschecked out beside the repo, as the workflow does — without the sibling,test_pipeline_invariants.pyfails rather than skips and inflates the count):main@9302554— control521 + 8 new tests = 529.
Four new assertions, because the coverage test has no teeth on its own.
test_wp_renders/test_email_rendersonly assert the output is non-empty and lacks the placeholder, so a branch returning"x"satisfies them. Measured: against deliberately hollowed branches, 76/76 of those cases still pass. The new assertions caught it 4/4. Each was verified against a failing control, not just the happy path:"x"block["value"]test_param_control_states_no_valuefails ×3The assertions: each specimen must name its label and its OPSL parameter on all three surface modes (WP, email, letter);
param_pagemust emit its nested controls;page_stripmust list its pages and mark the current one; and a specimen must not restate a DSP-owned value even when the block carriesvalue/default/min/max/unit.No generated-output drift.
build_surfaces.py check,artifact.py check --all(13 boards) andforgeblock_validate.py check(38 blocks, 28 fixtures) all report clean, and the tree stays clean after running them — expected, since all five blocks areused: falseand no deck renders them yet.yamllint .forgejo/workflows/clean.Impact Statement
Risk: low. Five new branches on two static renderers, reachable only by a block type that previously produced a placeholder comment. No existing block's output changes — the drift gates above confirm it byte-for-byte. No token, theme, pattern or asset is touched.
The trigger widening is a smaller fix than it looks, and I want to be precise about it. Adding
udg/**,tools/**and this workflow to thepush:paths closes a real gap: the ForgeBlock coverage tests are driven byudg/config/forgeblock.yml, so a merge tomaintouching onlyudg/previously produced no post-merge run from this lane at all. But it is not what would have stopped #210/#211 — those were PRs,pull_request:has no paths filter, and the lane ran and failed and was merged anyway.The actual gap this PR does not close:
Test Generator / testappears not to be a required status check onmain. Two PRs merged with it red. Making it required is a repo-settings change, not a code change, and is deliberately not in this PR — but until someone makes it, nothing prevents the next recurrence. Branch-protection settings need an authenticated read to confirm, which I could not do.One rule this now enforces: a new block in
forgeblock.ymlwithout a renderer branch on both static surfaces fails the lane twice — once on the count literal, once per surface — and the new assertions mean a stub branch does not buy a pass.