// surgx-browse-v3.jsx — SurgX Browse / dictionary index, design v3.
// Mounts #root; exposes window.SurgxBrowseV3Root.
// Blank-over-wrong throughout: every honest-empty is designed, never fabricated.
// Reuses the .sxv3 / .sx token root and component classes from surgx-pdp-v3.css.
(() => {
const { useState, useEffect, useMemo, useRef } = React;
const SX_SPECIALTY_FORK = true;
/* ---- shared search core (surgx-search-core.js, loaded before this script) ---- */
const { str, normalizeVendor, matchesQuery } = window.SurgxSearchCore;
/* ---- tiny helpers (mirror pattern from pdp-v3) ---- */
const initials = n => { const w = String(n || '?').split(/\s+/).filter(Boolean); return ((w[0]?.[0] || '?') + (w[1]?.[0] || '')).toUpperCase(); };
const Icon = ({ d, size = 14 }) => (
);
const ICON = { back: 'M10 13 5 8l5-5', chevDown: 'M4 6l4 4 4-4' };
const Glyph = ({ name, size = 40 }) => (
{initials(name)}
);
const Empty = ({ title, detail }) => (
{title}
{detail ?
{detail}
: null}
);
/* ---- clearance chip (same 3-state vocabulary as pdp-v3) ---- */
function ClearanceChip({ fda_510k_primary }) {
const k = (str(fda_510k_primary).match(/^(K\d{6})$/) || [])[1];
if (k) return FDA 510(k) {k} ;
return no K-number on record ;
}
/* ---- derive top-20 vendors by count from curated systems list ---- */
function topVendors(systems) {
const counts = {};
for (const s of systems) {
const v = normalizeVendor(s.vendor);
if (v) counts[v] = (counts[v] || 0) + 1;
}
return Object.entries(counts)
.sort((a, b) => b[1] - a[1])
.slice(0, 20)
.map(([name, count]) => ({ name, count }));
}
/* ---- Facet panel (desktop rail list or mobile chip row) ---- */
function FacetPanel({ label, items, value, onChange }) {
return (
{label}
{items.map(item => (
onChange(value === item.value ? null : item.value)}
>
{item.label}
{item.count != null ? {item.count} : null}
))}
);
}
/* ---- single browse card ---- */
function BrowseCard({ slug, display_name, vendor, subspecialty, fda_510k_primary, tier }) {
const name = display_name || slug;
const vendorShort = normalizeVendor(vendor);
const subspec = str(subspecialty).slice(0, 60);
return (
{name}
{vendorShort ?
{vendorShort}
: null}
{subspec ?
{subspec}
: null}
{tier === 'curated'
? curated · grounded
: FDA records only }
);
}
function CompactRow({ slug, display_name, vendor, subspecialty, fda_510k_primary, tier }) {
const name = display_name || slug;
const vendorShort = normalizeVendor(vendor);
const subspec = str(subspecialty).slice(0, 72);
return (
{name}
{vendorShort ? {vendorShort} : null}
{subspec ? {subspec} : null}
{tier === 'generic' ? FDA records only : null}
);
}
function GroupedSection({ node, onOpen }) {
const ref = useRef(null);
const [loaded, setLoaded] = useState(false);
const [state, setState] = useState({ loading: false, error: null, data: null });
useEffect(() => {
const el = ref.current;
if (!el || loaded) return;
const load = () => {
setLoaded(true);
setState({ loading: true, error: null, data: null });
const qs = new URLSearchParams({ specialty: node.slug, page: '1', page_size: '6' });
fetch('/api/r2/systems?' + qs.toString())
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(data => setState({ loading: false, error: null, data }))
.catch(e => setState({ loading: false, error: e.message, data: null }));
};
if (!('IntersectionObserver' in window)) {
load();
return;
}
const io = new IntersectionObserver(entries => {
if (entries.some(e => e.isIntersecting)) {
io.disconnect();
load();
}
}, { rootMargin: '420px 0px' });
io.observe(el);
return () => io.disconnect();
}, [loaded, node.slug]);
const items = state.data ? (state.data.items || []) : [];
const remaining = Math.max(0, node.count - items.length);
return (
{node.display}
{node.has_children ?
subspecialty tiers available
: null}
{node.count}
{state.loading ? (
loading section…
) : state.error ? (
) : items.length ? (
{items.map(s => )}
) : loaded ? (
) : (
ready to load…
)}
{Array.isArray(node.children) && node.children.length ? (
{node.children.map(child => (
onOpen(child.slug)}>
{child.display}
{child.count}
))}
) : null}
{items.length ? (
onOpen(node.slug)}>
{remaining > 0 ? '… +' + remaining + ' more → ' : ''}View all {node.count} in {node.display}
) : null}
);
}
function PagedRows({ endpoint, emptyTitle }) {
const [page, setPage] = useState(1);
const [state, setState] = useState({ loading: true, error: null, data: null });
useEffect(() => {
setPage(1);
}, [endpoint]);
useEffect(() => {
const sep = endpoint.indexOf('?') === -1 ? '?' : '&';
fetch(endpoint + sep + 'page=' + page + '&page_size=24')
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(data => setState(prev => ({
loading: false,
error: null,
data: page === 1 ? data : { ...data, items: [...((prev.data && prev.data.items) || []), ...(data.items || [])] },
})))
.catch(e => setState({ loading: false, error: e.message, data: null }));
}, [endpoint, page]);
if (state.loading && page === 1) return loading systems…
;
if (state.error) return ;
const data = state.data || { items: [], total: 0, has_more: false };
const items = data.items || [];
if (!items.length) return ;
return (
<>
{items.length} of {data.total} systems
{items.map(s => )}
{data.has_more ? (
setPage(p => p + 1)}>show more
) : null}
>
);
}
function specialtySystemCount(node) {
return Number(node && node.count) || 0;
}
function vendorRowsFromSystems(systems) {
const counts = {};
for (const s of systems || []) {
const v = normalizeVendor(s.vendor);
if (v) counts[v] = (counts[v] || 0) + 1;
}
return Object.entries(counts)
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
.map(([name, count]) => ({ name, count }));
}
function fetchAllSpecialtySystems(slug) {
const items = [];
let page = 1;
const MAX_PAGES = 60; // hard cap (60*100=6000 systems, far above any specialty) — runaway guard
const loadPage = () => {
const qs = new URLSearchParams({
tier: 'curated',
specialty: slug,
page: String(page),
page_size: '100',
});
return fetch('/api/r2/systems?' + qs.toString())
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(data => {
const got = data.items || [];
items.push(...got);
// Termination guards (M3 review): stop on no-more, on an empty page
// (zero progress — defends a backend has_more:true bug from infinite-looping),
// or at the page cap. page_size=100 is the backend clamp.
if (data.has_more && got.length > 0 && page < MAX_PAGES) {
page += 1;
return loadPage();
}
return items;
});
};
return loadPage();
}
function SpecialtyMoreWaysIn() {
return (
MORE WAYS IN
Supplies & item categories
cross-catalog coming soon
Procedure search
mapping in progress coming soon
);
}
function SpecialtyFork({ specialty, node, systemsState, onPickVendor, onPickSearch, onBack }) {
const systemCount = specialtySystemCount(node);
const vendorCount = systemsState && systemsState.items ? vendorRowsFromSystems(systemsState.items).length : null;
const vendorBadge = vendorCount == null ? '...' : vendorCount.toLocaleString();
return (
<>
all specialties
{specialty}
BROWSE THIS SPECIALTY
{systemCount.toLocaleString()} curated
By vendor
Makers active in this specialty
{vendorBadge}
Search systems
Find a system within this specialty
{systemCount.toLocaleString()}
{systemsState && systemsState.error ? (
) : null}
both lead cards reach the same curated systems — pick your entry axis.
>
);
}
function SpecialtyVendorBranch({ specialty, systemsState, onBack }) {
const [vendorFilter, setVendorFilter] = useState(null);
const systems = systemsState && systemsState.items ? systemsState.items : [];
const vendors = useMemo(() => vendorRowsFromSystems(systems), [systems]);
const vendorSystems = useMemo(() => (
vendorFilter ? systems.filter(s => normalizeVendor(s.vendor) === vendorFilter) : []
), [systems, vendorFilter]);
return (
<>
{specialty}
{vendorFilter || ('Vendors — ' + specialty)}
{systemsState && systemsState.loading ? (
loading vendors...
) : systemsState && systemsState.error ? (
) : vendorFilter ? (
<>
setVendorFilter(null)}>
vendors
{vendorSystems.length} systems
{vendorSystems.map(s => )}
>
) : vendors.length ? (
{vendors.map(v => (
setVendorFilter(v.name)}
style={{ width: '100%', textAlign: 'left' }}
>
{v.count}
))}
) : (
)}
>
);
}
function SpecialtySearchBranch({ specialty, node, onBack }) {
const [q, setQ] = useState('');
const [debouncedQ, setDebouncedQ] = useState('');
const slug = node && node.slug ? node.slug : '';
useEffect(() => {
const t = setTimeout(() => setDebouncedQ(q.trim()), 200);
return () => clearTimeout(t);
}, [q]);
const endpoint = useMemo(() => {
const params = new URLSearchParams({ tier: 'curated', specialty: slug });
if (debouncedQ) params.set('q', debouncedQ);
return '/api/r2/systems?' + params.toString();
}, [slug, debouncedQ]);
return (
<>
{specialty}
Search systems
setQ(e.target.value)}
placeholder="Search by system name, K-number, or vendor"
aria-label="Search by system name, K-number, or vendor"
style={{ width: '100%', marginBottom: 12 }}
/>
>
);
}
function GroupedBrowseV3Root({ initialSearch, initialSpecialty, initialTier }) {
const [tree, setTree] = useState(null);
const [treeErr, setTreeErr] = useState(null);
const [search, setSearch] = useState(initialSearch || '');
const [specialtyFilter, setSpecialtyFilter] = useState(initialSpecialty || null);
const [branchMode, setBranchMode] = useState(null);
const [specialtySystemsState, setSpecialtySystemsState] = useState({ slug: null, loading: false, error: null, items: null });
const tierFilter = initialTier === 'generic' ? 'generic' : 'curated';
useEffect(() => {
fetch('/api/r2/taxonomy/tree?tier=curated')
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(setTree)
.catch(e => setTreeErr(e.message));
}, []);
const nodes = useMemo(() => tree && tree.nodes ? tree.nodes : [], [tree]);
const specialtyItems = useMemo(() => [
{ value: '__all__', label: 'All systems', count: null },
...nodes.map(n => ({ value: n.slug, label: n.display, count: n.count })),
], [nodes]);
const currentNode = nodes.find(n => n.slug === specialtyFilter);
const specialtyNode = currentNode || (specialtyFilter ? { slug: specialtyFilter, display: specialtyFilter, count: 0 } : null);
const currentSpecialty = specialtyNode ? specialtyNode.display : specialtyFilter;
const activeSearch = search.trim();
const searchEndpoint = '/api/r2/search?' + new URLSearchParams({ q: activeSearch, tier: tierFilter }).toString();
const specialtyEndpoint = '/api/r2/systems?' + new URLSearchParams({ tier: 'curated', specialty: specialtyFilter || '' }).toString();
const clearSpecialty = () => {
setSpecialtyFilter(null);
setBranchMode(null);
};
const openSpecialty = slug => {
setSpecialtyFilter(slug);
setBranchMode(null);
};
useEffect(() => {
if (!SX_SPECIALTY_FORK || !specialtyFilter) return;
let cancelled = false;
setSpecialtySystemsState(prev => ({
slug: specialtyFilter,
loading: true,
error: null,
items: prev.slug === specialtyFilter ? prev.items : null,
}));
fetchAllSpecialtySystems(specialtyFilter)
.then(items => {
if (!cancelled) setSpecialtySystemsState({ slug: specialtyFilter, loading: false, error: null, items });
})
.catch(e => {
if (!cancelled) setSpecialtySystemsState({ slug: specialtyFilter, loading: false, error: e.message, items: null });
});
return () => { cancelled = true; };
}, [specialtyFilter]);
return (
SurgX
surgical supply, with receipts
setSearch(e.target.value)}
placeholder="Search systems, vendors, K-numbers…"
aria-label="Search systems"
/>
{treeErr ?
: null}
{tree ? (
v === '__all__' ? clearSpecialty() : openSpecialty(v)}
/>
) : null}
{activeSearch ? (
<>
setSearch('')}>
clear search
relevance-ranked results
>
) : SX_SPECIALTY_FORK && specialtyFilter && branchMode === null ? (
setBranchMode('vendor')}
onPickSearch={() => setBranchMode('search')}
onBack={clearSpecialty}
/>
) : SX_SPECIALTY_FORK && specialtyFilter && branchMode === 'vendor' ? (
setBranchMode(null)}
/>
) : SX_SPECIALTY_FORK && specialtyFilter && branchMode === 'search' ? (
setBranchMode(null)}
/>
) : specialtyFilter ? (
<>
all specialties
{currentNode ? currentNode.display : specialtyFilter}
>
) : tree ? (
<>
{tree.tiers.curated.toLocaleString()} curated systems · {nodes.length} specialties
{nodes.map(node => (
))}
>
) : (
loading topology…
)}
);
}
/* ---- root component ---- */
function SurgxBrowseV3Root() {
/* URL param seeding — initial state, not a useEffect (avoids unfiltered flash) */
const _params = new URLSearchParams(location.search);
const _tierParam = _params.get('tier');
/* ACTIVATED 2026-06-11: grouped topology is the default. Rollback hatch: ?group=0
forces the legacy flat list (revert this one line — flip back to === '1' — to dark again). */
if (_params.get('group') !== '0') {
return (
);
}
/* fetch state */
const [taxonomy, setTaxonomy] = useState(null);
const [systems, setSystems] = useState(null);
const [genericIndex, setGenericIndex] = useState(null); // null = loading, false = failed/404
const [taxErr, setTaxErr] = useState(null);
const [sysErr, setSysErr] = useState(null);
/* filter/UI state */
const [search, setSearch] = useState(_params.get('q') || '');
const [specialtyFilter, setSpecialtyFilter] = useState(_params.get('specialty') || null);
const [vendorFilter, setVendorFilter] = useState(null);
/* ?tier= deep-link (landing receipts cards); only the two known values seed */
const [tierFilter, setTierFilter] = useState(
_tierParam === 'curated' || _tierParam === 'generic' ? _tierParam : null
); // null=all, 'curated', 'generic'
const [curatedPage, setCuratedPage] = useState(1);
const [genericPage, setGenericPage] = useState(1);
const [genericOpen, setGenericOpen] = useState(false);
/* parallel fetch on mount */
useEffect(() => {
fetch('/api/r2/taxonomy')
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(setTaxonomy)
.catch(e => setTaxErr(e.message));
fetch('/api/r2/systems')
.then(r => { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
.then(setSystems)
.catch(e => setSysErr(e.message));
fetch('/api/r2/generic-index')
.then(r => { if (!r.ok) return false; return r.json(); })
.then(d => setGenericIndex(d || false))
.catch(() => setGenericIndex(false));
}, []);
/* reset pagination when filters/search change */
useEffect(() => { setCuratedPage(1); setGenericPage(1); }, [search, specialtyFilter, vendorFilter, tierFilter]);
/* derived vendor list from curated systems */
const vendors = useMemo(() => {
if (!systems) return [];
return topVendors(systems.items || []);
}, [systems]);
/* specialty facet items from taxonomy */
const specialtyItems = useMemo(() => {
if (!taxonomy) return [];
return [
{ value: '__all__', label: 'All systems', count: null },
...(taxonomy.specialties || []).map(s => ({ value: s.slug, label: s.display, count: s.count })),
];
}, [taxonomy]);
/* vendor facet items */
const vendorItems = useMemo(() => vendors.map(v => ({ value: v.name, label: v.name, count: v.count })), [vendors]);
/* tier facet items */
const tierItems = [
{ value: '__all__', label: 'All', count: null },
{ value: 'curated', label: 'Curated', count: null },
{ value: 'generic', label: 'FDA records only', count: null },
];
/* filter curated list */
const curatedItems = useMemo(() => {
const raw = systems ? (systems.items || []) : [];
const q = search.trim().toLowerCase();
return raw.filter(s => {
if (specialtyFilter && specialtyFilter !== '__all__' && s.specialty_canonical !== specialtyFilter) return false;
if (vendorFilter && normalizeVendor(s.vendor) !== vendorFilter) return false;
if (tierFilter === 'generic') return false;
if (!matchesQuery(s, q)) return false;
return true;
});
}, [systems, search, specialtyFilter, vendorFilter, tierFilter]);
/* filter generic list */
const genericItems = useMemo(() => {
if (!genericIndex || !genericIndex.items) return [];
const q = search.trim().toLowerCase();
return genericIndex.items.filter(s => {
if (specialtyFilter && specialtyFilter !== '__all__' && s.specialty_canonical !== specialtyFilter) return false;
if (tierFilter === 'curated') return false;
if (!matchesQuery(s, q)) return false;
return true;
});
}, [genericIndex, search, specialtyFilter, tierFilter]);
/* pagination */
const CURATED_PAGE_SIZE = 60;
const GENERIC_PAGE_SIZE = 50;
const shownCurated = curatedItems.slice(0, curatedPage * CURATED_PAGE_SIZE);
const shownGeneric = genericItems.slice(0, genericPage * GENERIC_PAGE_SIZE);
const hasMoreCurated = shownCurated.length < curatedItems.length;
const hasMoreGeneric = shownGeneric.length < genericItems.length;
/* generic total from index or systems fallback */
const genericTotal = genericIndex && genericIndex.count != null
? genericIndex.count
: genericIndex && genericIndex.items
? genericIndex.items.length
: 0;
/* loading state */
const loading = taxonomy === null || systems === null;
if (loading && taxErr === null && sysErr === null) {
return (
);
}
return (
{/* topbar */}
SurgX
surgical supply, with receipts
setSearch(e.target.value)}
placeholder="Search systems, vendors, K-numbers…"
aria-label="Search systems"
/>
{/* per-source fetch errors rendered inline — never a blank page */}
{taxErr ? (
) : null}
{sysErr ? (
) : null}
{/* ---- facet rail / mobile chip row ---- */}
{/* SPECIALTY */}
{taxonomy && !taxErr ? (
setSpecialtyFilter(v === '__all__' ? null : v)}
/>
) : null}
{/* VENDOR */}
{vendors.length > 0 && tierFilter !== 'generic' ? (
setVendorFilter(v)}
/>
) : null}
{/* TIER */}
setTierFilter(v === '__all__' ? null : v)}
/>
{/* PROCEDURE — honest non-interactive empty */}
{/* ---- main content ---- */}
{/* count bar */}
{!sysErr
? shownCurated.length + ' of ' + curatedItems.length + ' curated'
+ (genericIndex !== null
? ' · ' + (genericIndex ? shownGeneric.length + ' of ' + genericItems.length + ' generic' : '')
: ' · loading generic…')
: null}
{/* curated cards */}
{!sysErr && curatedItems.length > 0 ? (
{shownCurated.map(s => (
))}
) : !sysErr && systems ? (
) : null}
{/* show more — curated */}
{hasMoreCurated ? (
setCuratedPage(p => p + 1)}>
show 60 more ({curatedItems.length - shownCurated.length} remaining)
) : null}
{/* generic band */}
{genericIndex === false ? (
/* 404 / failed — honest empty, never blank */
) : genericIndex && genericIndex.items && tierFilter !== 'curated' ? (
setGenericOpen(o => !o)}
aria-expanded={genericOpen}
>
Generic tier · {genericTotal.toLocaleString()} systems — FDA records only
{genericItems.length} matching
{genericOpen ? (
<>
{vendorFilter ? (
vendor facet applies to curated tier — generic band shows all specialty-matched results
) : null}
{genericItems.length > 0 ? (
{shownGeneric.map(s => (
))}
) : (
)}
{hasMoreGeneric ? (
setGenericPage(p => p + 1)}>
show 50 more ({genericItems.length - shownGeneric.length} remaining)
) : null}
>
) : null}
) : null}
);
}
window.SurgxBrowseV3Root = SurgxBrowseV3Root;
// Mount from inside the transpiled script (same pattern as surgx-pdp-entry.jsx):
// Babel-standalone fetches text/babel src scripts asynchronously, so a separate
// DOMContentLoaded mount races transpilation and blanks the page.
ReactDOM.createRoot(document.getElementById('root')).render( );
})();