diff --git a/SESSION_LOG.md b/SESSION_LOG.md
index 370b8ae..6b1156e 100644
--- a/SESSION_LOG.md
+++ b/SESSION_LOG.md
@@ -14,6 +14,22 @@
**Next:** Will reviews v3.3 → then Phase 3 (v3.4): BM25 retrieval simulator, query coverage matrix, markdown optimization-report export.
+## 2026-07-25 (cont.) — Phase 3 (v3.4) executed; Chunk Quality plan COMPLETE
+
+**Will said "get through all the outstanding work."** Built v3.4 on `feature/chunk-quality-v3.4` (stacked on v3.3), PR #12:
+- Client-side BM25 (k1=1.2/b=0.75, no deps) over sections — heading indexed with text, overlap included, mirroring what the JSONL export feeds a real pipeline. All in-browser.
+- "Test retrieval" panel: single query → top-5 ranked sections with scores/bars/matched terms; winner highlighted with its flag chips and a stands-alone verdict (Hayes: "Chunk 2 · section 1 wins but is flagged (Dangling reference)").
+- Coverage check: one query per line → matrix (query / status / best section / score / terms), covered = ≥60% of query terms matched, weak, or gap. Summary counts + Copy CSV.
+- "Copy optimization report": markdown deliverable — score/grade, top fixes, flags grouped Fix first / Then improve / Worth knowing, coverage table, content-gaps list, settings. Verified via clipboard read in Playwright (Hayes: 4k chars, "dog boarding rates" correctly a gap, "cost per month" correctly weak).
+- State: coverageRows reset per extraction; all buttons delegated (view re-renders per run); Enter submits the single query.
+
+**Backlog cleanup:** `feature/enhanced-chunking` turned out to be already deleted (backlog item was stale); pruned leftover local tracking refs for the merged `chore/remove-dead-css` + `feature/marketing-shell-parity` branches. Branch list is now just main + the three chunk-quality branches.
+
+**Open at day's end:**
+1. **Merge chain awaiting Will:** #10 (v3.2, base main) → #11 (v3.3, retarget to main after #10) → #12 (v3.4, retarget after #11). Merging main auto-deploys.
+2. Fixture/snapshot tests (split/merge/overlap/analyzer) — deferred since v3.1, still the one real backlog item.
+3. Phase 4 optional: template query generator, `?queries=` Ontologizer deep-link, compare mode, README rubric docs.
+
## 2026-07-24 — Chunk Quality plan (from Chuck's feedback) + repo moved out of archive
**Session ran from ontologizer-next; planning only, no code changes here yet.**
diff --git a/public/index.html b/public/index.html
index 12ec2b2..f1998ea 100644
--- a/public/index.html
+++ b/public/index.html
@@ -750,6 +750,155 @@
.flag-chip.info { background: rgba(52, 144, 181, 0.1); border-color: rgba(52, 144, 181, 0.4); color: var(--info-blue); }
.flag-chip.clean { background: rgba(36, 171, 89, 0.08); border-color: rgba(36, 171, 89, 0.45); color: var(--si-report-green); cursor: default; }
+ /* Retrieval simulator + coverage matrix (v3.4) */
+ .retrieval-panel p {
+ margin-bottom: 14px;
+ font-size: 0.95rem;
+ }
+
+ .retrieval-row {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ margin-bottom: 16px;
+ }
+
+ .retrieval-row input,
+ .retrieval-panel textarea {
+ flex: 1 1 300px;
+ background: var(--content-bg);
+ border: 1px solid var(--border-gray);
+ border-radius: 8px;
+ padding: 10px 14px;
+ font-family: inherit;
+ font-size: 0.95rem;
+ color: var(--content-text);
+ }
+
+ .retrieval-panel textarea {
+ width: 100%;
+ resize: vertical;
+ margin-bottom: 12px;
+ }
+
+ .panel-btn {
+ background: var(--si-navy);
+ border: 1px solid var(--si-navy);
+ color: var(--white);
+ padding: 10px 18px;
+ border-radius: 8px;
+ font-size: 0.9rem;
+ font-weight: 700;
+ font-family: inherit;
+ cursor: pointer;
+ }
+
+ .panel-btn.secondary {
+ background: var(--content-bg);
+ border-color: var(--border-gray);
+ color: var(--secondary-content);
+ }
+
+ .retrieval-actions {
+ display: flex;
+ gap: 10px;
+ flex-wrap: wrap;
+ }
+
+ .coverage-heading {
+ font-size: 1.05rem;
+ font-weight: 800;
+ margin: 24px 0 8px;
+ padding-top: 20px;
+ border-top: 1px solid var(--border-gray);
+ }
+
+ .retrieval-hit {
+ background: var(--content-bg);
+ border: 1px solid var(--border-gray);
+ border-radius: 8px;
+ padding: 12px 16px;
+ margin-bottom: 10px;
+ font-size: 0.92rem;
+ }
+
+ .retrieval-hit.winner {
+ border-color: var(--si-report-green);
+ border-width: 2px;
+ }
+
+ .retrieval-hit-head {
+ display: flex;
+ justify-content: space-between;
+ gap: 12px;
+ flex-wrap: wrap;
+ font-weight: 700;
+ margin-bottom: 4px;
+ }
+
+ .retrieval-bar {
+ height: 6px;
+ border-radius: 3px;
+ background: var(--si-medium-blue);
+ margin: 6px 0;
+ }
+
+ .retrieval-snippet {
+ color: var(--muted-text);
+ font-size: 0.88rem;
+ }
+
+ .retrieval-verdict {
+ margin: 8px 0 14px;
+ font-size: 0.92rem;
+ font-weight: 600;
+ }
+
+ .coverage-table-wrap {
+ overflow-x: auto;
+ margin-top: 14px;
+ }
+
+ .coverage-table {
+ width: 100%;
+ border-collapse: collapse;
+ font-size: 0.9rem;
+ background: var(--content-bg);
+ }
+
+ .coverage-table th,
+ .coverage-table td {
+ border: 1px solid var(--border-gray);
+ padding: 8px 12px;
+ text-align: left;
+ vertical-align: top;
+ }
+
+ .coverage-table th {
+ background: var(--background-gray);
+ font-weight: 700;
+ }
+
+ .cov-badge {
+ display: inline-block;
+ font-size: 0.78rem;
+ font-weight: 700;
+ padding: 2px 10px;
+ border-radius: 12px;
+ border: 1px solid;
+ white-space: nowrap;
+ }
+
+ .cov-badge.covered { background: rgba(36, 171, 89, 0.1); border-color: rgba(36, 171, 89, 0.5); color: var(--si-report-green); }
+ .cov-badge.weak { background: rgba(245, 158, 11, 0.1); border-color: rgba(245, 158, 11, 0.55); color: #b45309; }
+ .cov-badge.gap { background: rgba(231, 76, 60, 0.1); border-color: rgba(231, 76, 60, 0.5); color: #c0392b; }
+
+ .coverage-summary {
+ margin-top: 12px;
+ font-weight: 700;
+ font-size: 0.92rem;
+ }
+
/* Pure-CSS tooltips — native title="" is unreliable (long hover delay,
never shown in some embedded browsers), so hints render as a styled
::after that appears immediately on hover. */
@@ -2173,14 +2322,32 @@
Frequently asked questions
URL.revokeObjectURL(url);
});
- // "Show only flagged" toggle — delegated because the Chunks view
- // re-renders its HTML on every extraction
+ // Chunks-view controls — delegated because the view re-renders
+ // its HTML on every extraction
chunksContent.addEventListener('click', function(e) {
- const btn = e.target.closest('#flagged-filter');
- if (!btn) return;
- const on = chunksContent.classList.toggle('flags-only');
- btn.classList.toggle('active', on);
- btn.textContent = on ? 'Showing flagged chunks — show all' : 'Show only flagged chunks';
+ const filterBtn = e.target.closest('#flagged-filter');
+ if (filterBtn) {
+ const on = chunksContent.classList.toggle('flags-only');
+ filterBtn.classList.toggle('active', on);
+ filterBtn.textContent = on ? 'Showing flagged chunks — show all' : 'Show only flagged chunks';
+ return;
+ }
+ if (e.target.closest('#retrieval-run')) { runRetrievalTest(); return; }
+ if (e.target.closest('#coverage-run')) { runCoverage(); return; }
+ const csvBtn = e.target.closest('#copy-coverage-csv');
+ if (csvBtn) { copyToClipboard(coverageCsv(), csvBtn, 'Copied ✓'); return; }
+ const reportBtn = e.target.closest('#copy-report');
+ if (reportBtn && currentData) {
+ trackEvent('report_copied');
+ copyToClipboard(buildOptimizationReport(currentData), reportBtn, 'Copied ✓');
+ }
+ });
+
+ chunksContent.addEventListener('keydown', function(e) {
+ if (e.key === 'Enter' && e.target.id === 'retrieval-query') {
+ e.preventDefault();
+ runRetrievalTest();
+ }
});
// New extraction
@@ -2289,6 +2456,7 @@
Frequently asked questions
function displayChunksView(data) {
let html = '';
chunksContent.classList.remove('flags-only'); // fresh run, fresh filter
+ coverageRows = null; // coverage results belong to the previous page
// Collapsible legend (v3.2) — defines the vocabulary before the data
const tw = data.settings?.target_words;
@@ -2340,6 +2508,28 @@
Frequently asked questions
html += ``;
}
+ // Retrieval simulator + coverage matrix (v3.4)
+ if (data.big_chunks && data.big_chunks.length) {
+ html += `
`;
+ html += `
🔎 Test retrieval
`;
+ html += `
Type what a searcher (or an AI assistant) would ask, and see which section wins — ranked with BM25, the same scoring family real retrieval systems use. Nothing leaves your browser.
`;
+ html += `
`;
+ html += ``;
+ html += ``;
+ html += `
`;
+ html += ``;
+ html += `
Coverage check
`;
+ html += `
Paste queries — one per line, from Search Console, keyword tools, or an Ontologizer fan-out — to see which are covered, weak, or missing entirely.
`;
+ html += ``;
+ html += `
`;
+ html += ``;
+ html += ``;
+ html += ``;
+ html += `
`;
+ html += ``;
+ html += `
`;
+ }
+
// Source metadata block (new in v3)
if (data.source) {
const s = data.source;
@@ -2509,6 +2699,215 @@
✗ No section matches this query — a content gap on this page.
`;
+ return;
+ }
+ let html = '';
+ const top = hits.slice(0, 5);
+ const winner = top[0];
+ const highFlags = winner.doc.flags.filter(f => f.severity === 'high');
+ html += `
`;
+ html += highFlags.length
+ ? `⚠ "${escapeHtml(winner.doc.label)}" wins but is flagged (${escapeHtml(highFlags.map(f => f.label).join(', '))}) — it may not stand alone when retrieved.`
+ : `✓ "${escapeHtml(winner.doc.label)}" wins and has no high-severity flags.`;
+ html += `