Code review · P3 Task 19, UI shell and launch form¶
review-code on autopilot/P3-t19, base 0713a1e, 2026-10-30. The step is P1 Task 19, built in the local-only order of inbox #80 (a), before Tasks 12 to 16, with the rules of inbox #88. It adds:
packages/spectio-server/src/spectio_server/static.py: the builtweb/distserved at/, only wherecreate_appmounts the API;app.pywires it in and keeps the plain page otherwise;web/: React, Vite, Mantine 7 and TanStack Router; the shell (routes.tsx), a hand-written client (api.ts), and the launch form (launch/LaunchPage.tsx,EngineButtons.tsx,schemaFields.ts,guessEngine.ts), with Vitest tests;- the CI job
webin.github/workflows/ci.yml, which feedsci-ok; - live screenshots in
tools/media/(live.js,shots.py) and theapp-launchshot; - tests:
packages/spectio-server/tests/api/test_web.py; - inbox #88.
The reviewers were code-reviewer and security-reviewer, independent and in parallel. Format: README.md. The rules are owned by dev-env §15.3 req. 17 (serving the screens) and the ui-spec §4.6 note of 2026-10-30 (the 0.0.1 form), both added by the docs commit.
| Round | Head | code-reviewer | security-reviewer |
|---|---|---|---|
| 1 | 8a06492 (the step) |
approve (5 minors, 1 simplify) | approve (3 minors) |
| 2 | 749f621 (engine guess forgotten on edit; enum values unconverted) |
approve (no finding) | approve (no finding) |
| simplify | ae5e988 |
code-simplifier: changed, −14 +8 |
The reports were relayed by the coordinator in summary.
Test-first. The worker reports that both fixes of 749f621 were written as tests first and seen to fail:
- CR-P3T19-01:
an edit of the Pipeline field cancels the inspect in flightnow loads the pipeline to completion, edits the field again, and checks that Nextflow and Snakemake both readaria-pressed=false. Before the fix Nextflow stayed pressed. The single assertion the finding proposed would have passed without the fix, because in the original test the edit happens before any guess. - CR-P3T19-02:
an integer enum is sent as its integer, not its textpicks2from an enum[1, 2]and checks that the request sendsparams: {level: 2}. Before the fix it sent"2".
Gate at ae5e988, from the worktree, re-run by doc-writer:
uv run --locked pytest -q # 503 passed, 15 deselected
uv run --locked ruff check . # All checks passed!
uv run --locked ruff format --check . # 80 files already formatted
uv run --locked mypy packages # Success: no issues found in 79 source files
uv run --locked lint-imports # Contracts: 4 kept, 0 broken.
uv lock --check # Resolved 55 packages
cd web && npx vitest run # 4 files, 17 tests passed
cd web && npx tsc --noEmit # no output, exit 0
cd web && npm run build # dist/index.html and two hashed assets
The web tests are not part of the uv gate. CI runs them in its own job, web.
CODE step=P3-t19 review=approve b/M/m=0/0/5 security=approve b/M/m=0/0/3 rounds=2 simplify=changed -14/+8 tests=pass
The counts add up the two rounds per reviewer. Round 2 found nothing. The one simplify finding (CR-P3T19-03) is not counted.
Findings¶
| Id | Severity | Finding | Status |
|---|---|---|---|
| CR-P3T19-01 | minor | An edit of Pipeline or Revision forgot the loaded pipeline but kept the old engine choice. The old guess stayed pressed while the note said "Load the pipeline to guess the engine", and the aside showed "Nextflow (version not found)". | fixed in 749f621: edit() calls setEngine(null) too; the test is extended (see Test-first) |
| CR-P3T19-02 | minor | Enum options were turned into strings (prop.enum.map(String)), and paramValue sent that text. A schema whose enum holds integers or booleans got the string "1", and the server's JSON Schema check refused it with a 422. |
fixed in 749f621: Field.options holds the raw values (unknown[]); the select shows String(option), and paramValue sends the original value whose text matches |
| CR-P3T19-03 | simplify | static.read repeated the per-step O_NOFOLLOW walk of api/workflows.py (_open, _read). The repository had two copies of one security primitive. |
fixed in ae5e988 by code-simplifier: read calls workflows._open(root.parent, root.name, *parts); root itself is still never followed |
| CR-P3T19-04 | minor | Inbox #88 rule 1 said "TODO(P1 Task 16) in app.py and static.py", but static.py carries only TODO(P1 Task 21). |
fixed by the docs commit: rule 1 names app.py alone |
| CR-P3T19-05 | minor | The live shot cannot be made again from the media.yaml generator line alone. It needs SPECTIO_URL, a running spectio serve, and SPECTIO_DEMO_PIPELINE at a path outside the repository. The capture also left /g/demo-lab on the host, which the user must remove by hand. |
partly fixed by the docs commit: the steps are written in the P3-M4 figures section. A script that starts the server and stages the pipeline is follow-up FU-P3-22. /g/demo-lab is still on the session host |
| CR-P3T19-06 | minor | Item 8 was not done on the branch: there was no Task 19 section in docs/progress/W5/P3-M4.md, and its "merged as #95 / #97" notes were not updated. |
fixed by the docs commit: P3-M4 · Task 19, the banner and the Tasks table |
| SEC-P3T19-01 | minor | The default screens directory is found by walking four levels up from the installed module, with no owner or mode check. Outside an editable checkout it can resolve to a directory that another user can write. Script from that directory would run on the server's origin with full access to the API. | open: follow-up FU-P3-22, due with P1 Task 21, which ships web/dist in the wheel |
| SEC-P3T19-02 | minor | The install scripts are not consistent. CI installs with --ignore-scripts, but the server's "not built" text and the steps for the live shot tell users to run npm --prefix web ci with scripts on, so esbuild's install script runs locally. Nothing in web/ makes ignore-scripts the default. |
open: follow-up FU-P3-22 |
| SEC-P3T19-03 | minor | The screen responses carry a CSP and nosniff, but no Referrer-Policy and no Cross-Origin-Opener-Policy. §15.3 req. 13 and 14 already use Referrer-Policy: no-referrer for served files, and Task 20 adds run pages whose URLs carry run IDs and may link out. |
open: follow-up FU-P3-22, before P1 Task 20 |
Fixes by round¶
| Commit | Findings |
|---|---|
749f621 fix(web): forget the engine guess on edit; send enum values unconverted |
CR-P3T19-01, CR-P3T19-02 |
ae5e988 refactor(P3-t19): simplify static.read onto workflows._open |
CR-P3T19-03 |
| docs commit of this step | CR-P3T19-04, CR-P3T19-06, CR-P3T19-05 (the steps) |
code-simplifier dropped O_CLOEXEC from the old walk: since PEP 446, os.open descriptors are not inheritable anyway. It left two things alone, because both change code from before this step:
static.pyimports_open, a private name ofapi/workflows.py. The clean fix is a shared module.api/logs.py:49,85has a third walk helper, also named_open(descriptor-based, with adirectory=flag). It could join the same shared module later.
For the Progress page¶
The Task 19 section of P3-M4 is written: P3-M4 · Task 19, with its findings and open questions.