Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Slice 67G — tips on every dashboard number, Glossary, Explorer planned total** — completes Slice 67 "Correct and Explainable Dashboard Numbers".
- `GlossaryButton` mounted on every screen via `AppPageChrome`; 10 fixed terms (Run, Planned runs, Configuration, Trial, Query result, Golden query/record, Similarity score, Lone run = 100, Observed vs planned, Est.) live in pure `glossary.ts`.
- Detail screen: `detailExplain.ts` (10 pure explain functions) on every stat card; `StatTile` renders one `InfoTip`; `estimateLabels.ts` expanded with scope-annotated estimates.
- Explorer: `explorerPlan.ts` maps `sweep_plan` → planned-total + formula; `ExplorePanels` header prints `(N of M planned runs have results)`; `FormulaLine` replaces the `observedTotal` prop; Explorer and Detail totals verified by a 5-fixture agreement test.
- List screen: `explainLabels.ts` — experiment count, pagination, Bayesian trials, delete-when-running tips.
- Compare screen: wires `duplicates_collapsed` + `rankScope="group"` to the leaderboard.
- Walkthru: `scoreLabels.ts` expanded; `CrossStoreLeaderboard` and `ImprovementPanel` carry score, rank, overlap and baseline tips.
- Determinism guard (`deterministicNumbers.guard.test.ts`) extended to cover all new pure modules and screens.
- "combination" vocabulary retired in favour of "configuration" throughout.
- **Slice 67F — compare reports collapsed duplicates** — `GET /api/v1/compare` now returns `duplicates_collapsed`, the number of earlier runs of the same config on the same store that were dropped in favour of the latest, so the dashboard can explain why a count is smaller than the raw count. Computed by a pure function; no UI change yet.
- **Slice 67D (first bundle) — dashboard numbers that add up** — the Detail screen, list and Compare/stats panels now agree and say how a number was reached.
- Bayesian experiments use one denominator everywhere on the Detail screen (Total tile, progress ring, subtitle, totals line, planned trials, overview sentence); the totals line reads `Total 20 = 13 attempted + 4 discarded + 3 not started`.
Expand Down
15 changes: 15 additions & 0 deletions docs/plan/GROWTH.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,3 +114,18 @@ Full list: [`invariants.md`](invariants.md).
- **Delta**: `resultsBreakdown.ts` turns the golden-record facts into a formula, an "(observed)" line, a group summary and a partial-runs note; `ResultsBreakdownView.tsx` renders them with the shared `FormulaLine`; `storageUsage.ts` takes the last arithmetic out of `VectorDbStatsPanel`.
- **L1/L2 impact**: frontend only, plus one Python contract test (`test_results_breakdown_types_contract.py`) that compares `types/index.ts` with the keys the real stats assembly emits. `pluralize` and `FormulaLine` gain an optional number formatter; `StatTile.hint` accepts a node.
- **Data-Flow**: db-stats response (`result_runs`, `golden_queries`, `golden_queries_source`, `partial_runs`, `total_results`) or group totals -> pure builders -> footprint line / tile hint; absent facts render nothing.

### 2026-11-06 — Slice 67F: pure duplicate-count function in compare
- **Delta**: a pure `count_duplicates_collapsed(slot_keys)` in `server/core/compare.py` replaces the implicit understanding that the leaderboard is smaller than the raw result set; `build_compare` attaches `duplicates_collapsed` to the response so the dashboard can show the difference.
- **L1/L2 impact**: server + TypeScript type only. No schema or store change; no frontend rendering yet.
- **Data-Flow**: compare slot keys -> `count_duplicates_collapsed` -> `CompareResponse.duplicates_collapsed` -> (67G) `CompareScreen`.

