Andrej Janchevski commited on
Commit Β·
f31c85c
1
Parent(s): 6cc7c3d
docs(plan): add frontend COINs demo plan
Browse filesPlan for the /demos/coins interactive page: query-structure picker,
dataset and algorithm selectors, SVG query-graph with per-node and
per-edge searchable dropdown pills that grow to fit entity/relation
names and reflow the graph to avoid overlap, and a results dashboard
covering top-K predictions, community-rank callout, and step-by-step
timing vs baseline. Exercises all seven COINs backend endpoints with
every documented input/output field.
.claude/plans/frontend_coins_demo.md
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Frontend COINs Demo: Interactive Query Builder + Reasoning Dashboard
|
| 2 |
+
|
| 3 |
+
Interactive Vue page at `/demos/coins` that lets the user pick a dataset, query structure, and embedding algorithm, fill anchors/relations/variables into a visual query template via searchable dropdowns, click "Reason", and view a dashboard with ranked entity predictions plus COINs-specific stats (community rank, step timing, speedup vs baseline). Exercises **every** COINs backend endpoint and every documented input/output field.
|
| 4 |
+
|
| 5 |
+
## Context
|
| 6 |
+
|
| 7 |
+
The backend exposes seven COINs endpoints under `/api/v1/coins/` (`src/backend/api/views/coins.py`, fully spec'd in `docs/api.yaml`): `GET /datasets`, `GET /datasets/{id}/entities`, `GET /datasets/{id}/relations`, `GET /datasets/{id}/sample-triples`, `GET /models`, `GET /query-structures`, `POST /predict`. The frontend (`src/frontend/`, Vue 3 + Vite + Fomantic-UI + axios + Pinia) currently only ships Home and CV. The Home page has a "Coming soon" `DemoPreviewCard` for COINs and already uses `sampleTriples('nell', ...)` in `FactOfTheDay.vue`, plus the SVG motif-rendering pattern in `components/background/FloatingMotif.vue` β both are the visual/technical reference for this demo. Plan `.claude/plans/backend_coins.md` describes the inference semantics (community rank `rank_c`, `c_err`, Prop. 3.1 speedup) that the dashboard surfaces to the user. This demo is the first of three research demos; `/demos/<slug>` namespacing leaves room for MultiProxAn and KG-Anomaly.
|
| 8 |
+
|
| 9 |
+
## Assumptions and Constraints
|
| 10 |
+
|
| 11 |
+
- Stack stays Vue 3 + Vite + vue-router 4 + Pinia + axios + Fomantic-UI CSS. **No new heavy deps** β the query-structure visualization is hand-rolled SVG (query templates have β€5 nodes / β€3 edges, mirroring the `FloatingMotif.vue` style).
|
| 12 |
+
- All calls go through `src/frontend/src/api/client.js` (`/api/v1` base URL, `apiError()` for messages). All COINs calls live in `src/frontend/src/api/coins.js` (extending the two existing functions).
|
| 13 |
+
- The backend enforces a **global inference lock** (`INFERENCE_BUSY` β HTTP 429). The UI must serialize `POST /coins/predict` per tab and surface 429 with a friendly retry hint.
|
| 14 |
+
- Entity/relation lists are **large** (NELL ~66 k entities, Freebase ~14 k, WordNet ~41 k). Searchable dropdowns must use server-side search via `?q=&page=&page_size=` with debounced typing (β₯200 ms) β never load the full list client-side.
|
| 15 |
+
- Algorithm support varies by structure (only `q2b` supports `2p/3p/2i/3i/ip/pi`; `transe/distmult/complex/rotate/kbgat` only `1p`) and by dataset (`models.available_datasets`). The UI filters selectable algorithms by both.
|
| 16 |
+
- Green palette and dark-mode CSS custom properties from `styles/tokens.css` drive all new styles. Respect `prefers-reduced-motion`. Responsive from 320 px up.
|
| 17 |
+
- **Docs hygiene (per CLAUDE.md):** this plan does not change backend endpoints, so no backend-docs updates needed. If inference shape changes later, update `docs/api.yaml`, `docs/postman/`, and `src/backend/README.md` in the same commit.
|
| 18 |
+
|
| 19 |
+
## Scope
|
| 20 |
+
|
| 21 |
+
**In scope**
|
| 22 |
+
- New route `/demos/coins` with one view and one Pinia store.
|
| 23 |
+
- Query-builder UI: dataset selector, query-structure selector (cards), algorithm selector (filtered), hand-rolled SVG query-graph visualization with entity/relation dropdowns positioned over nodes/edges, top-K slider, "Reason" button.
|
| 24 |
+
- Reusable `SearchableEntityDropdown` / `SearchableRelationDropdown` components (server-side paginated search with debounce).
|
| 25 |
+
- Reusable `QueryStructurePicker` cards that render each template via the shared SVG renderer.
|
| 26 |
+
- Results dashboard: human-readable `query_description`, top-K predictions list (rank, intra-community rank, entity name, score), **Community rank callout** explaining `rank_c`, **Timing panel** with step1/step2/total bars and speedup multiplier + baseline estimate, loading + 429/error states.
|
| 27 |
+
- API wrappers for the five new COINs endpoints; update the existing `sampleTriples` and add `getDatasets`.
|
| 28 |
+
- Nav: add "Demos" link in `NavBar.vue` pointing to `/demos/coins` (single entry now; becomes a dropdown when other demos ship).
|
| 29 |
+
- Home page wiring: update the COINs `DemoPreviewCard` to link to `/demos/coins` and drop the "Coming soon" label.
|
| 30 |
+
- Manual browser verification against a locally running backend (GPU box available per user memory).
|
| 31 |
+
|
| 32 |
+
**Out of scope**
|
| 33 |
+
- MultiProxAn and KG-Anomaly demos.
|
| 34 |
+
- Unit/e2e tests (manual browser verification only, matching the existing frontend-plan precedent).
|
| 35 |
+
- Persisting query state across reloads / shareable URLs (nice-to-have for a later iteration).
|
| 36 |
+
- Advanced KG subgraph visualization around the predicted entity (target node only; no neighbour expansion).
|
| 37 |
+
|
| 38 |
+
## Design
|
| 39 |
+
|
| 40 |
+
### New directory layout
|
| 41 |
+
|
| 42 |
+
```
|
| 43 |
+
src/frontend/src/
|
| 44 |
+
βββ api/
|
| 45 |
+
β βββ coins.js # extended: datasets, entities, relations, sampleTriples, models, queryStructures, predict
|
| 46 |
+
βββ components/
|
| 47 |
+
β βββ coins/
|
| 48 |
+
β βββ QueryStructurePicker.vue # grid of cards, each renders template via QueryGraph
|
| 49 |
+
β βββ QueryGraph.vue # shared SVG renderer: nodes + edges from a structure spec, with <slot> overlays per node/edge for dropdowns/labels
|
| 50 |
+
β βββ SearchableEntityDropdown.vue # debounced server-side entity search; emits {id, name, label}
|
| 51 |
+
β βββ SearchableRelationDropdown.vue # debounced server-side relation search
|
| 52 |
+
β βββ AlgorithmSelector.vue # filters registry by query_structure + dataset
|
| 53 |
+
β βββ ResultsDashboard.vue # composes the three panels below
|
| 54 |
+
β βββ QueryDescription.vue # renders response.query_description as a labelled chain
|
| 55 |
+
β βββ PredictionList.vue # ranked top-K with rank/intra_community_rank/score
|
| 56 |
+
β βββ CommunityRankCallout.vue # rank_c explainer card
|
| 57 |
+
β βββ TimingPanel.vue # step1/step2/total bars, speedup multiplier, baseline
|
| 58 |
+
βββ stores/
|
| 59 |
+
β βββ coinsDemo.js # Pinia: dataset, queryStructure, algorithm, anchors, variables, relations, topK, result, loading, error
|
| 60 |
+
βββ views/
|
| 61 |
+
β βββ demos/
|
| 62 |
+
β βββ CoinsView.vue # composes selector row + QueryGraph (in edit mode) + Reason button + ResultsDashboard
|
| 63 |
+
βββ router/
|
| 64 |
+
βββ index.js # adds `/demos/coins` route
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
### Data flow
|
| 68 |
+
|
| 69 |
+
1. **On mount** of `CoinsView.vue`, fire in parallel:
|
| 70 |
+
- `GET /coins/datasets` β populates dataset selector (shows `name`, `num_entities`, `num_relations`, `description` tooltip).
|
| 71 |
+
- `GET /coins/models` β registry of `{algorithm, name, description, supported_query_structures, available_datasets}`.
|
| 72 |
+
- `GET /coins/query-structures` β array of `{id, name, description, nodes[], edges[]}` consumed by both `QueryStructurePicker` and `QueryGraph`.
|
| 73 |
+
2. User picks **dataset** β store commits it. Anchors/variables cleared (entity IDs are dataset-scoped).
|
| 74 |
+
3. User picks **query structure** (`QueryStructurePicker` cards, each a mini `QueryGraph` render). Store resets `anchors`, `variables`, `relations` to the exact node/edge IDs defined in the chosen structure.
|
| 75 |
+
4. `AlgorithmSelector` recomputes the allowed list: `models.filter(m => m.supported_query_structures.includes(currentStructure) && m.available_datasets.includes(currentDataset))`. Auto-selects the first (typically `q2b` for multi-hop, `rotate` for `1p`).
|
| 76 |
+
5. Main edit area renders `QueryGraph` with the chosen structure. Over each **anchor node** it slots a `SearchableEntityDropdown` (required). Over each **variable node** it slots a `SearchableEntityDropdown` with a "Let model pick" toggle that deletes the key from `variables` when off. Over each **edge** it slots a `SearchableRelationDropdown` (required). Target node is rendered as a distinct "?" badge (never editable).
|
| 77 |
+
6. Dropdown search β `GET /coins/datasets/{id}/entities?q=&page=1&page_size=50` (or `/relations`). Debounced 250 ms, pagination via "Load more". Request cancels if input changes (axios `AbortController`).
|
| 78 |
+
7. Under the graph: a `FactOfTheDay`-style **"Prefill with a random triple"** link β `GET /coins/datasets/{id}/sample-triples?count=1&seed=<ISO date>` (reusing the existing seeded-daily convention), which fills `a`/`r1` for a `1p` query (skipped when structure β `1p`). Uses the existing `sampleTriples` function.
|
| 79 |
+
8. Top-K slider 1β10, defaults 10. "Reason" button disabled until all required anchor/relation slots are filled. Click β `POST /coins/predict` with the **exact** payload:
|
| 80 |
+
```json
|
| 81 |
+
{
|
| 82 |
+
"dataset_id": "...",
|
| 83 |
+
"algorithm": "...",
|
| 84 |
+
"query_structure": "...",
|
| 85 |
+
"anchors": { "a|a1|a2|a3": entity_id, ... },
|
| 86 |
+
"variables": { "v1|v2": entity_id, ... },
|
| 87 |
+
"relations": { "r1|r2|r3": relation_id, ... },
|
| 88 |
+
"top_k": 10
|
| 89 |
+
}
|
| 90 |
+
```
|
| 91 |
+
`variables` is only included for pinned entries.
|
| 92 |
+
9. Response drives `ResultsDashboard`:
|
| 93 |
+
- **`QueryDescription.vue`** renders `response.query_description`.
|
| 94 |
+
- **`PredictionList.vue`** lists each prediction with `rank`, `intra_community_rank`, `entity_name`, `score` (as a progress bar 0β1), linked to a `GET /coins/datasets/{id}/entities?q=<name>` detail peek.
|
| 95 |
+
- **`CommunityRankCallout.vue`** surfaces `timing.rank_c` with copy "Found in community #N out of the partitioned KG". If `rank_c == 0` the panel says "No valid KG answers" and the inference-error path is hit instead.
|
| 96 |
+
- **`TimingPanel.vue`** renders two horizontal bars (`step1_ms`, `step2_ms` with `step1_label`/`step2_label` captions), a third bar for `baseline_estimate_ms` (with `baseline_label`), and a headline "Γ{speedup.toFixed(1)} faster than full-graph inference (Prop. 3.1)". `total_ms` shown as the sum.
|
| 97 |
+
10. Error handling: `INFERENCE_BUSY` (429) β yellow banner "Another query is running β retry in a moment"; `INFERENCE_ERROR` (422) β red banner with `error.message` (typical case: no valid KG answers); `MODEL_UNAVAILABLE` (503) β banner suggesting a different algorithm. All via the existing `apiError()` helper.
|
| 98 |
+
|
| 99 |
+
### API wrappers (`src/frontend/src/api/coins.js`)
|
| 100 |
+
|
| 101 |
+
Extend the file to export:
|
| 102 |
+
- `listDatasets()` β `GET /coins/datasets`
|
| 103 |
+
- `searchEntities(datasetId, { q, page, pageSize, signal })` β `GET /coins/datasets/{id}/entities`
|
| 104 |
+
- `searchRelations(datasetId, { q, page, pageSize, signal })` β `GET /coins/datasets/{id}/relations`
|
| 105 |
+
- `sampleTriples(datasetId, count, seed)` β existing, untouched
|
| 106 |
+
- `listModels()` β `GET /coins/models`
|
| 107 |
+
- `listQueryStructures()` β `GET /coins/query-structures`
|
| 108 |
+
- `predict(payload, { signal })` β `POST /coins/predict`
|
| 109 |
+
|
| 110 |
+
All pass `signal` through to axios for cancellation. All return `response.data` unchanged.
|
| 111 |
+
|
| 112 |
+
### `QueryGraph.vue` layout strategy
|
| 113 |
+
|
| 114 |
+
Use the `nodes[]` and `edges[]` from `GET /coins/query-structures` directly. For each of the 7 structures, assign fixed 2D **direction unit vectors** (not fixed pixel coordinates) keyed by `node.id` β the component computes a geometric skeleton in `viewBox` units, but the dropdown pills are the source of truth for pixel positions. Render:
|
| 115 |
+
- `<line>` per edge, arrow marker reused from `FloatingMotif.vue`'s `motif-arrow` pattern. Edge endpoints are recomputed each layout pass from the actual bounding boxes of the node pills (see below).
|
| 116 |
+
- A `<foreignObject>` per node and per edge exposing `<slot name="node-{id}" :node="node">` / `<slot name="edge-{id}" :edge="edge">`. The slot contents β the dropdown pills β are plain HTML and size themselves to their label text. Falls back to a small `<circle>` + text label when no slot is provided, so `QueryStructurePicker` cards render clean compact mini-previews.
|
| 117 |
+
|
| 118 |
+
### Responsive pill sizing and layout
|
| 119 |
+
|
| 120 |
+
Entity and relation names vary by orders of magnitude (WordNet: "domestic_dog_NN_1"; Freebase: long multi-word labels; NELL: "concept_athletecoach_thomaspace"). The pills **must grow with their label** and the graph **must reflow** so pills never overlap.
|
| 121 |
+
|
| 122 |
+
- Each `SearchableEntityDropdown` / `SearchableRelationDropdown` renders as an **inline-block pill** (`.ui.label`-derived) with `width: max-content; max-width: min(40ch, 90vw); white-space: normal; overflow-wrap: anywhere; padding: 0.4em 0.8em`. Empty state shows a placeholder ("Pick entity", "Pick relation") so the pill has a sensible minimum size. Filled state shows `label || name`, truncated with a tooltip (`title=name`) only when it exceeds `max-width`.
|
| 123 |
+
- `QueryGraph.vue` uses a **two-pass layout**:
|
| 124 |
+
1. Initial pass places each node at its direction unit-vector Γ a base spacing (e.g. 160 px) inside the `viewBox`; edges are routed straight.
|
| 125 |
+
2. After the DOM paints, a `ResizeObserver` attached to every pill measures actual bounding boxes; the component then expands the skeleton along each edge until no two pill boxes overlap (padded by 16 px), recomputes the `viewBox` to fit, and reroutes edge endpoints to the pill box edges (intersect line with rectangle) instead of a circle. The container's `height` follows the final `viewBox` height.
|
| 126 |
+
- The `ResizeObserver` fires on: initial mount, each dropdown selection change, window resize, and font-load (prevents FOUT-triggered overlap). Throttle via `requestAnimationFrame` to one reflow per frame.
|
| 127 |
+
- Below 600 px width, the SVG viewBox falls back to a vertical stack of pills connected by short down-arrows (a "flow" mode) instead of the 2D layout β this keeps long labels legible on phones without horizontal scrolling. The `FloatingMotif` arrow marker is reused unchanged.
|
| 128 |
+
- `QueryStructurePicker` cards render `QueryGraph` in **preview mode** with short static labels ("Anchor", "?"), bypassing the two-pass reflow (fixed coord map) so cards stay compact and uniform.
|
| 129 |
+
|
| 130 |
+
### `stores/coinsDemo.js` shape
|
| 131 |
+
|
| 132 |
+
```js
|
| 133 |
+
state: {
|
| 134 |
+
datasets: [], models: [], structures: [],
|
| 135 |
+
datasetId: '', queryStructure: '', algorithm: '',
|
| 136 |
+
anchors: {}, variables: {}, relations: {},
|
| 137 |
+
topK: 10,
|
| 138 |
+
loading: false, error: '',
|
| 139 |
+
result: null
|
| 140 |
+
}
|
| 141 |
+
```
|
| 142 |
+
|
| 143 |
+
Getters: `selectedStructure`, `allowedAlgorithms`, `canReason` (all required anchor/relation slots filled).
|
| 144 |
+
Actions: `loadRegistry()`, `setStructure()`, `reason()`.
|
| 145 |
+
|
| 146 |
+
### Styling
|
| 147 |
+
|
| 148 |
+
Reuse CSS vars from `styles/tokens.css` (`--primary`, `--primary-soft`, `--motif-stroke`, `--motif-fill`, `--surface`, `--text-muted`, `--shadow-md`). SVG nodes mirror `FloatingMotif` stroke/fill tokens. Dropdowns use Fomantic `.ui.search` + a custom result list driven by Vue. Timing bars use `.ui.progress` overridden to green palette.
|
| 149 |
+
|
| 150 |
+
## Critical files to modify
|
| 151 |
+
|
| 152 |
+
- **Create**: all files under `src/frontend/src/components/coins/`, `src/frontend/src/views/demos/CoinsView.vue`, `src/frontend/src/stores/coinsDemo.js`.
|
| 153 |
+
- **Edit**: `src/frontend/src/api/coins.js` (add six endpoint wrappers), `src/frontend/src/router/index.js` (add `/demos/coins` route), `src/frontend/src/components/layout/NavBar.vue` (add "Demos" link), `src/frontend/src/views/HomeView.vue` or the COINs `DemoPreviewCard` instance (drop "coming soon", add `to="/demos/coins"`).
|
| 154 |
+
|
| 155 |
+
## Reusable references
|
| 156 |
+
|
| 157 |
+
- `src/frontend/src/api/client.js` β axios instance + `apiError()`.
|
| 158 |
+
- `src/frontend/src/api/coins.js:1-13` β existing `sampleTriples`; the new functions follow the same shape.
|
| 159 |
+
- `src/frontend/src/components/home/FactOfTheDay.vue` β canonical loading/error/success pattern (mirror it for the predict flow and dropdown fetches).
|
| 160 |
+
- `src/frontend/src/components/background/FloatingMotif.vue` β SVG node/edge/arrow styling, CSS-var palette, circular-ish layout scaffold to adapt per structure.
|
| 161 |
+
- `src/frontend/src/components/layout/PageSection.vue` β wraps the query-builder and dashboard blocks on the demo view.
|
| 162 |
+
- `src/frontend/src/components/home/SystemStatus.vue` β parallel-fetch + badge-status pattern for the registry-load step.
|
| 163 |
+
- `src/frontend/src/styles/tokens.css` β palette tokens.
|
| 164 |
+
- `docs/api.yaml` β authoritative request/response shapes (keep open while wiring `predict`).
|
| 165 |
+
|
| 166 |
+
## Implementation Steps
|
| 167 |
+
|
| 168 |
+
1. **API layer**: extend `src/frontend/src/api/coins.js` with `listDatasets`, `searchEntities`, `searchRelations`, `listModels`, `listQueryStructures`, `predict`. Thread `AbortSignal` through each. Manual smoke test via browser devtools against local backend.
|
| 169 |
+
2. **Pinia store**: create `src/frontend/src/stores/coinsDemo.js` with state/getters/actions described above. Add `loadRegistry()` firing the three parallel fetches.
|
| 170 |
+
3. **Router + nav**: register `/demos/coins` in `router/index.js` (lazy-loaded). Add "Demos" link to `NavBar.vue`. Point the COINs `DemoPreviewCard` on Home to `/demos/coins` and drop the "Coming soon" label.
|
| 171 |
+
4. **Shared `QueryGraph.vue`**: renders any structure (node+edge spec in β SVG out) with per-node and per-edge slots and a small hard-coded coord map for the 7 structures. Re-use `FloatingMotif`'s arrow marker and CSS vars.
|
| 172 |
+
5. **`QueryStructurePicker.vue`**: grid of cards, each showing a mini `QueryGraph` (no slots, labels-only) + the structure's `name` and `description`. Emits the chosen ID.
|
| 173 |
+
6. **`SearchableEntityDropdown.vue` / `SearchableRelationDropdown.vue`**: controlled input with 250 ms debounce, axios cancel on keystroke, "Load more" pager. Fomantic `.ui.search` styling. Emit `{id, name, label}`; display `label || name`. Pills size to content per the responsive rules above.
|
| 174 |
+
7. **`AlgorithmSelector.vue`**: plain dropdown, options derived from `store.allowedAlgorithms` getter. Shows `name` with `description` tooltip.
|
| 175 |
+
8. **`CoinsView.vue`**: composes the top selector row (dataset + structure picker + algorithm + top-K slider + "Prefill random" link for `1p`), the main `QueryGraph` in edit mode (slots wired to the dropdowns), and the bottom "Reason" button that calls `store.reason()`. Handles the three error classes with banner components.
|
| 176 |
+
9. **Dashboard panels**: `QueryDescription.vue`, `PredictionList.vue`, `CommunityRankCallout.vue`, `TimingPanel.vue`. Compose them in `ResultsDashboard.vue`. Bars via `.ui.progress` with inline-style widths proportional to `total_ms` (or `baseline_estimate_ms` as the denominator, whichever is larger, for a fair visual).
|
| 177 |
+
10. **Responsive + dark-mode pass**: verify β€768 px stacks vertically; SVG viewBox scales; dropdowns full-width on mobile; reflow kicks in below 600 px. Toggle `data-theme="dark"` and confirm palette.
|
| 178 |
+
11. **Polish**: accessibility labels on every SVG overlay (`aria-label`), `prefers-reduced-motion` respected, keyboard navigation through dropdowns.
|
| 179 |
+
|
| 180 |
+
## Verification
|
| 181 |
+
|
| 182 |
+
1. Start the backend locally on the GPU box: `python src/backend/manage.py runserver 0.0.0.0:8000` (checkpoints download on first boot). Confirm `GET http://localhost:8000/api/v1/health` returns all datasets loaded.
|
| 183 |
+
2. Run the frontend: `cd src/frontend && npm run dev`. Open `http://localhost:5173/demos/coins`.
|
| 184 |
+
3. For each of the 7 query structures (`1p, 2p, 3p, 2i, 3i, ip, pi`):
|
| 185 |
+
- Pick dataset (rotate through `wordnet`, `freebase`, `nell`).
|
| 186 |
+
- Verify `AlgorithmSelector` shows only compatible algorithms (only `q2b` for multi-hop; all for `1p`).
|
| 187 |
+
- Fill anchors and relations via the searchable dropdowns (verify `?q=` filtering and pagination).
|
| 188 |
+
- For structures with variables (`2p/3p/ip/pi`), test **both** letting the backend sample (omit key) and pinning a variable.
|
| 189 |
+
- Click "Reason". Dashboard renders `query_description`, exactly `top_k` predictions with monotonic `rank`, non-zero `timing.total_ms`, and a `speedup β₯ 1`.
|
| 190 |
+
4. Seeded-daily prefill on `1p`: click "Prefill with a random triple" β anchor + relation populate from the day's seeded `sample-triples` call.
|
| 191 |
+
5. 429 path: in one tab hold a long-running `predict`; fire a second in another tab β friendly "retry shortly" banner, no crash.
|
| 192 |
+
6. 422 path: craft a query with no valid KG answers (e.g., mismatched entity/relation combo) β red banner with `INFERENCE_ERROR` message; no results list rendered.
|
| 193 |
+
7. Responsive: resize to 375 px width, confirm SVG scales and controls stack. Toggle dark mode, confirm palette. Pick a long-named entity (e.g. NELL `concept_athletecoach_thomaspace`) and verify the pill grows and the graph reflows without overlap.
|
| 194 |
+
8. Postman: cross-check `POST /coins/predict` request body captured from devtools matches an example from `docs/api.yaml` / `docs/postman/` to confirm field names (`anchors`, `variables`, `relations`, `query_structure`, `top_k`).
|