# Workflow Reports

<!-- BEGIN GENERATED: guide-map -->
## Guide map

Read only the section needed for the next decision. Line ranges are exact
for this generated runtime target.

| Section | What it covers | Lines |
| --- | --- | --- |
| [Purpose](#purpose) | Scope, ownership, and the central operating question. | 27-36 |
| [Use This When](#use-this-when) | Tasks and conditions that belong in this guide. | 37-43 |
| [Do Not Use This For](#do-not-use-this-for) | Boundaries and the correct routes for excluded work. | 44-52 |
| [Mental Model](#mental-model) | Concepts and state boundaries needed for correct operation. | 53-137 |
| [Play UI Quality Contract](#play-ui-quality-contract) | Treat the generated source as the published verbiflow Play surface. | 138-215 |
| [Preflight](#preflight) | State, authority, inputs, and readiness to verify before execution. | 216-225 |
| [Risk And Side Effects](#risk-and-side-effects) | Effects, approvals, cost, privacy, and retry boundaries. | 226-235 |
| [Canonical Operating Pattern](#canonical-operating-pattern) | The normal end-to-end execution sequence. | 236-245 |
| [Core SDK Surfaces](#core-sdk-surfaces) | Exact source-backed callable contracts owned by this guide. | 246-276 |
| [Data Contracts](#data-contracts) | Required identities, fields, evidence, handles, and durable shapes. | 277-287 |
| [Examples](#examples) | Representative usage patterns and code. | 288-348 |
| [Verification](#verification) | Evidence required before reporting success. | 349-357 |
| [Troubleshooting](#troubleshooting) | Failure diagnosis and corrective routes. | 358-368 |
| [Related References](#related-references) | Adjacent boundaries and when to cross them. | 369-378 |
<!-- END GENERATED: guide-map -->

## Purpose

Use `vf.reports.*` to build final Play UI/report outputs from workflow rows,
artifact rows, or supplied rows. Reports are review artifacts: they write an
editable Svelte Play app plus sidecars, but they do not call providers, spend
credits, stage sequence rows, publish, send, or purchase.

This guide answers how to turn already-produced data into a business-facing
Play UI that can be inspected and published as a Workflow Play output.

## Use This When

- A generated workflow needs a final Play UI under `reports/<run_id>`.
- You need shortlist/table/cards/metrics/map/graph/chart/timeline/evidence views over already-acquired rows.
- You need the sidecar contract for `rows.json`, `rows.csv`, `summary.json`, `manifest.json`, and `play_spec.json`.
- You need to inspect or customize the generated Svelte app for one Play-specific output.

## Do Not Use This For

- Running, repairing, or replaying a workflow root. Use `workflow_operations.md`.
- Syncing a Play revision or eligible report output. Use `workflow_schedules.md`.
- Source acquisition, checkpointing, or provider freshness. Use `workflow_sources.md`.
- Provider-backed audience discovery, enrichment, CRM reads, or LinkedIn/web jobs. Use the relevant provider reference first.
- Sequence source staging, publish, send, or sender/domain purchases. Use `sequences.md` or `mailboxes.md`.
- Quick job debugging where a basic artifact report is enough. `JobResult.to_report(...)` is covered by `artifacts_jobs.md`.

## Mental Model

The generated Play UI is an output of a workflow run, not the workflow runtime and not the backend `/plays` detail page. The normal chain is: workflow run produces rows and outputs; `build_report.py` or `vf.reports.render(...)` writes an editable Vite/Svelte app; `vf.reports.build_static(...)` produces a static bundle; and `vf.workflows.sync(...)` publishes eligible run output to workspace Play history.
The local daemon may perform the same eligible output sync automatically.

Report views read sidecar files. Built-in views include a decision-first ranked
shortlist, real Leaflet clustered map, Sigma graph, schema-aware
tables/cards/metrics, charts, timelines, evidence galleries, and record sheets.
Keep business data in `rows.json`/`rows.csv` and typed view intent in
`play_spec.json`; do not hand-edit sidecars after rendering.

Record drawers treat reviewability as first-class data. Fields configured as
`rationale=` and `confidence=` on shortlist views render together in a dedicated
Reasoning & Confidence section. The generated drawer also recognizes common
suffixes such as `_rationale`, `_reason`, `_reasoning`, `_basis`, and
`_confidence`, so table and other row-driven views do not bury analysis metadata
under “All fields.” This presentation does not invent rationale or confidence;
the producing workflow must materialize them according to `row_operations.md`.

Mixed-entity reports must declare their data contract instead of relying on
column-name inference. Define entities with `vf.reports.entity(...)`, define
canonical property ownership and export behavior with
`vf.reports.property(...)`, and bind each view with
`vf.reports.views.scoped(..., entity="...")`. Property aliases are normalized
once at the report boundary. The renderer fills a missing canonical value from
the first populated declared alias, but removes an alias only when every
populated value is exactly equal to its canonical value. Conflicting source
aliases remain in `rows.json` for audit, but the exporter exposes declared
canonical properties only. Explicit reports reject undeclared row fields. Add
every field to the property contract and use `export="hidden"` for internal
identifiers or diagnostics that must remain in the bundle without appearing in
the picker. Entity and view selectors must use canonical keys.

Property ownership controls export grouping. Alias applicability controls which
input row entities may normalize provider fields. They are separate. Use
`alias_entities=("person",)` when an Account-owned joined property arrives on
Person rows under a provider alias.

```python
entities = (
    vf.reports.entity(
        "account",
        label="Account",
        plural_label="Accounts",
        where={"record_type": "account"},
    ),
    vf.reports.entity(
        "person",
        label="Person",
        plural_label="People",
        where={"record_type": "person"},
    ),
)
properties = (
    vf.reports.property(
        "person_name",
        label="Name",
        entity="person",
        aliases=("full_name", "name"),
        export="default",
    ),
    vf.reports.property(
        "account_name",
        label="Name",
        entity="account",
        aliases=("employer_name",),
        alias_entities=("person",),
        export="default",
    ),
    vf.reports.property("record_type", label="Record type", export="hidden"),
)
```

Use `result["serve_command"]` or generated `serve_play.py` for local review, not
`open result["html_path"]`.
Read `app_source_path` when you need to inspect or customize Play components.
The generated app uses `src/App.svelte` for shell/view routing,
`src/play-data.js` for sidecar loading/formatting, and `src/styles.css` for the
same semantic visual contract as the Flage frontend. To add first-class
Play-specific UI, first ask whether the intent belongs in typed native report
views. When adding typed native views, wire them through `App.svelte` using
`play_spec.json` view config, then edit the generated `src/*.svelte` files for
the views themselves. Custom Python views render through `custom_views.json` in
sandboxed iframes and are only for narrow visuals, never a second page shell.

## Play UI Quality Contract

Treat the generated source as the published verbiflow Play surface. The default
visual system is a compact, dark, full-viewport operator workspace: warm
charcoal tokens, Geist type with Geist Mono numerals, a facet sidebar with the
verbiflow brand block, compact view tabs, crisp table/evidence surfaces, a
record drawer, a pipeline methodology modal, and a floating "About this play"
card. Deep verbiflow green (`--brand: #4d726c`) is the brand accent for primary
actions, active states, and score emphasis; the real verbiflow logo ships in
`BrandMark.svelte`; categorical values get stable colors via `accentForValue`.
Do not fall back to a spacious light report, marketing hero, summary card
preamble, generic card grid, decorative gradient, or raw JSON as the primary
UI. The sidebar must show real decision dimensions and collapse on mobile.

Before customizing, identify the operator's primary review question and put its
answer first. In a mixed-entity Play, navigation has two levels: entity first
(`Companies`, `Contacts`), then only the modes applicable to that entity
(`List`, `Distribution`, `Data`, `Evidence`, `Map`, and so on). Add
`entity=` and `mode_label=` to every scoped view in a mixed report. The
shell derives entity copy from the declared entities and hides the entity control when only one entity exists, so do not add
a redundant Companies/Contacts switch to a single-entity Play. Use the
table-first `vf.reports.views.shortlist(...)` for ranked
account, company, lead, or opportunity decisions. Do not auto-promote the
highest numeric row as a "top candidate"; a rank is not a qualification. Keep
most reports to two to four non-overlapping views, then disclose full fields and
evidence through row detail. Do not invent a logo, brand lockup, marketing hero,
or duplicate page title inside the generated artifact. Use cards only when the
entities genuinely benefit from visual grouping.
Use dividers, whitespace, and typography for ordinary hierarchy. Important rows,
filters, and view changes need visible focus and selected states.
The overview may summarize only real fields from the active view. Prefer useful,
low-cardinality dimensions such as stage, metro, category, role family, round,
decision, or confidence; never turn IDs, domains, timestamps, or near-unique
values into decorative charts. Scoping works with every native modality, so a
company entity may expose a geo map or funding timeline while contacts expose a
role distribution without forcing irrelevant modes into both groups.

A polished Play explains its own construction. Put factual scope, sources, and
pipeline steps in `provenance` when defining the report. The shell exposes these
in an on-demand “How it was built” sheet, so the primary data surface stays
focused without discarding methodology. Use this shape:

```python
provenance={
    "story_open": True,  # opt in only when methodology should lead the first review
    "scope": "What was included, excluded, and whether the Play has side effects.",
    "sources": ["Funding database", "Official company websites"],
    "pipeline": [
        {"title": "Collect candidates", "description": "The factual acquisition step."},
        {"title": "Verify evidence", "description": "The validation and extraction step."},
        {"title": "Build the review surface", "description": "How final rows became this Play."},
    ],
}
```

Only emit a pipeline step that actually ran or is part of the checked-in Play
code. Do not invent marketing metrics, customer claims, sources, or methodology.

Every run/data state needs humane copy in addition to its machine status:

| State | Lead copy | Supporting behavior |
| --- | --- | --- |
| loading | `Opening Play` | Keep layout stable; show bounded progress feedback. |
| empty/filter miss | `No matching records` | Name the search, filter, or missing field to change. |
| queued | `Waiting to start` | Explain that execution begins when capacity is available. |
| running | `Run in progress` | Show completed/total steps and let outputs appear incrementally. |
| blocked | `Action required` | Name the approval, connection, or protected boundary. |
| failed | `Run needs attention` | Surface the exact failure and the safe retry path. |
| completed | `Run complete` | Lead into the reviewable outputs and next decision. |

Keep interactions fast and restrained (normally 100–220 ms), support keyboard
focus and Escape dismissal, and preserve reduced-motion, reduced-transparency,
and increased-contrast behavior. On small screens, move navigation above the
view, collapse secondary filters, and allow dense evidence tables to scroll.
Run the generated app in a browser at desktop and mobile widths before delivery;
inspect the table, cards, empty state, filters, drawer, and at least one
non-success state rather than validating only the build command.

## Preflight

1. Verify the input rows are already produced and sampled. Reports should not hide missing required columns or no-email/no-domain failures.
2. Decide the primary review question: ranked shortlist, table audit, entity cards, metrics, geo/map, relationship graph, timeline, chart, or evidence gallery. Use a custom embed only when no native view can express the visual. Retain every analyzed row in the report source; use status/tier fields, facets, search, and purpose-built views for subsets instead of truncating rows to make the default view smaller. When one report contains multiple entity kinds (for example companies and contacts), add a stable membership field, declare each entity predicate, and wrap every native view with `vf.reports.views.scoped(view, entity="company", mode_label="List")`. The shell then scopes counts, search, facets, exports, record detail, and secondary mode navigation to the active entity. Do not generate a second entity tab when the source contains only one entity kind.
3. Choose a stable output directory, normally `reports/<run_id>` inside a workflow root.
4. Preserve the render result paths: `play_spec_path`, `summary_json_path`,
   `manifest_path`, `app_source_path`, and `build_command`.
5. Preserve `serve_command` and use `build_report.py` / `serve_play.py` unless
   you are directly editing report code.

## Risk And Side Effects

| Operation | Risk class | Rule |
| --- | --- | --- |
| `vf.reports.define(...)` and view builders | local object construction | Safe; no platform call. |
| `vf.reports.render(...)` | local file write | Verify output dir and row count; preserve sidecar paths. |
| `vf.reports.build_static(...)` | local build/file write | Requires a generated Vite/Svelte app and `bun`; produces publishable `dist/`. |
| Serving the Play UI locally | local dev server | Use `serve_play.py` or `serve_command`; choose an available port. |
| `vf.workflows.sync(...)` after report build, or daemon auto-sync | platform write | Uploads changed Play code and eligible run output bundle only; sequence staging/publish/send remain separate. |

## Canonical Operating Pattern

1. Load final workflow rows or artifact rows.
2. Define a `ReportSource` with `vf.reports.source.rows(...)` or an artifact-backed source.
3. Define explicit entities and properties for mixed rows. Then define views with `vf.reports.views.*` using real canonical column names. For LLM-ranked or qualified rows, configure the rationale, confidence, score, and evidence fields that answer “why this result?” in the record drawer. Use `vf.reports.views.scoped(..., entity="...")` around every native view in a mixed-entity report and set `mode_label` so the shell renders entity-first navigation. A scoped view is presentation membership, not a data-deletion rule: keep every analyzed row in the shared report source.
4. Build a `PlaySpec` with `vf.reports.define(...)`.
5. Render with `vf.reports.render(report, output_dir=...)`.
6. Serve locally with `serve_play.py` or the returned `serve_command` and inspect hierarchy, row count, filters, record sheet, keyboard focus, and responsive behavior.
7. Build/sync the static bundle only after the report reflects the final run output.

## Core SDK Surfaces

<!-- BEGIN GENERATED: core-sdk-surfaces -->
| Function | Use When | Inputs | Output And Handles To Preserve | Side Effect | Source |
| --- | --- | --- | --- | --- | --- |
| `vf.reports.build_static` | Build the generated Vite/Svelte Play app into a static publishable dist directory. | Required: `report_dir` (`str \| Path`) pointing at an editable generated Play app. Optional: `install` (`bool`, default from SDK). Builds the publishable Play UI bundle from that report directory. | `dict` with report dir/build metadata and publishable `dist/` path. Preserve the dist path for Workflow Play sync. | local_file_write | installed SDK: `verbiflow/reports.py:1417-1487` |
| `vf.reports.define` | Use after choosing rows and views to create a deterministic Play UI spec. | Required: `name` (`str`), `source` (`ReportSource`), `views` (`Sequence[ReportView]`). Optional: `title`, `description`, `provenance`. Controls the typed Play UI contract written to `play_spec.json`. | `PlaySpec`/`ReportSpec`. Preserve name, source, views, title, description, and provenance for deterministic rendering. | read | installed SDK: `verbiflow/reports.py:1234-1299` |
| `vf.reports.entity` | Declare explicit row membership and copy for one report entity. | key required, label required, plural_label required, where | `report_entity_spec` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data. | read | installed SDK: `verbiflow/reports.py:187-221` |
| `vf.reports.presets.activation_readiness` | Return views for sequence/source readiness, variable coverage, and activation prep. | No inputs. Returns sequence/source readiness and variable coverage views. | `list[ReportView]`. Inspect/adjust view ids and columns before rendering if the preset does not match the row shape. | read | installed SDK: `verbiflow/reports.py:1189-1226` |
| `vf.reports.presets.company_shortlist` | Return a focused ranked review, audit table, and source-evidence view. | Optional: `score_column`, `cluster_by`. Returns shortlist views for prioritized companies/accounts. | `list[ReportView]`. Inspect/adjust view ids and columns before rendering if the preset does not match the row shape. | read | installed SDK: `verbiflow/reports.py:1126-1187` |
| `vf.reports.presets.evidence_gallery` | Return evidence-first views for host/web capture and company evidence plays. | Optional: `title`. Returns evidence-first views for host/web capture outputs. | `list[ReportView]`. Inspect/adjust view ids and columns before rendering if the preset does not match the row shape. | read | installed SDK: `verbiflow/reports.py:1071-1124` |
| `vf.reports.presets.market_map` | Return table, map, clusters, chart, and cards views for market-map plays. | Optional: `columns`, `cluster_by`, `chart_by`. Returns a standard market-map view set. | `list[ReportView]`. Inspect/adjust view ids and columns before rendering if the preset does not match the row shape. | read | installed SDK: `verbiflow/reports.py:952-1013` |
| `vf.reports.presets.review_mining` | Return table, rating chart, cards, and evidence views for review-mining plays. | Optional: `columns`, `rating_column`. Returns views for review/rating analysis. | `list[ReportView]`. Inspect/adjust view ids and columns before rendering if the preset does not match the row shape. | read | installed SDK: `verbiflow/reports.py:1015-1069` |
| `vf.reports.property` | Declare canonical property ownership, aliases, display copy, and export policy. | alias_entities, aliases, entity, export, group, key required | `report_property_spec` payload returned by the SDK operation. Inspect the documented function reference and `raw`/dictionary fields when debugging provider-specific data. | read | installed SDK: `verbiflow/reports.py:224-286` |
| `vf.reports.render` | Use to render a local editable Play app with sidecar JSON, CSV, manifest, app source, and provenance. | Required: `report` (`PlaySpec`/`ReportSpec`), `output_dir` (`str \| Path`). Controls where the editable Svelte Play app, sidecars, manifest, and package files are written. | `dict` with `html_path`, `play_spec_path`, `custom_views_json_path`, `app_source_path`, `package_json_path`, `serve_command`, `build_command`, `manifest_path`, `summary_json_path`, sidecar row files, row count, and rendered view ids. | local_file_write | installed SDK: `verbiflow/reports.py:1302-1414` |
| `vf.reports.source.artifact` | Use when artifact rows were already fetched and should be rendered without another provider call. | Required: `artifact_id` (`str`), `rows` (`Iterable[Mapping[str, object]]`). Use after fetching artifact rows; preserves artifact provenance without another provider call. | `ReportSource` with artifact provenance. Preserve `artifact_id` and row count. | read | installed SDK: `verbiflow/reports.py:323-355` |
| `vf.reports.source.rows` | Use for local rows or workflow rows already loaded into Python. | Required: `rows` (`Iterable[Mapping[str, object]]`), `name` (`str`). Use for rows already loaded from a workflow table, artifact, CSV, or local transform. | `ReportSource` for local rows. Preserve source name and row count. | read | installed SDK: `verbiflow/reports.py:357-387` |
| `vf.reports.source.workflow_table` | Use to render rows directly from a workflow-local SQLite table and run id. | Required: `state_path` (`str \| Path`), `run_id` (`str`), `table` (`str`). Use to render a workflow-local SQLite table for one run. | `ReportSource` tied to workflow state. Preserve `state_path`, `run_id`, and table name. | read | installed SDK: `verbiflow/reports.py:290-321` |
| `vf.reports.views.cards` | Use when each row should be scanned as a compact entity or evidence card. | Optional: `title`, `subtitle`, `fields`, `evidence`, `view_id`. Use for entity/evidence cards; title/subtitle point at row keys. | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:516-570` |
| `vf.reports.views.chart` | Use when a report needs grouped counts or a simple metric summary. | Required: `by` (`str`). Optional: `metric`, `chart_type`, `view_id`. Creates grouped counts or metric charts. | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:785-823` |
| `vf.reports.views.clusters` | Use when rows have a segment, cluster, category, or label field. | Optional: `by`, `label`, `view_id`. Groups rows by a segment/category field. | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:748-783` |
| `vf.reports.views.custom` | Use when built-in views are insufficient and the agent should render local HTML through a Python module. | Required: `view_id` (`str`), `module_path` (`str \| Path`). Optional: `config`. Runs a local Python custom view module into sandboxed iframe output. | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:905-948` |
| `vf.reports.views.evidence_gallery` | Use when report rows contain screenshots, markdown captures, URLs, or other evidence paths. | Optional: `evidence`, `title`, `view_id`. Use when rows contain screenshots, markdown captures, URLs, or evidence file paths. | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:862-903` |
| `vf.reports.views.graph` | Use when rows have a meaningful label plus grouping or link field and the user needs a relationship/cluster view. | Optional: `label`, `group_by`, `link_by`, `view_id`. Use for relationship/cluster views; fields must exist in rows. | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:702-746` |
| `vf.reports.views.map` | Use for place/location rows that include latitude and longitude. | Optional: `lat`, `lng`, `label`, `view_id`. Use only when rows have coordinate fields. | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:667-700` |
| `vf.reports.views.scoped` | Scope any Play UI view to an exact row predicate so mixed-entity reports can keep search, filters, counts, exports, and details entity-specific. | entity, entity_group, entity_label, mode_label, view required, where | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:391-473` |
| `vf.reports.views.shortlist` | Create a compact ranked table with evidence available in record detail. | badge, confidence, entity_label, evidence, fields, links | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:572-665` |
| `vf.reports.views.table` | Use as the default inspectable view for any rowset. | Optional: `columns`, `filters`, `view_id`. Default inspectable tabular view; columns must be real row keys. | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:475-514` |
| `vf.reports.views.timeline` | Use when rows have dates or event ordering. | Required: `date` (`str`). Optional: `title`, `view_id`. Use for chronological rows. | `ReportView`. Preserve `view_id` and configured field names because render and frontend view routing depend on them. | read | installed SDK: `verbiflow/reports.py:825-860` |
<!-- END GENERATED: core-sdk-surfaces -->

## Data Contracts

| File or field | Meaning | Preserve because |
| --- | --- | --- |
| `rows.json` / `rows.csv` | Flattened report rows. | Frontend views and exports read these sidecars. |
| `summary.json` | Row counts, field coverage, source/provenance, evidence counts, and quick review signals. | Agents use it to verify output quality without loading every row. |
| `manifest.json` | File inventory for generated report output. | Snapshot builders and debuggers need all local paths. |
| `play_spec.json` | Typed Play UI contract: source, title, description, views, fields, rationale, confidence, and provenance. | Frontend/report code should read this instead of guessing view intent. |
| `src/*.svelte` | Editable generated Play app source. | Play-specific UI polish belongs here, not in sidecar mutation. |
| `dist/` | Static publishable Play UI bundle. | `vf.workflows.sync(...)` or daemon auto-sync uploads this output when present. |

## Examples

```python
import verbiflow as vf

source = vf.reports.source.rows(rows, name="companies")
report = vf.reports.define(
    name="company-research",
    source=source,
    views=[
        vf.reports.views.shortlist(
            title="name",
            rank_by="score",
            rationale=("score_rationale",),
            confidence=("score_confidence",),
            evidence=("source_quote",),
        )
    ],
)
rendered = vf.reports.render(report, output_dir="/absolute/path/to/workflow/reports/run_1")
print(rendered["serve_command"])
```

For a mixed company/contact report, reuse one source and scope native views
rather than generating separate apps:

```python
company_view = vf.reports.views.scoped(
    vf.reports.views.shortlist(
        view_id="company_list",
        title="company_name",
        rank_by="company_score",
        entity_label="company",
    ),
    entity="company",
    mode_label="List",
)
company_distribution = vf.reports.views.scoped(
    vf.reports.views.chart(
        view_id="company_distribution",
        by="outreach_tier",
        chart_type="bar",
    ),
    entity="company",
    mode_label="Distribution",
)
contact_view = vf.reports.views.scoped(
    vf.reports.views.shortlist(
        view_id="contact_list",
        title="contact_full_name",
        subtitle="contact_title",
        rank_by="contact_score",
        rationale=("contact_validation_rationale",),
        confidence=("contact_validation_confidence",),
        entity_label="contact",
    ),
    entity="contact",
    mode_label="List",
)
```

## Verification

- `summary_json_path` exists and reports the expected row count and key field coverage.
- `play_spec_path` exists and names the intended source and views.
- `serve_command` or `serve_play.py` opens the generated Play UI without blank views.
- Table/card/detail views show the important output fields without requiring source code inspection.
- LLM-ranked or qualified rows expose score, rationale, confidence, and source evidence in the primary record-detail hierarchy; none is buried only in “All fields.”
- If syncing, `vf.workflows.sync(...)` sees a publishable Play UI output and the workspace `/plays` run shows the same final counts.

## Troubleshooting

| Symptom | Check | Fix |
| --- | --- | --- |
| Report opens blank | Verify `rows.json`, `play_spec.json`, and browser console asset paths. | Re-render instead of hand-editing sidecars. |
| UI shows old rows | Confirm `output_dir` matches the selected `run_id`. | Rebuild report for the current run and sync again. |
| Static build fails | Check that the report dir is an editable Vite/Svelte app and `bun` is available. | Run `vf.reports.build_static(report_dir=...)` after render. |
| `/plays` output missing Play UI | Confirm `dist/index.html` exists or let `vf.workflows.sync(...)` publish the eligible completed run output. | Build report bundle before final sync. |
| Custom view missing | Inspect `custom_views.json` and generated `src/App.svelte` view wiring. | Keep custom module under `views/` and re-render. |
| Scores appear without an explanation | Check whether the source rows actually contain rationale/confidence fields and whether shortlist config names them. | Fix the producing `vf.rows.ai_analyze(...)` schema per `row_operations.md`, then pass those fields through `rationale=` and `confidence=`; the report must not fabricate them. |

## Related References

| If you are doing this | Read next |
| --- | --- |
| Operating a generated workflow root | `workflow_operations.md` |
| Syncing a Play or scheduling its synced revision | `workflow_schedules.md` |
| Designing which outputs a Play should expose | `workflow_composition.md` |
| Inspecting jobs or artifact rows before reporting | `artifacts_jobs.md` |
| Preparing rows and checking fields before report rendering | `row_operations.md` |
| Staging report rows to a sequence source | `sequences.md` |
