B3a — Konfig+Start Flow-Stationen Implementation Plan¶
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: /config (config.html) von drei parallelen Cards zu einem Flow-Stationen-Layout umbauen — Eval → Judge → Ergebnis als sichtbare, je eigenständige Sequenz; Import/Resume als kompakte Nebeneinstiege; kontextabhängige Default-Station. Reine Template-/Anordnungs-Arbeit, kein Funktions-Umbau.
Architecture: Ein Alpine-x-data über der Seite hält die aktive Station; eine Flow-Leiste (Eval → Judge → Ergebnis, klickbar, ohne 1/2/3-Zahlen) klappt die aktive Station auf. Die Eval- und Judge-Blöcke wandern unverändert in Partials. Die Default-Station wird client-seitig aus den bereits gelieferten Kontext-Daten (eval_only_bundles, resume) bestimmt — keine Änderung an der config_get-Route.
Tech Stack: FastAPI + Jinja2 ([gui] extra) · Alpine.js · model_picker.js (unverändert) · pytest · ruff · mypy strict.
Global Constraints¶
- Python 3.12,
uvonly. Ruff (line-length 100) + mypy strict nach jedem Task grün (uv run ruff check . && uv run ruff format . && uv run mypy touchstone/). - Scope-Disziplin: nur die je Task genannten Dateien. Format nur geänderte Python-Dateien (
uv run ruff format <files>), nieruff format .tree-weit.git addexplizit, niegit add -A.git status --shortvor jedem Commit. - Git-Disziplin (Subagenten): Commit NUR auf dem aktuellen Branch — kein
git checkout/merge/push/main-touch. - Kein Funktions-Umbau: Modell-Picker (
x-data="modelPicker(...)", Checkboxen, Ad-hoc, „vom Endpoint hinzufügen",models_json-Hidden-Field), Judge-Modell-Picker (judgeModelPicker()), Conflict/Error-Banner bleiben funktional identisch — nur umgeordnet. - Routen unberührt:
/runs/eval,/runs/judge,/import-bundle,/endpoint-models,/judge-endpoint-models,/config-view/…,config_getselbst — nicht ändern. model_picker.jsbleibt nicht-deferred und VOR Alpine eingebunden (es registriertmodelPickervoralpine:init) — die Script-Reihenfolge inconfig.htmlbeibehalten (Kommentar dokumentiert es).- Keine 1/2/3-Zahlen im In-Seite-Flow (kollidieren mit Sidebar 1/3/6) — Marker = Aktions-Namen + Pfeile. Deutsch in UI-Text.
Task 1: config.html → Flow-Stationen (Eval/Judge/Ergebnis) + kontextabhängige Default-Station¶
Files:
- Modify: touchstone/gui/templates/config.html
- Create: touchstone/gui/templates/macros/_config_eval.html, touchstone/gui/templates/macros/_config_judge.html
- Test: tests/test_gui_config_flow.py (neu)
Interfaces:
- Consumes (Route-Kontext, unverändert): packs, configs, models_by_config, judge_configs, eval_only_bundles (list[str]), resume (str|None), bundle (str|None), conflict (bool), error.
- Produces: config.html rendert eine Flow-Leiste + Accordion mit den Stationen „Eval starten" / „Judge starten" + einem Ergebnis-Verweis auf /.
- [ ] Step 1: Write the failing test
# tests/test_gui_config_flow.py
from fastapi.testclient import TestClient
from touchstone.gui import app as appmod
from touchstone.gui.control import RunRegistry
class _FakeLauncher:
def spawn(self, argv): return 1
def alive(self, pid): return False
def terminate(self, pid): return None
def _client(tmp_path):
reg = RunRegistry(runs_dir=tmp_path, launcher=_FakeLauncher())
return TestClient(appmod.create_app(runs_dir=tmp_path, registry=reg))
def test_config_renders_flow_stations(tmp_path):
body = _client(tmp_path).get("/config").text
# the flow markers (Eval → Judge → Ergebnis), no 1/2/3 station numbers
assert "Eval starten" in body
assert "Judge starten" in body
assert "Ergebnis" in body
# an Alpine scope holding the active station
assert "x-data" in body and "station" in body
# result step links to the overview
assert 'href="/"' in body
def test_config_preserves_eval_and_judge_function_markers(tmp_path):
body = _client(tmp_path).get("/config").text
# model picker + judge picker + their hidden/submit machinery survive the reorder
assert "modelPicker(" in body
assert "judgeModelPicker(" in body
assert 'name="models_json"' in body
assert 'action="/runs/eval"' in body and 'action="/runs/judge"' in body
- [ ] Step 2: Run test to verify it fails
Run: uv run pytest tests/test_gui_config_flow.py -v
Expected: FAIL — config.html has no flow stations / x-data station scope yet.
-
[ ] Step 3: Implement
-
Lies
config.htmlvollständig. Extrahiere den Eval-Block (die<form action="/runs/eval">-Card, inkl. Resume-Zweig +modelPicker-x-data) nachmacros/_config_eval.htmlund den Judge-Block (<form action="/runs/judge">-Card mitjudgeModelPicker()) nachmacros/_config_judge.html— unverändert im Inhalt (nur ausgeschnitten + via{% include %}eingebunden). - In
config.html: behalte die<script src="/static/model_picker.js">-Zeile nicht-deferred, ganz oben (mit dem bestehenden Kommentar). Lege einen Seiten-x-dataan, der die aktive Station hält und die Default-Station kontextabhängig setzt: - Flow-Leiste (klickbare Marker, keine Zahlen):
- Stationen (Accordion via
x-show):<div x-show="station==='eval'">{% include "macros/_config_eval.html" %}</div>und analog fürjudge. Die Conflict/Error-Banner bleiben oben (vor der Flow-Leiste). - Judge-Hinweis: im Judge-Block (oder davor) bei nicht-leerem
eval_only_bundleseinen dezenten Hinweis:{% if eval_only_bundles %}<p class="muted text-xs">{{ eval_only_bundles|length }} Bundle(s) bereit zum Bewerten.</p>{% endif %}. - Ergebnis-Verweis: der „Ergebnis ansehen"-Link in der Flow-Leiste zeigt auf
/(Übersicht).
(Falls app.css einen .flow-bar-Stil braucht: füge minimale Regeln dort hinzu — das ist erlaubt, static/app.css zur Scope-Liste nehmen und im Commit nennen. Inline-Styles wie im Rest der Templates sind auch ok, dann css unberührt.)
- [ ] Step 4: Run test to verify it passes
Run: uv run pytest tests/test_gui_config_flow.py -v && uv run pytest -q && uv run mypy touchstone/
Expected: PASS, volle Suite grün (bestehende /config-bezogene Tests bleiben grün).
- [ ] Step 5: Commit
git add touchstone/gui/templates/config.html touchstone/gui/templates/macros/_config_eval.html touchstone/gui/templates/macros/_config_judge.html tests/test_gui_config_flow.py
# falls app.css geändert: zusätzlich git add touchstone/gui/static/app.css
git commit -m "feat(gui): /config as flow stations (Eval→Judge→Ergebnis) + context default station"
Task 2: Import + Resume als kompakte Nebeneinstiege¶
Files:
- Modify: touchstone/gui/templates/config.html
- Create: touchstone/gui/templates/macros/_config_sidesteps.html
- Test: tests/test_gui_config_flow.py
Interfaces:
- Consumes: resume (str|None) aus dem Route-Kontext. Das Import-Formular postet an das bestehende /import-bundle.
- [ ] Step 1: Write the failing test
def test_config_sidesteps_import_always_resume_conditional(tmp_path):
# no resume → import present, resume hint absent
body = _client(tmp_path).get("/config").text
assert "Bundle importieren" in body
assert 'action="/import-bundle"' in body
assert "Fortsetzen" not in body # resume hint only in resume mode
# resume mode → resume hint present
body2 = _client(tmp_path).get("/config?resume=2026-01-01_run").text
assert "Fortsetzen" in body2
assert "2026-01-01_run" in body2
- [ ] Step 2: Run test to verify it fails
Run: uv run pytest tests/test_gui_config_flow.py::test_config_sidesteps_import_always_resume_conditional -v
Expected: FAIL — sidesteps not yet structured (or "Fortsetzen" leaks outside resume mode).
-
[ ] Step 3: Implement
-
Extrahiere den Bundle-Import-Block (die
<form action="/import-bundle">-Card) nachmacros/_config_sidesteps.htmlund binde ihn unter den Stationen als kompakten, klar abgesetzten Bereich ein (z. B. unter einer dezenten Überschrift „Weitere Aktionen" /text-sm muted). -
Resume als Nebeneinstieg: der Resume-Modus (
{% if resume %}mit „Fortsetzen: …") wird im Eval-Block angezeigt (Resume ist eine Eval-Fortsetzung) — stelle sicher, dass „Fortsetzen"/der Resume-Hinweis nur bei gesetztemresumerendert (kein Leak in den Normal-Modus). Da_config_eval.html(Task 1) den Resume-Zweig schon trägt, verifiziert dieser Task primär die Bedingung; ergänze in_config_sidesteps.htmlnur den Import. Falls der Resume-Hinweis im Normalmodus leakt, hier fixen. -
[ ] Step 4: Run test to verify it passes
Run: uv run pytest tests/test_gui_config_flow.py -v && uv run pytest -q && uv run mypy touchstone/ && uv run ruff check .
Expected: PASS, volle Suite grün, lint + types clean.
- [ ] Step 5: Commit
git add touchstone/gui/templates/config.html touchstone/gui/templates/macros/_config_sidesteps.html tests/test_gui_config_flow.py
git commit -m "feat(gui): import/resume as compact side-entries on the config flow page"
Notes für die Umsetzung¶
- Kein Funktions-Umbau: Eval-/Judge-Block-Inhalt 1:1 ausschneiden + einbinden; die Tests
modelPicker(/judgeModelPicker(/models_json/action="/runs/…"sind die Regression-Guards. model_picker.js-Script-Reihenfolge (nicht-deferred, vor Alpine) beim Umbau beibehalten.config_get-Route nicht anfassen — die Default-Station kommt rein auseval_only_bundles/resumeim Template.- Nach jedem Task:
uv run pytest -q && uv run ruff check . && uv run mypy touchstone/grün.