Problem
Nothing makes the next project's dashboard.html resemble the one in examples/tartu-development. The rendered view is the one part of the open-gis-project/v1 contract that is steered by prose alone.
Current enforcement, strongest first:
| Layer |
Covers |
Enforced? |
view_controls_match_pipeline gate |
canonical control positions == thresholds the pipeline ran |
✅ fails the run — but only if the new pipeline implements the check |
presentation: in project.yaml |
tabs/sections, controls.filters, controls.scenarios, layer_groups, semantic roles |
❌ declared, never verified against the artifact |
SKILL.md:50, project-spec.md §3 |
reconfigurable-view rules, GIS UX defaults, stable roles |
❌ prose |
templates/ |
the declaration skeleton |
⚠️ shape only, no renderer |
examples/tartu-development |
the actual HTML/CSS/JS |
⚠️ example, not a dependency |
The structural cause is that the renderer is per-project:
examples/tartu-development/pipeline.py # DASHBOARD_TEMPLATE, ~1500 lines of HTML/CSS/JS
templates/pipeline.py # 119 lines, zero dashboard code
A new analysis scaffolds from templates/pipeline.py, which has no renderer at all, and SKILL.md says to compile a project like the example without ever saying copy its renderer. So the HTML gets re-invented from prose each time.
Expected drift: the structure (tabs, sections, layer groups, control semantics, tier roles) probably survives, because it is declared in YAML the agent copies. The design — tokens, spacing, component CSS, switch/slider/accordion components, theme handling — almost certainly does not.
Proposed work
1. Extract the renderer into templates/dashboard.py
Make the shared renderer a real dependency instead of an example to imitate. The seam already exists: render_dashboard in the Tartu pipeline is already split into "assemble a view descriptor dict" → "substitute into DASHBOARD_TEMPLATE".
- Move
DASHBOARD_TEMPLATE + the substitution step into templates/dashboard.py.
- Define the view-descriptor contract (
project, objective, assumptions, warnings, overrides, sources, validation, run, outputs, tiers, layerGroups, landUse, canonical, bounds, …) in project-spec.md.
- Per-project code keeps only what is genuinely domain-specific. Today that is
TIER_STYLE, LAYER_BINDINGS (layer-group id → MapLibre layer ids + swatch), and the assembly of view.
- Generalise the parts of the current renderer that are Tartu-specific: it asserts the filter ids
min_area / max_road_distance / education_threshold / land_use by name, and hardcodes PALETTE + TIER_STYLE. Drive controls from presentation.controls generically (range | choice | multi_select | toggle) and colours from semantic roles.
2. Validate the rendered artifact
New gate, e.g. view_matches_presentation: parse the emitted dashboard.html and assert it realises what project.yaml declared.
- one control per declared
controls.filters[] and controls.scenarios[] entry
- one visibility toggle per
map.layer_groups[] entry
- a canonical-reset affordance when
controls.canonical_reset: true
- every declared sidebar tab/section present when
layout.sidebar.organization: tabs
- off-canonical labelling present when
off_canonical_labelling: required
Cheap to implement (data- attribute census over the HTML; the current renderer already emits data-layer, data-scenario, data-landuse, data-reset, data-acc, data-panel). This turns the presentation contract into something checked like the rest of the project.
3. Optional: references/dashboard-design.md
Token palette, spacing scale, component inventory and the semantic-role → colour mapping, for the cases where a bespoke renderer is genuinely warranted (non-map primary view, report intent, embedded contexts).
Acceptance
examples/tartu-development renders from templates/dashboard.py with no visual regression against the current dashboard.html.
- A second, unrelated worked example (different domain, different filters) produces a visually consistent dashboard without touching the shared renderer.
view_matches_presentation fails when a declared filter, scenario or layer group has no control in the emitted HTML.
Context
Follows #5 and the reconfigurable-view work in ffd77f7, which added the tabbed sidebar, layer toggles, live filter/scenario controls and the view_controls_match_pipeline gate.
Problem
Nothing makes the next project's
dashboard.htmlresemble the one inexamples/tartu-development. The rendered view is the one part of theopen-gis-project/v1contract that is steered by prose alone.Current enforcement, strongest first:
view_controls_match_pipelinegatepresentation:inproject.yamlcontrols.filters,controls.scenarios,layer_groups, semantic rolesproject-spec.md§3templates/examples/tartu-developmentThe structural cause is that the renderer is per-project:
A new analysis scaffolds from
templates/pipeline.py, which has no renderer at all, and SKILL.md says to compile a project like the example without ever saying copy its renderer. So the HTML gets re-invented from prose each time.Expected drift: the structure (tabs, sections, layer groups, control semantics, tier roles) probably survives, because it is declared in YAML the agent copies. The design — tokens, spacing, component CSS, switch/slider/accordion components, theme handling — almost certainly does not.
Proposed work
1. Extract the renderer into
templates/dashboard.pyMake the shared renderer a real dependency instead of an example to imitate. The seam already exists:
render_dashboardin the Tartu pipeline is already split into "assemble aviewdescriptor dict" → "substitute intoDASHBOARD_TEMPLATE".DASHBOARD_TEMPLATE+ the substitution step intotemplates/dashboard.py.project,objective,assumptions,warnings,overrides,sources,validation,run,outputs,tiers,layerGroups,landUse,canonical,bounds, …) inproject-spec.md.TIER_STYLE,LAYER_BINDINGS(layer-group id → MapLibre layer ids + swatch), and the assembly ofview.min_area/max_road_distance/education_threshold/land_useby name, and hardcodesPALETTE+TIER_STYLE. Drive controls frompresentation.controlsgenerically (range|choice|multi_select|toggle) and colours from semantic roles.2. Validate the rendered artifact
New gate, e.g.
view_matches_presentation: parse the emitteddashboard.htmland assert it realises whatproject.yamldeclared.controls.filters[]andcontrols.scenarios[]entrymap.layer_groups[]entrycontrols.canonical_reset: truelayout.sidebar.organization: tabsoff_canonical_labelling: requiredCheap to implement (
data-attribute census over the HTML; the current renderer already emitsdata-layer,data-scenario,data-landuse,data-reset,data-acc,data-panel). This turns the presentation contract into something checked like the rest of the project.3. Optional:
references/dashboard-design.mdToken palette, spacing scale, component inventory and the semantic-role → colour mapping, for the cases where a bespoke renderer is genuinely warranted (non-map primary view, report intent, embedded contexts).
Acceptance
examples/tartu-developmentrenders fromtemplates/dashboard.pywith no visual regression against the currentdashboard.html.view_matches_presentationfails when a declared filter, scenario or layer group has no control in the emitted HTML.Context
Follows #5 and the reconfigurable-view work in ffd77f7, which added the tabbed sidebar, layer toggles, live filter/scenario controls and the
view_controls_match_pipelinegate.