Eliminate all custom HTML - dataface YAML everywhere
Problem
Custom hand-crafted HTML exists in multiple places across the extension and server. Every rendered page should be a dataface page — this simplifies the codebase, ensures consistent look/feel, and establishes a clean invariant: dft serve never returns non-dataface HTML.
Custom HTML audit (complete inventory)
| Location | What | Lines | Action |
|---|---|---|---|
inspector-pages.ts — renderCacheMissPage |
"No profile" page with button | ~40 | Replace with conditional layout in model.yml |
inspector-pages.ts — renderErrorPage |
Error with retry/dbt/profile buttons | ~35 | Replace with error template |
inspector-pages.ts — renderLoadingPage |
Spinner while inspecting | ~20 | Replace with static SVG (media/icons/loading-spinner.svg) |
inspector-pages.ts — renderIdlePage |
"Open a SQL file" placeholder | ~5 | Replace with static SVG or minimal static file |
renderer.py — render_cache_miss_page() |
Server-side "no profile" HTML | ~50 | Delete — template handles both states |
preview-manager.ts — renderError() |
Preview error (CLI not found, render failure) | ~120 | Replace with error dataface template |
preview-manager.ts — wrapInWebview() |
Webview shell (toolbar, CSP) | ~80 | Keep — VS Code webview container, not a dataface page |
server.py — _render_directory_listing() |
File browser at / |
~25 | Replace with dataface template using markdown content: |
html.py — to_html() |
Core render pipeline HTML wrapper | ~30 | Keep — this IS the dataface render output |
Goal: after this task, only wrapInWebview() and to_html() produce HTML — everything else is dataface YAML.
Bugs this fixes
- "Profile this table" button goes blank — the extension replaces server HTML with its own TS-rendered version, triggers CLI profiling, and the state machine races with editor change listeners
- Duplicate cache-miss page — exists in both
inspector-pages.tsandrenderer.py
Context
Philosophy docs consulted
.cursor/rules/anti-slop.mdc— delete duplicate render paths instead of wrapping them.cursor/rules/core.mdc— write regression tests first and keep error states explicitdocs/docs/contributing/architecture/index.md— app surfaces call into core render/serve pathsdocs/docs/contributing/architecture/platform-overview.md— keep engine logic indataface/core, app-specific webview plumbing inapps/ideapps/cloud/DESIGN_PHILOSOPHY.md— server-rendered/YAML-first UI is the intended direction even for transient UI states
Existing conditional layout feature
Dataface already supports include: on any board/chart (see docs/variables/advanced.md):
rows:
- include: "{{ overview_path }}"
rows: [inspector_overview, ...]
- include: "{{ not overview_path }}"
content: |
**No profile for {{ model }}**
Run `dft inspect table {{ model }}` to generate one.
This is the natural way to handle "no data" states in dataface — no new features needed.
Server directory listing as dataface
The _render_directory_listing() in server.py is ~25 lines of custom HTML for a file browser. This becomes a dataface template with a table chart — the server passes discovered files/dirs as inline data and renders a table with clickable links. This means dft serve has a clean invariant: every route returns a dataface-rendered page.
Key files
apps/ide/vscode-extension/src/inspector/inspector-pages.tsapps/ide/vscode-extension/src/inspector/inspector-panel.tsapps/ide/vscode-extension/src/utils/path-validation.tsapps/ide/vscode-extension/src/preview/preview-manager.ts(renderError)dataface/core/inspect/renderer.py(render_cache_miss_page)dataface/core/inspect/templates/model.ymldataface/core/serve/server.py(inspect routes +_render_directory_listing)tests/core/test_inspect_cache_first.pytests/core/test_inspect_server.pyapps/ide/vscode-extension/src/inspector/inspector-panel.test.tsapps/ide/vscode-extension/src/preview/preview-manager.test.ts
Possible Solutions
Option A: Conditional layout in model.yml template plus temp YAML error faces in the extension — Recommended
Extend model.yml to handle the "no profile" state using the existing include: conditional layout. The Jinja pre-processing already knows whether the model has profile data (via overview_path being empty or not). The cache-miss state becomes a content: block in the same template.
The extension always goes through the server for inspect states. For preview errors, render a tiny temporary YAML face through the normal CLI/Python render path instead of hand-writing HTML. Keep only the VS Code webview shell around the rendered page. Loading/idle inspector states stay as static bundled assets because they are not data pages and never go through the server.
Pros: Zero new features, uses existing patterns, eliminates all custom HTML
Cons: Preview needs a small temp-file render helper; if both dft and Python are unavailable, there is no runtime to render the error face
Option B: First-class fallback: / empty: on queries/boards
Add an empty: property to queries or boards that renders alternative content when a query returns no data or fails. More powerful long-term (works for any dashboard), but requires new compile/render work and is a separate feature.
Pros: Reusable across all dashboards, handles any "no data" scenario Cons: New feature to design/build/test, scope creep for this task
Option C: redirect: with condition
Add a redirect: field to YAML spec for routing. Over-engineered — introduces server-side routing into a declarative viz layer.
Pros: Clean separation of concerns Cons: Wrong abstraction level, adds complexity
Plan
Use Option A — conditional layout in templates, no new features. Three areas of work:
Part 1: Inspector status pages
- Modify
model.yml— wrap dashboard content in the conditionalinclude:pattern shown above (overview vs cache-miss branch), showing the "no profile" message anddft inspect tablecommand - Update
renderer.py— deleterender_cache_miss_page(), have the server always render the template (which now handles both states) - Update
server.py— remove thedata-cache-missdetection path, always return rendered template HTML - Simplify
inspector-panel.ts— removedata-cache-missstring matching, remove TS-renderedshowCacheMissPrompt, always use server response. Loading state uses the static SVG spinner atmedia/icons/loading-spinner.svg - Delete
inspector-pages.tsentirely — all four render functions replaced: cache-miss/error by YAML template, loading/idle by static SVG - Fix the "Profile" button — the button in the YAML-rendered cache-miss page triggers profiling via
POST /inspect/profile/, then refresh. Extension intercepts the form POST via webview message
Part 2: Preview error page
- Replace
preview-manager.ts:renderError()— render error states through a dataface YAML error face (a simplecontent:block with markdown showing the error title, summary, and setup instructions). Use the same runtime fallback order as the inspector (dft, thenpython -m dataface.cli.main). ThewrapInWebview()shell stays (it's the VS Code container, not a page)
Part 3: Server directory listing
- Replace
server.py:_render_directory_listing()— create a built-in_directory.ymltemplate with a table chart. The server passes discovered entries as inline query data (name, type, link) and the table renders them with clickable links. Everydft serveroute now returns dataface-rendered output
Tests & invariant
- Tests — update
test_inspect_cache_first.pyandtest_inspect_server.pyfor template-based states. Add a test for directory listing rendering - Enforce invariant — after this task, only two places produce raw HTML:
to_html()(core render pipeline) andwrapInWebview()(VS Code container). Everything else is dataface YAML. Document this in architecture docs
Implementation Progress
- 2026-03-17: Read the required philosophy/architecture docs, audited the current inspector/server/preview render paths, and confirmed the three remaining non-pipeline HTML producers targeted by this task:
inspector-pages.ts,render_cache_miss_page(), and_render_directory_listing(). Also identified that preview errors can use the existing Python-path fallback rather than treating a missingdftbinary as a hard stop. - 2026-03-17: Moved the inspect cache-miss state into
dataface/core/inspect/templates/model.ymlusinginclude:layout branches plus a POST form to/inspect/profile/. Deletedrender_cache_miss_page()and removed the server-side cache-miss HTML special case so/inspect/model/always goes through the normal inspect template render path. - 2026-03-17: Replaced
_render_directory_listing()with a built-in dataface page atdataface/core/serve/templates/directory.yml, rendered via the standard compile/render pipeline with generated markdown links for directories, parent navigation, and.yml/.yamlfaces. - 2026-03-17: Deleted
apps/ide/vscode-extension/src/inspector/inspector-pages.ts. Inspector errors now render through a shared temporary markdown-face helper, while loading/idle states are reduced to static extension-owned shell pages. The webview now intercepts dataface-rendered inspect links, command buttons, and profile form submissions. - 2026-03-17: Preview errors now render through the same shared markdown-face helper, and preview dashboard rendering now uses the same runtime fallback order as the inspector (
dft, thenpython -m dataface.cli.main). - 2026-03-17: Validation run:
uv run pytest tests/core/test_inspect_cache_first.py tests/core/test_inspect_server.py -qnpm run test:unit -- src/inspector/inspector-panel.test.ts src/preview/preview-manager.test.tsnpm run compile
QA Exploration
- N/A: this task touches FastAPI HTML responses and VS Code webviews, not a standalone browser app that can be exercised through the repo's Playwright QA flow.
- [x] QA exploration completed (or N/A for non-UI tasks)
Review Feedback
- 2026-03-17:
cbox reviewwas attempted repeatedly but failed due review infrastructure, not branch-specific findings. One live review session completed exploration and then hit repeated Claude API500 Internal server errorfailures during final synthesis. A clean--model sonnetretry then stalled in review-container bootstrap before the reviewer launched. - 2026-03-17: PR #615 is green and mergeable after
scripts/pr-validate pre,scripts/pr-validate post 615, and GitHub checks completed successfully. - [x] Review cleared