feat(templates): GUI template authoring + per-instance template folders - #24
Merged
Conversation
Lets operators author Copier templates entirely in the GUI and scopes
templates per instance. Approved design:
docs/superpowers/specs/2026-05-30-template-authoring-gui-design.md.
Authoring (new template/ service layer, no NiceGUI):
- manifest.py: typed TemplateManifest with round-tripping from_yaml/to_yaml
(hosts TemplateQuestion/template_questions; templates.py re-exports them).
- lint.py: single-source validation (LintFinding error/warn) factored out of
TemplateEngine.resolve + a Jinja2 .jinja parse check; resolve re-raises the
same TemplateLoadError/TemplateCoreFieldRedeclaredError on the same codes.
- authoring.py: atomic writes via atomic_write_bytes, one _safe_target
path-traversal chokepoint (reuses paths.project_name_violations), upload
size/count caps, opt-in .jinja, stale-edit guard.
- ui/pages/template_editor.py + /templates/edit route + manager Edit action
and scope/location selector.
Per-instance template folders (.exlab-wizard/templates/{equipment|project|run}/):
- resolution.py: nearest-wins chain (project -> equipment -> global),
run-scope narrowing, TemplateChoices bundle, reconcile_selection.
- provenance.py: post-CACHE_WRITE frozen copy of the rendering template into
the instance's own typed subfolder; creation.json gains template
.provenance_path (CREATION_JSON_VERSION 1.9 -> 1.10); POST_VALIDATE skips
.exlab-wizard/ so the copy isn't validated as run output.
Reactive wizard wiring (Phase 5):
- Project/run wizards re-resolve their template list from the operator's
equipment/project selection and submit the resolved absolute path (not
templates_dir/name), so a per-instance template renders the right source.
Tests: lint/authoring/manifest/resolution/provenance units, editor + mount
units, and a creation-provenance integration test. Full feature suite green;
ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI feedback on PR #24: - lint job (ruff format --check): reformat template/lint.py. - lint job (mypy): the Phase-5 reactive handlers used a `lambda e: (setattr(...), cb())` tuple idiom; mypy rejects setattr in an expression (func-returns-value). Replaced with named nested handlers in both wizards (also clearer per review feedback). - coverage job (91% floor; was 90.02%): template_editor.py was only 17% covered (headless payload path only). Added a fake-NiceGUI `ui` surface that records widgets + click/upload handlers by data-testid, exercising the rich render branch end to end: lint banners (clean/warn/error), file list + inline edit/save/delete, codemirror vs textarea fallback, question add/remove/save with default coercion (int/float/bool/choice + uncoercible fallback), upload (with/without callback, unavailable build), unreadable-manifest and unreadable-file branches, and Back. Editor coverage 17% -> 99%. ruff check/format + mypy clean; affected suites green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lets operators author Copier templates entirely in the GUI, and scopes templates per instance so each created project/run carries its own template folder. Implements the approved design at
docs/superpowers/specs/2026-05-30-template-authoring-gui-design.md.The hierarchy stays equipment → project → run (no new "experiment" level). Single feature commit on top of
main(which already includes the distribution/#23 work).What's included
GUI template authoring (new
template/service layer, pure / no NiceGUI):manifest.py— typedTemplateManifestwith round-trippingfrom_yaml/to_yaml(now hostsTemplateQuestion/template_questions;templates.pyre-exports them).lint.py— single source of validation (LintFindingerror/warn) factored out ofTemplateEngine.resolve, plus a Jinja2.jinjaparse check.resolvere-raises the sameTemplateLoadError/TemplateCoreFieldRedeclaredErroron the same conditions (regression-tested).authoring.py— atomic writes viaatomic_write_bytes; one_safe_targetpath-traversal chokepoint (reusespaths.project_name_violations); upload size/count caps; opt-in.jinja; stale-edit guard.ui/pages/template_editor.py+/templates/editroute; manager gains a per-row Edit action and a scope/location selector.Per-instance template folders (
.exlab-wizard/templates/{equipment|project|run}/):resolution.py— nearest-wins chain (project → equipment → global), run-scope narrowing,TemplateChoicesbundle,reconcile_selection.provenance.py— afterCACHE_WRITE, copies the rendering template into the instance's own typed subfolder;creation.jsongainstemplate.provenance_path(CREATION_JSON_VERSION1.9 → 1.10).POST_VALIDATEskips.exlab-wizard/so the frozen copy isn't validated as run output.Reactive wizard wiring:
templates_dir/name), so a per-instance template renders the right source. A review-caught bug — a same-named per-instance override leaving the stored path stale — is fixed via the sharedreconcile_selectionhelper.Robustness
Validate-on-save/lint · atomic & safe writes · path-traversal + upload caps · don't-break-templates-in-use (frozen provenance copy + version-bump guard). Copier stays
unsafe=False/skip_tasks=True.Testing
New units: lint / authoring / manifest / resolution / provenance; editor + mount UI; a creation-provenance integration test. Verified per-suite (sync excluded — known flakiness): 425 tests pass (template 143, mount 185, wizards 13+13, editor 8, templates-page 20, controller-integration 43).
uvx ruff check+format --checkclean.Out of scope (spec §7 — future work)
Raw-YAML advanced editor tab · equipment-template rendering through Copier · opt-in provenance-copy config flag.
🤖 Generated with Claude Code