### 2026-11-06 — Slice 67G: explain every dashboard number
- **Delta**: the remaining Slice 67 items land as one bundled PR via four parallel worktrees (A/B/C/D) with disjoint file ownership. Every screen now has: a Glossary button (10 fixed terms), an `InfoTip` on each stat card or count, and a planned-total line that comes from `sweep_plan` rather than JSX arithmetic.
- **Glossary**: `glossary.ts` (pure data), `GlossaryButton.tsx` (keyboard-accessible dialog), mounted on `AppPageChrome`.
- **Detail + Stats tips**: `detailExplain.ts` (10 pure functions), `StatTile` one-tip-per-tile rule, `estimateLabels.ts` expanded.
- **Explorer planned total**: `explorerPlan.ts` maps `sweep_plan` → `{plannedTotal, isBayesian, formula}`; `FormulaLine` replaces the retired `observedTotal` prop; agreement test over 5 fixtures.
- **List/Compare/Walkthru tips**: `explainLabels.ts` for the list screen; `CompareScreen` wires `duplicates_collapsed`; `scoreLabels.ts` expanded for Walkthru.
- **Determinism guard**: `deterministicNumbers.guard.test.ts` now covers all new pure modules and the new screens.
- **L1/L2 impact**: frontend only (all four sub-streams). No server, store or API change; "combination" vocabulary retired.
- **Data-Flow**: `sweep_plan` (API) -> `explorerPlan` -> `FormulaLine` / `InfoTip`; pure label functions -> `InfoTip.explain`; `duplicates_collapsed` -> `CompareScreen` tip.
7 changes: 5 additions & 2 deletions docs/plan/slices/PROGRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,10 @@
| 67A — Explainable numbers: headline fix + `FormulaLine` + inventory | 🔀 ON BRANCH | ~3–4 h | **Must** — Detail "N combinations" omits retrievers and shows 0 for empty paddings; becomes the Cartesian line; shared `FormulaLine`; number inventory table — [`SLICE-67`](02-dashboard/SLICE-67-EXPLAINABLE-NUMBERS.md) · DECISIONS #304 · branch `slice/67a-explainable-headline`, gates green, reviewers APPROVED, real-browser check done · DECISIONS #307–#309 |
| 67B — Explainable numbers: backend `sweep_plan()` | 🔀 ON BRANCH | ~6–8 h | **Must** — one pure function beside `expand_sweep`, computed on read, `grid_equivalent_count` untouched (Bayesian sampler control variable) — [`SLICE-67`](02-dashboard/SLICE-67-EXPLAINABLE-NUMBERS.md) · DECISIONS #305 · branch `slice/67b-sweep-plan`, gates green, four reviews APPROVED · DECISIONS #310 |
| 67C — Explainable numbers: `InfoTip` + `explain` prop | 🔀 ON BRANCH | ~4–5 h | **Should** — dependency-free, keyboard and touch, one tooltip primitive — [`SLICE-67`](02-dashboard/SLICE-67-EXPLAINABLE-NUMBERS.md) · branch `slice/67c-infotip`, gates green, reviews done · DECISIONS #311 |
| 67D — Explainable numbers: apply to every card | 🔨 IN PROGRESS | ~10–14 h | **Should** — fix Bayesian not-started, progress/ETA and Bayesian sums before the tips describe them; glossary — [`SLICE-67`](02-dashboard/SLICE-67-EXPLAINABLE-NUMBERS.md) · first bundle (D-1, D-2, D-3, Detail wiring, finding F) on branch `slice/67d-explainable-cards`, gates green; tips on every row, glossary, Explorer planned total still open · DECISIONS #312 |
| 67E — Query Results breakdown by golden-dataset record | 🔨 IN PROGRESS | ~4–6 h | **Should** — backend merged (PR #270; per-experiment and group facts, lazy golden count); frontend (`resultsBreakdown` helpers, footprint line and tile hint) built on branch `slice/67e-results-breakdown-frontend`, gates green, review fixes applied — [`SLICE-67`](02-dashboard/SLICE-67-EXPLAINABLE-NUMBERS.md) · DECISIONS #313, #314 |
| 67D — Explainable numbers: apply to every card | ✅ COMPLETE | ~10–14 h | **Should** — Bayesian denominator, progress/ETA, Compare scope, stats estimates — [`SLICE-67`](02-dashboard/SLICE-67-EXPLAINABLE-NUMBERS.md) · merged PR #268 · DECISIONS #312 |
| 67E — Query Results breakdown by golden-dataset record | ✅ COMPLETE | ~4–6 h | **Should** — `resultsBreakdown.ts`, footprint line, tile hint; backend golden facts — [`SLICE-67`](02-dashboard/SLICE-67-EXPLAINABLE-NUMBERS.md) · merged PRs #270, #271 · DECISIONS #313, #314 |
| 67F — duplicates_collapsed backend count | ✅ COMPLETE | ~2 h | **Should** — pure `count_duplicates_collapsed`; `build_compare` returns `duplicates_collapsed` — [`SLICE-67`](02-dashboard/SLICE-67-EXPLAINABLE-NUMBERS.md) · merged PR #272 |
| 67G — Tips on every row, Glossary, Explorer planned total | ✅ COMPLETE | ~12–16 h | **Should** — Glossary button every screen; Detail+Stats InfoTips; Explorer planned-total from `sweep_plan`; List/Compare/Walkthru tips; determinism guard extended — [`SLICE-67`](02-dashboard/SLICE-67-EXPLAINABLE-NUMBERS.md) · PR #273 · DECISIONS #315–#316 |

**Legend**: 📋 PLANNED, 🔨 IN PROGRESS, ✅ COMPLETE, 🔀 BRANCH, 📦 DEFERRED

Expand Down Expand Up @@ -1110,6 +1112,7 @@ Tracks skill runs across slices and sessions. Appended automatically by `/verify

| Date | Branch | Skill | Slice | Outcome | Notes |
|---|---|---|---|---|---|
| 2026-11-06 | slice/67g-explainable-tips | /sync-docs | 67G tips + glossary + explorer planned total | APPLIED | DECISIONS #315 — bundled one PR (67G-A/B/C/D) instead of four separate PRs: disjoint file ownership made parallel worktrees safe; bundling keeps the review trail in one place. DECISIONS #316 — one-tip-per-tile: when a tile has both `hint` and `explain`, a single `InfoTip` renders them stacked (explain first, hint below); "combination" vocabulary retired throughout in favour of "configuration". |
| 2026-10-09 | fix/redis-readiness-audit-remediation | /sync-docs | 53 E2E evidence + audit doc update | APPLIED | Audit doc `REDIS-READINESS-AUDIT.md` verdict NO-GO → **GO**; all 26 finding statuses updated (22 ✅ FIXED, 4 OPEN P2); NOT-RUN ledger updated with E2E results (N1/N2/N6/N7/N10/N12 → PASSED); Release Readiness → GO. SLICE-53 Gate Status — E2E verification evidence appended. PROGRESS Decision Log — E2E evidence entry added (4 runs, 4 retrievers, deletion cascade, healthz/stores verified). |
| 2026-10-09 | fix/redis-readiness-audit-remediation | /sync-docs | 54/55 slice specs + TRAIL.md 53–55 + ES prompt | APPLIED | SLICE-55 Gate Status `📋 PLANNED` → `✅ COMPLETE` (PR #214); TRAIL.md rows 53/54 `📋 PLANNED` → `✅ PASSED` with PR refs + actual durations; TRAIL.md row 55 added (✅ COMPLETE, PR #214). Created `docs/prompts/es-audit-remediation-prompt.md`. |
| 2026-10-09 | fix/redis-readiness-audit-remediation | /sync-docs | 53/54 PROGRESS + Decision Log + slice specs | APPLIED | PROGRESS.md: SLICE-53 row — `95 MB cap` → `256 MB cap` + post-PASSED remediation note; SLICE-54 row — post-PASSED `pipe.execute()` guard note; Decision Log entry 2026-10-09/53 replaced with accurate commit-verified description (import path, precedence bug, batching, unlink, EF_RUNTIME, sparse tokenization, pipelined HGET, run_config_preflight, Docker bind/memory, nightly CI, ADR-007 licence/defaults). SLICE-53 Gate Status — post-PASSED note appended. SLICE-54 spec — Status header and Gate Status updated from `📋 PLANNED` to `✅ PASSED`. |
Expand Down
21 changes: 21 additions & 0 deletions docs/user-guide/dashboard-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,27 @@ This allows direct comparison of configs that used different models or retrieval

---

## 📖 Glossary

Every screen has a **Glossary** button under the app name (next to **About**). It opens a dialog (Escape closes it and focus returns to the button). The terms, in the order shown:

| Term | Meaning |
|---|---|
| Run | One executed pipeline pass: parse, chunk, embed, store, query. |
| Planned runs | Runs the sweep is expected to execute, worked out from the config before it starts. |
| Configuration | One distinct set of parameters (model, chunking, size, overlap, retriever). The sweep header counts configurations. |
| Trial | One Bayesian-search attempt. Only Bayesian sweeps have trials. |
| Query result | The top-K chunks a run returned for one query. |
| Golden query / record | A query with a known good answer in the dataset. |
| Similarity score | Highest single-chunk similarity in a run (max, not average), normalised per group to 0 to 100. |
| Lone run = 100 | A group with one run has nothing to compare against, so min-max normalisation makes it 100 by construction. |
| Observed vs planned | Observed is what stored data shows now; planned is what the config says should happen. |
| Est. (estimated) | Derived from a fixed formula, not measured. |

The wording lives in `frontend/src/components/chrome/glossary.ts`.

---

## 🔄 Polling Behavior

| Screen | Polls | Interval | Stops When |
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/chrome/AppPageChrome.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import AboutButton from '../about/AboutButton';
import GlossaryButton from './GlossaryButton';
import { APP_FOOTNOTE_SUMMARY, APP_NAME, APP_READ_ONLY_NOTE, APP_TAGLINE } from '../../constants';

/** canvas: warm page treatment · darkFrame: shared ink application frame */
Expand Down Expand Up @@ -35,7 +36,10 @@ function BrandBlock({ isDark }: { isDark: boolean }) {
<p className={`mt-2 max-w-md text-xs leading-relaxed sm:text-sm ${isDark ? 'text-slate-300' : 'text-muted'}`}>
{APP_TAGLINE}
</p>
<AboutButton isDark={isDark} />
<div className="flex flex-wrap gap-2">
<AboutButton isDark={isDark} />
<GlossaryButton isDark={isDark} />
</div>
</div>
</div>
);
Expand Down
79 changes: 79 additions & 0 deletions frontend/src/components/chrome/GlossaryButton.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
/**
* Tests for the Glossary control and its pure data module.
*
* Author: RAG Params Finder contributors
* Created: 2026-11-05
* Scope: Slice 67 glossary - open/close/focus and vocabulary GWT scenarios
*/
import { fireEvent, render, screen, within } from '@testing-library/react';
import { describe, expect, it } from 'vitest';
import GlossaryButton from './GlossaryButton';
import { GLOSSARY } from './glossary';

describe('GlossaryButton', () => {
it('Given the control, when clicked, then a Glossary dialog lists every term in order', () => {
/**
* Scenario: Clicking Glossary opens a dialog with the fixed term list.
* Slice: 67 - one glossary in the UI
*/
// -- Given --
render(<GlossaryButton />);
expect(screen.queryByRole('dialog')).toBeNull();

// -- When --
fireEvent.click(screen.getByRole('button', { name: 'Glossary' }));

// -- Then --
const dialog = screen.getByRole('dialog', { name: 'Glossary' });
const terms = within(dialog).getAllByRole('term').map((node) => node.textContent);
expect(terms).toEqual(GLOSSARY.map((entry) => entry.term));
expect(terms).toEqual(
expect.arrayContaining(['Run', 'Planned runs', 'Configuration', 'Trial', 'Query result']),
);
});

it('Given the dialog is open, when Escape is pressed, then it closes and focus returns to the button', () => {
/**
* Scenario: Keyboard users can dismiss the dialog and keep their place.
* Slice: 67 - one glossary in the UI
*/
// -- Given --
render(<GlossaryButton isDark />);
const button = screen.getByRole('button', { name: 'Glossary' });
fireEvent.click(button);

// -- When --
fireEvent.keyDown(document, { key: 'Escape' });

// -- Then --
expect(screen.queryByRole('dialog')).toBeNull();
expect(button).toHaveFocus();
});
});

describe('GLOSSARY data', () => {
it('Given every entry, when scanned, then the retired word "combination" never appears', () => {
/**
* Scenario: The vocabulary decision retired "combination".
* Slice: 67 - vocabulary decision
*/
// -- Given --
const text = GLOSSARY.map((entry) => `${entry.term} ${entry.definition}`).join(' ');

// -- When / Then --
expect(text).not.toMatch(/combination/i);
});

it('Given the entries, when listed, then terms are unique and non-empty', () => {
/**
* Scenario: Stable, duplicate-free data.
* Slice: 67 - one glossary in the UI
*/
// -- Given --
const terms = GLOSSARY.map((entry) => entry.term);

// -- Then --
expect(new Set(terms).size).toBe(terms.length);
expect(GLOSSARY.every((entry) => entry.term && entry.definition)).toBe(true);
});
});
38 changes: 38 additions & 0 deletions frontend/src/components/chrome/GlossaryButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useCallback, useRef, useState } from 'react';
import Dialog from './Dialog';
import { GLOSSARY } from './glossary';

/** Header button that opens the Glossary dialog and gets focus back when it closes. */
export default function GlossaryButton({ isDark = false }: { isDark?: boolean }) {
const [open, setOpen] = useState(false);
const buttonRef = useRef<HTMLButtonElement>(null);
const close = useCallback(() => setOpen(false), []);

return (
<>
<button
ref={buttonRef}
type="button"
aria-haspopup="dialog"
onClick={() => setOpen(true)}
className={`mt-3 min-h-11 rounded-lg border px-3 text-xs font-semibold ${
isDark
? 'border-white/30 text-white hover:bg-white/10'
: 'border-line text-ink hover:bg-canvas'
}`}
>
Glossary
</button>
<Dialog open={open} onClose={close} title="Glossary" returnFocusRef={buttonRef}>
<dl className="space-y-4">
{GLOSSARY.map(({ term, definition }) => (
<div key={term}>
<dt className="text-sm font-semibold text-ink">{term}</dt>
<dd className="mt-1 text-sm leading-relaxed text-muted">{definition}</dd>
</div>
))}
</dl>
</Dialog>
</>
);
}
19 changes: 19 additions & 0 deletions frontend/src/components/chrome/glossary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Slice 67 glossary: the one place the dashboard's number words are defined.
* Pure fixed strings in a stable order; no counts are computed here.
* The retired word for a distinct parameter set must not appear in any entry.
*/
export type GlossaryEntry = { term: string; definition: string };

export const GLOSSARY: readonly GlossaryEntry[] = [
{ term: 'Run', definition: 'One executed pipeline pass: parse, chunk, embed, store, query. Counts of runs are runs that have actually started.' },
{ term: 'Planned runs', definition: 'The number of runs the sweep is expected to execute, worked out from the config before anything starts. Shown as "est." when it is an estimate.' },
{ term: 'Configuration', definition: 'One distinct set of parameters (model, chunking, size, overlap, retriever). The sweep header counts configurations.' },
{ term: 'Trial', definition: 'One Bayesian-search attempt. Only Bayesian sweeps have trials; grid sweeps have runs.' },
{ term: 'Query result', definition: 'The top-K chunks a run returned for one query.' },
{ term: 'Golden query / record', definition: 'A query with a known good answer in the dataset, used to judge whether a run found the right chunk.' },
{ term: 'Similarity score', definition: 'The highest single-chunk similarity found in a run (the max, not the average), normalised per group to 0 to 100.' },
{ term: 'Lone run = 100', definition: 'A group with one run has nothing to compare against, so min-max normalisation makes it 100 by construction.' },
{ term: 'Observed vs planned', definition: 'Observed is what the stored data shows now; planned is what the config says should happen. They differ while a sweep is running or after a cancel.' },
{ term: 'Est. (estimated)', definition: 'A figure derived from a fixed formula rather than measured, for example storage size.' },
];
Loading
Loading