Independent education · Sources and limitations documented
Portfolio / Smart NOA simulation

Software simulation

Source snapshot · 5 November 2026

simulator/ui/sources.js

// Sources panel (built from SOURCES in data/drugs.js) and inline source tags.
import { h, clear } from './dom.js';

let LIB = null;

export function initSources(lib) {
  LIB = lib;
  const body = document.getElementById('sources-body');
  clear(body);
  const filter = h('input', { type: 'search', id: 'sources-filter', class: 'input', placeholder: 'Filter by id, author, drug…', autocomplete: 'off' });
  const list = h('div', { class: 'sources-list', id: 'sources-list' });
  body.append(
    h('p', { class: 'muted' }, 'Every dose, range, ceiling, threshold and score cut-off in the simulator traces to one of these entries. ',
      '“Verified via” records exactly how each was confirmed. Full registry, including drugs that were left out and why, and the illustrative model assumptions: ',
      h('a', { href: 'SOURCES.md' }, 'SOURCES.md'), '.'),
    h('label', { for: 'sources-filter', class: 'label' }, 'Filter sources'), filter, list,
  );
  const ids = Object.keys(lib.SOURCES).sort((a, b) => Number(a.slice(1)) - Number(b.slice(1)));
  for (const id of ids) {
    const s = lib.SOURCES[id];
    const url = typeof s.url === 'string' && /^https:\/\//.test(s.url) ? h('a', { href: s.url, rel: 'noopener noreferrer', target: '_blank' }, s.url) : (s.url ? h('code', {}, s.url) : null);
    list.append(h('article', { class: 'source', id: `src-${id}`, tabindex: '-1', dataset: { text: `${id} ${s.citation} ${s.supports}`.toLowerCase() } },
      h('h3', {}, h('span', { class: 'source__id' }, `[${id}]`), ' ', s.type ? h('span', { class: 'source__type' }, s.type) : null),
      h('p', { class: 'source__citation' }, s.citation),
      h('dl', { class: 'source__meta' },
        h('dt', {}, 'Supports'), h('dd', {}, s.supports || '—'),
        h('dt', {}, 'Identifiers'), h('dd', {}, s.identifiers || 'none'),
        url ? [h('dt', {}, 'Link'), h('dd', {}, url)] : null,
        h('dt', {}, 'Verified via'), h('dd', {}, s.verifiedVia || '—')),
    ));
  }
  const notes = lib.LIBRARY_NOTES || {};
  if (Object.keys(notes).length) {
    list.append(h('article', { class: 'source source--notes' }, h('h3', {}, 'Library notes'),
      h('ul', {}, Object.values(notes).map((n) => h('li', {}, n)))));
  }
  filter.addEventListener('input', () => {
    const q = filter.value.trim().toLowerCase();
    for (const a of list.querySelectorAll('article.source[data-text]')) a.hidden = !!q && !a.dataset.text.includes(q);
  });

  document.addEventListener('click', (e) => {
    const t = e.target.closest('[data-src]');
    if (t) { e.preventDefault(); openSource(t.dataset.src, t); }
  });
}

export function openSource(id, returnFocus = null) {
  const dlg = document.getElementById('sources-dialog');
  if (!dlg.open) dlg.showModal();
  const list = document.getElementById('sources-list');
  const f = document.getElementById('sources-filter');
  if (f && f.value) { f.value = ''; f.dispatchEvent(new Event('input')); }
  for (const a of list.querySelectorAll('.source--hl')) a.classList.remove('source--hl');
  const el = id ? document.getElementById(`src-${id}`) : null;
  if (el) { el.classList.add('source--hl'); el.scrollIntoView({ block: 'start' }); el.focus({ preventScroll: true }); }
  if (returnFocus) dlg.addEventListener('close', () => { if (document.contains(returnFocus)) returnFocus.focus(); }, { once: true });
}

/** Inline tags for a list of source ids. Non-source markers (e.g. 'illustrative') render as plain badges. */
export function srcTags(ids) {
  const list = [...new Set((ids || []).filter(Boolean))];
  if (!list.length) return null;
  return h('span', { class: 'src-tags' }, list.map((id) => {
    const s = LIB && LIB.SOURCES[id];
    if (!s) return h('span', { class: 'badge badge--illustrative' }, String(id));
    const short = s.citation.length > 110 ? `${s.citation.slice(0, 107)}…` : s.citation;
    return h('button', { type: 'button', class: 'src-tag', dataset: { src: id }, title: short, 'aria-label': `Source ${id}: ${short}` }, `[${id}]`);
  }));
}

/** Pull "[S7]" / "[S7, S9]" / "(S35)" references out of a free-text message for tagging. */
export function sourcesInText(text) {
  const out = [];
  const re = /\bS(\d{1,3})\b/g; let m;
  while ((m = re.exec(String(text || '')))) { const id = `S${m[1]}`; if (LIB && LIB.SOURCES[id] && !out.includes(id)) out.push(id); }
  return out;
}
Source and scope

Published from Smart-NOA-Controller/simulator/ui/sources.js at revision 6e5b205e3843. Full source text is retained; HTML presentation does not establish independent clinical review. In-memory simulation with no patient, monitor or pump interface; not clinically validated.