Skip to content

feat: the build configurator page (docs/configurador/) - #187

Merged
angeloINTJ merged 2 commits into
mainfrom
feat/configurator-page
Sep 26, 2026
Merged

angeloINTJ merged 2 commits into
mainfrom
feat/configurator-page

Conversation

@angeloINTJ

Copy link
Copy Markdown
Owner

What

This is the build configurator page, the last piece of P7, on top of the backend from #186. It lives at https://angelointj.github.io/simut/configurador/ once merged. English and Portuguese, both themes, installable as an app, in the Ângulo standard.

  • Choose. Products appear as selectable cards. Switches sit in one card per group, each with its help text and its measured price on that product: Bluetooth costs 148.9 KB on SIMUT and 134.3 KB on Air.
  • Locks. A switch a rule forbids is locked, with the reason in words. A switch the matrix found to change nothing (same) says so instead of showing +0 B.
  • Result. Flash is measured against the slot and the .bin against the OTA ceiling, with a fit selo: Fits, Tight or Does not fit. The card also shows the RAM figure, broken rules and lit hazards. It is labelled an estimate, and its note gives the measured error of the sum over the 8 real combined builds: up to 4,392 B of flash and 8,192 B of .bin. The "Tight" margin comes from that data.
  • Act.
    • Compile this build dispatches build-custom.yml, finds the run by its title, follows it, and reads the sizes back from the artifact name.
    • Request this build opens a pre-filled issue, for readers without write access.
    • Copy link shares the choice, which lives in the URL.
  • Phone. A bar keeps the figure in sight while the tree scrolls.

Supporting changes:

  • gen_logo.py draws the three app icons from the tokens.
  • check_angulo.py now reads the page's CSS, checks that the standalone pages carry the generated brand, and checks that the app manifest names token colours (4 of 4 by mutation).
  • The language switch CSS moved into site.css.
  • The landing's "Download the firmware" step links here.
  • build.json records the commit the build came from.

Verified

  • test_configurator_page.py (gates job) runs logic.js under node against the generated model:

    • 468 profiles and links, spelled exactly as build_custom.py spells them;
    • 66 single-switch estimates equal to the matrix;
    • 66 locks equal to rule_violations;
    • 9 hostile links refused.

    Negative controls: keys out of order, a lock that never locks, and a link that takes an unknown switch are each caught.

  • Chrome, at 390, 927 and 1280 px, in both themes and both languages:

    • no horizontal overflow;
    • locks, prices and the fit selo are right on all three products;
    • the GitHub flow against a mocked API, where no request leaves the machine: success with sizes read from the artifact name, a 401, a profile refused by the workflow, and a run link from another host dropped.
  • End to end on the real workflow (run 36273093811, Air without Bluetooth): artifact …-bin877604-ram100796. Its .bin (b199d20e) and .uf2 (8f6404c0) are byte-identical to the same profile built locally.

Not verified: the compile button with a real token. That needs the maintainer's own fine-grained token (Actions: read and write, this repository only). The dispatch itself was proven with gh workflow run, the same API call the page makes.

Security (the six failures)

  1. Token storage. The token stays in sessionStorage unless the reader ticks "Remember" (localStorage). It is only ever sent to api.github.com, and the page asks for a short-lived token scoped to this repository. It is readable by any page served from angelointj.github.io, and all of those belong to the owner.
  2. Rules in the front end. The page is not the authority: build_custom.py re-validates the profile and re-applies the rules.
  3. and 4. Not applicable: no database and no object references.
  4. Hardcoded secrets. There are none.
  5. XSS. Text from the model, the URL and the API is set only as textContent. The CSP allows script-src 'self' and connect-src 'self' https://api.github.com, with no inline script and no style attributes. The hash is parsed strictly, and a run link is followed only if it points at this repository's runs.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3

