Skip to content

Commit cc5e571

Browse files
rdhyeeclaude
andauthored
Move Explorer perf panel to plain <script> (OJS cell wasn't firing) (#125)
* Make Explorer perfPanel an observed cell so OJS evaluates it Without output: false and with an html return value, the cell is treated as displayed and therefore evaluated. The original version was being skipped by OJS's dead-code elimination because no other cell referenced perfPanel and output was suppressed. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> * Move Explorer perf panel to plain <script> in header OJS reactive graph was inconsistently evaluating the perfPanel cell (appeared in docs/isamples_explorer.html as ojs-cell-34 but never fired on page load, even 50s after all explorer_* marks landed and sampleData was observed). Progressive_globe's structurally identical cell works fine — suspect page-specific timing or dependency ordering. Replace with a plain <script> block in include-in-header that runs independent of OJS. Polls for performance marks every 500ms and re-renders the panel as each new mark arrives, stops polling once explorer-samples-end and explorer-count-end are both present. Gated on ?perf=1 (no-op otherwise). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
1 parent 0fe19f2 commit cc5e571

1 file changed

Lines changed: 51 additions & 77 deletions

File tree

tutorials/isamples_explorer.qmd

Lines changed: 51 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,53 @@ format:
1111
<link rel="preconnect" href="https://data.isamples.org" crossorigin>
1212
<link rel="preload" as="fetch" crossorigin="anonymous" href="https://data.isamples.org/isamples_202601_facet_summaries.parquet">
1313
<link rel="preload" as="fetch" crossorigin="anonymous" href="https://data.isamples.org/isamples_202601_facet_cross_filter.parquet">
14+
<script>
15+
// Opt-in perf panel (?perf=1). Plain script — independent of the
16+
// OJS reactive graph, which was unreliable at firing this cell.
17+
(function() {
18+
if (new URLSearchParams(location.search).get('perf') !== '1') return;
19+
const mark = (n) => { const e = performance.getEntriesByName(n, 'mark').pop(); return e ? e.startTime : null; };
20+
const meas = (n) => { const e = performance.getEntriesByName(n, 'measure').pop(); return e ? e.duration : null; };
21+
const fmt = (ms) => ms == null ? '—' : ms >= 1000 ? (ms/1000).toFixed(2)+' s' : Math.round(ms)+' ms';
22+
function render() {
23+
const paints = performance.getEntriesByType('paint');
24+
const fp = paints.find(e => e.name === 'first-paint')?.startTime;
25+
const fcp = paints.find(e => e.name === 'first-contentful-paint')?.startTime;
26+
const rows = [
27+
['first-paint (browser)', fp],
28+
['first-contentful-paint', fcp],
29+
['DuckDB init + views', meas('explorer_db')],
30+
['facet summaries query', meas('explorer_facets')],
31+
['count query', meas('explorer_count')],
32+
['sample data query', meas('explorer_samples')],
33+
['nav → DuckDB ready', mark('explorer-db-end')],
34+
['nav → facets ready', mark('explorer-facets-end')],
35+
['nav → count ready', mark('explorer-count-end')],
36+
['nav → samples ready', mark('explorer-samples-end')],
37+
].filter(([,v]) => v != null);
38+
const prior = document.getElementById('perfPanel');
39+
if (prior) prior.remove();
40+
const version = new URLSearchParams(location.search).get('v') === '2' ? 'v2' : 'v1';
41+
const panel = document.createElement('div');
42+
panel.id = 'perfPanel';
43+
panel.style.cssText = 'position:fixed;bottom:12px;right:12px;z-index:9999;background:rgba(0,0,0,0.82);color:#e8f5e9;padding:10px 12px;border-radius:6px;font:11px/1.4 ui-monospace,SFMono-Regular,monospace;max-width:340px;box-shadow:0 2px 12px rgba(0,0,0,0.3);';
44+
panel.innerHTML = '<div style="font-weight:600;color:#fff;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center;"><span>⏱ Explorer perf (' + version + ')</span><button id="perfClose" style="background:none;border:none;color:#aaa;cursor:pointer;font-size:14px;padding:0 4px;">×</button></div><table style="border-collapse:collapse;width:100%;">' + rows.map(([l,v])=>'<tr><td style="padding:1px 8px 1px 0;color:#bbb;">'+l+'</td><td style="padding:1px 0;text-align:right;color:#a5d6a7;font-variant-numeric:tabular-nums;">'+fmt(v)+'</td></tr>').join('') + '</table>';
45+
document.body.appendChild(panel);
46+
panel.querySelector('#perfClose').onclick = () => panel.remove();
47+
console.table(Object.fromEntries(rows.map(([k,v])=>[k, fmt(v)])));
48+
}
49+
// Poll for the last-expected mark; re-render as each new mark arrives.
50+
let done = false;
51+
const id = setInterval(() => {
52+
const haveAll = mark('explorer-samples-end') != null && mark('explorer-count-end') != null;
53+
const haveSome = mark('explorer-db-end') != null;
54+
if (haveSome) render();
55+
if (haveAll && !done) { done = true; setTimeout(() => clearInterval(id), 500); }
56+
}, 500);
57+
// Safety: stop polling after 2 minutes regardless
58+
setTimeout(() => clearInterval(id), 120000);
59+
})();
60+
</script>
1461
---
1562

1663
Search and explore **6.7 million physical samples** from scientific collections worldwide.
@@ -919,83 +966,10 @@ Loaded: ${sampleData.length.toLocaleString()}
919966

920967
---
921968

922-
```{ojs}
923-
//| echo: false
924-
//| output: false
925-
926-
// === Performance timing panel (opt-in: append ?perf=1 to URL) ===
927-
// Ported from progressive_globe.qmd. Reads performance.mark/measure entries
928-
// and renders a small fixed panel. Ship with perf=1 to measure baseline,
929-
// then v2=1 to compare.
930-
perfPanel = {
931-
// Depend on sampleData so the panel appears after initial data loads
932-
if (sampleData == null) return;
933-
934-
const params = new URLSearchParams(location.search);
935-
if (params.get('perf') !== '1') return;
936-
937-
await new Promise(r => setTimeout(r, 100));
938-
939-
const mark = (name) => {
940-
const e = performance.getEntriesByName(name, 'mark').pop();
941-
return e ? e.startTime : null;
942-
};
943-
const measure = (name) => {
944-
const e = performance.getEntriesByName(name, 'measure').pop();
945-
return e ? e.duration : null;
946-
};
947-
948-
const paintEntries = performance.getEntriesByType('paint');
949-
const fcp = paintEntries.find(e => e.name === 'first-contentful-paint')?.startTime;
950-
const fp = paintEntries.find(e => e.name === 'first-paint')?.startTime;
951-
952-
const rows = [
953-
['first-paint (browser)', fp],
954-
['first-contentful-paint', fcp],
955-
['DuckDB init + views', measure('explorer_db')],
956-
['facet summaries query', measure('explorer_facets')],
957-
['count query', measure('explorer_count')],
958-
['sample data query', measure('explorer_samples')],
959-
['nav → DuckDB ready', mark('explorer-db-end')],
960-
['nav → facets ready', mark('explorer-facets-end')],
961-
['nav → count ready', mark('explorer-count-end')],
962-
['nav → samples ready', mark('explorer-samples-end')],
963-
].filter(([, v]) => v != null);
964-
965-
console.table(Object.fromEntries(rows.map(([k, v]) => [k, `${v.toFixed(0)} ms`])));
966-
967-
const fmt = (ms) => ms == null ? '—' : ms >= 1000 ? `${(ms/1000).toFixed(2)} s` : `${ms.toFixed(0)} ms`;
968-
// Remove any prior panel (page re-renders on filter change)
969-
const prior = document.getElementById('perfPanel');
970-
if (prior) prior.remove();
971-
972-
const version = params.get('v') === '2' ? 'v2' : 'v1';
973-
const panel = document.createElement('div');
974-
panel.id = 'perfPanel';
975-
panel.style.cssText = `
976-
position: fixed; bottom: 12px; right: 12px; z-index: 9999;
977-
background: rgba(0,0,0,0.82); color: #e8f5e9; padding: 10px 12px;
978-
border-radius: 6px; font: 11px/1.4 ui-monospace, SFMono-Regular, monospace;
979-
max-width: 340px; box-shadow: 0 2px 12px rgba(0,0,0,0.3);
980-
`;
981-
panel.innerHTML = `
982-
<div style="font-weight:600;color:#fff;margin-bottom:6px;display:flex;justify-content:space-between;align-items:center;">
983-
<span>⏱ Explorer perf (${version})</span>
984-
<button id="perfClose" style="background:none;border:none;color:#aaa;cursor:pointer;font-size:14px;padding:0 4px;">×</button>
985-
</div>
986-
<table style="border-collapse:collapse;width:100%;">
987-
${rows.map(([label, v]) => `
988-
<tr><td style="padding:1px 8px 1px 0;color:#bbb;">${label}</td>
989-
<td style="padding:1px 0;text-align:right;color:#a5d6a7;font-variant-numeric:tabular-nums;">${fmt(v)}</td></tr>
990-
`).join('')}
991-
</table>
992-
`;
993-
document.body.appendChild(panel);
994-
panel.querySelector('#perfClose').onclick = () => panel.remove();
995-
996-
return "shown";
997-
}
998-
```
969+
<!-- Perf timing panel is implemented as a plain <script> in the header
970+
(see include-in-header in the YAML frontmatter above). Using a plain
971+
script rather than an OJS cell because OJS was inconsistently evaluating
972+
the cell on this page. -->
999973

1000974
---
1001975

0 commit comments

Comments
 (0)