# Verbiflow Play App

This directory is generated by `vf.reports.render(...)`. The data contract lives in:

- `play_spec.json`
- `rows.json`
- `summary.json`
- `manifest.json`
- `custom_views.json`

Run locally:

```bash
bun install
bun run dev -- --port 8767
```

For a distributable build:

```bash
bun run build
bun run preview -- --port 8767
```

`bun run build` copies the JSON/CSV sidecars into `dist/`, so `bun run preview`
serves the same data contract as the local dev server.

Agents should edit `src/*.svelte`, `src/play-data.js`, or `src/styles.css` for play-specific visuals. Do not hand-edit generated JSON sidecars unless you are intentionally changing the underlying data.

## Play Design System

The generated app is the same compact operator product as the Flage frontend,
not a separate dashboard theme. Preserve its visual grammar when tailoring a
Play:

- **Hierarchy:** treat the Play as a full-viewport operator application. Put the
  real dataset in the dominant canvas, keep applicable modalities in the compact
  toolbar, expose useful facets in the filter rail, and use sheets for record
  evidence and methodology. Do not add a marketing-style hero or summary-card
  preamble before the data.
- **Color:** use the semantic tokens in `styles.css`. The default is the
  verbiflow Play house system: a warm charcoal canvas (`--bg-0` … `--bg-3`),
  warm off-white type, and the deep verbiflow brand green (`--brand: #4d726c`,
  with `--brand-strong` for text/hover emphasis) for primary actions, active
  states, and score emphasis. Categorical values (badges, facet dots, avatars,
  graph groups) get stable muted colors through `accentForValue`; do not
  hand-pick per-value colors. The brand mark is the real verbiflow logo in
  `BrandMark.svelte`; never replace it with an invented glyph.
- **Density:** tables are crisp, compact evidence surfaces. Cards are for
  entities or comparisons, not wrappers around every label and value. Prefer
  dividers and spacing over nested card stacks.
- **Type:** Geist (bundled via `@fontsource-variable/geist`) at a dense
  10/11/12/13/14/16 px scale, and Geist Mono for numerals, counts, ranks, and
  identifiers with tabular figures. Micro-labels are 9.5–10 px uppercase with
  letter-spacing. Never expose `snake_case` as UI.
- **Interaction:** every clickable row or card needs a visible focus state and a
  clear selected/active state. Table rows use roving Arrow/j/k/Home/End keyboard
  navigation and update immediately; do not stagger high-frequency data rows.
  Explicit boolean table filters become one-click “Show” filters above the
  result surface; use them for operator outcomes such as available evidence,
  verified contacts, or readiness. Filtered exports must use the visible rows.
  Render external HTTP links as `target="_blank"` anchors with
  `rel="noopener noreferrer"`, opened synchronously from the user's click.
  Artifact hosts own popup escape permissions; generated Plays must never
  replace the report frame as a navigation fallback.
  Motion is limited to 100–220 ms state changes, loading feedback, cards, and
  sheets. Modal focus must remain contained until dismissal. Preserve
  reduced-motion, reduced-transparency, and increased-contrast behavior.
- **Responsive behavior:** keep the primary result available on small screens.
  Move navigation above the view, collapse low-value facets, and let dense data
  scroll rather than shrinking it into illegibility.
- **Copy:** state what happened and what the operator can do next. Use “Waiting
  to start,” “Action required,” and “Run needs attention” instead of exposing
  raw state names as the only explanation. Empty states should name the missing
  input or the filter to change.

The first view must answer the operator's actual review question. Prefer the
table-first `shortlist` view when prioritizing records. It ranks without
automatically declaring a winner; evidence and score detail stay behind row
selection. Keep most reports to two to four non-overlapping views. The shell
already carries the verbiflow brand mark and an on-demand About drawer for
methodology; do not invent an additional logo, marketing hero, duplicate title,
or floating summary card. Custom iframe
views are an escape hatch for isolated visuals; they
must not become the primary shell or recreate navigation, headers, filters, or
page-level spacing inside the Play.

When a Play needs a different aesthetic, change the art direction deliberately
while retaining these hierarchy, state, accessibility, and evidence rules. Do
not replace the base with a stock admin template, marketing hero, decorative
gradient, or a grid of identical rounded cards. The filter rail must contain
real decision dimensions and collapse into an on-demand drawer on small screens.

## Agent Extension Contract

Use the generated source as the working component library for this play:

- `src/App.svelte` owns shell layout, navigation, search, filters, active view routing, and row selection.
- `src/OverviewPanel.svelte` owns live record count and useful categorical distributions. It must derive only from real report fields and never invent insights.
- `src/PlayStoryDrawer.svelte` owns the on-demand scope, sources, metrics, and pipeline narrative. Populate `provenance.scope`, `provenance.sources`, and `provenance.pipeline` with facts from the checked-in workflow; omit the sheet when those facts are unavailable. Set `provenance.story_open` only when that context should explicitly lead the first review; the default is closed.
- `src/ShortlistView.svelte`, `TableView.svelte`, `CardsView.svelte`, `MapView.svelte`, `GraphView.svelte`, `ChartView.svelte`, `TimelineView.svelte`, `EvidenceView.svelte`, and `MetricsView.svelte` are the built-in view components.
- `src/DetailDrawer.svelte` owns selected-row inspection.
- `src/play-data.js` owns sidecar loading, formatting, labels, facets, and filter helpers.
- `src/styles.css` owns the shared operator UI tokens and component styling.

When a play needs a first-class UI beyond the built-in views, add or edit Svelte
components in `src/` and wire them through `App.svelte` using the existing
`play_spec.json` view config. Keep data reads on the sidecar contract
(`rows.json`, `summary.json`, `manifest.json`, `play_spec.json`) so the report
remains replayable.

Use Python custom views in the workflow `views/` directory only for narrow,
one-off visuals that must stay isolated. Those modules render to
`custom_views.json` and the Play displays them in sandboxed iframes. They cannot
inherit the shell's responsive layout or tokens, so prefer typed native views
for primary decisions and Svelte components for genuinely new interaction.