The page P7 promised, on the backend of #186: pick a product, switch
features on or off in a tree grouped by what they are, see whether the
image fits, and compile it. English and Portuguese, both themes, installable
as an app, in the Ângulo standard.

What the page does:
- Products as selectable cards; the switches in one card per group, each
  with its help text and its measured price on that product ("148.9 KB" for
  Bluetooth on SIMUT, "134.3 KB" on Air).
- A switch a rule forbids is locked, with the rule's reason in words. A
  switch the matrix found to change nothing on that product (`same`) says
  so instead of showing +0 B.
- The result: flash used against the slot and .bin against the OTA ceiling,
  with the fit as a selo (Fits, Tight, Does not fit), the RAM figure, the
  rules broken and the hazards lit. It is labelled an estimate, and the
  note says how far the sum missed the 8 real combined builds (up to
  4,392 B of flash, 8,192 B of .bin); "Tight" starts that far from a
  ceiling, derived from the data, not typed.
- Compile this build: dispatches build-custom.yml with the reader's token,
  finds the run by its title, follows it, and reads the measured sizes back
  from the artifact's name. Request this build: a pre-filled issue with the
  profile, for anyone without write access. Copy link: the choice lives in
  the URL (#b=...&on=...&off=...), parsed strictly.
- On a phone, a bar keeps the figure in sight while the tree scrolls.

Safety: text from the model, the link and the GitHub API is set as
textContent only; a Content-Security-Policy allows scripts from the page
alone and connections to api.github.com alone; the token is kept for the
session unless the reader asks to remember it, and goes nowhere else; a
run link from the API is used only if it points at this repository's runs.
The service worker revalidates every request, so a new model.json never
meets a stale app.js inside GitHub Pages' ten-minute cache.

Supporting changes: tools/gen_logo.py draws the three app icons from the
tokens (a maskable one included); tools/check_angulo.py now reads the
page's CSS and checks that each standalone page carries the generated
brand and that the app manifest names token colours (4 of 4 by mutation);
the language switch moved from the landing's <style> to site.css, which
both pages load; the landing's "Download the firmware" step links here;
build.json records the commit the build came from (the Actions checkout is
shallow, so "last commit touching src" was HEAD there anyway).

Verified:
- tools/test_configurator_page.py runs logic.js under node against the
  generated model: 468 profiles and links spelled as build_custom.py
  spells them, 66 single-switch estimates equal to the matrix, 66 locks
  equal to rule_violations, 9 hostile links refused. Negative controls: a
  profile with its keys out of order, a lock that never locks, a link that
  takes an unknown switch, each caught.
- In Chrome, at 390, 927 and 1280 px, both themes, both languages: no
  horizontal overflow; locks, prices and the fit selo right on all three
  products; the GitHub flow against a mocked API (no request left the
  machine): success with sizes read from the artifact name, a 401, a
  profile refused by the workflow, and a run link from another host
  dropped.
- End to end on the real workflow (run 36273093811, Air without
  Bluetooth): artifact simut-...-bin877604-ram100796, .bin b199d20e and
  .uf2 8f6404c0, byte-identical to the same profile built on this machine.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
@github-actions github-actions Bot added documentation Improvements or additions to documentation ci Continuous integration and automation tools Build tools, scripts, and developer tooling labels Sep 26, 2026
…passes

tools/scan_secrets.sh reads `NAME...TOKEN = "six or more characters"` as a
literal credential, and docs/configurador/app.js had
`const TOKEN_KEY = "simut:configurador:token"` — the localStorage key the
token is kept under, not a token. The allowlist is for credentials published
on purpose, which this is not, so the constant is renamed STORAGE_KEY instead.
Every gates-job step now passes locally, in CI order.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jq4auDQppGzmAx1gLKn1z3
@angeloINTJ
angeloINTJ merged commit 1572df9 into main Sep 26, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci Continuous integration and automation documentation Improvements or additions to documentation tools Build tools, scripts, and developer tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant