@osqd/bothandlerjs 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +227 -1
- package/README.md +18 -10
- package/dist/adapters/fastify.d.ts +10 -0
- package/dist/adapters/index.cjs +38 -13
- package/dist/adapters/index.cjs.map +1 -1
- package/dist/adapters/index.js +38 -13
- package/dist/adapters/index.js.map +1 -1
- package/dist/challenge/index.d.ts +40 -0
- package/dist/cli.cjs +1783 -83
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +1783 -83
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +55 -0
- package/dist/core.d.ts +33 -21
- package/dist/corpus/index.cjs +282 -8
- package/dist/corpus/index.cjs.map +1 -1
- package/dist/corpus/index.js +282 -8
- package/dist/corpus/index.js.map +1 -1
- package/dist/corpus/schema.d.ts +25 -0
- package/dist/dashboard/client/actions.d.ts +1 -1
- package/dist/dashboard/client/app.d.ts +9 -2
- package/dist/dashboard/client/boot.d.ts +32 -3
- package/dist/dashboard/client/query.d.ts +72 -12
- package/dist/dashboard/client/registry.d.ts +25 -0
- package/dist/dashboard/client/saved.d.ts +29 -0
- package/dist/dashboard/client/store.d.ts +16 -2
- package/dist/dashboard/client/types.d.ts +2 -0
- package/dist/dashboard/client.generated.d.ts +1 -1
- package/dist/dashboard/types.d.ts +15 -0
- package/dist/detectors/blended-identity.d.ts +34 -0
- package/dist/detectors/challenge-integrity.d.ts +26 -0
- package/dist/detectors/challenge-reaction.d.ts +39 -0
- package/dist/detectors/clearance.d.ts +1 -23
- package/dist/detectors/index.d.ts +16 -1
- package/dist/detectors/marker.d.ts +106 -0
- package/dist/detectors/probe-signature.d.ts +27 -0
- package/dist/detectors/site-baseline.d.ts +135 -0
- package/dist/detectors/target-integrity.d.ts +16 -0
- package/dist/detectors/trap.d.ts +10 -3
- package/dist/detectors/types.d.ts +17 -0
- package/dist/element/index.cjs +726 -79
- package/dist/element/index.cjs.map +1 -1
- package/dist/element/index.js +726 -79
- package/dist/element/index.js.map +1 -1
- package/dist/index.cjs +1657 -125
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +1643 -125
- package/dist/index.js.map +1 -1
- package/dist/internal/async.d.ts +0 -3
- package/dist/internal/ip.d.ts +18 -0
- package/dist/internal/text.d.ts +28 -0
- package/dist/metrics.d.ts +18 -0
- package/dist/probe/index.d.ts +153 -0
- package/dist/probe/marker.d.ts +119 -0
- package/dist/site/index.d.ts +122 -0
- package/dist/state.d.ts +130 -0
- package/dist/stores/redis.d.ts +24 -1
- package/dist/types.d.ts +70 -0
- package/docs/course/05-detectors.md +6 -5
- package/docs/course/06-identity.md +1 -1
- package/docs/course/16-proving-it.md +15 -9
- package/docs/course/index.md +1 -1
- package/docs/design/decisions.md +1 -1
- package/docs/detection/correlation.md +284 -0
- package/docs/detection/detectors.md +139 -3
- package/docs/detection/index.md +2 -1
- package/docs/detection/shadow-mode.md +147 -0
- package/docs/detection/signatures.md +1 -1
- package/docs/index.md +3 -2
- package/docs/integration/client-ip.md +16 -0
- package/docs/operations/dashboard.md +40 -1
- package/docs/operations/filters.md +143 -0
- package/docs/operations/index.md +1 -0
- package/docs/operations/metrics.md +18 -0
- package/docs/policy/presets.md +1 -1
- package/docs/start/choosing-a-policy.md +1 -1
- package/docs/start/first-integration.md +1 -1
- package/docs/start/installation.md +2 -2
- package/docs/testing/cli.md +7 -1
- package/docs/testing/corpus.md +12 -8
- package/docs/testing/index.md +1 -1
- package/docs/testing/try-it.md +1 -1
- package/package.json +4 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/dashboard/client/css.ts","../../src/dashboard/client/dom.ts","../../src/dashboard/client/boot.ts","../../src/dashboard/client/app.ts","../../src/dashboard/client/outcome.ts","../../src/dashboard/client/query.ts","../../src/dashboard/client/store.ts","../../src/dashboard/client/api.ts","../../src/dashboard/client/format.ts","../../src/dashboard/client/pager.ts","../../src/dashboard/client/replay.ts","../../src/dashboard/client/actions.ts","../../src/dashboard/client/bars.ts","../../src/dashboard/client/actor.ts","../../src/dashboard/client/result.ts","../../src/dashboard/client/guard.ts","../../src/dashboard/client/ranges.ts","../../src/dashboard/client/draft.ts","../../src/dashboard/client/policy.ts","../../src/dashboard/client/feed.ts","../../src/dashboard/client/stream.ts","../../src/dashboard/client/panels.ts","../../src/dashboard/client/charts.ts","../../src/dashboard/client/registry.ts","../../src/dashboard/client/tester.ts","../../src/dashboard/client/index.ts","../../src/element/index.ts","../../src/element/config.ts","../../src/dashboard/page.ts"],"sourcesContent":["/**\n * Selector escaping, on its own so both halves can have it.\n *\n * `dom.ts` is the browser client's entry into the document and carries module state that\n * has to be initialised in order; the embeddable element imports it lazily for exactly\n * that reason. But the element also assembles selectors from developer-supplied panel ids\n * *synchronously*, before any of that has happened — so the escape lives here, in a leaf\n * with nothing behind it, rather than being duplicated or dragging the client graph in\n * early.\n */\nexport function cssEscape(value: string): string {\n return typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\" ? CSS.escape(value) : String(value).replace(/[^\\w-]/g, \"\\\\$&\");\n}\n","/**\n * The page's only way of putting something in the document.\n *\n * `textContent`, always. Every string that reaches this file — a User-Agent, a path, a\n * detector's summary, a rule id somebody typed into the editor — was written by\n * somebody else, and one `innerHTML` anywhere below turns a bot's User-Agent into\n * script running in an operator's browser. A test asserts the served page contains\n * none of the four ways to do that, and `scripts/build-client.mjs` refuses to bundle\n * one.\n */\nimport { cssEscape } from \"./css.js\";\n\nexport function el<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string | null, value?: string | number | null): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className !== undefined && className !== null && className !== \"\") node.className = className;\n if (value !== undefined && value !== null) node.textContent = String(value);\n return node;\n}\n\nexport function svgEl(name: string, attributes: Record<string, string | number> = {}): SVGElement {\n const node = document.createElementNS(\"http://www.w3.org/2000/svg\", name);\n for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, String(value));\n return node;\n}\n\nexport function svgText(attributes: Record<string, string | number>, text: string | number): SVGElement {\n const node = svgEl(\"text\", attributes);\n node.textContent = String(text);\n return node;\n}\n\nexport function clear(node: Node): void {\n while (node.firstChild) node.removeChild(node.firstChild);\n}\n\n/**\n * Ids are base64url today, which needs no escaping — but a selector assembled from a\n * value is only safe until somebody widens the alphabet, and `CSS.escape` costs\n * nothing. It lives in its own module because the embeddable element needs it without\n * loading this one; re-exported here so the rest of the client reads unchanged.\n */\nexport { cssEscape };\n\n/**\n * Where this dashboard lives.\n *\n * The standalone page owns the whole document; the embeddable element owns a shadow root\n * inside somebody else's. Everything below looks things up through here rather than\n * through `document`, so the same client code drives both — a shadow root is not\n * reachable from `document.getElementById`, and a document is not reachable from a\n * detached fragment.\n */\nlet root: Document | ShadowRoot | HTMLElement = typeof document === \"undefined\" ? (undefined as never) : document;\n\n/**\n * The element the theme tokens hang off.\n *\n * On the page that is the document element. Inside a shadow tree it is the host, because\n * custom properties inherit *into* a shadow root from the host and `:root` matches\n * nothing in there.\n */\nlet themeHost: HTMLElement = typeof document === \"undefined\" ? (undefined as never) : document.documentElement;\n\n/**\n * True when this dashboard is rendered inside somebody else's page.\n *\n * Three things the standalone page may do and an embedded one may not: write its tab and\n * filter into `location.hash` — which is the host's URL, and whose back button would then\n * walk through somebody's tab changes; listen for keys on the window, which hijacks the\n * host's own shortcuts; and rely on fragment navigation, which does not cross a shadow\n * boundary at all.\n */\nlet embedded = false;\n\n/** Points the client at a shadow root. Call it before the rest of the client loads. */\nexport function setRoot(node: Document | ShadowRoot | HTMLElement, host?: HTMLElement): void {\n root = node as Document | ShadowRoot;\n embedded = node !== (globalThis as unknown as { document?: Document }).document;\n if (host !== undefined) themeHost = host;\n}\n\n/** Updates the element the theme hangs off, without disturbing the rest. */\nexport function setThemeHost(host: HTMLElement): void {\n themeHost = host;\n}\n\nexport function isEmbedded(): boolean {\n return embedded;\n}\n\n/**\n * What global-ish events are listened on.\n *\n * The document when this owns the page. The dashboard's own subtree when it does not, so\n * a digit pressed on the host's page is the host's business and not a tab change here.\n */\nexport function eventTarget(): EventTarget {\n return embedded ? (root as unknown as EventTarget) : (globalThis as unknown as EventTarget);\n}\n\n/** The node every lookup runs against. */\nexport function rootNode(): Document | ShadowRoot {\n return root as Document | ShadowRoot;\n}\n\n/** The element carrying `data-theme` and the custom properties. */\nexport function themeElement(): HTMLElement {\n return themeHost;\n}\n\nexport function $(id: string): HTMLElement {\n // `querySelector` rather than `getElementById`, because the latter is a method of\n // Document and of DocumentFragment but the two are reached differently, and one call\n // that works against both is worth more than the microseconds.\n const node = root.querySelector<HTMLElement>(`#${cssEscape(id)}`);\n // Every id this is called with is one the page's own markup declares, so a miss is a\n // typo in a rename rather than a condition to handle. Failing loudly here beats a\n // null dereference three frames later.\n if (node === null || node === undefined) throw new Error(`dashboard: no element #${id}`);\n return node;\n}\n\nexport function byId<T extends HTMLElement>(id: string): T {\n return $(id) as T;\n}\n\n/** A CSS custom property's value, for the charts — which draw in the theme's own colours. */\nexport function css(name: string): string {\n return getComputedStyle(themeHost).getPropertyValue(name).trim();\n}\n\nlet sequence = 0;\n\n/**\n * Ties a `<label>` to the control it names.\n *\n * A label sitting next to an input is a label to a reader who can see the layout and\n * nothing at all to a screen reader: it announces \"edit, blank\" and moves on. Both form\n * builders on this page had that defect, and both were caught by the automated\n * accessibility pass rather than by anybody looking — which is the argument for having\n * one.\n *\n * Only for the controls a label can point at. A group of chips or a segmented control is\n * several buttons, and those carry their own names.\n */\nexport function label(text: string, control: HTMLElement | HTMLElement[]): HTMLLabelElement {\n const node = document.createElement(\"label\");\n node.textContent = text;\n const single = Array.isArray(control) ? (control.length === 1 ? control[0] : undefined) : control;\n if (single !== undefined && /^(input|select|textarea)$/i.test(single.tagName)) {\n if (single.id === \"\") single.id = `field-${++sequence}`;\n node.htmlFor = single.id;\n } else {\n // Nothing to point at, so the group gets the name instead and the text becomes\n // decoration rather than a promise the page does not keep.\n const group = Array.isArray(control) ? control : [control];\n for (const node_ of group) if (!node_.hasAttribute(\"aria-label\")) node_.setAttribute(\"aria-label\", text);\n }\n return node;\n}\n","import type { Boot } from \"./types.js\";\n\n/**\n * What the server told the page about itself.\n *\n * Stamped into the one nonced `<script>` as a JSON string and parsed here, rather than\n * fetched: the title, the mount path and — the part that matters — which sections and\n * controls this listener has. A page that had to ask for those would spend its first\n * frame drawing tabs it is about to remove.\n */\nconst raw = (globalThis as unknown as { __BOOTSTRAP__?: Boot }).__BOOTSTRAP__;\n\nexport const BOOT: Boot = raw ?? {\n base: \"\",\n title: \"bothandlerjs\",\n allowReset: false,\n allowEdit: false,\n allowGuardEdit: false,\n allowActing: false,\n peers: [],\n sections: { feed: true, evidence: true, actors: true, registry: true, tester: true, statistics: true, audit: true, notices: true, changes: true, policy: true, guard: true, robots: true, ranges: true },\n links: [],\n};\n\n/** Every request the page makes is relative to the mount path. */\nexport const API = BOOT.base;\n\nexport const SECTIONS = BOOT.sections;\n","import { el, rootNode } from \"./dom.js\";\nimport type { TabName } from \"./types.js\";\n\n/**\n * The handful of things every module needs to be able to do to the page as a whole.\n *\n * A mutable record filled in by `index.ts` at start-up, rather than each module\n * importing the module that owns `draw`. The feed opens the actor panel, the actor\n * panel drafts a rule into the policy editor, the policy editor switches to its own\n * tab: written as direct imports that is a cycle, and a cycle in a bundle is a\n * half-initialised module waiting to be discovered at run time. One indirection costs\n * a property lookup and makes the shape obvious.\n */\nexport interface App {\n /** Redraws the active view on the next frame. Coalesced; safe to call per event. */\n draw: () => void;\n /** Redraws immediately, for the cases where a click has to be reflected before the next frame. */\n drawNow: () => void;\n showTab: (tab: TabName, options?: { focus?: boolean; replace?: boolean; push?: boolean }) => void;\n /** Puts the feed's filter state into the URL, so a view can be sent to somebody. */\n syncUrl: () => void;\n}\n\nexport const app: App = {\n draw: () => {},\n drawNow: () => {},\n showTab: () => {},\n syncUrl: () => {},\n};\n\n/**\n * A transient message in the corner.\n *\n * Lives here rather than in the policy editor because the guard form, the feed's\n * exporter and the stream all raise them now, and three copies of a six-line function\n * is how they end up looking different from each other.\n */\nexport function toast(kind: \"ok\" | \"bad\" | \"warn\", title: string, detail = \"\"): void {\n const node = el(\"div\", `toast ${kind}`);\n node.appendChild(el(\"b\", null, title));\n if (detail !== \"\") node.appendChild(el(\"span\", null, detail));\n const host = rootNode().querySelector<HTMLElement>(\"#toasts\");\n if (host === null) return;\n host.appendChild(node);\n setTimeout(() => node.remove(), 6000);\n}\n\n/** Hands the viewer a file. Used by the settings export and the feed export. */\nexport function download(text: string, filename: string, type: string): void {\n const blob = new Blob([text], { type });\n const url = URL.createObjectURL(blob);\n const anchor = el(\"a\");\n anchor.href = url;\n anchor.download = filename;\n anchor.click();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\n/** Today, as a filename fragment. Every export the page produces is stamped with it. */\nexport function today(): string {\n return new Date().toISOString().slice(0, 10);\n}\n","import type { DashboardEntry } from \"./types.js\";\n\n/**\n * One request has one of four outcomes, and every part of the page agrees on which.\n *\n * Served, mitigated (something happened that a legitimate client can still get past),\n * denied, or pending — assessed, with no decision recorded, which is what `assess()`\n * on its own produces and what a row looks like for the moment between the two events.\n *\n * Pure, and exported on its own, because the table, the left edges, the timeline, the\n * actor mix and the statistics panels all classify the same way. Two of them disagreeing\n * about what a denial is would be a bug nobody could see.\n */\nexport type Outcome = \"allow\" | \"mitigate\" | \"deny\" | \"pending\";\n\nconst DENY = new Set([\"block\", \"drop\", \"redirect\"]);\nconst MITIGATE = new Set([\"challenge\", \"rate-limit\", \"delay\"]);\n\nexport function outcome(entry: Pick<DashboardEntry, \"action\">): Outcome {\n return actionKind(entry.action);\n}\n\n/** The same classification for a bare action name, which is what the rule editor has. */\nexport function actionKind(action: string | undefined): Outcome {\n if (action === undefined) return \"pending\";\n if (DENY.has(action)) return \"deny\";\n if (MITIGATE.has(action)) return \"mitigate\";\n return \"allow\";\n}\n\n/** The verdict badge: its class, and the word on it. */\nexport function verdictBadge(entry: Pick<DashboardEntry, \"verdict\" | \"bypass\">): [className: string, label: string] {\n if (entry.verdict === \"verified-bot\" || entry.verdict === \"confirmed-bot\") return [\"b-proven\", entry.verdict];\n if (entry.verdict === \"suspected-bot\") return [\"b-suspected\", \"suspected-bot\"];\n if (entry.verdict === \"human\") return [\"b-human\", \"human\"];\n return [\"b-unknown\", entry.bypass !== undefined ? `skipped · ${entry.bypass}` : \"unknown\"];\n}\n\n/**\n * How many assessments proved a *bot*.\n *\n * Not `metrics.proven`, which is what the tile used to show. That counter is honestly\n * named for what it holds — assessments resting on at least one piece of proven evidence —\n * and evidence has a direction: a client holding an operator or interaction clearance\n * produces *certain human* evidence, so it lands there too. A dashboard watching nothing\n * but cleared humans therefore read \"Proven bots: 100% of traffic\", and the same requests\n * were counted again under Unremarkable.\n *\n * The two proven-bot verdicts are the exact answer. Only the proven path can reach them —\n * the probabilistic path produces `suspected-bot`, `human` or `unknown` and nothing else —\n * so this is a rename of the truth rather than an approximation of it.\n */\nexport function provenBots(verdicts: Record<string, number>): number {\n return (verdicts[\"confirmed-bot\"] ?? 0) + (verdicts[\"verified-bot\"] ?? 0);\n}\n","import { outcome } from \"./outcome.js\";\nimport type { DashboardEntry } from \"./types.js\";\n\n/**\n * The feed's search.\n *\n * It used to be one `indexOf` over every field of a request joined into a string,\n * which answers \"does this word appear anywhere\" and nothing else. That is the wrong\n * question in the two cases somebody actually reaches for the box: an address that\n * also appears inside a User-Agent, and a path that is a prefix of ten others.\n *\n * So terms can name a field, and can be negated:\n *\n * ```text\n * actor:203.0.113.4 -path:/health that address, except its health checks\n * rule:no-scrapers action:tag the rule that fired, and what it settled on\n * score:>70 -certain probabilistic traffic close to the line\n * \"GET /api/v2/orders\" a phrase, spaces and all\n * ```\n *\n * Every term must match — this is an `AND`, because that is what narrowing means and\n * a filter box that quietly `OR`s is a filter box that lies. Anything that does not\n * parse as a field term is matched against the whole request, so a plain word still\n * behaves exactly as it did.\n *\n * Kept free of the DOM so it can be unit-tested, which is the point: this is the piece\n * of the page most likely to be wrong in a way nobody notices.\n */\nexport interface Term {\n /** The field named before the colon, already resolved to a canonical name. */\n field: string | undefined;\n value: string;\n negated: boolean;\n /** For numeric fields: how `value` should be compared. */\n compare?: \"<\" | \">\" | \"=\" | undefined;\n}\n\n/** Field names, plus the shorthands people type instead. */\nconst FIELDS: Record<string, string> = {\n path: \"path\",\n url: \"path\",\n actor: \"actor\",\n ip: \"actor\",\n ua: \"userAgent\",\n useragent: \"userAgent\",\n agent: \"userAgent\",\n verdict: \"verdict\",\n action: \"action\",\n rule: \"rule\",\n detector: \"detector\",\n identity: \"identity\",\n method: \"method\",\n class: \"botClass\",\n botclass: \"botClass\",\n id: \"requestId\",\n request: \"requestId\",\n bypass: \"bypass\",\n score: \"score\",\n outcome: \"outcome\",\n certain: \"certain\",\n};\n\nconst NUMERIC = new Set([\"score\"]);\n\n/** Splits on whitespace, keeping double-quoted runs together. */\nfunction tokenize(input: string): string[] {\n const tokens: string[] = [];\n let current = \"\";\n let quoted = false;\n for (const character of input) {\n if (character === '\"') {\n quoted = !quoted;\n continue;\n }\n if (!quoted && /\\s/.test(character)) {\n if (current !== \"\") tokens.push(current);\n current = \"\";\n continue;\n }\n current += character;\n }\n if (current !== \"\") tokens.push(current);\n return tokens;\n}\n\nexport function parseQuery(input: string): Term[] {\n const terms: Term[] = [];\n for (const token of tokenize(input.trim())) {\n const negated = token.startsWith(\"-\") || token.startsWith(\"!\");\n const body = negated ? token.slice(1) : token;\n if (body === \"\") continue;\n\n const colon = body.indexOf(\":\");\n const name = colon === -1 ? \"\" : body.slice(0, colon).toLowerCase();\n const field = FIELDS[name];\n // A colon with an unknown name in front of it is not a field term — a path can\n // contain one, and so can a User-Agent. It falls through to a free term.\n if (colon === -1 || field === undefined) {\n terms.push({ field: undefined, value: body.toLowerCase(), negated });\n continue;\n }\n\n let value = body.slice(colon + 1).toLowerCase();\n let compare: Term[\"compare\"];\n if (NUMERIC.has(field)) {\n compare = value.startsWith(\">\") ? \">\" : value.startsWith(\"<\") ? \"<\" : \"=\";\n if (compare !== \"=\") value = value.slice(1);\n }\n if (value === \"\") continue;\n terms.push({ field, value, negated, compare });\n }\n return terms;\n}\n\n/** Everything about a request, as one lower-case string. What a free term is matched against. */\nexport function searchableText(entry: DashboardEntry): string {\n const parts = [\n entry.method,\n entry.path,\n entry.actor,\n entry.userAgent,\n entry.verdict,\n entry.botClass,\n entry.identity ?? \"\",\n entry.action ?? \"\",\n entry.rule ?? \"\",\n entry.requestId,\n ];\n for (const item of entry.evidence) parts.push(item.detector, item.summary);\n return parts.join(\" \").toLowerCase();\n}\n\nfunction fieldValue(entry: DashboardEntry, field: string): string {\n switch (field) {\n case \"path\":\n return entry.path;\n case \"actor\":\n return entry.actor;\n case \"userAgent\":\n return entry.userAgent;\n case \"verdict\":\n return entry.verdict;\n case \"action\":\n return entry.action ?? \"\";\n case \"rule\":\n return entry.rule ?? \"\";\n case \"identity\":\n return entry.identity ?? \"\";\n case \"method\":\n return entry.method;\n case \"botClass\":\n return entry.botClass;\n case \"requestId\":\n return entry.requestId;\n case \"bypass\":\n return entry.bypass ?? \"\";\n case \"outcome\":\n return outcome(entry);\n case \"certain\":\n return String(entry.certain);\n case \"detector\":\n return entry.evidence.map((item) => item.detector).join(\" \");\n default:\n return \"\";\n }\n}\n\nfunction matchesTerm(term: Term, entry: DashboardEntry, haystack: string): boolean {\n if (term.field === undefined) return haystack.includes(term.value);\n if (term.field === \"score\") {\n const wanted = Number(term.value);\n if (Number.isNaN(wanted)) return false;\n if (term.compare === \">\") return entry.score > wanted;\n if (term.compare === \"<\") return entry.score < wanted;\n return entry.score === wanted;\n }\n return fieldValue(entry, term.field).toLowerCase().includes(term.value);\n}\n\n/** True when every term is satisfied. An empty query matches everything. */\nexport function matchesQuery(terms: readonly Term[], entry: DashboardEntry, haystack: string): boolean {\n for (const term of terms) {\n if (matchesTerm(term, entry, haystack) === term.negated) return false;\n }\n return true;\n}\n\n/** The named filter buttons, which are a second, independent narrowing. */\nexport type FilterName = \"all\" | \"proven\" | \"suspected\" | \"human\" | \"guard\" | \"deny\" | \"mitigate\" | \"allow\";\n\nexport function matchesFilter(filter: FilterName, entry: DashboardEntry): boolean {\n switch (filter) {\n case \"proven\":\n return entry.certain;\n case \"suspected\":\n return entry.verdict === \"suspected-bot\";\n case \"human\":\n return entry.verdict === \"human\";\n case \"guard\":\n return entry.downgradedFrom !== undefined;\n case \"deny\":\n return outcome(entry) === \"deny\";\n case \"mitigate\":\n return outcome(entry) === \"mitigate\";\n case \"allow\":\n return outcome(entry) === \"allow\";\n default:\n return true;\n }\n}\n","import { matchesFilter, matchesQuery, parseQuery, searchableText } from \"./query.js\";\nimport { outcome } from \"./outcome.js\";\nimport type { EditorRule } from \"./draft.js\";\nimport type { FilterName, Term } from \"./query.js\";\nimport type { ActorRow, DashboardEntry, Policy, Row, Snapshot, TabName } from \"./types.js\";\n\n/** Requests the page keeps. The server's ring is smaller; this is the ceiling, not the target. */\nconst MAX_ROWS = 1000;\n\n/** Rows built into the table at once. Beyond this a feed is scrolled past, not read. */\nexport const FEED_LIMIT = 300;\n\nexport interface State {\n rows: Row[];\n byId: Map<string, Row>;\n snapshot: Snapshot | undefined;\n policy: Policy | undefined;\n /** The Actors screen's list, fetched rather than streamed. See `registry.ts`. */\n actors: ActorRow[];\n actorsTracked: number;\n paused: boolean;\n filter: FilterName;\n search: string;\n terms: Term[];\n tab: TabName;\n open: Set<string>;\n actor: string | undefined;\n rangeMs: number;\n /** Which population the score distribution is drawn from. See `charts.drawScores`. */\n scoreScope: \"run\" | \"window\";\n editorRules: EditorRule[];\n editorDirty: boolean;\n editorMode: \"gui\" | \"json\";\n guardDirty: boolean;\n /** Rows that arrived while paused, so the button can say what resuming will show. */\n bufferedWhilePaused: number;\n /** Frames the server dropped because *this* connection was too slow to take them. */\n laggedDrops: number;\n /**\n * Which page of the feed is showing, newest first, zero-based.\n *\n * Zero follows the live feed. Any other page is a position in history, so arriving\n * requests must not shuffle it under the reader — see `feedFrozen`.\n */\n feedPage: number;\n /**\n * The matching rows as they stood when the reader left page zero.\n *\n * Without this, one request arriving while somebody reads page three moves every row\n * down by one and they are silently reading different rows than the ones they were\n * looking at. Frozen on leaving page zero, dropped on returning to it.\n */\n feedFrozen: Row[] | undefined;\n /** Which page of the Actors table is showing. Paged on the server, by offset. */\n actorsPage: number;\n /** How many rows a page of each table holds. Chosen in the page, not configured. */\n feedPageSize: number;\n actorsPageSize: number;\n /**\n * How many skipped entries have already been fetched back and merged.\n *\n * The server's `skipped` only ever grows, so the badge subtracts this to say how many\n * are *still* missing rather than how many ever were.\n */\n caughtUp: number;\n}\n\nexport const state: State = {\n rows: [],\n byId: new Map(),\n snapshot: undefined,\n policy: undefined,\n actors: [],\n actorsTracked: 0,\n paused: false,\n filter: \"all\",\n search: \"\",\n terms: [],\n tab: \"live\",\n open: new Set(),\n actor: undefined,\n rangeMs: 300_000,\n scoreScope: \"run\",\n editorRules: [],\n editorDirty: false,\n editorMode: \"gui\",\n guardDirty: false,\n bufferedWhilePaused: 0,\n laggedDrops: 0,\n feedPage: 0,\n feedFrozen: undefined,\n actorsPage: 0,\n feedPageSize: 50,\n actorsPageSize: 25,\n caughtUp: 0,\n};\n\n/**\n * Files one entry into the ring.\n *\n * The same request arrives twice — once when it is assessed and again when the\n * decision lands, and again after a reconnect that replays what was missed — so this\n * is an upsert keyed by request id, and `rev` is what tells the renderer that a row on\n * screen is now stale. Rebuilding a row nothing changed about is what used to wipe a\n * text selection every time a request came in.\n */\nexport function ingest(entry: DashboardEntry): void {\n const existing = state.byId.get(entry.requestId);\n if (existing !== undefined) {\n existing.entry = entry;\n existing.rev++;\n existing.text = undefined;\n return;\n }\n const row: Row = { entry, rev: 0 };\n state.byId.set(entry.requestId, row);\n state.rows.push(row);\n if (state.rows.length > MAX_ROWS) {\n for (const dropped of state.rows.splice(0, state.rows.length - MAX_ROWS)) {\n state.byId.delete(dropped.entry.requestId);\n state.open.delete(dropped.entry.requestId);\n }\n }\n}\n\nexport function clearFeed(): void {\n state.rows = [];\n state.byId = new Map();\n state.open.clear();\n state.actor = undefined;\n state.bufferedWhilePaused = 0;\n // The lag count goes with them. It is half of the \"N not streamed\" badge — the server's\n // own `skipped` is the other half, and `FeedRing.clear()` resets that — so leaving this\n // one behind made the badge disagree with itself: a number that survived the feed it\n // described, under a tooltip promising the entries were \"still in the window, the\n // preview and the export\" when the window had just been replaced. Both ways a feed is\n // cleared lead here, and the second is the one that matters: a viewer whose stream was\n // dropped for lagging reconnects with a stale cursor and is sent a fresh backlog, so the\n // gap this counted is exactly what has just been filled in.\n state.laggedDrops = 0;\n // And the frozen page goes with them, for the same reason: it holds rows this store no\n // longer has, so a reader left on page three would be paging through a list of things\n // that are gone.\n resetPaging();\n}\n\nexport function setSearch(value: string): void {\n state.search = value;\n state.terms = parseQuery(value);\n // A narrower search over a frozen page-three is somebody reading rows their filter no\n // longer selects. Any change to what matches goes back to the live first page.\n resetPaging();\n}\n\n/** Back to the live first page, thawed. Called whenever what matches changes. */\nexport function resetPaging(): void {\n state.feedPage = 0;\n state.feedFrozen = undefined;\n}\n\nfunction textOf(row: Row): string {\n if (row.text === undefined) row.text = searchableText(row.entry);\n return row.text;\n}\n\n/**\n * Puts the ring back in time order after a bulk merge.\n *\n * `ingest` appends, because the stream delivers in order and appending is what that\n * costs. A backlog fetched over HTTP is not in order relative to what is already held —\n * it is *older* — so merging without this leaves yesterday's requests sitting at the\n * newest end, which is where the feed reads from.\n */\nexport function sortRows(): void {\n state.rows.sort((a, b) => a.entry.at - b.entry.at);\n}\n\nexport function matches(row: Row): boolean {\n return matchesFilter(state.filter, row.entry) && matchesQuery(state.terms, row.entry, textOf(row));\n}\n\n/**\n * Every row matching the filter, newest first.\n *\n * The whole set rather than a screenful: the feed pages through it, and the export means\n * \"what I am looking at\" rather than \"the first page of it\".\n */\nexport function matchingRows(limit = Number.POSITIVE_INFINITY): Row[] {\n const shown: Row[] = [];\n for (let i = state.rows.length - 1; i >= 0 && shown.length < limit; i--) {\n const row = state.rows[i];\n if (row !== undefined && matches(row)) shown.push(row);\n }\n return shown;\n}\n\n/** The rows the feed would draw, newest first. Also what \"export what I am looking at\" means. */\nexport function visibleRows(limit = FEED_LIMIT): Row[] {\n return matchingRows(limit);\n}\n\n/**\n * One page of the feed, and what the pager needs to describe itself.\n *\n * Page zero reads live and is recomputed every draw. Any other page reads the list as it\n * was when the reader left page zero, because a feed that renumbers itself under somebody\n * paging through it is a feed they cannot read.\n */\nexport function feedPage(size: number): { rows: Row[]; page: number; pages: number; total: number } {\n const all = state.feedPage === 0 || state.feedFrozen === undefined ? matchingRows() : state.feedFrozen;\n const pages = Math.max(1, Math.ceil(all.length / size));\n // A filter that narrows while somebody is on the last page must not leave them past the\n // end looking at nothing.\n const page = Math.min(Math.max(0, state.feedPage), pages - 1);\n if (page !== state.feedPage) state.feedPage = page;\n return { rows: all.slice(page * size, page * size + size), page, pages, total: all.length };\n}\n\n/** Moves to a page, freezing the list on the way off page zero and thawing on the way back. */\nexport function goToFeedPage(page: number): void {\n const next = Math.max(0, page);\n if (next === 0) {\n state.feedFrozen = undefined;\n } else if (state.feedFrozen === undefined) {\n state.feedFrozen = matchingRows();\n }\n state.feedPage = next;\n}\n\nexport function matchingCount(): number {\n let count = 0;\n for (const row of state.rows) if (matches(row)) count++;\n return count;\n}\n\nexport function oldestAt(): number | undefined {\n return state.rows[0]?.entry.at;\n}\n\nexport interface Aggregates {\n detectors: Map<string, number>;\n actors: Map<string, number>;\n identities: Map<string, number>;\n paths: Map<string, number>;\n deniedPaths: Map<string, number>;\n guardStops: Map<string, number>;\n ruleHits: Map<string, number>;\n bypassed: Map<string, number>;\n}\n\nfunction bump(counter: Map<string, number>, key: string): void {\n counter.set(key, (counter.get(key) ?? 0) + 1);\n}\n\n/**\n * Everything the window-scoped panels count, in one pass over the ring.\n *\n * One pass rather than eight, and — more to the point — it is now called only by the\n * screen that draws them. It used to run on every frame of the live feed to fill in\n * panels on a tab nobody was looking at.\n */\nexport function aggregate(rows: readonly Row[]): Aggregates {\n const totals: Aggregates = {\n detectors: new Map(),\n actors: new Map(),\n identities: new Map(),\n paths: new Map(),\n deniedPaths: new Map(),\n guardStops: new Map(),\n ruleHits: new Map(),\n bypassed: new Map(),\n };\n\n for (const { entry } of rows) {\n for (const item of entry.evidence) bump(totals.detectors, item.detector);\n bump(totals.actors, entry.actor);\n\n if (entry.bypass !== undefined) {\n bump(totals.bypassed, `${entry.path} (${entry.bypass})`);\n continue;\n }\n bump(totals.paths, entry.path);\n if (entry.identity !== undefined && entry.identity !== \"\") {\n // Claimed and confirmed are different facts about the same name, and a panel\n // that merged them would report a forged Googlebot as Googlebot.\n bump(totals.identities, `${entry.identity} · ${entry.verdict === \"verified-bot\" ? \"verified\" : \"claimed\"}`);\n }\n if (outcome(entry) === \"deny\") bump(totals.deniedPaths, entry.path);\n if (entry.downgradedFrom !== undefined && entry.rule !== undefined) bump(totals.guardStops, `${entry.rule} → ${entry.downgradedFrom}`);\n if (entry.rule !== undefined) bump(totals.ruleHits, entry.rule);\n }\n\n return totals;\n}\n","import { API } from \"./boot.js\";\n\n/**\n * The page's two ways of talking to its server.\n *\n * Every write is JSON with a JSON content type, and that is not a style choice: the\n * server refuses a body it did not get as `application/json` precisely because an HTML\n * form cannot send one, which is what stops somebody else's page from posting here\n * with an operator's credentials attached.\n */\nexport async function getJson<T>(path: string): Promise<T> {\n const response = await fetch(API + path);\n if (!response.ok) throw new Error(await errorFrom(response));\n return (await response.json()) as T;\n}\n\nexport interface PostResult<T> {\n ok: boolean;\n data: T;\n error?: string;\n}\n\nexport async function postJson<T>(path: string, body: unknown): Promise<PostResult<T>> {\n const response = await fetch(API + path, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n const data = (await response.json().catch(() => ({}))) as T & { error?: string };\n return response.ok ? { ok: true, data } : { ok: false, data, error: String(data.error ?? \"The server refused this.\") };\n}\n\nasync function errorFrom(response: Response): Promise<string> {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n return body.error ?? `${response.status} ${response.statusText}`;\n}\n","/** Number, duration and window formatting. Pure, so the awkward cases can be tested. */\n\nconst numbers = new Intl.NumberFormat();\n\nexport function n(value: number | undefined): string {\n return numbers.format(value ?? 0);\n}\n\nexport function pct(part: number, whole: number): string {\n return whole > 0 ? `${Math.round((part / whole) * 100)}%` : \"—\";\n}\n\nexport function ms(value: number): string {\n return value >= 10 ? `${value.toFixed(1)}ms` : `${value.toFixed(2)}ms`;\n}\n\nexport function uptime(milliseconds: number): string {\n const seconds = Math.max(0, Math.round(milliseconds / 1000));\n if (seconds < 90) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 90) return `${minutes}m`;\n return `${Math.round(minutes / 60)}h`;\n}\n\nexport function rangeLabel(milliseconds: number): string {\n const seconds = Math.round(milliseconds / 1000);\n if (seconds < 90) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n return minutes < 90 ? `${minutes} min` : `${Math.round(minutes / 60)}h`;\n}\n\n/** Wall-clock time for a feed row. Local, seconds included, because a feed moves in seconds. */\n/**\n * A time, always as 24-hour HH:MM:SS.\n *\n * Built from the local components rather than handed to `toLocaleTimeString`, which\n * answers in whatever the viewer's locale prefers — so the same feed read \"4:40:46 PM\" on\n * one operator's screen and \"16:40:46\" on the next, and a dashboard two people look at\n * together should not disagree with itself about what time it is. Local time, not UTC:\n * this is the clock on the wall next to the server somebody is watching.\n */\nexport function clockTime(at: number): string {\n const when = new Date(at);\n return `${pad(when.getHours())}:${pad(when.getMinutes())}:${pad(when.getSeconds())}`;\n}\n\n/** A date, always as DD-MM-YYYY. */\nexport function clockDate(at: number): string {\n const when = new Date(at);\n return `${pad(when.getDate())}-${pad(when.getMonth() + 1)}-${when.getFullYear()}`;\n}\n\n/**\n * Both, for the places where the time alone is ambiguous.\n *\n * An actor's first sighting can be days back, and \"first seen 09:14:02\" invites the reader\n * to assume it was this morning.\n */\nexport function clockStamp(at: number): string {\n return `${clockDate(at)} ${clockTime(at)}`;\n}\n\nfunction pad(value: number): string {\n return String(value).padStart(2, \"0\");\n}\n\n/**\n * What \"this window\" actually holds, said out loud.\n *\n * Half the panels on the Statistics screen count the retained ring and half count\n * since the process started, and until now both wore the same quiet grey subtitle.\n * They are different populations — on a busy server the ring can be ninety seconds of\n * a three-week run — so comparing a panel from one against a panel from the other is a\n * mistake the page was inviting. Every \"this window\" label now says how much window\n * there is.\n */\nexport function windowLabel(count: number, oldestAt: number | undefined, now: number): string {\n if (count === 0) return \"this window · empty\";\n const span = oldestAt === undefined ? 0 : Math.max(0, now - oldestAt);\n return `last ${n(count)} requests · ${rangeLabel(span)}`;\n}\n","import { clear, el } from \"./dom.js\";\nimport { n } from \"./format.js\";\n\n/**\n * The pager both tables use.\n *\n * One renderer rather than two, because the feed pages in the browser and the Actors\n * table pages on the server and *neither of those facts belongs in the control*. What it\n * needs to know is where you are and what it should do when you press something.\n *\n * Rendered twice per table, above and below. A table of fifty rows is taller than the\n * window on most screens, and a pager only at the bottom means scrolling to the end to go\n * back to the top of the next page.\n */\nexport interface PagerModel {\n /** Zero-based. */\n page: number;\n /** What the range reads: `1–50 of 212`. Total omitted where the total is not known. */\n from: number;\n to: number;\n total?: number;\n atStart: boolean;\n atEnd: boolean;\n /** Shown next to the range when the list is being held still. */\n held?: string;\n go: (page: number) => void;\n size?: {\n current: number;\n choices: readonly number[];\n set: (size: number) => void;\n };\n}\n\n/** Which way a step goes, in words a screen reader can use. */\ninterface Step {\n to: number;\n glyph: string;\n label: string;\n disabled: boolean;\n}\n\n/**\n * What the last render of each pager was showing.\n *\n * The tables redraw on every arriving request, and rebuilding this control each time\n * replaces the very nodes somebody is reaching for: a select loses its open list mid-choice\n * and a click lands on a node that has just been detached. So it is rebuilt only when what\n * it says changes — the same argument the counter tiles already make for themselves.\n */\nconst painted = new WeakMap<HTMLElement, string>();\n\nexport function renderPager(host: HTMLElement, model: PagerModel, options: { withSize: boolean }): void {\n const signature = JSON.stringify([model.page, model.from, model.to, model.total, model.atStart, model.atEnd, model.held ?? \"\", options.withSize, model.size?.current]);\n if (painted.get(host) === signature && host.childElementCount > 0) return;\n painted.set(host, signature);\n clear(host);\n\n const steps: Step[] = [\n { to: model.page - 1, glyph: \"‹\", label: \"Previous page\", disabled: model.atStart },\n { to: model.page + 1, glyph: \"›\", label: \"Next page\", disabled: model.atEnd },\n ];\n\n const [previous, next] = steps as [Step, Step];\n host.appendChild(stepButton(previous, model));\n\n const range = model.total === undefined ? `${n(model.from)}–${n(model.to)}` : `${n(model.from)}–${n(model.to)} of ${n(model.total)}`;\n const where = el(\"span\", \"where\", range);\n // Announced on change, because the rows themselves are not a live region: somebody\n // paging with the keyboard has to hear that the page moved.\n where.setAttribute(\"aria-live\", \"polite\");\n host.appendChild(where);\n\n host.appendChild(stepButton(next, model));\n\n if (model.held !== undefined) {\n const held = el(\"span\", \"held\", model.held);\n held.title = \"New requests are still arriving and are still counted. They appear when you return to the first page.\";\n host.appendChild(held);\n }\n\n // The size chooser goes on one of the two pagers only. Two of them would be two\n // controls for one setting, which is a thing to keep in sync and a second stop in the\n // tab order that does nothing new.\n if (options.withSize && model.size !== undefined) {\n const size = model.size;\n const label = el(\"label\", \"size\");\n label.appendChild(document.createTextNode(\"Per page\"));\n const select = document.createElement(\"select\");\n for (const choice of size.choices) {\n const option = document.createElement(\"option\");\n option.value = String(choice);\n option.textContent = String(choice);\n option.selected = choice === size.current;\n select.appendChild(option);\n }\n select.addEventListener(\"change\", () => {\n const chosen = Number(select.value);\n if (Number.isFinite(chosen) && chosen > 0) size.set(chosen);\n });\n label.appendChild(select);\n host.appendChild(label);\n }\n}\n\nfunction stepButton(step: Step, model: PagerModel): HTMLElement {\n const button = el(\"button\", \"step\", step.glyph);\n const element = button as HTMLButtonElement;\n element.type = \"button\";\n element.disabled = step.disabled;\n // The glyph is decoration; the name is the label. A button whose accessible name is\n // \"›\" tells somebody using a screen reader nothing at all.\n button.setAttribute(\"aria-label\", step.label);\n button.title = step.label;\n button.addEventListener(\"click\", () => model.go(Math.max(0, step.to)));\n return button;\n}\n","import type { DashboardEntry } from \"./types.js\";\n\n/**\n * A request as a line `bothandlerjs replay` can read.\n *\n * The point of the button this backs: a verdict you disagree with on screen becomes a\n * fixture you can re-run offline, and then a corpus case that stops it coming back.\n * Values the dashboard redacted stay redacted — the shape is what replays, and a\n * session cookie is not part of the shape.\n */\nexport function replayLine(entry: DashboardEntry): string {\n const headers: Record<string, string> = {};\n for (const [name, value] of entry.headers ?? []) headers[name] = value;\n const query = Object.keys(entry.query)\n .map((name) => `${encodeURIComponent(name)}=${encodeURIComponent(entry.query[name] ?? \"\")}`)\n .join(\"&\");\n return JSON.stringify({\n method: entry.method,\n url: entry.path + (query === \"\" ? \"\" : `?${query}`),\n headers,\n ip: entry.actor,\n timestamp: new Date(entry.at).toISOString(),\n protocol: entry.protocol ?? \"https\",\n httpVersion: entry.httpVersion ?? \"1.1\",\n });\n}\n\n/** Every shown request as replay JSONL, oldest first — the whole filtered window at once. */\nexport function replayFile(entries: readonly DashboardEntry[]): string {\n return entries.map(replayLine).join(\"\\n\");\n}\n\n/** The same request as a traffic-corpus case, ready to paste into a fixture file. */\nexport function corpusCase(entry: DashboardEntry): string {\n const headers = (entry.headers ?? []).map((pair) => ` [${JSON.stringify(pair[0])}, ${JSON.stringify(pair[1])}]`).join(\",\\n\");\n return [\n \"bot({\",\n ` id: ${JSON.stringify(`case-${entry.requestId.toLowerCase().replace(/[^a-z0-9]+/g, \"-\")}`)},`,\n ` title: ${JSON.stringify(`${entry.method} ${entry.path} from ${entry.userAgent.slice(0, 60)}`)},`,\n ' audience: \"unwanted-bot\", // human | benign-bot | declared-bot | unwanted-bot | hostile | infrastructure',\n ' category: \"observed\",',\n ` provenance: \"Captured from the live dashboard on ${new Date(entry.at).toISOString().slice(0, 10)}\",`,\n \" requests: [\",\n \" {\",\n \" headers: [\",\n headers,\n \" ],\",\n ` protocol: ${JSON.stringify(entry.protocol ?? \"https\")},`,\n ` httpVersion: ${JSON.stringify(entry.httpVersion ?? \"1.1\")},`,\n ` path: ${JSON.stringify(entry.path)},`,\n \" },\",\n \" ],\",\n ` expect: { verdict: ${JSON.stringify(entry.verdict)}, certain: ${String(entry.certain)} },`,\n \"}),\",\n ].join(\"\\n\");\n}\n","import { el } from \"./dom.js\";\nimport { BOOT } from \"./boot.js\";\nimport { postJson } from \"./api.js\";\nimport { toast } from \"./app.js\";\n\n/**\n * The three things you can do to one client, rather than to a class of request.\n *\n * Shared by the actor drill-down and the Actors screen, which offer the same three and\n * would otherwise offer them slightly differently.\n *\n * Allowlisting is the consequential one and it is the one that asks twice. **An\n * allowlisted address is not judged leniently; it is not judged at all** — detection\n * does not run, no evidence is produced, no rule sees it — so the button states the\n * address it is about to exempt and waits for a second click. That is deliberately not\n * a dialog: a confirmation you can dismiss without reading is a click with extra steps,\n * whereas a button that changes into the sentence it is about to enact has to be read\n * to be pressed.\n */\nexport type AfterAction = () => void;\n\n/**\n * How many confirmations are half-pressed right now.\n *\n * The Actors screen repaints on every counters frame — every two seconds — and a repaint\n * builds new buttons. An operator who clicked \"Allowlist\", read the address it offered\n * back and reached for the second click could therefore have the button quietly reset\n * under their cursor, so that the second click armed it again instead of doing anything.\n * Which is the worst outcome available for a confirmation: it teaches people that the\n * first click does nothing and the way through is to click twice, quickly.\n *\n * So the list holds still while somebody is deciding. See `drawActors`.\n */\nlet armed = 0;\n\nexport function isConfirming(): boolean {\n return armed > 0;\n}\n\nexport function actorActions(key: string, after: AfterAction): HTMLElement[] {\n if (!BOOT.allowActing) return [];\n\n const forget = el(\"button\", null, \"Forget\");\n forget.title = \"Discard this actor's history — the cure for a false positive that has stuck\";\n forget.addEventListener(\"click\", () => {\n void act({ key, action: \"forget\" }, `Forgot ${key}`, \"Its next request is assessed as a first request.\", after);\n });\n\n const clear = el(\"button\", null, \"Clear as human\");\n clear.title = \"Grant this actor human clearance for an hour, as though it had solved a challenge\";\n clear.addEventListener(\"click\", () => {\n void act({ key, action: \"clear\", forMs: 60 * 60_000 }, `Cleared ${key}`, \"Held as human for an hour, then reassessed.\", after);\n });\n\n return [confirmingButton(\"Allowlist\", `Allowlist ${key} — it stops being assessed at all`, () => allowlist(key, after)), forget, clear];\n}\n\n/**\n * A button that has to be pressed twice, and says what it will do in between.\n *\n * Reverts after a few seconds, so a half-pressed destructive action does not sit there\n * waiting for an accidental second click.\n */\nfunction confirmingButton(label: string, confirmation: string, run: () => void): HTMLButtonElement {\n const button = el(\"button\", \"danger\", label);\n let pending = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const disarm = (): void => {\n if (!pending) return;\n pending = false;\n armed--;\n button.textContent = label;\n button.className = \"danger\";\n };\n\n button.addEventListener(\"click\", () => {\n if (pending) {\n if (timer !== undefined) clearTimeout(timer);\n disarm();\n run();\n return;\n }\n pending = true;\n armed++;\n button.textContent = confirmation;\n button.className = \"danger primary\";\n timer = setTimeout(disarm, 5000);\n });\n return button;\n}\n\nasync function allowlist(key: string, after: AfterAction): Promise<void> {\n const result = await postJson<{ entries?: string[]; error?: string }>(\"/api/ranges\", { name: \"allowlist\", add: [key] });\n if (!result.ok) {\n toast(\"bad\", \"Not allowlisted\", result.error ?? \"\");\n return;\n }\n toast(\"warn\", `Allowlisted ${key}`, \"Requests from it are no longer assessed at all.\");\n after();\n}\n\nasync function act(body: { key: string; action: string; forMs?: number }, title: string, detail: string, after: AfterAction): Promise<void> {\n const result = await postJson<{ error?: string }>(\"/api/actor\", body);\n if (!result.ok) {\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n toast(\"ok\", title, detail);\n after();\n}\n","import { clear, el } from \"./dom.js\";\nimport { n } from \"./format.js\";\n\n/** A horizontal bar list, sorted by value, capped at what a panel can show without scrolling. */\nexport function drawBars(target: HTMLElement, rows: Iterable<[string, number]>, emptyText: string): void {\n clear(target);\n const filtered = [...rows].filter(([, value]) => value > 0);\n if (filtered.length === 0) {\n target.appendChild(el(\"div\", \"note\", emptyText));\n return;\n }\n filtered.sort((a, b) => b[1] - a[1]);\n const max = filtered[0]?.[1] ?? 1;\n for (const [label, value] of filtered.slice(0, 14)) {\n const bar = el(\"div\", \"bar\");\n const track = el(\"div\", \"track\");\n const fill = el(\"div\", \"fill\");\n fill.style.width = `${Math.max(2, Math.round((value / max) * 100))}%`;\n track.appendChild(fill);\n track.appendChild(el(\"div\", \"lbl\", label));\n bar.appendChild(track);\n bar.appendChild(el(\"div\", \"v tnum\", n(value)));\n target.appendChild(bar);\n }\n}\n\n/** A record of counters as bar rows. */\nexport function pairs(record: Record<string, number> | undefined): Array<[string, number]> {\n return Object.entries(record ?? {});\n}\n","import { $, clear, el } from \"./dom.js\";\nimport { actorActions } from \"./actions.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { app } from \"./app.js\";\nimport { clockStamp, n } from \"./format.js\";\nimport { drawBars } from \"./bars.js\";\nimport { state } from \"./store.js\";\n\n/** Wires the panel's own Close button. Called once, and only when the panel exists. */\nexport function initActor(): void {\n if (!SECTIONS.actors) return;\n $(\"actor-close\").addEventListener(\"click\", closeActor);\n}\n\n/** Opens the drill-down above the feed and scrolls it into view. */\nexport function openActor(key: string): void {\n state.actor = key;\n app.drawNow();\n $(\"actor-panel\").scrollIntoView({ block: \"nearest\" });\n}\n\nfunction closeActor(): void {\n state.actor = undefined;\n app.drawNow();\n}\n\nexport function drawActor(): void {\n // The whole panel is removed when the actors section is off, so nothing below here\n // has anything to draw into.\n if (!SECTIONS.actors) return;\n const panel = $(\"actor-panel\");\n if (state.actor === undefined) {\n panel.hidden = true;\n return;\n }\n panel.hidden = false;\n $(\"actor-key\").textContent = state.actor;\n\n const mine = state.rows.filter((row) => row.entry.actor === state.actor).map((row) => row.entry);\n const latest = mine[mine.length - 1];\n const stats = latest?.actorStats;\n\n const gaps: number[] = [];\n for (let i = 1; i < mine.length; i++) gaps.push((mine[i] as { at: number }).at - (mine[i - 1] as { at: number }).at);\n const meanGap = gaps.length > 0 ? gaps.reduce((a, b) => a + b, 0) / gaps.length : 0;\n\n const box = $(\"actor-stats\");\n clear(box);\n const rows: Array<[string, string]> = [\n [\"In this window\", `${n(mine.length)} requests`],\n [\"Engine sees\", stats !== undefined ? `${n(stats.requests)} requests, ${n(stats.distinctPaths)} distinct paths` : \"—\"],\n [\"Prior confirmations\", stats !== undefined ? n(stats.priorConfirmations) : \"—\"],\n [\"Holds clearance\", stats !== undefined ? (stats.cleared ? \"yes\" : \"no\") : \"—\"],\n [\"First seen\", stats !== undefined ? clockStamp(stats.firstSeen) : \"—\"],\n [\"Mean gap\", gaps.length > 0 ? `${Math.round(meanGap)}ms over ${n(gaps.length)} gaps` : \"one request only\"],\n ];\n for (const [key, value] of rows) {\n box.appendChild(el(\"dt\", null, key));\n box.appendChild(el(\"dd\", null, value));\n }\n\n const verdicts = new Map<string, number>();\n const actions = new Map<string, number>();\n for (const entry of mine) {\n verdicts.set(entry.verdict, (verdicts.get(entry.verdict) ?? 0) + 1);\n if (entry.action !== undefined) actions.set(entry.action, (actions.get(entry.action) ?? 0) + 1);\n }\n drawBars($(\"actor-mix\"), [...verdicts, ...actions], \"Nothing yet.\");\n\n // The same three operations the Actors screen offers, on the actor already in front\n // of you. Absent entirely without `controls.editRanges`.\n const bar = $(\"actor-actions\");\n clear(bar);\n const buttons = actorActions(state.actor, () => app.drawNow());\n bar.hidden = buttons.length === 0;\n for (const button of buttons) bar.appendChild(button);\n}\n","import { $, clear, el } from \"./dom.js\";\nimport { n } from \"./format.js\";\nimport type { Preview } from \"./types.js\";\n\n/** The result box under the editor. One box, whichever of the two forms wrote to it. */\nexport function showResult(kind: \"ok\" | \"bad\" | \"warn\", build: (box: HTMLElement) => void): void {\n const box = $(\"policy-result\");\n box.hidden = false;\n box.className = `result ${kind}`;\n clear(box);\n build(box);\n}\n\n/**\n * What a candidate would have done to the traffic still in the window.\n *\n * `newDenials` is called out on its own because it is the one direction of change that\n * costs somebody their access, and it is easy to miss in a table of counts.\n */\nexport function renderPreview(preview: Preview, notes: readonly string[] = []): void {\n showResult(preview.newDenials > 0 ? \"warn\" : \"ok\", (box) => {\n const head = el(\"div\");\n head.appendChild(el(\"b\", null, `${n(preview.changed)} of ${n(preview.evaluated)} requests would be treated differently.`));\n box.appendChild(head);\n // Whatever led to this preview, kept beside its result. A draft explains what it\n // matched on and why it went last, and that explanation is worth more next to the\n // numbers than it was in the two seconds before they arrived.\n for (const note of notes) box.appendChild(el(\"div\", \"ev-meta\", note));\n if (preview.newDenials > 0) {\n box.appendChild(el(\"div\", null, `${n(preview.newDenials)} request(s) that are served today would be denied. Read the samples before applying this.`));\n }\n for (const warning of preview.warnings) box.appendChild(el(\"div\", \"ev-meta\", warning));\n\n const dead = preview.ruleHits.filter((row) => row.hits === 0 && row.rule !== \"default\");\n if (dead.length > 0) box.appendChild(el(\"div\", \"ev-meta\", `Never matched in this window: ${dead.map((row) => row.rule).join(\", \")}`));\n\n if (preview.samples.length > 0) {\n const table = el(\"table\", \"diff\");\n const head2 = el(\"tr\");\n for (const label of [\"Request\", \"Now\", \"Would be\"]) head2.appendChild(el(\"th\", null, label));\n table.appendChild(head2);\n for (const sample of preview.samples) {\n const row = el(\"tr\");\n const what = el(\"td\");\n what.appendChild(el(\"div\", \"mono\", sample.path));\n what.appendChild(el(\"div\", \"ev-meta\", `${sample.verdict} · ${sample.userAgent.slice(0, 48)}`));\n row.appendChild(what);\n const from = el(\"td\", \"from\");\n from.appendChild(el(\"div\", null, sample.from));\n from.appendChild(el(\"div\", \"ev-meta\", sample.fromRule));\n row.appendChild(from);\n const kind = sample.to === \"block\" || sample.to === \"drop\" || sample.to === \"redirect\" ? \"deny\" : sample.to === \"allow\" ? \"allow\" : \"\";\n const to = el(\"td\", `to ${kind}`);\n to.appendChild(el(\"div\", null, sample.to));\n to.appendChild(el(\"div\", \"ev-meta\", sample.toRule));\n row.appendChild(to);\n table.appendChild(row);\n }\n box.appendChild(table);\n } else if (preview.evaluated === 0) {\n box.appendChild(el(\"div\", \"ev-meta\", \"No traffic in the window to preview against — send some requests first.\"));\n }\n });\n}\n","import { $, clear, el, label } from \"./dom.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { app, toast } from \"./app.js\";\nimport { postJson } from \"./api.js\";\nimport { renderPreview, showResult } from \"./result.js\";\nimport { state } from \"./store.js\";\nimport type { Preview } from \"./types.js\";\n\n/** The guard as the form holds it. The same five fields the server accepts back. */\ninterface GuardDraft {\n falsePositivePolicy: string;\n fallbackAction: string;\n defaultAction: string;\n terminalScoreThreshold: number;\n suspectThreshold: number;\n}\n\nlet draft: GuardDraft | undefined;\n\n/**\n * What each mode actually does, in the place somebody is about to change it.\n *\n * The words `strict`, `balanced` and `aggressive` sound like a slider from cautious to\n * effective. They are not: they are three different answers to \"may a guess close the\n * door\", and the third one is \"yes\". Anybody moving this setting should read what it\n * means without going to find the documentation, so the documentation comes to them.\n */\nconst MODE_NOTES: Record<string, string> = {\n strict: \"A terminal action survives only on proven evidence. Nothing is ever denied on a guess. This is the default, and it is the claim this library makes about itself.\",\n balanced:\n \"A terminal action also survives on a probabilistic verdict that clears the score threshold with at least two independent strong signals. Real people do trip two signals — a hardened browser behind a corporate proxy is the usual pair — so this setting will eventually deny somebody who should have been served.\",\n aggressive: \"The guard is off. Every rule does exactly what it says, on proof or on suspicion alike, and the people it turns away first are the ones with the most unusual and most legitimate setups.\",\n};\n\nexport function drawGuard(): void {\n // Removed with its section, which can be off while the Policy tab as a whole is on.\n if (!SECTIONS.guard) return;\n const document_ = state.policy;\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n\n const editable = document_?.guardEditable === true;\n const panel = $(\"stat-policy\");\n clear(panel);\n\n if (!editable || document_ === undefined) {\n const rows: Array<[string, string]> = [\n [\"False-positive policy\", snapshot.policy.falsePositivePolicy],\n [\"Fallback when the guard stops a rule\", snapshot.policy.fallbackAction],\n [\"Terminal score threshold\", String(snapshot.policy.terminalScoreThreshold)],\n [\"Suspect threshold\", String(snapshot.policy.suspectThreshold)],\n [\"Action when no rule matches\", snapshot.policy.defaultAction],\n [\"Challenge configured\", snapshot.policy.challengeEnabled ? \"yes\" : \"no\"],\n [\"Range sets\", snapshot.ranges.length === 0 ? \"none\" : snapshot.ranges.map((range) => `${range.name} (${range.size})`).join(\", \")],\n ];\n for (const [key, value] of rows) {\n const line = el(\"div\", \"stat-row\");\n line.appendChild(el(\"span\", \"k\", key));\n line.appendChild(el(\"span\", \"v\", value));\n panel.appendChild(line);\n }\n $(\"guard-mode\").textContent = \"not editable here\";\n $(\"guard-note\").textContent =\n \"These are fixed at construction on this dashboard. It can change which rules exist; it cannot change how far a rule is allowed to go, because relaxing that is the one edit that can start denying people. Enable it deliberately with controls: { editGuard: true }.\";\n return;\n }\n\n if (draft === undefined) draft = { ...document_.guard };\n const vocabulary = document_.vocabulary;\n $(\"guard-mode\").textContent = state.guardDirty ? \"unsaved changes\" : \"editable\";\n $(\"guard-note\").textContent =\n \"Changing these changes what every rule is allowed to do, on the next request. Preview it against the traffic in the window first — that is the only view you get of who it would start turning away.\";\n\n panel.appendChild(\n guardField(\n // \"Mode\" rather than \"Guard\": the panel is already called that, and a field\n // repeating its own panel's name reads as a heading rather than as a control.\n \"Mode\",\n segmented(\n vocabulary.falsePositivePolicies.map((mode) => [mode, mode] as [string, string]),\n draft.falsePositivePolicy,\n (value) => {\n setDraft({ falsePositivePolicy: value });\n drawGuard();\n },\n ),\n ),\n );\n panel.appendChild(el(\"div\", \"note guard-explains\", MODE_NOTES[draft.falsePositivePolicy] ?? \"\"));\n\n panel.appendChild(\n guardField(\n \"Fallback\",\n select(vocabulary.fallbackActions, draft.fallbackAction, (value) => setDraft({ fallbackAction: value })),\n \"what a stopped rule becomes — the terminal actions are absent because a terminal fallback would deny the request the guard just protected\",\n ),\n );\n panel.appendChild(\n guardField(\n \"Default action\",\n select(vocabulary.actions, draft.defaultAction, (value) => setDraft({ defaultAction: value })),\n \"when no rule matches\",\n ),\n );\n panel.appendChild(\n guardField(\n \"Terminal score\",\n number(draft.terminalScoreThreshold, (value) => setDraft({ terminalScoreThreshold: value })),\n \"balanced mode only: the score a probabilistic verdict must clear\",\n ),\n );\n panel.appendChild(\n guardField(\n \"Suspect at\",\n number(draft.suspectThreshold, (value) => setDraft({ suspectThreshold: value })),\n \"the score at which a request becomes suspected-bot\",\n ),\n );\n\n const bar = el(\"div\", \"bar-actions\");\n const preview = el(\"button\", null, \"Preview\");\n preview.addEventListener(\"click\", () => {\n void previewGuard();\n });\n const apply = el(\"button\", \"primary\", \"Apply\");\n apply.addEventListener(\"click\", () => {\n void applyGuard();\n });\n const revert = el(\"button\", null, \"Revert\");\n revert.addEventListener(\"click\", () => {\n draft = { ...document_.guard };\n state.guardDirty = false;\n drawGuard();\n });\n bar.appendChild(preview);\n bar.appendChild(apply);\n bar.appendChild(revert);\n panel.appendChild(bar);\n}\n\nfunction setDraft(change: Partial<GuardDraft>): void {\n if (draft === undefined) return;\n draft = { ...draft, ...change };\n state.guardDirty = true;\n $(\"guard-mode\").textContent = \"unsaved changes\";\n}\n\n/** Forgets an in-progress edit, so a reload of the policy document is reflected. */\nexport function resetGuardDraft(): void {\n draft = undefined;\n state.guardDirty = false;\n}\n\n/**\n * The rules a guard preview is run against.\n *\n * The *running* rules, not whatever is sitting unsaved in the editor, so the answer is\n * about the guard alone. The server splices its predicate rules back in by position, so\n * this is the policy in force.\n */\nfunction liveRules(): unknown[] {\n return (state.policy?.rules ?? []).filter((row) => row.editable && row.rule !== undefined).map((row) => row.rule);\n}\n\nasync function previewGuard(): Promise<void> {\n if (draft === undefined) return;\n const result = await postJson<Preview & { error?: string }>(\"/api/policy/preview\", { rules: liveRules(), guard: draft });\n if (!result.ok) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, result.error ?? \"\")));\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n renderPreview(result.data, state.editorDirty ? [\"Previewed against the rules currently in force, not the unsaved edits in the editor.\"] : []);\n app.showTab(\"policy\");\n}\n\nasync function applyGuard(): Promise<void> {\n if (draft === undefined) return;\n const result = await postJson<{ guard: GuardDraft; error?: string }>(\"/api/guard\", draft);\n if (!result.ok) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, result.error ?? \"\")));\n toast(\"bad\", \"Guard unchanged\", result.error ?? \"\");\n return;\n }\n state.guardDirty = false;\n draft = { ...result.data.guard };\n if (state.policy !== undefined) state.policy.guard = { ...result.data.guard };\n toast(\"ok\", \"Guard changed\", `${result.data.guard.falsePositivePolicy}, falling back to ${result.data.guard.fallbackAction}.`);\n showResult(\"warn\", (box) => {\n box.appendChild(el(\"div\", null, \"The guard changed. It applies from the next request, and it is in the notices panel and in your logs.\"));\n if (result.data.guard.falsePositivePolicy !== \"strict\") {\n box.appendChild(\n el(\n \"div\",\n \"ev-meta\",\n \"Requests can now be denied without proof. The guard-stop count is the series to watch: every stop that no longer happens is a request that used to be recoverable and is not any more.\",\n ),\n );\n }\n });\n app.draw();\n}\n\nfunction guardField(name: string, control: HTMLElement, hint?: string): HTMLElement {\n const row = el(\"div\", \"field\");\n row.appendChild(label(name, control));\n const right = el(\"div\", \"field-row\");\n right.appendChild(control);\n if (hint !== undefined) right.appendChild(el(\"span\", \"hint\", hint));\n row.appendChild(right);\n return row;\n}\n\nfunction segmented(options: Array<[string, string]>, value: string, onChange: (value: string) => void): HTMLElement {\n const box = el(\"div\", \"seg\");\n for (const [label, option] of options) {\n const button = el(\"button\", null, label);\n button.type = \"button\";\n button.setAttribute(\"aria-pressed\", String(option === value));\n button.addEventListener(\"click\", () => onChange(option));\n box.appendChild(button);\n }\n return box;\n}\n\nfunction select(options: readonly string[], value: string, onChange: (value: string) => void): HTMLElement {\n const node = el(\"select\");\n for (const option of options) {\n const item = el(\"option\", null, option);\n item.value = option;\n if (option === value) item.selected = true;\n node.appendChild(item);\n }\n node.addEventListener(\"change\", () => onChange(node.value));\n return node;\n}\n\nfunction number(value: number, onChange: (value: number) => void): HTMLElement {\n const input = el(\"input\");\n input.type = \"number\";\n input.min = \"1\";\n input.max = \"100\";\n input.value = String(value);\n input.addEventListener(\"input\", () => {\n if (input.value !== \"\") onChange(Number(input.value));\n });\n return input;\n}\n","import { $, clear, el } from \"./dom.js\";\nimport { BOOT, SECTIONS } from \"./boot.js\";\nimport { getJson, postJson } from \"./api.js\";\nimport { n } from \"./format.js\";\nimport { toast } from \"./app.js\";\n\n/**\n * The range sets, and what is in them.\n *\n * The Guard panel used to report these as `allowlist (3)` — a count, which tells you\n * that something is exempt without telling you what. These are the lists that decide\n * which traffic is never assessed and which crawler ranges are believed, so \"what is\n * actually in it\" is the whole question.\n *\n * Editing is behind `controls.editRanges`, and the allowlist carries a warning wherever\n * it appears: an address on it is not judged leniently, it is not judged at all.\n */\ninterface RangeSet {\n name: string;\n size: number;\n entries: string[];\n}\n\ninterface RangesBody {\n ranges: RangeSet[];\n editable: boolean;\n}\n\nlet sets: RangeSet[] = [];\n\nexport async function loadRanges(): Promise<void> {\n if (!SECTIONS.ranges) return;\n try {\n const body = await getJson<RangesBody>(\"/api/ranges\");\n sets = body.ranges;\n drawRanges();\n } catch {\n /* the panel simply stays as it was */\n }\n}\n\nexport function drawRanges(): void {\n if (!SECTIONS.ranges) return;\n const body = $(\"ranges-body\");\n clear(body);\n\n $(\"ranges-mode\").textContent = BOOT.allowActing ? \"editable\" : \"read-only\";\n $(\"ranges-note\").textContent = BOOT.allowActing\n ? \"An address on the allowlist is not judged leniently — it is not judged at all. Detection does not run on it, no evidence is produced, and no rule sees it.\"\n : \"These come from the code that constructed the handler. Enable controls.editRanges to add an address from here.\";\n\n if (sets.length === 0) {\n body.appendChild(el(\"div\", \"note\", \"No range sets are configured. Add an address to the allowlist and one appears.\"));\n }\n\n for (const set of sets) {\n const block = el(\"div\", \"rangeset\");\n const heading = el(\"h3\");\n heading.appendChild(document.createTextNode(set.name));\n heading.appendChild(el(\"span\", null, `${n(set.size)} entr${set.size === 1 ? \"y\" : \"ies\"}`));\n block.appendChild(heading);\n\n const list = el(\"div\", \"cidrs\");\n for (const entry of set.entries) {\n const chip = el(\"span\", BOOT.allowActing ? \"cidr\" : \"cidr readonly\");\n chip.appendChild(document.createTextNode(entry));\n if (BOOT.allowActing) {\n const remove = el(\"button\", null, \"×\");\n remove.title = `Remove ${entry} from ${set.name}`;\n remove.setAttribute(\"aria-label\", `Remove ${entry} from ${set.name}`);\n remove.addEventListener(\"click\", () => void update(set.name, { remove: [entry] }));\n chip.appendChild(remove);\n }\n list.appendChild(chip);\n }\n if (set.entries.length === 0) list.appendChild(el(\"span\", \"hint\", \"empty\"));\n block.appendChild(list);\n body.appendChild(block);\n }\n\n if (!BOOT.allowActing) return;\n\n const form = el(\"div\", \"rangeset\");\n const row = el(\"div\", \"field-row\");\n const name = el(\"input\", \"mono-input\");\n name.type = \"text\";\n name.value = \"allowlist\";\n name.setAttribute(\"aria-label\", \"Range set\");\n name.style.maxWidth = \"150px\";\n const value = el(\"input\", \"mono-input\");\n value.type = \"text\";\n value.placeholder = \"203.0.113.0/24\";\n value.setAttribute(\"aria-label\", \"Address or CIDR to add\");\n const add = el(\"button\", null, \"Add\");\n const submit = (): void => {\n const entry = value.value.trim();\n if (entry === \"\") return;\n value.value = \"\";\n void update(name.value.trim(), { add: [entry] });\n };\n add.addEventListener(\"click\", submit);\n value.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Enter\") submit();\n });\n row.appendChild(name);\n row.appendChild(value);\n row.appendChild(add);\n form.appendChild(row);\n body.appendChild(form);\n}\n\nasync function update(name: string, change: { add?: string[]; remove?: string[] }): Promise<void> {\n const result = await postJson<{ entries?: string[]; error?: string }>(\"/api/ranges\", { name, ...change });\n if (!result.ok) {\n toast(\"bad\", \"Ranges unchanged\", result.error ?? \"\");\n return;\n }\n toast(name === \"allowlist\" && change.add !== undefined ? \"warn\" : \"ok\", `“${name}” updated`, `${n(result.data.entries?.length ?? 0)} entr${result.data.entries?.length === 1 ? \"y\" : \"ies\"} now.`);\n await loadRanges();\n}\n","import type { DashboardEntry } from \"./types.js\";\n\n/** A rule as the editor holds it: plain JSON, plus the card's open/closed state. */\nexport interface EditorRule {\n id: string;\n match: Record<string, unknown>;\n action: string;\n params?: Record<string, unknown>;\n reason?: string;\n /** Editor-only: whether this card is expanded. Stripped before anything is submitted. */\n _open?: boolean;\n}\n\nexport interface Draft {\n rule: EditorRule;\n /** One line saying what the draft matched on, and why that and not something else. */\n because: string;\n}\n\n/**\n * Turns a request you are looking at into a rule you can preview.\n *\n * This is the half of the loop the dashboard was missing. The row detail could already\n * turn a request into a replay line and a corpus case — the offline loop, where a\n * verdict you disagree with becomes a test. The online loop ran the other way and had\n * no help at all: you saw an actor worth acting on, then went and hand-wrote a rule in\n * a different tab, guessing at which field would catch it.\n *\n * Three decisions in here, and all three are about not being clever on somebody's\n * behalf:\n *\n * **The action is always `tag`.** Never `block`, never `challenge`, whatever the\n * request looks like. A drafted rule is a starting point that has not been reviewed by\n * anyone, and the dashboard picking a terminal action for a request that annoyed you is\n * exactly the reflex this library exists to interrupt. The operator picks the action;\n * the guard still checks it afterwards either way.\n *\n * **It matches on the strongest thing the request actually proves**, in a fixed order:\n * a claimed-and-checked identity, else the detectors whose evidence was proven, else\n * the verdict with a score floor. Each of those is a fact about the client. A path\n * would narrow it further and is deliberately left out — the request happens to have\n * one, which is not the same as the rule being about it.\n *\n * **It goes last.** Appending is the only position that cannot change what any\n * existing rule does, because first match wins. A rule that never fires shows up as\n * \"never matched\" in the preview, which is a better way to learn it is shadowed than\n * discovering it silently took over from something else.\n */\nexport function draftRule(entry: DashboardEntry, existingIds: readonly string[] = []): Draft {\n const match: Record<string, unknown> = {};\n let because: string;\n let stem: string;\n\n const proven = entry.evidence.filter((item) => item.certainty === \"certain\" && item.direction !== \"human\");\n const detectors = [...new Set((proven.length > 0 ? proven : entry.evidence.filter((item) => item.direction !== \"human\")).map((item) => item.detector))];\n\n if (entry.identity !== undefined && entry.identity !== \"\") {\n match[\"identity\"] = [entry.identity];\n if (entry.certain) match[\"certain\"] = true;\n stem = entry.identity;\n because = entry.certain\n ? `Matched on the identity “${entry.identity}”, and on proof — so a client merely claiming that name does not match.`\n : `Matched on the claimed identity “${entry.identity}”. Nothing has verified it, so this matches anything that says so.`;\n } else if (proven.length > 0) {\n match[\"detector\"] = detectors;\n match[\"certain\"] = true;\n stem = detectors[0] ?? \"proven\";\n because = `Matched on proof from ${detectors.join(\", \")}. Only requests that carry the same proof match.`;\n } else if (detectors.length > 0) {\n match[\"verdict\"] = [entry.verdict];\n match[\"detector\"] = detectors;\n match[\"minScore\"] = Math.max(0, Math.floor(entry.score / 10) * 10);\n stem = detectors[0] ?? entry.verdict;\n because = `Matched on ${entry.verdict} at score ${String(match[\"minScore\"])} or more, from ${detectors.join(\", \")}. Every one of those is probabilistic, so the guard will not let this rule deny anybody.`;\n } else {\n match[\"verdict\"] = [entry.verdict];\n stem = entry.verdict;\n because = `Nothing fired on this request, so there is nothing sharper to match on than the verdict itself. Narrow it before you use it.`;\n }\n\n return {\n rule: {\n id: uniqueId(`from-${slug(stem)}`, existingIds),\n match,\n action: \"tag\",\n reason: \"Drafted from a request on the dashboard.\",\n _open: true,\n },\n because,\n };\n}\n\nfunction slug(value: string): string {\n const cleaned = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return cleaned === \"\" ? \"request\" : cleaned.slice(0, 40);\n}\n\n/** Rule ids name every decision and every log line, so two rules sharing one is a real problem. */\nfunction uniqueId(wanted: string, taken: readonly string[]): string {\n if (!taken.includes(wanted)) return wanted;\n for (let suffix = 2; suffix < 1000; suffix++) {\n const candidate = `${wanted}-${suffix}`;\n if (!taken.includes(candidate)) return candidate;\n }\n return `${wanted}-${Date.now()}`;\n}\n","import { $, byId, clear, el, label } from \"./dom.js\";\nimport { API, SECTIONS } from \"./boot.js\";\nimport { app, download, today, toast } from \"./app.js\";\nimport { getJson, postJson } from \"./api.js\";\nimport { actionKind } from \"./outcome.js\";\nimport { drawGuard, resetGuardDraft } from \"./guard.js\";\nimport { drawRanges } from \"./ranges.js\";\nimport { n } from \"./format.js\";\nimport { renderPreview, showResult } from \"./result.js\";\nimport { state } from \"./store.js\";\nimport { draftRule } from \"./draft.js\";\nimport type { EditorRule } from \"./draft.js\";\nimport type { DashboardEntry, Policy, Preview } from \"./types.js\";\n\n/**\n * The rule editor works on a model — an array of plain rule objects — and both views\n * render from it. The GUI mutates the model in place on each keystroke, which is what\n * keeps focus and cursor position while somebody types; only structural changes (add,\n * remove, move, a different action) re-render a card.\n */\nexport async function loadPolicy(): Promise<void> {\n if (!SECTIONS.policy) return;\n try {\n const document_ = await getJson<Policy>(\"/api/policy\");\n state.policy = document_;\n if (!state.editorDirty) {\n setEditorRules(document_.rules.filter((row) => row.editable).map((row) => row.rule as unknown as EditorRule));\n }\n if (!state.guardDirty) resetGuardDraft();\n drawPolicyTab();\n } catch {\n /* the page still works without it */\n }\n}\n\nfunction setEditorRules(rules: readonly EditorRule[]): void {\n state.editorRules = JSON.parse(JSON.stringify(rules ?? [])) as EditorRule[];\n renderEditor();\n}\n\nfunction markDirty(): void {\n state.editorDirty = true;\n $(\"policy-dirty\").hidden = false;\n}\n\nfunction editorRules(): { rules?: EditorRule[]; error?: string } {\n if (state.editorMode === \"json\") {\n try {\n const parsed: unknown = JSON.parse(byId<HTMLTextAreaElement>(\"policy-json\").value);\n if (!Array.isArray(parsed)) return { error: \"The JSON must be an array of rules.\" };\n return { rules: parsed as EditorRule[] };\n } catch (error) {\n return { error: `The editor does not contain valid JSON: ${String(error)}` };\n }\n }\n return { rules: state.editorRules };\n}\n\n/** Strips the empty fields a form inevitably leaves behind, so the JSON stays readable. */\nfunction cleanRule(rule: EditorRule): Record<string, unknown> {\n const match: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(rule.match ?? {})) {\n if (value === undefined || value === null || value === \"\") continue;\n if (Array.isArray(value) && value.length === 0) continue;\n match[key] = value;\n }\n const params: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(rule.params ?? {})) {\n if (value === undefined || value === null || value === \"\") continue;\n if (key === \"limit\") {\n const limit = value as { max?: unknown; windowMs?: unknown };\n if (limit.max === undefined || limit.windowMs === undefined) continue;\n }\n params[key] = value;\n }\n const out: Record<string, unknown> = { id: rule.id, match, action: rule.action };\n if (Object.keys(params).length > 0) out[\"params\"] = params;\n if (rule.reason !== undefined && rule.reason !== \"\") out[\"reason\"] = rule.reason;\n return out;\n}\n\nfunction cleanRules(rules: readonly EditorRule[]): Array<Record<string, unknown>> {\n return rules.map(cleanRule);\n}\n\n// ---- field widgets ---------------------------------------------------------\n\nfunction field(name: string, control: HTMLElement | HTMLElement[], hint?: string): HTMLElement {\n const row = el(\"div\", \"field\");\n row.appendChild(label(name, control));\n const right = el(\"div\", \"field-row\");\n for (const node of Array.isArray(control) ? control : [control]) right.appendChild(node);\n if (hint !== undefined) right.appendChild(el(\"span\", \"hint\", hint));\n row.appendChild(right);\n return row;\n}\n\nfunction chipSelect(options: readonly string[], selected: unknown, onChange: (value: string[] | undefined) => void): HTMLElement {\n const box = el(\"div\", \"chips-select\");\n const chosen: string[] = Array.isArray(selected) ? [...(selected as string[])] : selected === undefined ? [] : [String(selected)];\n for (const option of options) {\n const button = el(\"button\", null, option);\n button.type = \"button\";\n button.setAttribute(\"aria-pressed\", String(chosen.includes(option)));\n button.addEventListener(\"click\", () => {\n const at = chosen.indexOf(option);\n if (at === -1) chosen.push(option);\n else chosen.splice(at, 1);\n button.setAttribute(\"aria-pressed\", String(at === -1));\n onChange(chosen.length === 0 ? undefined : [...chosen]);\n });\n box.appendChild(button);\n }\n return box;\n}\n\nfunction segmented<T>(options: Array<[string, T]>, value: T, onChange: (value: T) => void): HTMLElement {\n const box = el(\"div\", \"seg\");\n for (const [label, option] of options) {\n const button = el(\"button\", null, label);\n button.type = \"button\";\n button.setAttribute(\"aria-pressed\", String(option === value));\n button.addEventListener(\"click\", () => {\n for (const other of Array.from(box.children)) other.setAttribute(\"aria-pressed\", \"false\");\n button.setAttribute(\"aria-pressed\", \"true\");\n onChange(option);\n });\n box.appendChild(button);\n }\n return box;\n}\n\nfunction textInput(value: unknown, placeholder: string, onChange: (value: string | undefined) => void, mono = false): HTMLInputElement {\n const input = el(\"input\", mono ? \"mono-input\" : null);\n input.type = \"text\";\n input.value = value === undefined || value === null ? \"\" : String(value);\n input.placeholder = placeholder;\n input.addEventListener(\"input\", () => onChange(input.value.trim() === \"\" ? undefined : input.value));\n return input;\n}\n\n/** A comma-separated list, because a tag widget is a lot of keyboard surface for one path. */\nfunction listInput(value: unknown, placeholder: string, onChange: (value: string | string[] | undefined) => void): HTMLInputElement {\n const current = value === undefined ? \"\" : Array.isArray(value) ? (value as string[]).join(\", \") : String(value);\n const input = textInput(current, placeholder, () => {}, true);\n input.addEventListener(\"input\", () => {\n const parts = input.value\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n onChange(parts.length === 0 ? undefined : parts.length === 1 ? parts[0] : parts);\n });\n return input;\n}\n\nfunction numberInput(value: unknown, placeholder: string, onChange: (value: number | undefined) => void): HTMLInputElement {\n const input = el(\"input\");\n input.type = \"number\";\n input.value = value === undefined || value === null ? \"\" : String(value);\n input.placeholder = placeholder;\n input.addEventListener(\"input\", () => onChange(input.value === \"\" ? undefined : Number(input.value)));\n return input;\n}\n\n// ---- one rule --------------------------------------------------------------\n\n/** What a collapsed card says about itself: the match, in the order a reader scans it. */\nfunction matchSummary(rule: EditorRule): HTMLElement {\n const box = el(\"div\", \"rule-summary\");\n const match = rule.match ?? {};\n const parts: Array<[string, string]> = [];\n for (const key of [\"verdict\", \"botClass\", \"category\", \"identity\", \"detector\", \"method\", \"path\"]) {\n const value = match[key];\n if (value === undefined) continue;\n parts.push([key, Array.isArray(value) ? (value as string[]).join(\", \") : String(value)]);\n }\n if (match[\"certain\"] !== undefined) parts.push([\"certain\", String(match[\"certain\"])]);\n if (match[\"minScore\"] !== undefined || match[\"maxScore\"] !== undefined) {\n parts.push([\"score\", `${String(match[\"minScore\"] ?? 0)}–${String(match[\"maxScore\"] ?? 99)}`]);\n }\n if (match[\"minPriorConfirmations\"] !== undefined) parts.push([\"prior\", String(match[\"minPriorConfirmations\"])]);\n if (match[\"minUnsolvedChallenges\"] !== undefined) parts.push([\"unsolved\", String(match[\"minUnsolvedChallenges\"])]);\n\n if (parts.length === 0) {\n box.appendChild(el(\"span\", \"none\", \"matches everything\"));\n return box;\n }\n for (const [key, value] of parts.slice(0, 4)) box.appendChild(el(\"span\", \"t\", `${key}: ${value}`));\n if (parts.length > 4) box.appendChild(el(\"span\", \"k\", `+${parts.length - 4} more`));\n return box;\n}\n\nfunction ruleCard(rule: EditorRule, index: number): HTMLElement {\n const vocabulary = state.policy?.vocabulary;\n const open = rule._open === true;\n const card = el(\"div\", `rule${open ? \"\" : \" collapsed\"}`);\n\n const head = el(\"div\", \"rule-head\");\n\n // Collapsed by default: eight rules with every field on screen is a wall, and what a\n // reader wants first is the order and what each one matches.\n const chevron = el(\"button\", \"chev\", open ? \"▾\" : \"▸\");\n chevron.title = open ? \"Collapse\" : \"Expand\";\n chevron.setAttribute(\"aria-expanded\", String(open));\n chevron.addEventListener(\"click\", () => {\n rule._open = !open;\n renderEditor();\n });\n head.appendChild(chevron);\n head.appendChild(el(\"span\", \"ord\", index + 1));\n\n const id = textInput(rule.id, \"rule-id\", (value) => {\n rule.id = value ?? \"\";\n markDirty();\n }, true);\n id.setAttribute(\"aria-label\", \"Rule id\");\n head.appendChild(id);\n\n if (open) {\n const action = el(\"select\");\n action.setAttribute(\"aria-label\", \"Action\");\n for (const name of vocabulary?.actions ?? []) {\n const option = el(\"option\", null, name);\n option.value = name;\n if (name === rule.action) option.selected = true;\n action.appendChild(option);\n }\n action.addEventListener(\"change\", () => {\n rule.action = action.value;\n rule.params = {};\n markDirty();\n renderEditor();\n });\n head.appendChild(action);\n } else {\n head.appendChild(matchSummary(rule));\n head.appendChild(el(\"span\", `act-pill ${actionKind(rule.action)}`, rule.action));\n }\n\n const up = el(\"button\", \"icon\", \"↑\");\n up.title = \"Move earlier — the first matching rule wins\";\n up.addEventListener(\"click\", () => moveRule(index, -1));\n const down = el(\"button\", \"icon\", \"↓\");\n down.title = \"Move later\";\n down.addEventListener(\"click\", () => moveRule(index, 1));\n const remove = el(\"button\", \"icon danger\", \"Remove\");\n remove.addEventListener(\"click\", () => {\n state.editorRules.splice(index, 1);\n markDirty();\n renderEditor();\n });\n head.appendChild(up);\n head.appendChild(down);\n head.appendChild(remove);\n card.appendChild(head);\n\n if (!open) return card;\n\n const body = el(\"div\", \"rule-body\");\n rule.match = rule.match ?? {};\n const match = rule.match;\n\n body.appendChild(field(\"Verdict\", chipSelect(vocabulary?.verdicts ?? [], match[\"verdict\"], (value) => { match[\"verdict\"] = value; markDirty(); })));\n body.appendChild(field(\"Bot class\", chipSelect(vocabulary?.botClasses ?? [], match[\"botClass\"], (value) => { match[\"botClass\"] = value; markDirty(); })));\n body.appendChild(field(\"Category\", chipSelect(vocabulary?.categories ?? [], match[\"category\"], (value) => { match[\"category\"] = value; markDirty(); })));\n body.appendChild(field(\"Detector\", chipSelect(vocabulary?.detectors ?? [], match[\"detector\"], (value) => { match[\"detector\"] = value; markDirty(); })));\n body.appendChild(field(\"Method\", chipSelect(vocabulary?.methods ?? [], match[\"method\"], (value) => { match[\"method\"] = value; markDirty(); })));\n\n body.appendChild(\n field(\n \"Evidence\",\n segmented<boolean | undefined>(\n [\n [\"any\", undefined],\n [\"proven\", true],\n [\"unproven\", false],\n ],\n match[\"certain\"] as boolean | undefined,\n (value) => {\n match[\"certain\"] = value;\n markDirty();\n },\n ),\n \"proven means at least one piece of certain evidence — including a proven human\",\n ),\n );\n\n body.appendChild(\n field(\"Score\", [\n numberInput(match[\"minScore\"], \"min\", (value) => { match[\"minScore\"] = value; markDirty(); }),\n el(\"span\", \"hint\", \"to\"),\n numberInput(match[\"maxScore\"], \"max\", (value) => { match[\"maxScore\"] = value; markDirty(); }),\n ]),\n );\n\n body.appendChild(field(\"Identity\", listInput(match[\"identity\"], \"googlebot, gptbot\", (value) => { match[\"identity\"] = value; markDirty(); })));\n body.appendChild(field(\"Path\", listInput(match[\"path\"], \"/api/, /search\", (value) => { match[\"path\"] = value; markDirty(); }), \"a string matches as a prefix\"));\n body.appendChild(\n field(\n \"Prior bots\",\n numberInput(match[\"minPriorConfirmations\"], \"0\", (value) => { match[\"minPriorConfirmations\"] = value; markDirty(); }),\n \"times this actor was already proven a bot\",\n ),\n );\n body.appendChild(\n field(\n \"Unsolved\",\n numberInput(match[\"minUnsolvedChallenges\"], \"0\", (value) => { match[\"minUnsolvedChallenges\"] = value; markDirty(); }),\n \"challenges issued to this actor that were never answered — solving one clears the count\",\n ),\n );\n\n rule.params = rule.params ?? {};\n const params = rule.params;\n for (const node of paramFields(rule.action, params)) body.appendChild(node);\n\n body.appendChild(field(\"Reason\", textInput(rule.reason, \"shown in the decision and in your logs\", (value) => { rule.reason = value ?? \"\"; markDirty(); })));\n\n card.appendChild(body);\n return card;\n}\n\nfunction paramFields(action: string, params: Record<string, unknown>): HTMLElement[] {\n switch (action) {\n case \"block\":\n return [\n field(\"Status\", numberInput(params[\"status\"], \"403\", (value) => { params[\"status\"] = value; markDirty(); })),\n field(\"Body\", textInput(params[\"body\"], \"Automated traffic is not served here.\", (value) => { params[\"body\"] = value; markDirty(); })),\n ];\n case \"redirect\":\n return [field(\"Location\", textInput(params[\"location\"], \"/too-fast\", (value) => { params[\"location\"] = value; markDirty(); }, true))];\n case \"delay\":\n return [field(\"Delay\", numberInput(params[\"delayMs\"], \"250\", (value) => { params[\"delayMs\"] = value; markDirty(); }), \"milliseconds\")];\n case \"rate-limit\": {\n const limit = (params[\"limit\"] ?? {}) as Record<string, unknown>;\n params[\"limit\"] = limit;\n return [\n field(\"Limit\", [\n numberInput(limit[\"max\"], \"60\", (value) => { limit[\"max\"] = value; markDirty(); }),\n el(\"span\", \"hint\", \"requests per\"),\n numberInput(limit[\"windowMs\"], \"60000\", (value) => { limit[\"windowMs\"] = value; markDirty(); }),\n el(\"span\", \"hint\", \"ms\"),\n ]),\n ];\n }\n case \"custom\":\n return [field(\"Handler\", textInput(params[\"handler\"], \"handler-id\", (value) => { params[\"handler\"] = value; markDirty(); }, true), \"id of a handler you registered\")];\n default:\n return [];\n }\n}\n\nfunction lockedCard(row: { id: string; index: number }): HTMLElement {\n const card = el(\"div\", \"rule locked\");\n const head = el(\"div\", \"rule-head\");\n head.appendChild(el(\"span\", \"ord\", `#${row.index + 1}`));\n head.appendChild(el(\"span\", \"mono\", row.id));\n head.appendChild(el(\"span\", \"grow\"));\n head.appendChild(el(\"span\", \"pill\", \"predicate — locked\"));\n card.appendChild(head);\n card.appendChild(\n el(\"div\", \"rule-body\", \"This rule matches with a function, which cannot be represented here or sent over HTTP. It stays exactly as it is, at this position, whatever else you change.\"),\n );\n return card;\n}\n\nfunction moveRule(index: number, delta: number): void {\n const target = index + delta;\n if (target < 0 || target >= state.editorRules.length) return;\n const moved = state.editorRules.splice(index, 1)[0];\n if (moved === undefined) return;\n state.editorRules.splice(target, 0, moved);\n markDirty();\n renderEditor();\n}\n\nfunction renderEditor(): void {\n if (!SECTIONS.policy) return;\n const list = $(\"rulelist\");\n clear(list);\n const locked = (state.policy?.rules ?? []).filter((row) => !row.editable);\n\n if (state.editorRules.length === 0 && locked.length === 0) {\n list.appendChild(el(\"div\", \"note\", \"No rules. Every request takes the default action — add one, or import a set.\"));\n }\n\n // Locked rules are drawn at the index they hold, because that is where the server\n // will splice them back in and therefore what decides whether they run first.\n const rendered: HTMLElement[] = state.editorRules.map((rule, index) => ruleCard(rule, index));\n for (const row of locked) rendered.splice(Math.min(row.index, rendered.length), 0, lockedCard(row));\n for (const node of rendered) list.appendChild(node);\n\n if (state.editorMode === \"json\") byId<HTMLTextAreaElement>(\"policy-json\").value = JSON.stringify(cleanRules(state.editorRules), null, 2);\n}\n\nexport function drawPolicyTab(): void {\n const document_ = state.policy;\n if (document_ === undefined) return;\n\n const editable = document_.editable;\n $(\"policy-apply\").hidden = !editable;\n byId<HTMLTextAreaElement>(\"policy-json\").readOnly = !editable;\n $(\"policy-mode\").textContent = editable ? \"editable\" : \"read-only\";\n $(\"rule-add\").hidden = !editable;\n $(\"policy-import\").hidden = !editable;\n\n const preserved = document_.rules.filter((row) => !row.editable);\n let note = editable ? \"First match wins — order matters.\" : \"Read-only. Enable controls.editPolicy to change these.\";\n if (preserved.length > 0) note += ` ${preserved.length} rule(s) use a predicate function and are locked.`;\n $(\"policy-note\").textContent = note;\n\n renderPresetButtons();\n drawGuard();\n drawRanges();\n\n if (SECTIONS.robots) {\n $(\"robots-preview\").textContent = document_.robots === \"\" ? \"(this policy declines no crawler by name)\" : document_.robots;\n const notes = $(\"robots-notes\");\n clear(notes);\n for (const note_ of document_.robotsNotes) notes.appendChild(el(\"div\", \"ev-meta\", `${note_.rule}: ${note_.reason}`));\n }\n\n const rules = $(\"stat-rules\");\n clear(rules);\n const installed = state.snapshot?.rules ?? [];\n if (installed.length === 0) rules.appendChild(el(\"div\", \"note\", \"No rules configured — every request takes the default action.\"));\n else installed.forEach((rule, index) => rules.appendChild(el(\"span\", \"chip\", `${index + 1}. ${rule}`)));\n}\n\n/**\n * Starts a rule from a request the operator was looking at.\n *\n * Appended, expanded, and previewed straight away — the three things somebody would\n * otherwise do by hand between noticing an actor and having a rule they can judge.\n * Nothing is applied: this fills the editor, exactly like an import.\n *\n * It loads the policy document first, and that `await` is load-bearing rather than\n * tidy. Drafting is reachable from the Live tab, which somebody can use for an hour\n * without ever opening the Policy one — and appending to an editor that has not been\n * filled yet leaves it holding the draft *and nothing else*, while marking it dirty so\n * the load that follows will not correct it. Apply that and the running policy becomes\n * one drafted rule.\n */\nexport async function draftIntoEditor(entry: DashboardEntry): Promise<void> {\n if (state.policy === undefined) await loadPolicy();\n const drafted = draftRule(entry, state.editorRules.map((rule) => rule.id));\n state.editorRules.push(drafted.rule);\n markDirty();\n if (state.editorMode === \"json\") byId<HTMLTextAreaElement>(\"policy-json\").value = JSON.stringify(cleanRules(state.editorRules), null, 2);\n renderEditor();\n app.showTab(\"policy\");\n\n const notes = [\n `Drafted “${drafted.rule.id}”, tagging only. Nothing is applied — read it, choose the action, then apply.`,\n drafted.because,\n \"It was added last, which is the only position that cannot change what an existing rule does. Move it up with ↑ if it needs to win.\",\n ];\n // Said immediately, because the preview is a round trip and an empty box in the\n // meantime reads as nothing having happened.\n showResult(\"warn\", (box) => {\n for (const note of notes) box.appendChild(el(\"div\", note === notes[0] ? null : \"ev-meta\", note));\n });\n toast(\"ok\", \"Rule drafted\", \"In the editor, tagging only, not applied.\");\n await runPreview(notes);\n}\n\n// ---- mode switch, add, expand ----------------------------------------------\n\nfunction switchToGui(): void {\n if (state.editorMode === \"gui\") return;\n const parsed = editorRules();\n if (parsed.error !== undefined) {\n toast(\"bad\", \"That JSON will not parse\", parsed.error);\n return;\n }\n state.editorRules = parsed.rules ?? [];\n state.editorMode = \"gui\";\n $(\"mode-gui\").setAttribute(\"aria-pressed\", \"true\");\n $(\"mode-json\").setAttribute(\"aria-pressed\", \"false\");\n $(\"editor-gui\").hidden = false;\n $(\"editor-json\").hidden = true;\n renderEditor();\n}\n\nfunction switchToJson(): void {\n state.editorMode = \"json\";\n $(\"mode-gui\").setAttribute(\"aria-pressed\", \"false\");\n $(\"mode-json\").setAttribute(\"aria-pressed\", \"true\");\n $(\"editor-gui\").hidden = true;\n $(\"editor-json\").hidden = false;\n byId<HTMLTextAreaElement>(\"policy-json\").value = JSON.stringify(cleanRules(state.editorRules), null, 2);\n}\n\n// ---- import and export ------------------------------------------------------\n\nasync function exportSettings(): Promise<void> {\n // The whole settings document, not just the rules: a file that records the detectors,\n // ranges and guard alongside them is the one you want when comparing two deployments\n // six months from now. Only the rules half is importable, and the file says so.\n try {\n const response = await fetch(`${API}/api/settings`);\n const text = await response.text();\n if (!response.ok) throw new Error(text);\n download(text, `bothandler-settings-${today()}.json`, \"application/json\");\n toast(\"ok\", \"Settings exported\", \"Rules, plus a record of the configuration around them.\");\n } catch (error) {\n toast(\"bad\", \"Export failed\", String(error));\n }\n}\n\nfunction readSettingsFile(file: File): void {\n const reader = new FileReader();\n reader.onload = (): void => {\n try {\n applyImported(JSON.parse(String(reader.result)) as Record<string, unknown>, file.name);\n } catch (error) {\n toast(\"bad\", \"That file is not JSON\", String(error));\n }\n };\n reader.onerror = (): void => toast(\"bad\", \"Could not read that file\", \"\");\n reader.readAsText(file);\n}\n\n/**\n * Loads a file into the editor — and only into the editor.\n *\n * Nothing is applied: an import that took effect on drop would be a policy change made\n * by a mis-drag. What it does is fill the editor and say what it ignored, so the next\n * step is the same Preview and Apply as any other edit.\n */\nfunction applyImported(document_: Record<string, unknown> | unknown[], name: string): void {\n const rules = Array.isArray(document_)\n ? (document_ as EditorRule[])\n : Array.isArray((document_ as { rules?: unknown }).rules)\n ? ((document_ as { rules: EditorRule[] }).rules)\n : undefined;\n if (rules === undefined) {\n toast(\"bad\", \"Nothing to import\", \"Expected an array of rules, or a settings file with a rules array.\");\n return;\n }\n setEditorRules(rules);\n markDirty();\n switchToGui();\n\n const ignored: string[] = [];\n const readOnly = (document_ as { readOnly?: { lockedRules?: unknown[] } }).readOnly;\n if (readOnly !== undefined) {\n ignored.push(\"the guard, the detectors, the ranges and the audit — those come from the code that built the handler, not from a file\");\n if (Array.isArray(readOnly.lockedRules) && readOnly.lockedRules.length > 0) {\n ignored.push(`${readOnly.lockedRules.length} predicate rule(s), which stay as they are`);\n }\n }\n showResult(\"warn\", (box) => {\n box.appendChild(el(\"div\", null, `Loaded ${rules.length} rule(s) from ${name}. Nothing has been applied yet — preview it first.`));\n for (const line of ignored) box.appendChild(el(\"div\", \"ev-meta\", `Ignored: ${line}`));\n });\n toast(\"ok\", `Imported ${rules.length} rule(s)`, \"Review, preview, then apply.\");\n}\n\n// ---- preview and apply ------------------------------------------------------\n\nfunction collectForSubmit(): Array<Record<string, unknown>> | undefined {\n const parsed = editorRules();\n if (parsed.error !== undefined) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, parsed.error ?? \"\")));\n toast(\"bad\", \"That JSON will not parse\", parsed.error);\n return undefined;\n }\n const cleaned = cleanRules(parsed.rules ?? []);\n const blank = cleaned.filter((rule) => rule[\"id\"] === undefined || rule[\"id\"] === \"\").length;\n if (blank > 0) {\n toast(\"bad\", \"Every rule needs an id\", \"It is what every decision and log line names.\");\n return undefined;\n }\n return cleaned;\n}\n\nasync function runPreview(notes: readonly string[] = []): Promise<void> {\n const rules = collectForSubmit();\n if (rules === undefined) return;\n const result = await postJson<Preview & { error?: string }>(\"/api/policy/preview\", { rules });\n if (!result.ok) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, result.error ?? \"\")));\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n renderPreview(result.data, notes);\n}\n\nasync function applyPolicy(): Promise<void> {\n const rules = collectForSubmit();\n if (rules === undefined) return;\n const result = await postJson<{ rules: number; warnings?: string[]; error?: string }>(\"/api/policy/apply\", { rules });\n if (!result.ok) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, result.error ?? \"\")));\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n state.editorDirty = false;\n $(\"policy-dirty\").hidden = true;\n await loadPolicy();\n toast(\"ok\", \"Applied\", `${n(result.data.rules)} rule(s) now in force.`);\n showResult(\"ok\", (box) => {\n box.appendChild(el(\"div\", null, `Applied. ${n(result.data.rules)} rule(s) are now in force.`));\n for (const warning of result.data.warnings ?? []) box.appendChild(el(\"div\", \"ev-meta\", warning));\n });\n}\n\n/** Built from the registry the server sends, so a new preset appears here on its own. */\nfunction renderPresetButtons(): void {\n const box = $(\"preset-buttons\");\n const presets = state.policy?.vocabulary.presets ?? [];\n if (box.childElementCount === presets.length) return;\n clear(box);\n for (const preset of presets) {\n const button = el(\"button\", null, preset);\n button.addEventListener(\"click\", () => {\n void (async () => {\n const result = await postJson<Preview & { error?: string }>(\"/api/policy/preview\", { preset });\n if (!result.ok) {\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n renderPreview(result.data);\n })();\n });\n box.appendChild(button);\n }\n}\n\nexport function initPolicy(): void {\n if (!SECTIONS.policy) return;\n $(\"mode-gui\").addEventListener(\"click\", switchToGui);\n $(\"mode-json\").addEventListener(\"click\", switchToJson);\n\n $(\"rule-add\").addEventListener(\"click\", () => {\n state.editorRules.push({ id: `new-rule-${state.editorRules.length + 1}`, match: {}, action: \"tag\", params: {}, _open: true });\n markDirty();\n renderEditor();\n });\n\n $(\"rule-expand\").addEventListener(\"click\", () => {\n const anyClosed = state.editorRules.some((rule) => rule._open !== true);\n for (const rule of state.editorRules) rule._open = anyClosed;\n $(\"rule-expand\").textContent = anyClosed ? \"Collapse all\" : \"Expand all\";\n renderEditor();\n });\n\n byId<HTMLTextAreaElement>(\"policy-json\").addEventListener(\"input\", markDirty);\n $(\"policy-preview\").addEventListener(\"click\", () => void runPreview());\n $(\"policy-apply\").addEventListener(\"click\", () => void applyPolicy());\n $(\"policy-revert\").addEventListener(\"click\", () => {\n state.editorDirty = false;\n $(\"policy-dirty\").hidden = true;\n $(\"policy-result\").hidden = true;\n resetGuardDraft();\n void loadPolicy();\n });\n\n $(\"policy-export\").addEventListener(\"click\", () => void exportSettings());\n $(\"policy-import\").addEventListener(\"click\", () => byId<HTMLInputElement>(\"policy-file\").click());\n byId<HTMLInputElement>(\"policy-file\").addEventListener(\"change\", () => {\n const input = byId<HTMLInputElement>(\"policy-file\");\n const file = input.files?.[0];\n if (file !== undefined) readSettingsFile(file);\n input.value = \"\";\n });\n\n const panel = $(\"editor-panel\");\n for (const name of [\"dragenter\", \"dragover\"]) {\n panel.addEventListener(name, (event) => {\n if (state.policy?.editable !== true) return;\n event.preventDefault();\n panel.classList.add(\"drop\");\n });\n }\n for (const name of [\"dragleave\", \"drop\"]) {\n panel.addEventListener(name, (event) => {\n panel.classList.remove(\"drop\");\n if (name !== \"drop\") return;\n event.preventDefault();\n const file = (event as DragEvent).dataTransfer?.files?.[0];\n if (file !== undefined) readSettingsFile(file);\n });\n }\n}\n","import { $, byId, clear, cssEscape, el, rootNode } from \"./dom.js\";\nimport { feedPage, goToFeedPage, ingest, matchingCount, matchingRows, resetPaging, setSearch, sortRows, state } from \"./store.js\";\nimport { getJson } from \"./api.js\";\nimport { renderPager } from \"./pager.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { app, download, today, toast } from \"./app.js\";\nimport { clockTime, n } from \"./format.js\";\nimport { corpusCase, replayFile, replayLine } from \"./replay.js\";\nimport { openActor } from \"./actor.js\";\nimport { outcome, verdictBadge } from \"./outcome.js\";\nimport { draftIntoEditor } from \"./policy.js\";\nimport type { FilterName } from \"./query.js\";\nimport type { DashboardEntry } from \"./types.js\";\n\nconst FILTERS: Array<[FilterName, string]> = [\n [\"all\", \"All\"],\n [\"proven\", \"Proven\"],\n [\"suspected\", \"Suspected\"],\n [\"human\", \"Human\"],\n [\"guard\", \"Guard stops\"],\n [\"deny\", \"Denied\"],\n [\"mitigate\", \"Mitigated\"],\n [\"allow\", \"Served\"],\n];\n\n/** A row currently in the table, and what it was built from. */\ninterface Rendered {\n row: HTMLTableRowElement;\n detail: HTMLTableRowElement | undefined;\n rev: number;\n open: boolean;\n}\n\nconst rendered = new Map<string, Rendered>();\n\nexport function initFeed(): void {\n const loadThem = byId<HTMLButtonElement>(\"feed-load-skipped\");\n loadThem.addEventListener(\"click\", () => {\n // Disabled while it is in flight, because the fetch is the whole ring and a second\n // press would ask for it again.\n loadThem.disabled = true;\n void loadSkipped().finally(() => {\n loadThem.disabled = false;\n });\n });\n\n const filters = $(\"filters\");\n for (const [name, label] of FILTERS) {\n const button = el(\"button\", null, label);\n button.setAttribute(\"aria-pressed\", String(name === state.filter));\n button.dataset[\"filter\"] = name;\n button.addEventListener(\"click\", () => {\n state.filter = name;\n resetPaging();\n for (const other of Array.from(filters.children)) other.setAttribute(\"aria-pressed\", \"false\");\n button.setAttribute(\"aria-pressed\", \"true\");\n app.syncUrl();\n app.drawNow();\n });\n filters.appendChild(button);\n }\n\n const search = byId<HTMLInputElement>(\"search\");\n search.addEventListener(\"input\", () => {\n setSearch(search.value.trim());\n app.syncUrl();\n app.drawNow();\n });\n\n // Downloading the window is the bulk half of what the per-row buttons do one request\n // at a time. It exports what is on screen rather than everything held, because the\n // filter is how you said which requests you meant.\n const exportShown = byId<HTMLButtonElement>(\"feed-export\");\n exportShown.hidden = !SECTIONS.evidence;\n exportShown.addEventListener(\"click\", () => {\n const rows = matchingRows();\n if (rows.length === 0) {\n toast(\"warn\", \"Nothing to export\", \"No request in the window matches this filter.\");\n return;\n }\n // Oldest first, so the file replays in the order the traffic happened.\n const entries = rows.map((row) => row.entry).reverse();\n download(`${replayFile(entries)}\\n`, `bothandler-feed-${today()}.jsonl`, \"application/x-ndjson\");\n toast(\"ok\", `Exported ${n(entries.length)} request(s)`, \"Replay them with `bothandlerjs replay`.\");\n });\n}\n\n/** Applies filter state that arrived in the URL rather than from a click. */\nexport function reflectFilterButtons(): void {\n for (const button of Array.from($(\"filters\").children)) {\n button.setAttribute(\"aria-pressed\", String(button instanceof HTMLElement && button.dataset[\"filter\"] === state.filter));\n }\n const search = byId<HTMLInputElement>(\"search\");\n if (search.value !== state.search) search.value = state.search;\n}\n\n/**\n * Fetches the entries this viewer never received, and merges them.\n *\n * They were never lost. The rate cap keeps a burst off the *stream* and the lag guard\n * drops frames a slow socket cannot take, but both leave the ring alone — and the opening\n * replay is droppable too, so a first load of a busy dashboard can arrive with most of the\n * backlog missing. `/api/feed` serves that ring whole, and `ingest` is keyed on the request\n * id, so merging it is idempotent: what is already held is refreshed, what is missing is\n * added.\n *\n * On demand rather than on a timer. Skips happen exactly when the origin is busiest, and a\n * dashboard that answered every skip by re-fetching the whole ring would be a load\n * amplifier pointed at the process it is meant to be watching — which is the thing\n * `maxEventsPerSecond` exists to prevent.\n */\nexport async function loadSkipped(): Promise<void> {\n const before = state.rows.length;\n try {\n const body = await getJson<{ entries: DashboardEntry[] }>(\"/api/feed\");\n for (const entry of body.entries) ingest(entry);\n sortRows();\n } catch {\n toast(\"bad\", \"Could not load them\", \"The dashboard did not answer. The entries are still in the window; try again.\");\n return;\n }\n // What the badge counted has now been asked for, whether or not the ring still had all\n // of it — anything it no longer holds is gone and saying so forever helps nobody.\n state.caughtUp = (state.snapshot?.skipped ?? 0) + state.laggedDrops;\n const added = state.rows.length - before;\n resetPaging();\n app.drawNow();\n toast(\n added > 0 ? \"ok\" : \"warn\",\n added > 0 ? `Loaded ${n(added)}` : \"Nothing left to load\",\n added > 0 ? \"They are in the feed now, in the order they happened.\" : \"The window no longer holds them; the ring had already rotated past.\",\n );\n}\n\n/** Page sizes the feed offers. */\nconst FEED_PAGE_SIZES = [25, 50, 100, 200] as const;\n\n/**\n * The pager, above the table and below it.\n *\n * Hidden outright on a single page, because a control that can only say \"1 of 1\" is\n * furniture — and with it the size chooser goes too, which is the one thing lost by that\n * rule and not worth a permanent row of chrome to keep.\n */\nfunction drawPager(paged: { page: number; pages: number; total: number }): void {\n const size = state.feedPageSize;\n const hidden = paged.pages <= 1;\n const model = {\n page: paged.page,\n from: paged.page * size + 1,\n to: Math.min(paged.total, (paged.page + 1) * size),\n total: paged.total,\n atStart: paged.page === 0,\n atEnd: paged.page >= paged.pages - 1,\n ...(paged.page > 0 ? { held: \"held while you read\" } : {}),\n go: (page: number): void => {\n goToFeedPage(page);\n app.drawNow();\n },\n size: {\n current: size,\n choices: FEED_PAGE_SIZES,\n set: (next: number): void => {\n state.feedPageSize = next;\n // Back to the front: page four of fifty is not page four of two hundred, and\n // keeping the number while changing what it counts moves the reader somewhere\n // they did not ask to go.\n resetPaging();\n app.drawNow();\n },\n },\n };\n for (const [id, withSize] of [\n [\"feed-pager-top\", true],\n [\"feed-pager\", false],\n ] as const) {\n const host = $(id);\n host.hidden = hidden;\n if (hidden) clear(host);\n else renderPager(host, model, { withSize });\n }\n}\n\nexport function drawFeed(): void {\n const body = byId<HTMLTableSectionElement>(\"rows\");\n const paged = feedPage(state.feedPageSize);\n const shown = paged.rows;\n\n // Keyed reconciliation against what is already in the table.\n //\n // The old renderer emptied the tbody and rebuilt three hundred rows on every frame\n // that carried a new request. Two things were wrong with that, and the invisible one\n // is not the expensive one: rebuilding a node destroys any text selection inside it,\n // so a live feed could not be *read* — drag across a User-Agent to copy it and the\n // next request wiped the selection. Rows are now rebuilt only when the entry behind\n // them changed or they were opened, which on a busy feed is one row in three hundred.\n let index = 0;\n const place = (node: Node): void => {\n const current = body.childNodes[index] ?? null;\n // insertBefore moves a node that is already in the document, so this handles a\n // reorder as well as an insert.\n if (current !== node) body.insertBefore(node, current);\n index++;\n };\n\n for (const row of shown) {\n const id = row.entry.requestId;\n const open = state.open.has(id);\n let cached = rendered.get(id);\n if (cached === undefined || cached.rev !== row.rev || cached.open !== open) {\n cached = {\n row: buildRow(row.entry, open),\n detail: open ? buildDetail(row.entry) : undefined,\n rev: row.rev,\n open,\n };\n rendered.set(id, cached);\n }\n place(cached.row);\n if (cached.detail !== undefined) place(cached.detail);\n }\n\n while (body.childNodes.length > index) body.removeChild(body.childNodes[index] as Node);\n\n // The cache outlives the table — a row scrolled past the filter is likely to come\n // back — but not indefinitely. Anything not on screen goes once it has grown past\n // twice the visible set.\n if (rendered.size > shown.length * 2 + 100) {\n const live = new Set(shown.map((row) => row.entry.requestId));\n for (const id of Array.from(rendered.keys())) if (!live.has(id)) rendered.delete(id);\n }\n\n const total = state.rows.length;\n const matching = matchingCount();\n $(\"empty\").hidden = total > 0;\n // No \"showing 300\" any more: the pager reaches the rest, so the count says what is in\n // the window and the pager says where in it you are.\n $(\"feed-count\").textContent = matching === total ? `${n(total)} in this window` : `${n(matching)} of ${n(total)}`;\n drawPager(paged);\n // `#feed-window` carries the `win` class, so `updateWindowLabels` fills it — one\n // owner for a label that appears on eight panels.\n\n // A thinned feed must never look like a quiet one. The ring still has these — they\n // are in the preview, in the export and in whatever a reconnect replays — but they\n // were kept off the stream to stop a busy origin handing every open browser a\n // megabyte a second.\n // Minus what has already been fetched back: the server's own count only ever grows, so\n // without this the badge goes on reporting a gap that has been filled.\n const skipped = Math.max(0, (state.snapshot?.skipped ?? 0) + state.laggedDrops - state.caughtUp);\n const note = $(\"feed-skipped\");\n note.hidden = skipped === 0;\n note.textContent = `${n(skipped)} not streamed`;\n // The badge used to state the gap and leave it there. It is now next to the button that\n // closes it.\n byId<HTMLButtonElement>(\"feed-load-skipped\").hidden = skipped === 0;\n note.title =\n state.laggedDrops > 0\n ? `${n(state.laggedDrops)} were skipped because this connection could not keep up, and the rest by the rate cap. All of them are still in the window, the preview and the export.`\n : \"Entries the rate cap kept off this stream. They are still in the window, the preview and the export — raise maxEventsPerSecond to see them live.\";\n}\n\n/** Drops every cached node. Used when the feed is cleared under the page's feet. */\nexport function resetFeedCache(): void {\n rendered.clear();\n}\n\nfunction buildRow(entry: DashboardEntry, open: boolean): HTMLTableRowElement {\n const out = outcome(entry);\n const tr = el(\"tr\", `row a-${entry.downgradedFrom !== undefined ? \"guard\" : out}${open ? \" open\" : \"\"}`);\n\n // When it happened, which the feed never used to say. Ordering implies it while the\n // stream is live and stops implying it the moment you type into the filter box.\n tr.appendChild(el(\"td\", \"num mono tnum when\", clockTime(entry.at)));\n\n const request = el(\"td\", \"edge req\");\n // The row's one control. It carries the name, the state and the keys; the row itself\n // keeps a click handler for the mouse, which needs no role to be useful.\n const toggle = el(\"button\", \"row-toggle\", `${entry.method} ${entry.path}`);\n toggle.type = \"button\";\n toggle.setAttribute(\"aria-expanded\", String(open));\n toggle.setAttribute(\"aria-label\", `${entry.method} ${entry.path}, ${entry.verdict}. Evidence.`);\n toggle.dataset[\"request\"] = entry.requestId;\n request.appendChild(toggle);\n const ua = el(\"span\", \"ua\");\n if (SECTIONS.actors) {\n const actorLink = el(\"a\", null, entry.actor);\n actorLink.href = \"#actor\";\n actorLink.title = \"Show everything from this actor\";\n actorLink.addEventListener(\"click\", (event) => {\n event.preventDefault();\n event.stopPropagation();\n openActor(entry.actor);\n });\n ua.appendChild(actorLink);\n ua.appendChild(document.createTextNode(` · ${entry.userAgent}`));\n } else {\n ua.appendChild(document.createTextNode(`${entry.actor} · ${entry.userAgent}`));\n }\n ua.title = `${entry.actor} · ${entry.userAgent}`;\n request.appendChild(ua);\n tr.appendChild(request);\n\n const verdictCell = el(\"td\");\n const [badgeClass, badgeLabel] = verdictBadge(entry);\n verdictCell.appendChild(el(\"span\", `badge ${badgeClass}`, badgeLabel));\n if (entry.identity !== undefined) verdictCell.appendChild(el(\"span\", \"sub\", entry.identity));\n tr.appendChild(verdictCell);\n\n tr.appendChild(el(\"td\", \"num mono tnum\", entry.certain ? \"proven\" : String(entry.score)));\n\n const actionCell = el(\"td\");\n if (entry.action !== undefined) {\n const kind = out === \"deny\" ? \"act-deny\" : out === \"mitigate\" ? \"act-mitigate\" : out === \"allow\" ? \"act-allow\" : \"act-tag\";\n actionCell.appendChild(el(\"span\", `act ${kind}`, entry.action));\n if (entry.rule !== undefined) {\n const ruleLabel = el(\"span\", \"sub\", entry.rule);\n ruleLabel.title = entry.rule;\n actionCell.appendChild(ruleLabel);\n }\n if (entry.downgradedFrom !== undefined) actionCell.appendChild(el(\"span\", \"guard\", `guard stopped ${entry.downgradedFrom}`));\n } else {\n actionCell.appendChild(el(\"span\", \"sub\", \"assessed only\"));\n }\n tr.appendChild(actionCell);\n\n tr.appendChild(el(\"td\", \"num mono tnum\", entry.durationMs.toFixed(2)));\n\n tr.dataset[\"request\"] = entry.requestId;\n const flip = (): void => {\n if (state.open.has(entry.requestId)) state.open.delete(entry.requestId);\n else state.open.add(entry.requestId);\n app.drawNow();\n // The row was rebuilt, so focus has to be put back or a keyboard user is returned\n // to the top of the document every time they open one.\n rootNode().querySelector<HTMLElement>(`button.row-toggle[data-request=\"${cssEscape(entry.requestId)}\"]`)?.focus();\n };\n toggle.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n flip();\n });\n // Clicking anywhere in the row is the mouse affordance people expect from a feed, and\n // it needs no role to work — a click is not an accessibility contract.\n tr.addEventListener(\"click\", flip);\n return tr;\n}\n\nfunction buildDetail(entry: DashboardEntry): HTMLTableRowElement {\n const tr = el(\"tr\", \"detail\");\n const cell = el(\"td\");\n cell.colSpan = 6;\n\n if (!SECTIONS.evidence) {\n cell.appendChild(\n el(\n \"div\",\n \"ev-meta\",\n \"The evidence section is switched off on this dashboard, so the reasons behind this verdict are not sent to it. What fired, and why, is on a dashboard that has `sections: { evidence: true }`.\",\n ),\n );\n } else if (entry.evidence.length === 0) {\n cell.appendChild(\n el(\n \"div\",\n \"ev-meta\",\n entry.bypass !== undefined\n ? `Detection was skipped for this request: ${entry.bypass}.`\n : \"No detector produced any evidence. This is what ordinary traffic looks like.\",\n ),\n );\n } else {\n const list = el(\"div\", \"ev\");\n for (const item of entry.evidence) {\n const row = el(\"div\", `ev-item${item.direction === \"human\" ? \" human\" : \"\"}`);\n row.appendChild(el(\"div\", `tier t-${item.certainty}`, item.certainty));\n const body = el(\"div\");\n body.appendChild(el(\"div\", null, item.summary));\n let meta = `${item.detector} · points to ${item.direction}`;\n if (item.family !== undefined) meta += ` · family “${item.family}”, counted once with its siblings`;\n body.appendChild(el(\"div\", \"ev-meta\", meta));\n if (item.deterministicBasis !== undefined) body.appendChild(el(\"div\", \"basis\", item.deterministicBasis));\n row.appendChild(body);\n list.appendChild(row);\n }\n cell.appendChild(list);\n }\n\n for (const failure of entry.failures) {\n cell.appendChild(el(\"div\", \"ev-meta\", `Detector ${failure.detector} ${failure.reason}: ${failure.message}`));\n }\n if (entry.downgradeReason !== undefined) cell.appendChild(el(\"div\", \"basis\", `Guard: ${entry.downgradeReason}`));\n\n const queryNames = Object.keys(entry.query);\n if (queryNames.length > 0) {\n const queryTable = el(\"table\", \"hdr\");\n for (const name of queryNames) {\n const value = entry.query[name] ?? \"\";\n const row = el(\"tr\");\n row.appendChild(el(\"td\", \"n\", `?${name}`));\n row.appendChild(el(\"td\", `v${value === \"[redacted]\" ? \" red\" : \"\"}`, value));\n queryTable.appendChild(row);\n }\n cell.appendChild(queryTable);\n }\n\n // The header set in wire order: what half the detectors are actually reading, and the\n // first thing worth looking at when a verdict seems wrong.\n if (entry.headers !== undefined && entry.headers.length > 0) {\n const table = el(\"table\", \"hdr\");\n for (const [name, value] of entry.headers) {\n const row = el(\"tr\");\n row.appendChild(el(\"td\", \"n\", `${name}:`));\n row.appendChild(el(\"td\", `v${value === \"[redacted]\" ? \" red\" : \"\"}`, value));\n table.appendChild(row);\n }\n cell.appendChild(table);\n }\n\n const tools = el(\"div\", \"tools\");\n if (SECTIONS.evidence) {\n tools.appendChild(copyButton(\"Copy replay line\", () => replayLine(entry)));\n tools.appendChild(downloadButton(\"Download replay line\", () => replayLine(entry), `request-${entry.requestId}.jsonl`));\n tools.appendChild(copyButton(\"Copy corpus case\", () => corpusCase(entry)));\n }\n if (SECTIONS.policy) {\n const draft = el(\"button\", null, \"Draft a rule\");\n draft.title = \"Start a rule from this request, in the policy editor\";\n draft.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n void draftIntoEditor(entry);\n });\n tools.appendChild(draft);\n }\n if (SECTIONS.actors) {\n const actorButton = el(\"button\", null, \"Show this actor\");\n actorButton.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n openActor(entry.actor);\n });\n tools.appendChild(actorButton);\n }\n cell.appendChild(tools);\n\n const foot = el(\"div\", \"detail-foot\");\n foot.appendChild(el(\"span\", null, clockTime(entry.at)));\n foot.appendChild(el(\"span\", null, `actor ${entry.actor}`));\n if (entry.rule !== undefined) foot.appendChild(el(\"span\", null, `rule “${entry.rule}”`));\n foot.appendChild(el(\"span\", null, `assessed in ${entry.durationMs.toFixed(3)}ms`));\n foot.appendChild(el(\"span\", \"mono\", entry.requestId));\n cell.appendChild(foot);\n\n tr.appendChild(cell);\n return tr;\n}\n\nfunction copyButton(label: string, produce: () => string): HTMLButtonElement {\n const button = el(\"button\", null, label);\n button.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n const text = produce();\n const done = (): void => {\n button.textContent = \"Copied\";\n setTimeout(() => {\n button.textContent = label;\n }, 1200);\n };\n // navigator.clipboard needs a secure context, which a dashboard on a plain HTTP LAN\n // address is not. The fallback selects the text so a keyboard copy still works\n // rather than leaving a button that does nothing.\n if (navigator.clipboard?.writeText !== undefined) navigator.clipboard.writeText(text).then(done, () => showText(text));\n else showText(text);\n });\n return button;\n}\n\nfunction downloadButton(label: string, produce: () => string, filename: string): HTMLButtonElement {\n const button = el(\"button\", null, label);\n button.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n download(`${produce()}\\n`, filename, \"application/x-ndjson\");\n });\n return button;\n}\n\nfunction showText(text: string): void {\n const box = el(\"pre\", \"code\", text);\n const host = $(\"policy-result\");\n host.hidden = false;\n host.className = \"result\";\n clear(host);\n host.appendChild(el(\"div\", \"ev-meta\", \"Copying needs a secure context; here is the text.\"));\n host.appendChild(box);\n const selection = getSelection();\n if (selection !== null) {\n const range = document.createRange();\n range.selectNodeContents(box);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n}\n","import { $ } from \"./dom.js\";\nimport { API, SECTIONS } from \"./boot.js\";\nimport { app } from \"./app.js\";\nimport { clearFeed, ingest, state } from \"./store.js\";\nimport { getJson } from \"./api.js\";\nimport { resetFeedCache } from \"./feed.js\";\nimport type { DashboardEntry, Snapshot } from \"./types.js\";\n\n/**\n * The event stream.\n *\n * `EventSource` reconnects on its own and resends the last `id:` it saw as\n * `Last-Event-ID`, so the server can answer a reconnect with the handful of frames\n * that were missed instead of the whole ring — five hundred entries with their\n * headers, evidence and actor history attached, every time a laptop lid closes. The\n * page's side of that bargain is this: nothing here tracks the cursor, because the\n * browser already does, and a second copy would be the one that is wrong.\n */\n/**\n * The open connection, so it can be closed again.\n *\n * It used to be a local, which meant nothing could stop it. Embedded in somebody's page\n * that mattered: removing the element left the stream open and the client drawing into a\n * detached tree for the life of the page — one held server connection and a steady trickle\n * of work for a dashboard nobody was looking at.\n */\nlet source: EventSource | undefined;\n\n/**\n * Closes the stream, keeping everything the page has drawn.\n *\n * Reconnecting resumes from where this left off: the server is told the last id seen and\n * sends only what was missed, so suspending across a route change costs a reconnect rather\n * than a reload.\n */\nexport function suspendStream(): void {\n source?.close();\n source = undefined;\n}\n\nexport function connectStream(): void {\n if (!SECTIONS.feed) {\n $(\"dot\").className = \"dot\";\n $(\"conn\").textContent = \"feed off\";\n return;\n }\n if (source !== undefined) return;\n\n source = new EventSource(`${API}/api/stream`);\n\n source.addEventListener(\"open\", () => {\n $(\"dot\").className = \"dot on\";\n $(\"conn\").textContent = \"live\";\n });\n\n // A full backlog is on its way and it replaces what the page holds. Sent when the\n // server cannot honour the cursor — a restarted process, a cleared feed, or a gap\n // longer than the ring — so keeping the old rows would mean showing requests\n // nothing will ever correct.\n source.addEventListener(\"sync\", (event) => {\n const detail = JSON.parse((event as MessageEvent<string>).data) as { replace: boolean };\n if (!detail.replace) return;\n clearFeed();\n resetFeedCache();\n });\n\n source.addEventListener(\"entry\", (event) => {\n ingest(JSON.parse((event as MessageEvent<string>).data) as DashboardEntry);\n if (state.paused) {\n // Paused means the page stops redrawing, so the only thing that may move is the\n // button that says how much you are not being shown.\n state.bufferedWhilePaused++;\n $(\"pause\").textContent = `Resume (${state.bufferedWhilePaused})`;\n }\n app.draw();\n });\n\n source.addEventListener(\"update\", (event) => {\n ingest(JSON.parse((event as MessageEvent<string>).data) as DashboardEntry);\n app.draw();\n });\n\n // Somebody pressed Reset — possibly in another browser. Without this a second viewer\n // goes on showing a feed of requests the server has forgotten.\n source.addEventListener(\"reset\", () => {\n clearFeed();\n resetFeedCache();\n app.draw();\n });\n\n // The server could not keep up with this connection and skipped part of the feed\n // rather than queueing it in its own memory. Said out loud: a gap the viewer knows\n // about is a different thing from one it does not.\n source.addEventListener(\"lagged\", (event) => {\n const detail = JSON.parse((event as MessageEvent<string>).data) as { dropped: number };\n state.laggedDrops += detail.dropped;\n app.draw();\n });\n\n source.addEventListener(\"stats\", (event) => {\n state.snapshot = JSON.parse((event as MessageEvent<string>).data) as Snapshot;\n app.draw();\n });\n\n source.addEventListener(\"error\", () => {\n $(\"dot\").className = \"dot off\";\n $(\"conn\").textContent = \"reconnecting…\";\n // EventSource reconnects on its own; this only reports it. A stream closed by the\n // server on shutdown ends up here too, which is why the dot matters.\n });\n}\n\n/**\n * A first fetch, so the page has counters before the stream opens — and so a browser\n * with `EventSource` blocked still shows something useful.\n */\nexport async function loadInitialSnapshot(): Promise<void> {\n try {\n state.snapshot = await getJson<Snapshot>(\"/api/stats\");\n app.drawNow();\n } catch {\n /* the stream is the primary path */\n }\n}\n","import { $, clear, el, rootNode } from \"./dom.js\";\nimport { BOOT, SECTIONS } from \"./boot.js\";\nimport { aggregate, oldestAt, state } from \"./store.js\";\nimport { drawBars, pairs } from \"./bars.js\";\nimport { clockTime, ms, n, pct, rangeLabel, uptime, windowLabel } from \"./format.js\";\nimport { provenBots } from \"./outcome.js\";\nimport { app } from \"./app.js\";\nimport type { Aggregates } from \"./store.js\";\nimport type { TabName } from \"./types.js\";\n\n/**\n * The counter tiles.\n *\n * Redrawn only when the snapshot behind them changes, which is every two seconds\n * rather than every frame. They sit above the tab strip, so they are the one part of\n * the page that is on screen whichever view is showing, and they were being rebuilt on\n * every incoming request for numbers that could not have moved.\n */\nlet paintedSnapshot: unknown;\n\n/** A screen a counter tile can send you to. */\ninterface Destination {\n tab: TabName;\n label: string;\n}\n\nexport function drawTiles(force = false): void {\n // The tiles are the statistics section's, and `applySections` removes the node when\n // that section is off — so this has to check before it looks.\n if (!SECTIONS.statistics) return;\n const box = $(\"tiles\");\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n if (!force && paintedSnapshot === snapshot) return;\n paintedSnapshot = snapshot;\n\n const metrics = snapshot.metrics;\n clear(box);\n if (metrics === undefined) {\n box.appendChild(el(\"div\", \"note\", \"Counters are switched off on this handler (metrics: false). The live feed still works.\"));\n return;\n }\n\n const actions = metrics.actions;\n const denied = actions.block + actions.drop + actions.redirect;\n const mitigated = actions.challenge + actions[\"rate-limit\"] + actions.delay;\n const served = actions.allow + actions.tag + actions.log;\n const total = metrics.requests;\n const unremarkable = metrics.verdicts.unknown + metrics.verdicts.human;\n // From the verdicts rather than from `metrics.proven`: that counter includes proven\n // *humans* — a clearance token is certain evidence too — so a dashboard watching a\n // logged-in audience reported all of them as proven bots. See `provenBots`.\n const provenBotCount = provenBots(metrics.verdicts);\n\n // A tile that counts what a screen shows can take you to it. Only where that screen\n // exists: `registry` off means there is no Actors tab to reach, and a control that\n // navigates nowhere is worse than no control.\n const actors: Destination | undefined = SECTIONS.registry ? { tab: \"actors\", label: \"Actors\" } : undefined;\n\n const tiles: Array<[string, string, string, string, Destination | undefined]> = [\n [\"\", n(total), \"Requests\", \"since start\", undefined],\n [\"proven\", n(provenBotCount), \"Proven bots\", `${pct(provenBotCount, total)} of traffic`, undefined],\n [\"warn\", n(metrics.verdicts[\"suspected-bot\"]), \"Suspected\", \"never denied alone\", undefined],\n [\"\", n(unremarkable), \"Unremarkable\", `${pct(unremarkable, total)} of traffic`, undefined],\n [\"warn\", n(metrics.downgrades), \"Guard stops\", metrics.downgrades > 0 ? \"a rule over-reached\" : \"no rule overreached\", undefined],\n [\"crit\", n(denied), \"Denied\", `${pct(denied, total)} of traffic`, undefined],\n [\n \"\",\n n(mitigated),\n \"Mitigated\",\n metrics.challenges.issued > 0 ? `${n(metrics.challenges.solved)} of ${n(metrics.challenges.issued)} challenges solved` : \"challenged or limited\",\n undefined,\n ],\n [\"good\", n(served), \"Served\", `${pct(served, total)} of traffic`, undefined],\n [\"\", n(metrics.actorsTracked), \"Actors tracked\", \"in the registry now\", actors],\n ];\n for (const [kind, value, key, sub, goes] of tiles) {\n // A real button where it navigates, so it is in the tab order, answers Enter and\n // Space, and is announced as something to press — none of which a div with a click\n // handler gets, and all of which somebody expects from a thing that moves the page.\n const tile = goes === undefined ? el(\"div\", `tile ${kind}`) : el(\"button\", `tile ${kind} go`);\n tile.appendChild(el(\"div\", \"v tnum\", value));\n tile.appendChild(el(\"div\", \"k\", key));\n tile.appendChild(el(\"div\", \"s\", sub));\n if (goes !== undefined) {\n (tile as HTMLButtonElement).type = \"button\";\n // Appended rather than an `aria-label`, which would replace the count and the\n // caption a screen reader is here to hear. This adds to them.\n tile.appendChild(el(\"span\", \"sr-only\", `. Show the ${goes.label} screen`));\n tile.addEventListener(\"click\", () => app.showTab(goes.tab, { replace: false }));\n }\n box.appendChild(tile);\n }\n}\n\n/**\n * The configuration facts at the end of the tab strip.\n *\n * Label first, value second, every time — \"suspect at 60\", not \"60 suspect at\" — and\n * as plain text, which is the one treatment nothing else on the page uses for a\n * control.\n */\nexport function drawChips(): void {\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n const box = $(\"chips\");\n clear(box);\n const facts: Array<[string, string]> = [\n // Which process this is, first, because everything after it is a fact about this\n // process and nothing else. Behind a load balancer there are as many of these\n // dashboards as there are pods, each showing its own share of the traffic.\n [\"instance\", snapshot.instance],\n [\"guard\", snapshot.policy.falsePositivePolicy],\n [\"suspect at\", String(snapshot.policy.suspectThreshold)],\n ];\n if (SECTIONS.statistics) facts.push([\"detectors\", String(snapshot.detectors.length)]);\n if (SECTIONS.policy) facts.push([\"rules\", String(snapshot.rules.length)]);\n facts.push([\"uptime\", uptime(snapshot.now - snapshot.startedAt)]);\n\n for (const [label, value] of facts) {\n const fact = el(\"span\");\n fact.appendChild(document.createTextNode(`${label} `));\n fact.appendChild(el(\"b\", null, value));\n box.appendChild(fact);\n }\n}\n\n/**\n * Every panel that counts the retained ring says how much ring there is.\n *\n * Half the Statistics screen counts the window and half counts since the process\n * started; they were wearing the same grey subtitle, which invited exactly the\n * comparison that does not hold.\n */\nexport function updateWindowLabels(): void {\n const label = windowLabel(state.rows.length, oldestAt(), Date.now());\n for (const node of Array.from(rootNode().querySelectorAll<HTMLElement>(\".win\"))) node.textContent = label;\n}\n\nexport function drawLivePanels(): void {\n if (!SECTIONS.statistics) return;\n const totals = aggregate(state.rows);\n drawBars($(\"live-detectors\"), totals.detectors, \"Nothing has fired in this window.\");\n if (SECTIONS.actors) drawBars($(\"live-actors\"), totals.actors, \"No traffic in this window.\");\n}\n\nexport function drawStatsPanels(): void {\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n const metrics = snapshot.metrics;\n\n if (metrics !== undefined) {\n drawBars($(\"stat-verdicts\"), pairs(metrics.verdicts), \"Nothing assessed yet.\");\n drawBars($(\"stat-actions\"), pairs(metrics.actions), \"No decisions yet.\");\n drawBars($(\"stat-classes\"), pairs(metrics.botClasses), \"Nothing classified yet.\");\n drawBars($(\"stat-detectors\"), pairs(metrics.detectorFirings), \"No detector has produced evidence yet.\");\n\n const challenges = $(\"stat-challenges\");\n clear(challenges);\n if (!snapshot.policy.challengeEnabled) {\n challenges.appendChild(el(\"div\", \"note\", \"No challenge is configured, so a rule asking for one degrades to a tag. Set challenge.secrets to enable it.\"));\n } else {\n const funnel: Array<[string, string]> = [\n [\"Issued\", n(metrics.challenges.issued)],\n [\"Solved\", n(metrics.challenges.solved)],\n [\"Rejected\", n(metrics.challenges.rejected)],\n [\"Solve rate\", metrics.challenges.issued > 0 ? pct(metrics.challenges.solved, metrics.challenges.issued) : \"—\"],\n ];\n for (const [key, value] of funnel) challenges.appendChild(statRow(key, value));\n }\n\n const health = $(\"stat-health\");\n clear(health);\n const bypassed = metrics.bypassed.allowlist + metrics.bypassed[\"ignored-path\"];\n const rows: Array<[string, string]> = [\n [\"Requests assessed\", n(metrics.requests)],\n [\"Bypassed — allowlist\", n(metrics.bypassed.allowlist)],\n [\"Bypassed — ignored path\", n(metrics.bypassed[\"ignored-path\"])],\n [\"Detection ran on\", pct(metrics.requests - bypassed, metrics.requests)],\n [\"Actors tracked\", n(metrics.actorsTracked)],\n [\"Guard stops\", n(metrics.downgrades)],\n ];\n const failures = pairs(metrics.detectorFailures);\n for (const [detector, count] of failures) rows.push([`Detector failures — ${detector}`, n(count)]);\n for (const [key, value] of rows) health.appendChild(statRow(key, value));\n if (failures.length === 0) health.appendChild(el(\"div\", \"note\", \"No detector has thrown or timed out.\"));\n }\n\n const totals = aggregate(state.rows);\n if (SECTIONS.actors) drawBars($(\"stat-identities\"), totals.identities, \"No client has named itself in this window.\");\n drawBars($(\"stat-paths\"), totals.paths, \"No traffic in this window.\");\n drawBars($(\"stat-denied-paths\"), totals.deniedPaths, \"Nothing has been denied in this window.\");\n drawBars($(\"stat-guard\"), totals.guardStops, \"No rule has asked for more than its evidence supports.\");\n drawBars($(\"stat-bypassed\"), totals.bypassed, \"Nothing bypassed detection.\");\n drawRuleHits(totals);\n\n const list = $(\"stat-detector-list\");\n clear(list);\n $(\"detector-count\").textContent = `${snapshot.detectors.length} installed`;\n for (const detector of snapshot.detectors) {\n const row = el(\"div\", \"det\");\n const left = el(\"div\");\n left.appendChild(el(\"div\", \"mono\", detector.id));\n left.appendChild(el(\"div\", \"d\", detector.description));\n row.appendChild(left);\n const fires = metrics?.detectorFirings[detector.id] ?? 0;\n const timing = metrics?.detectorTimings[detector.id];\n let right = `${n(fires)} · ${detector.cost} · ${detector.stage}`;\n // Only when the operator asked for timing; the field is empty otherwise.\n if (timing !== undefined && timing.count > 0) right += ` · ${ms(timing.totalMs / timing.count)} avg`;\n row.appendChild(el(\"div\", \"n\", right));\n list.appendChild(row);\n }\n}\n\nfunction statRow(key: string, value: string): HTMLElement {\n const line = el(\"div\", \"stat-row\");\n line.appendChild(el(\"span\", \"k\", key));\n line.appendChild(el(\"span\", \"v\", value));\n return line;\n}\n\n/**\n * Rule hit counts, including the rules that never fire.\n *\n * The zeros are the point. A rule that has matched nothing is either dead\n * configuration or a rule sitting behind a broader one that swallows its traffic, and\n * both are invisible in a chart that only draws what happened.\n */\nfunction drawRuleHits(totals: Aggregates): void {\n const target = $(\"stat-rule-hits\");\n clear(target);\n const rules = state.snapshot?.rules ?? [];\n if (rules.length === 0) {\n target.appendChild(el(\"div\", \"note\", \"No rules configured.\"));\n return;\n }\n\n let max = 1;\n for (const rule of rules) max = Math.max(max, totals.ruleHits.get(rule) ?? 0);\n for (const rule of rules) {\n const value = totals.ruleHits.get(rule) ?? 0;\n const bar = el(\"div\", `bar${value === 0 ? \" dead\" : \"\"}`);\n const track = el(\"div\", \"track\");\n if (value > 0) {\n const fill = el(\"div\", \"fill\");\n fill.style.width = `${Math.max(2, Math.round((value / max) * 100))}%`;\n track.appendChild(fill);\n }\n track.appendChild(el(\"div\", \"lbl\", rule));\n bar.appendChild(track);\n bar.appendChild(el(\"div\", \"v tnum\", value === 0 ? \"never\" : n(value)));\n target.appendChild(bar);\n }\n}\n\n/**\n * The audit panel: the window against its baseline.\n *\n * Both, side by side, because that is the only way the audit's output means anything.\n * A bot share of 60% is a number; a bot share of 60% against a baseline of 12% is an\n * incident.\n */\nexport function drawAudit(): void {\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n const body = $(\"audit-body\");\n const checks = $(\"audit-checks\");\n clear(body);\n clear(checks);\n\n const audit = snapshot.audit;\n if (audit === undefined) {\n $(\"audit-spans\").textContent = \"\";\n body.appendChild(\n el(\n \"div\",\n \"note\",\n \"The traffic audit is switched off on this handler (audit: false). It watches the shape of your traffic rather than any one request — a spike in automation, a collapse in human traffic, a policy suddenly denying far more than usual.\",\n ),\n );\n return;\n }\n\n const current = audit.window;\n const baseline = audit.baseline;\n $(\"audit-spans\").textContent = `last ${rangeLabel(current.spanMs)} against the ${rangeLabel(baseline.spanMs)} before`;\n\n // A share is only legible next to the one it is being compared with, so every row\n // carries both. The delta is left to the reader: an arrow implying \"worse\" would be\n // the dashboard editorialising about traffic it cannot see the purpose of.\n const rows: Array<[string, string, string]> = [\n [\"Requests\", n(current.requests), n(baseline.requests)],\n [\"Rate\", `${current.rate.toFixed(1)}/min`, `${baseline.rate.toFixed(1)}/min`],\n [\"Bot share\", `${Math.round(current.botShare * 100)}%`, `${Math.round(baseline.botShare * 100)}%`],\n [\"Bots\", n(current.bots), n(baseline.bots)],\n [\"Humans\", n(current.humans), n(baseline.humans)],\n [\"Denials\", n(current.denials), n(baseline.denials)],\n [\"Challenges\", n(current.challenges), n(baseline.challenges)],\n [\"Guard stops\", n(current.downgrades), n(baseline.downgrades)],\n [\"Detector failures\", n(current.failures), n(baseline.failures)],\n [\"Bypassed\", n(current.bypassed), n(baseline.bypassed)],\n ];\n for (const [key, now, was] of rows) {\n const line = el(\"div\", \"stat-row\");\n line.appendChild(el(\"span\", \"k\", key));\n const values = el(\"span\", \"v\");\n values.appendChild(el(\"b\", null, now));\n values.appendChild(el(\"span\", \"was\", ` was ${was}`));\n line.appendChild(values);\n body.appendChild(line);\n }\n\n if (audit.checks.length === 0) {\n checks.appendChild(el(\"div\", \"note\", \"No checks are installed, so nothing here will ever raise an anomaly.\"));\n return;\n }\n for (const check of audit.checks) {\n const row = el(\"div\", \"det\");\n const left = el(\"div\");\n left.appendChild(el(\"div\", \"mono\", check.id));\n left.appendChild(el(\"div\", \"d\", check.description));\n row.appendChild(left);\n checks.appendChild(row);\n }\n}\n\n/** The count on the Policy tab. Cheap, so it runs on every frame; the panel itself does not. */\nexport function drawNoticeBadge(): void {\n const notices = state.snapshot?.notices ?? [];\n const badge = $(\"notice-badge\");\n badge.hidden = notices.length === 0 || !SECTIONS.notices;\n badge.textContent = String(notices.length);\n}\n\nexport function drawNotices(): void {\n const box = $(\"stat-notices\");\n const notices = state.snapshot?.notices ?? [];\n clear(box);\n $(\"notice-count\").textContent = notices.length > 0 ? `${notices.length} total` : \"\";\n if (notices.length === 0) {\n box.appendChild(el(\"div\", \"note\", \"Nothing to report: no startup warnings, no detector errors.\"));\n return;\n }\n for (const notice of notices.slice().reverse().slice(0, 40)) {\n const row = el(\"div\", `notice ${notice.kind}`);\n const when = el(\"div\", \"when\");\n when.appendChild(el(\"div\", \"tag\", notice.kind));\n when.appendChild(el(\"div\", null, clockTime(notice.at)));\n row.appendChild(when);\n const body = el(\"div\");\n body.appendChild(el(\"div\", null, notice.message));\n if (notice.source !== undefined) body.appendChild(el(\"div\", \"ev-meta\", notice.source));\n row.appendChild(body);\n box.appendChild(row);\n }\n}\n\n/**\n * What somebody did to this process, and when.\n *\n * The same list the traffic timeline marks, written out — because a marker answers\n * \"was there a change here?\" and a person auditing wants \"what were they, in order\".\n * Bounded and in memory, which the panel says: the durable copy is the change events.\n */\nexport function drawChanges(): void {\n if (!SECTIONS.changes) return;\n const box = $(\"stat-changes\");\n const changes = state.snapshot?.changes ?? [];\n clear(box);\n $(\"change-count\").textContent = changes.length > 0 ? `${changes.length} this run` : \"\";\n\n if (changes.length === 0) {\n box.appendChild(el(\"div\", \"note\", \"Nothing has been changed at runtime. Rules, guard and ranges are as the code that built this handler left them.\"));\n return;\n }\n\n for (const change of changes.slice().reverse()) {\n const row = el(\"div\", \"notice\");\n const when = el(\"div\", \"when\");\n when.appendChild(el(\"div\", \"tag\", change.kind));\n when.appendChild(el(\"div\", null, clockTime(change.at)));\n row.appendChild(when);\n const body = el(\"div\");\n body.appendChild(el(\"div\", null, change.summary));\n // \"by nobody in particular\" is a fact about the dashboard's `auth`, not a gap to\n // hide: a bearer token is a credential and not an identity.\n body.appendChild(el(\"div\", \"ev-meta\", change.by === undefined || change.by === \"\" ? \"by an unnamed viewer — this listener's auth carries no identity\" : `by ${change.by}`));\n row.appendChild(body);\n box.appendChild(row);\n }\n}\n\n/** Sibling instances, when the operator listed any. */\nexport function drawPeers(): void {\n const box = $(\"peers\");\n if (BOOT.peers.length === 0) {\n box.hidden = true;\n return;\n }\n if (box.childElementCount > 0) return;\n box.appendChild(el(\"span\", \"hint\", \"also:\"));\n for (const peer of BOOT.peers) {\n const link = el(\"a\", \"linkbtn\", peer.label);\n link.href = peer.href;\n link.rel = \"noreferrer noopener\";\n box.appendChild(link);\n }\n}\n","import { $, clear, css, el, svgEl, svgText } from \"./dom.js\";\nimport { clockTime, n, pct, ms, rangeLabel, windowLabel } from \"./format.js\";\nimport { oldestAt, state } from \"./store.js\";\nimport { outcome } from \"./outcome.js\";\n\nconst BUCKETS = 60;\nconst LATENCY_BOUNDS = [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100];\nconst SCORE_BOUNDS = 10;\n\ninterface Bucket {\n at: number;\n served: number;\n mitigated: number;\n denied: number;\n total: number;\n}\n\n/** Puts a tooltip beside the pointer without letting it hang off the panel. */\nfunction positionTip(tip: HTMLElement, host: HTMLElement, clientX: number, boxLeft: number): void {\n tip.style.opacity = \"1\";\n tip.style.left = `${Math.min(host.clientWidth - 150, Math.max(4, clientX - boxLeft - 60))}px`;\n tip.style.top = \"14px\";\n}\n\nfunction tipRow(label: string, value: string, colour?: string): HTMLElement {\n const line = el(\"div\", \"r\");\n const left = el(\"em\");\n if (colour !== undefined) {\n const swatch = el(\"span\", \"swatch\");\n swatch.style.background = colour;\n left.appendChild(swatch);\n }\n left.appendChild(document.createTextNode(label));\n line.appendChild(left);\n line.appendChild(el(\"b\", null, value));\n return line;\n}\n\nfunction timeline(): Bucket[] {\n const bucketMs = state.rangeMs / BUCKETS;\n const now = Date.now();\n const start = now - state.rangeMs;\n const buckets: Bucket[] = [];\n for (let i = 0; i < BUCKETS; i++) buckets.push({ at: start + i * bucketMs, served: 0, mitigated: 0, denied: 0, total: 0 });\n for (const { entry } of state.rows) {\n const index = Math.floor((entry.at - start) / bucketMs);\n if (index < 0 || index >= BUCKETS) continue;\n const bucket = buckets[index];\n if (bucket === undefined) continue;\n bucket.total++;\n const out = outcome(entry);\n if (out === \"deny\") bucket.denied++;\n else if (out === \"mitigate\") bucket.mitigated++;\n else bucket.served++;\n }\n return buckets;\n}\n\n/**\n * Stacked bars, two series, plus a marker row for denials.\n *\n * Two series rather than three because served/mitigated/denied as three fills would\n * need a third hue that survives colour-vision simulation beside these two, and none\n * does; a denial is a status rather than a series, so it gets a status marker and a\n * label instead.\n */\nexport function drawTraffic(): void {\n const host = $(\"traffic-chart\");\n const svg = $(\"traffic\");\n const width = Math.max(320, host.clientWidth - 30);\n const height = 190;\n const padBottom = 26;\n const markerRow = 8;\n const plot = height - padBottom - markerRow;\n const buckets = timeline();\n const now = Date.now();\n const start = now - state.rangeMs;\n let peak = 1;\n for (const bucket of buckets) if (bucket.total > peak) peak = bucket.total;\n // Round the ceiling up to an even number so the mid gridline lands on a whole\n // request rather than on 1.5 rendered as \"2\".\n const max = Math.max(2, Math.ceil(peak / 2) * 2);\n\n clear(svg);\n svg.setAttribute(\"viewBox\", `0 0 ${width} ${height}`);\n svg.setAttribute(\"height\", String(height));\n\n const s1 = css(\"--s1\");\n const s2 = css(\"--s2\");\n const crit = css(\"--crit\");\n const grid = css(\"--grid\");\n const muted = css(\"--muted\");\n const step = width / BUCKETS;\n const barWidth = Math.max(2, step - 2);\n\n // Three gridlines: enough to read a magnitude, few enough to stay recessive.\n for (const fraction of [0, 0.5, 1]) {\n const y = markerRow + plot - fraction * plot;\n svg.appendChild(svgEl(\"line\", { class: \"gridline\", x1: 0, x2: width, y1: y, y2: y, stroke: grid, \"stroke-width\": 1 }));\n if (fraction > 0) svg.appendChild(svgText({ x: 2, y: y - 3, fill: muted, \"font-size\": 10 }, Math.round(max * fraction)));\n }\n\n buckets.forEach((bucket, index) => {\n const x = index * step + 1;\n const servedHeight = (bucket.served / max) * plot;\n const mitigatedHeight = (bucket.mitigated / max) * plot;\n let y = markerRow + plot;\n\n if (servedHeight > 0) {\n y -= servedHeight;\n svg.appendChild(svgEl(\"rect\", { x, y, width: barWidth, height: servedHeight, fill: s1, rx: 2 }));\n }\n if (mitigatedHeight > 0) {\n // A 2px gap between stacked segments, so the boundary is a surface line rather\n // than two colours meeting.\n y -= mitigatedHeight + (servedHeight > 0 ? 2 : 0);\n svg.appendChild(svgEl(\"rect\", { x, y: Math.max(markerRow, y), width: barWidth, height: mitigatedHeight, fill: s2, rx: 2 }));\n }\n if (bucket.denied > 0) svg.appendChild(svgEl(\"rect\", { x, y: 0, width: barWidth, height: 5, fill: crit, rx: 2 }));\n });\n\n svg.appendChild(svgEl(\"line\", { x1: 0, x2: width, y1: markerRow + plot, y2: markerRow + plot, stroke: css(\"--line\"), \"stroke-width\": 1 }));\n const span = rangeLabel(state.rangeMs);\n const labels: Array<[number, string]> = [\n [0, `${span} ago`],\n [BUCKETS / 2, rangeLabel(state.rangeMs / 2)],\n [BUCKETS - 1, \"now\"],\n ];\n for (const [position, text] of labels) {\n svg.appendChild(svgText({ x: Math.min(width - 26, Math.max(0, position * step)), y: height - 8, fill: muted, \"font-size\": 10 }, text));\n }\n\n // Runtime changes, on the same axis as the traffic they changed.\n //\n // A preview says \"44 of 151 requests would be treated differently\", which is a\n // prediction. Nothing on the page used to tell you whether it held: the moment of the\n // change left no trace, so the traffic before and after it were the same undifferentiated\n // line. A marker per change turns the chart into the answer.\n const changes = (state.snapshot?.changes ?? []).filter((change) => change.at >= start && change.at <= now);\n const markColour = css(\"--proven-text\");\n for (const change of changes) {\n const x = ((change.at - start) / state.rangeMs) * width;\n svg.appendChild(svgEl(\"line\", { x1: x, x2: x, y1: 0, y2: markerRow + plot, stroke: markColour, \"stroke-width\": 1.5, \"stroke-dasharray\": \"3 2\", opacity: 0.85 }));\n const dot = svgEl(\"circle\", { cx: x, cy: 3, r: 3, fill: markColour });\n const title = svgEl(\"title\");\n title.textContent = `${clockTime(change.at)} · ${change.kind}: ${change.summary}${change.by === undefined ? \"\" : ` (by ${change.by})`}`;\n dot.appendChild(title);\n svg.appendChild(dot);\n }\n\n const hover = svgEl(\"rect\", { x: 0, y: 0, width: 0, height: markerRow + plot, fill: css(\"--ink\"), opacity: 0.06 });\n svg.appendChild(hover);\n\n const tip = $(\"traffic-tip\");\n host.onmousemove = (event: MouseEvent): void => {\n const box = svg.getBoundingClientRect();\n const index = Math.floor(((event.clientX - box.left) / box.width) * BUCKETS);\n const bucket = buckets[index];\n if (bucket === undefined) {\n tip.style.opacity = \"0\";\n hover.setAttribute(\"width\", \"0\");\n return;\n }\n hover.setAttribute(\"x\", String(index * step));\n hover.setAttribute(\"width\", String(step));\n clear(tip);\n tip.appendChild(el(\"div\", \"t\", `${clockTime(bucket.at)} · ${Math.round(state.rangeMs / BUCKETS / 1000)}s`));\n tip.appendChild(tipRow(\"Served\", n(bucket.served), s1));\n tip.appendChild(tipRow(\"Mitigated\", n(bucket.mitigated), s2));\n tip.appendChild(tipRow(\"Denied\", n(bucket.denied), crit));\n positionTip(tip, host, event.clientX, box.left);\n };\n host.onmouseleave = (): void => {\n tip.style.opacity = \"0\";\n hover.setAttribute(\"width\", \"0\");\n };\n\n const total = buckets.reduce((sum, bucket) => sum + bucket.total, 0);\n $(\"traffic-window\").textContent = `last ${rangeLabel(state.rangeMs)}`;\n\n // What the chart says, in words.\n //\n // `role=\"img\"` with a label told a screen reader that a picture is here and its name.\n // It did not tell anybody what is *in* it, so every number on this screen was\n // unreachable to a reader who cannot see the bars. The chart is the presentation; this\n // is the data, and it is the same data.\n const served = buckets.reduce((sum, bucket) => sum + bucket.served, 0);\n const mitigated = buckets.reduce((sum, bucket) => sum + bucket.mitigated, 0);\n const denied = buckets.reduce((sum, bucket) => sum + bucket.denied, 0);\n const busiest = buckets.reduce((best, bucket) => (bucket.total > best.total ? bucket : best), buckets[0] ?? { at: now, total: 0, served: 0, mitigated: 0, denied: 0 });\n $(\"traffic-alt\").textContent =\n `Traffic over the last ${rangeLabel(state.rangeMs)}: ${n(total)} requests — ${n(served)} served, ${n(mitigated)} mitigated, ${n(denied)} denied. ` +\n (total === 0\n ? \"No traffic in this range.\"\n : `Busiest ${Math.round(state.rangeMs / BUCKETS / 1000)}-second interval: ${n(busiest.total)} requests at ${clockTime(busiest.at)}.`) +\n (changes.length === 0 ? \"\" : ` ${n(changes.length)} runtime change${changes.length === 1 ? \"\" : \"s\"} in this range: ${changes.map((change) => `${change.kind}, ${change.summary}`).join(\"; \")}.`);\n\n const legend = $(\"traffic-legend\");\n clear(legend);\n legend.appendChild(el(\"span\", null, `${n(total)} requests in the last ${rangeLabel(state.rangeMs)} ·`));\n // The ring is finite. Saying so beats drawing an empty half-hour that only means the\n // feed forgot it.\n const oldest = oldestAt();\n if (oldest !== undefined && Date.now() - oldest < state.rangeMs * 0.9) {\n legend.appendChild(el(\"span\", null, `window holds ${rangeLabel(Date.now() - oldest)} ·`));\n }\n for (const [label, colour] of [\n [\"Served\", s1],\n [\"Mitigated — challenged, limited or delayed\", s2],\n [\"Denied\", crit],\n ] as Array<[string, string]>) {\n const item = el(\"span\");\n const swatch = el(\"span\", \"swatch\");\n swatch.style.background = colour;\n item.appendChild(swatch);\n item.appendChild(document.createTextNode(label));\n legend.appendChild(item);\n }\n if (changes.length > 0) {\n const item = el(\"span\");\n item.appendChild(el(\"span\", \"mark\"));\n item.appendChild(document.createTextNode(`${n(changes.length)} runtime change${changes.length === 1 ? \"\" : \"s\"} — hover a marker`));\n legend.appendChild(item);\n }\n}\n\n/** Cumulative bucket counts to per-bucket ones. Prometheus wants the first; a histogram needs the second. */\nfunction differences(cumulative: readonly number[]): number[] {\n const counts: number[] = [];\n for (let i = 0; i < cumulative.length; i++) counts.push((cumulative[i] ?? 0) - (i > 0 ? (cumulative[i - 1] ?? 0) : 0));\n return counts;\n}\n\n/**\n * The score distribution: how close ordinary traffic runs to the line.\n *\n * Two populations, and the panel now says which one it is drawing rather than leaving\n * the reader to assume. **Since start** comes from the same counters as the Prometheus\n * endpoint, so it covers the whole run and agrees with whatever your alerting says.\n * **This window** counts the few hundred requests the page is still holding, which is\n * the right answer to \"what is happening right now\" and the wrong answer to \"where\n * should the threshold be\" — and it was the only answer available before, sitting\n * beside panels counting since start under an identical grey subtitle.\n *\n * Proven requests are excluded from both rather than piled into the last bucket: their\n * score is 100 by definition and plays no part in any decision.\n */\nexport function drawScores(): void {\n const host = $(\"score-chart\");\n const svg = $(\"scores\");\n clear(svg);\n\n const metrics = state.snapshot?.metrics;\n const fromRun = state.scoreScope === \"run\" && metrics !== undefined;\n\n let buckets: number[];\n let scored: number;\n let proven: number;\n if (fromRun && metrics !== undefined) {\n buckets = differences(metrics.scores.buckets);\n scored = metrics.scores.count;\n proven = metrics.proven;\n } else {\n buckets = new Array<number>(SCORE_BOUNDS).fill(0);\n scored = 0;\n proven = 0;\n for (const { entry } of state.rows) {\n if (entry.bypass !== undefined) continue;\n if (entry.certain) {\n proven++;\n continue;\n }\n const index = Math.min(SCORE_BOUNDS - 1, Math.floor(entry.score / 10));\n buckets[index] = (buckets[index] ?? 0) + 1;\n scored++;\n }\n }\n\n const width = Math.max(280, host.clientWidth - 30);\n const height = 190;\n const padBottom = 30;\n const plot = height - padBottom;\n let max = 1;\n for (const value of buckets) if (value > max) max = value;\n\n svg.setAttribute(\"viewBox\", `0 0 ${width} ${height}`);\n svg.setAttribute(\"height\", String(height));\n\n const fill = css(\"--s1\");\n const muted = css(\"--muted\");\n const grid = css(\"--grid\");\n const crit = css(\"--crit\");\n svg.appendChild(svgEl(\"line\", { class: \"gridline\", x1: 0, x2: width, y1: plot, y2: plot, stroke: grid, \"stroke-width\": 1 }));\n\n const step = width / SCORE_BOUNDS;\n buckets.forEach((value, index) => {\n const barHeight = (value / max) * (plot - 8);\n // Capped and centred: ten buckets across a wide panel would otherwise be ten slabs,\n // and a histogram reads better as marks than as a wall.\n const barWidth = Math.max(2, Math.min(step - 6, 56));\n if (barHeight > 0) {\n svg.appendChild(svgEl(\"rect\", { x: index * step + (step - barWidth) / 2, y: plot - barHeight, width: barWidth, height: barHeight, fill, rx: 3 }));\n }\n svg.appendChild(svgText({ x: index * step + 1, y: height - 14, fill: muted, \"font-size\": 9.5 }, index * 10));\n });\n\n const threshold = state.snapshot?.policy.suspectThreshold ?? 60;\n const x = (threshold / 100) * width;\n svg.appendChild(svgEl(\"line\", { x1: x, x2: x, y1: 0, y2: plot, stroke: crit, \"stroke-width\": 2, \"stroke-dasharray\": \"4 3\" }));\n svg.appendChild(svgText({ x: Math.min(width - 92, x + 5), y: 11, fill: crit, \"font-size\": 10 }, `suspect at ${threshold}`));\n svg.appendChild(svgText({ x: 0, y: height - 2, fill: muted, \"font-size\": 10 }, \"score (probabilistic requests only)\"));\n\n const tip = $(\"score-tip\");\n host.onmousemove = (event: MouseEvent): void => {\n const box = svg.getBoundingClientRect();\n const index = Math.floor(((event.clientX - box.left) / box.width) * SCORE_BOUNDS);\n const value = buckets[index];\n if (value === undefined) {\n tip.style.opacity = \"0\";\n return;\n }\n clear(tip);\n tip.appendChild(el(\"div\", \"t\", `score ${index * 10}–${index * 10 + 9}`));\n tip.appendChild(tipRow(\"requests\", n(value)));\n tip.appendChild(tipRow(\"share\", pct(value, scored)));\n positionTip(tip, host, event.clientX, box.left);\n };\n host.onmouseleave = (): void => {\n tip.style.opacity = \"0\";\n };\n\n let over = 0;\n for (let bucket = 0; bucket < SCORE_BOUNDS; bucket++) if (bucket * 10 >= threshold) over += buckets[bucket] ?? 0;\n\n const legend = $(\"score-legend\");\n clear(legend);\n legend.appendChild(el(\"span\", null, `${n(scored)} scored · ${n(over)} at or over the threshold · ${n(proven)} proven, which carry no score`));\n\n $(\"score-alt\").textContent =\n `Distribution of probabilistic scores ${fromRun ? \"since start\" : \"in the retained window\"}, with the suspect threshold at ${threshold}. ` +\n `${n(scored)} scored requests, ${n(over)} at or over the threshold, ${n(proven)} proven and therefore unscored. ` +\n (scored === 0 ? \"Nothing scored yet.\" : `By ten-point band: ${buckets.map((value, index) => `${index * 10}–${index * 10 + 9}: ${n(value)}`).join(\", \")}.`);\n $(\"score-window\").textContent = fromRun ? \"since start\" : windowLabel(state.rows.length, oldestAt(), Date.now());\n if (state.scoreScope === \"run\" && metrics === undefined) {\n legend.appendChild(el(\"span\", null, \"· counters are off on this handler, so this is the retained window\"));\n }\n}\n\nexport function drawLatency(): void {\n const host = $(\"latency-chart\");\n const svg = $(\"latency\");\n clear(svg);\n const metrics = state.snapshot?.metrics;\n if (metrics === undefined || metrics.duration.count === 0) {\n $(\"latency-summary\").textContent = \"No assessments yet.\";\n $(\"latency-alt\").textContent = \"Assessment latency: no assessments yet.\";\n svg.setAttribute(\"viewBox\", \"0 0 100 40\");\n svg.setAttribute(\"height\", \"40\");\n svg.appendChild(svgText({ x: 0, y: 20, fill: css(\"--muted\"), \"font-size\": 11 }, \"No assessments yet.\"));\n return;\n }\n\n const cumulative = metrics.duration.buckets;\n const counts = differences(cumulative);\n\n const width = Math.max(280, host.clientWidth - 30);\n const height = 190;\n const padBottom = 30;\n const plot = height - padBottom;\n let max = 1;\n for (const value of counts) if (value > max) max = value;\n\n svg.setAttribute(\"viewBox\", `0 0 ${width} ${height}`);\n svg.setAttribute(\"height\", String(height));\n\n const fill = css(\"--s1\");\n const muted = css(\"--muted\");\n const grid = css(\"--grid\");\n svg.appendChild(svgEl(\"line\", { class: \"gridline\", x1: 0, x2: width, y1: plot, y2: plot, stroke: grid, \"stroke-width\": 1 }));\n\n const step = width / counts.length;\n counts.forEach((value, index) => {\n const barHeight = (value / max) * (plot - 6);\n if (barHeight > 0) {\n svg.appendChild(svgEl(\"rect\", { x: index * step + 1, y: plot - barHeight, width: Math.max(2, step - 3), height: barHeight, fill, rx: 3 }));\n }\n if (index % 2 === 0) {\n svg.appendChild(svgText({ x: index * step + 1, y: height - 14, fill: muted, \"font-size\": 9.5 }, index < LATENCY_BOUNDS.length ? String(LATENCY_BOUNDS[index]) : \"more\"));\n }\n });\n svg.appendChild(svgText({ x: 0, y: height - 2, fill: muted, \"font-size\": 10 }, \"milliseconds (upper bound of each bucket)\"));\n\n const tip = $(\"latency-tip\");\n host.onmousemove = (event: MouseEvent): void => {\n const box = svg.getBoundingClientRect();\n const index = Math.floor(((event.clientX - box.left) / box.width) * counts.length);\n const value = counts[index];\n if (value === undefined) {\n tip.style.opacity = \"0\";\n return;\n }\n clear(tip);\n tip.appendChild(el(\"div\", \"t\", index < LATENCY_BOUNDS.length ? `≤ ${LATENCY_BOUNDS[index]}ms` : \"over 100ms\"));\n tip.appendChild(tipRow(\"requests\", n(value)));\n tip.appendChild(tipRow(\"share\", pct(value, metrics.duration.count)));\n positionTip(tip, host, event.clientX, box.left);\n };\n host.onmouseleave = (): void => {\n tip.style.opacity = \"0\";\n };\n\n const mean = metrics.duration.totalMs / metrics.duration.count;\n const p95 = percentile(cumulative, metrics.duration.count, 0.95);\n $(\"latency-summary\").textContent = `Time spent in detection, per request · mean ${ms(mean)} · p95 ${ms(p95)} · max ${ms(metrics.duration.maxMs)}`;\n $(\"latency-alt\").textContent =\n `Assessment latency since start over ${n(metrics.duration.count)} requests: mean ${ms(mean)}, 95th percentile ${ms(p95)}, maximum ${ms(metrics.duration.maxMs)}. ` +\n `By bucket: ${counts.map((value, index) => `${index < LATENCY_BOUNDS.length ? `up to ${LATENCY_BOUNDS[index]}ms` : \"over 100ms\"}: ${n(value)}`).join(\", \")}.`;\n}\n\n/** Bucket-boundary percentile. Coarse by construction — it is a histogram, not a series. */\nfunction percentile(cumulative: readonly number[], count: number, fraction: number): number {\n const target = count * fraction;\n const last = LATENCY_BOUNDS[LATENCY_BOUNDS.length - 1] ?? 100;\n for (let i = 0; i < cumulative.length; i++) {\n if ((cumulative[i] ?? 0) >= target) return i < LATENCY_BOUNDS.length ? (LATENCY_BOUNDS[i] ?? last) : last;\n }\n return last;\n}\n","import { $, byId, clear, el } from \"./dom.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { actorActions, isConfirming } from \"./actions.js\";\nimport { app } from \"./app.js\";\nimport { clockStamp, n } from \"./format.js\";\nimport { getJson } from \"./api.js\";\nimport { setSearch, state } from \"./store.js\";\nimport { renderPager } from \"./pager.js\";\nimport type { ActorRow } from \"./types.js\";\n\n/**\n * The Actors screen.\n *\n * The feed is a ring of *requests* — five hundred of them, which on a busy origin is a\n * few seconds. The registry is a bounded map of *clients*, up to twenty thousand of\n * them, each carrying the rate series, path breadth and confirmation count that\n * `cadence`, `crawl-breadth` and `rate-anomaly` are reading. Until this screen existed\n * the page could only show you the actors that happened to appear in the last few\n * hundred requests, which is a different and much smaller question than \"who is hitting\n * me hardest right now\".\n *\n * It is fetched rather than streamed, and refreshed while it is the visible tab.\n * Streaming twenty thousand actors to every viewer to keep a sorted list of fifty\n * fresh would be a great deal of traffic to answer a question nobody asks continuously.\n */\n\ninterface ActorsBody {\n actors: ActorRow[];\n tracked: number;\n actionable: boolean;\n}\n\nlet timer: ReturnType<typeof setInterval> | undefined;\n\nexport async function loadActors(): Promise<void> {\n if (!SECTIONS.registry) return;\n try {\n const body = await getJson<ActorsBody>(`/api/actors?limit=${state.actorsPageSize}&offset=${state.actorsPage * state.actorsPageSize}`);\n state.actors = body.actors;\n state.actorsTracked = body.tracked;\n drawActors();\n } catch {\n /* the rest of the page is unaffected */\n }\n}\n\n/** Polls while the screen is showing, and stops the moment it is not. */\nexport function trackActors(): void {\n if (timer !== undefined) clearInterval(timer);\n timer = undefined;\n if (state.tab !== \"actors\") return;\n void loadActors();\n timer = setInterval(() => {\n if (state.tab !== \"actors\" || state.paused || isConfirming()) return;\n void loadActors();\n }, 4000);\n}\n\n/** Page sizes the Actors table offers. The endpoint will not serve more than 200 at once. */\nconst ACTORS_PAGE_SIZES = [25, 50, 100, 200] as const;\n\n/**\n * The pager, above the table and below it.\n *\n * Paged on the server by offset, because the registry holds far more clients than the\n * feed's ring holds requests — `maxActors` of them — and the point of this screen is the\n * population the feed cannot show. Without paging the dashboard could only ever see the\n * busiest handful.\n *\n * The end of the list is \"a short page came back\". There is no count of how many rank\n * below the current page that is cheaper than asking for it, and asking in order to grey\n * out a button is not worth a request.\n */\nfunction drawActorsPager(full: boolean): void {\n const page = state.actorsPage;\n const hidden = page === 0 && !full;\n const from = page * state.actorsPageSize + 1;\n const model = {\n page,\n from,\n to: from + state.actors.length - 1,\n total: state.actorsTracked,\n atStart: page === 0,\n atEnd: !full,\n go: (next: number): void => {\n state.actorsPage = Math.max(0, next);\n void loadActors();\n },\n size: {\n current: state.actorsPageSize,\n choices: ACTORS_PAGE_SIZES,\n set: (next: number): void => {\n state.actorsPageSize = next;\n state.actorsPage = 0;\n void loadActors();\n },\n },\n };\n for (const [id, withSize] of [\n [\"actors-pager-top\", true],\n [\"actors-pager\", false],\n ] as const) {\n const host = $(id);\n host.hidden = hidden;\n if (hidden) clear(host);\n else renderPager(host, model, { withSize });\n }\n}\n\nexport function drawActors(): void {\n if (!SECTIONS.registry) return;\n // A repaint replaces every button in the table, including a confirmation somebody is\n // halfway through. The list holds still while they decide.\n if (isConfirming()) return;\n const body = byId<HTMLTableSectionElement>(\"actor-rows\");\n clear(body);\n\n const actors = state.actors;\n $(\"actors-count\").textContent = `${n(actors.length)} shown · ${n(state.actorsTracked)} tracked`;\n drawActorsPager(actors.length === state.actorsPageSize);\n byId<HTMLElement>(\"actors-empty\").hidden = actors.length > 0;\n\n for (const actor of actors) {\n const row = el(\"tr\");\n row.appendChild(el(\"td\", \"who\", actor.key));\n row.appendChild(el(\"td\", \"num tnum\", n(actor.requests)));\n row.appendChild(el(\"td\", \"num tnum\", n(actor.recentRate)));\n row.appendChild(el(\"td\", \"num tnum\", n(actor.distinctPaths)));\n\n // The coefficient of variation of the gaps between requests. Near zero is a\n // metronome, which no person is; a dash is \"too few gaps to say\", which is not the\n // same as regular and must not look like it.\n const cadence = el(\"td\", \"num tnum\", actor.cadenceCv === undefined ? \"—\" : actor.cadenceCv.toFixed(2));\n if (actor.cadenceCv !== undefined && actor.cadenceCv < 0.15) cadence.className += \" warn-text\";\n row.appendChild(cadence);\n\n row.appendChild(el(\"td\", \"num tnum\", n(actor.priorConfirmations)));\n\n // Outstanding challenges. Amber past two, because one abandoned challenge is a\n // person having a moment and three is a client that does not answer.\n const unsolved = el(\"td\", \"num tnum\", n(actor.unsolvedChallenges));\n if (actor.unsolvedChallenges >= 3) unsolved.className += \" warn-text\";\n row.appendChild(unsolved);\n\n const stateCell = el(\"td\");\n const tags: string[] = [];\n if (actor.cleared) tags.push(\"cleared as human\");\n if (actor.distinctUserAgents > 1) tags.push(`${actor.distinctUserAgents} User-Agents`);\n tags.push(`first seen ${clockStamp(actor.firstSeen)}`);\n stateCell.appendChild(el(\"div\", \"tagline\", tags.join(\" · \")));\n row.appendChild(stateCell);\n\n const actions = el(\"td\", \"acts\");\n const inFeed = el(\"button\", null, \"In feed\");\n inFeed.title = \"Show this actor's requests in the live feed\";\n inFeed.addEventListener(\"click\", () => {\n // Reuses the feed's own search rather than a second mechanism, which means the\n // result is a shareable URL like every other view of the feed.\n setSearch(`actor:${actor.key}`);\n const search = byId<HTMLInputElement>(\"search\");\n search.value = state.search;\n app.showTab(\"live\");\n app.syncUrl();\n });\n actions.appendChild(inFeed);\n for (const button of actorActions(actor.key, () => void loadActors())) actions.appendChild(button);\n row.appendChild(actions);\n\n body.appendChild(row);\n }\n}\n","import { $, byId, clear, el } from \"./dom.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { ms } from \"./format.js\";\nimport { outcome, verdictBadge } from \"./outcome.js\";\nimport { postJson } from \"./api.js\";\nimport { toast } from \"./app.js\";\nimport type { DashboardEntry } from \"./types.js\";\n\n/**\n * The request tester.\n *\n * The page's answer to the question that arrives by ticket rather than by traffic: *why\n * is this client being challenged?* Until now the only way to find out was to wait for\n * them to come back and hope you were watching, or to reproduce their request by hand\n * against the live site — which puts it in the feed, in the counters and in that\n * actor's history, so investigating a complaint changed the thing being investigated.\n *\n * This runs a **dry run** on the server: every detector runs and the decision is real,\n * including the guard, but nothing is recorded anywhere. The one thing it cannot know\n * is history — it gets an actor with no past — so what it answers precisely is \"what\n * would this look like as a first request\", which is what a ticket is asking.\n */\ninterface TestResult {\n entry: DashboardEntry;\n reason: string;\n assumed: string[];\n error?: string;\n}\n\nexport function initTester(): void {\n if (!SECTIONS.tester) return;\n $(\"test-run\").addEventListener(\"click\", () => void run());\n byId<HTMLTextAreaElement>(\"test-input\").addEventListener(\"keydown\", (event) => {\n // Enter is a newline in a textarea, so the shortcut is the one every console uses\n // for \"send this\".\n if (event.key === \"Enter\" && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n void run();\n }\n });\n}\n\nasync function run(): Promise<void> {\n const raw = byId<HTMLTextAreaElement>(\"test-input\").value;\n if (raw.trim() === \"\") {\n toast(\"warn\", \"Nothing to assess\", \"Paste a User-Agent, a curl command, or a header block.\");\n return;\n }\n const result = await postJson<TestResult>(\"/api/test\", {\n raw,\n ip: byId<HTMLInputElement>(\"test-ip\").value.trim(),\n url: byId<HTMLInputElement>(\"test-url\").value.trim(),\n });\n\n const box = $(\"test-result\");\n box.hidden = false;\n clear(box);\n\n if (!result.ok) {\n box.className = \"result bad\";\n box.appendChild(el(\"div\", null, result.error ?? \"The server could not read that.\"));\n return;\n }\n\n const { entry, reason, assumed } = result.data;\n const out = outcome(entry);\n box.className = `result ${out === \"deny\" ? \"bad\" : out === \"mitigate\" ? \"warn\" : \"ok\"}`;\n\n const head = el(\"div\");\n const [badgeClass, badgeLabel] = verdictBadge(entry);\n head.appendChild(el(\"span\", `badge ${badgeClass}`, badgeLabel));\n head.appendChild(document.createTextNode(\" \"));\n head.appendChild(el(\"b\", null, entry.action ?? \"no decision\"));\n if (entry.rule !== undefined) head.appendChild(el(\"span\", \"ev-meta\", ` via ${entry.rule}`));\n box.appendChild(head);\n\n box.appendChild(el(\"div\", \"ev-meta\", `${entry.certain ? \"proven\" : `score ${entry.score}`} · assessed in ${ms(entry.durationMs)}`));\n if (entry.downgradedFrom !== undefined) {\n box.appendChild(el(\"div\", \"guard\", `The guard stopped ${entry.downgradedFrom} here.`));\n if (entry.downgradeReason !== undefined) box.appendChild(el(\"div\", \"basis\", entry.downgradeReason));\n }\n\n if (entry.evidence.length === 0) {\n box.appendChild(el(\"div\", \"ev-meta\", entry.bypass !== undefined ? `Detection was skipped: ${entry.bypass}.` : \"No detector produced any evidence.\"));\n } else {\n const list = el(\"div\", \"ev\");\n for (const item of entry.evidence) {\n const row = el(\"div\", `ev-item${item.direction === \"human\" ? \" human\" : \"\"}`);\n row.appendChild(el(\"div\", `tier t-${item.certainty}`, item.certainty));\n const detail = el(\"div\");\n detail.appendChild(el(\"div\", null, item.summary));\n detail.appendChild(el(\"div\", \"ev-meta\", `${item.detector} · points to ${item.direction}`));\n if (item.deterministicBasis !== undefined) detail.appendChild(el(\"div\", \"basis\", item.deterministicBasis));\n row.appendChild(detail);\n list.appendChild(row);\n }\n box.appendChild(list);\n }\n\n box.appendChild(el(\"div\", \"ev-meta\", reason));\n\n // Said every time, not only when it matters. A tester that silently invents a client\n // address is a tester whose answer about `ip-intelligence` cannot be trusted, and the\n // reader has no way to know which run was which.\n const notes = [...assumed, \"no history: this is assessed as a first request, so cadence and crawl breadth have nothing to read\"];\n box.appendChild(el(\"div\", \"assumed\", `Assumed — ${notes.join(\"; \")}.`));\n}\n","import { $, byId, el, eventTarget, isEmbedded, rootNode, themeElement } from \"./dom.js\";\nimport { API, BOOT, SECTIONS } from \"./boot.js\";\nimport { app, toast } from \"./app.js\";\nimport { clearFeed, setSearch, state } from \"./store.js\";\nimport { drawActor, initActor } from \"./actor.js\";\nimport { connectStream, loadInitialSnapshot } from \"./stream.js\";\nimport { drawAudit, drawChanges, drawChips, drawLivePanels, drawNoticeBadge, drawNotices, drawPeers, drawStatsPanels, drawTiles, updateWindowLabels } from \"./panels.js\";\nimport { drawFeed, initFeed, reflectFilterButtons, resetFeedCache } from \"./feed.js\";\nimport { drawLatency, drawScores, drawTraffic } from \"./charts.js\";\nimport { drawActors, trackActors } from \"./registry.js\";\nimport { drawPolicyTab, initPolicy, loadPolicy } from \"./policy.js\";\nimport { loadRanges } from \"./ranges.js\";\nimport { initTester } from \"./tester.js\";\nimport type { FilterName } from \"./query.js\";\nimport type { TabName } from \"./types.js\";\n\n/** The views this listener has, in tab-strip order. Sections decide which exist. */\nconst TABS: Array<[id: string, name: TabName, enabled: boolean]> = [\n [\"tab-live\", \"live\", SECTIONS.feed],\n [\"tab-actors\", \"actors\", SECTIONS.registry],\n [\"tab-stats\", \"stats\", SECTIONS.statistics],\n [\"tab-policy\", \"policy\", SECTIONS.policy],\n];\n\nconst available = TABS.filter(([, , enabled]) => enabled);\nconst FIRST: TabName = available[0]?.[1] ?? \"live\";\n\nfunction tabIndexOf(name: TabName): number {\n const at = available.findIndex(([, tab]) => tab === name);\n return at === -1 ? 0 : at;\n}\n\nfunction isTab(value: string): value is TabName {\n return available.some(([, name]) => name === value);\n}\n\n// ---- header ----------------------------------------------------------------\n\n/**\n * The skip link, which is a plain fragment anchor and therefore inert inside a shadow\n * root: fragment navigation does not cross the boundary, so the one affordance that lets\n * a keyboard past the header did nothing at all when embedded. Given the same behaviour\n * by hand.\n */\nfunction initSkipLink(): void {\n if (!isEmbedded()) return;\n const skip = rootNode().querySelector<HTMLAnchorElement>(\"a.skip\");\n if (skip === null) return;\n skip.addEventListener(\"click\", (event) => {\n event.preventDefault();\n const target = rootNode().querySelector<HTMLElement>(`#view-${state.tab}`) ?? rootNode().querySelector<HTMLElement>(\"#view-live\");\n if (target === null) return;\n target.tabIndex = -1;\n target.focus();\n target.scrollIntoView({ block: \"start\" });\n });\n}\n\nfunction initHeader(): void {\n const box = $(\"links\");\n for (const link of BOOT.links) {\n const anchor = el(\"a\", \"linkbtn\", link.label);\n anchor.href = link.href;\n anchor.rel = \"noreferrer noopener\";\n box.appendChild(anchor);\n }\n\n let stored: string | null = null;\n try {\n stored = localStorage.getItem(\"bothandler-dashboard-theme\");\n } catch {\n stored = null;\n }\n if (stored === \"dark\" || stored === \"light\") themeElement().setAttribute(\"data-theme\", stored);\n $(\"theme\").addEventListener(\"click\", () => {\n let current = themeElement().getAttribute(\"data-theme\");\n if (current === null) current = matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n const next = current === \"dark\" ? \"light\" : \"dark\";\n themeElement().setAttribute(\"data-theme\", next);\n try {\n localStorage.setItem(\"bothandler-dashboard-theme\", next);\n } catch {\n /* private mode */\n }\n // The charts read their colours from the CSS custom properties, so they have to be\n // redrawn rather than restyled.\n drawNow();\n });\n\n const pause = byId<HTMLButtonElement>(\"pause\");\n pause.hidden = !SECTIONS.feed;\n pause.addEventListener(\"click\", () => {\n state.paused = !state.paused;\n pause.setAttribute(\"aria-pressed\", String(state.paused));\n pause.textContent = state.paused ? `Resume${state.bufferedWhilePaused > 0 ? ` (${state.bufferedWhilePaused})` : \"\"}` : \"Pause\";\n if (!state.paused) {\n state.bufferedWhilePaused = 0;\n drawNow();\n }\n });\n\n if (BOOT.allowReset) {\n const reset = byId<HTMLButtonElement>(\"reset\");\n reset.hidden = false;\n reset.addEventListener(\"click\", () => {\n reset.disabled = true;\n void fetch(`${API}/api/reset`, { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body: \"{}\" })\n .then(async (response) => {\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n toast(\"bad\", \"Reset refused\", body.error ?? String(response.status));\n return;\n }\n clearFeed();\n resetFeedCache();\n drawNow();\n })\n .catch(() => {\n /* the stream will resync */\n })\n .finally(() => {\n reset.disabled = false;\n });\n });\n }\n\n /**\n * The sticky column headers used to sit at a hard-coded 54px — the height of the top\n * row alone — so the tab strip covered them the moment the feed scrolled. The header\n * is not a fixed height: the chip row wraps on a narrow window and grows. Measure it,\n * and let the CSS read the measurement.\n */\n const header = rootNode().querySelector(\"header\");\n if (header !== null) {\n const apply = (): void => {\n themeElement().style.setProperty(\"--header-h\", `${header.getBoundingClientRect().height}px`);\n };\n apply();\n if (typeof ResizeObserver === \"function\") new ResizeObserver(apply).observe(header);\n else addEventListener(\"resize\", apply);\n }\n}\n\n// ---- navigation -------------------------------------------------------------\n\n/**\n * The view, the filter and the search live in the URL.\n *\n * The view already did. The other two are what make a screen shareable — \"look at the\n * guard stops on /export\" is a link now rather than a set of instructions — and what\n * survives the refresh that is everybody's reflex when a live feed looks stuck.\n * `replaceState` for the filter and the search, because a back button that walks\n * backwards through every keystroke is not a back button.\n */\nfunction syncUrl(replace = true): void {\n // Embedded, the URL belongs to the page around us. Writing a tab into it rewrites\n // somebody else's address bar and puts a history entry between them and wherever they\n // were going.\n if (isEmbedded()) return;\n const params = new URLSearchParams();\n if (state.filter !== \"all\") params.set(\"f\", state.filter);\n if (state.search !== \"\") params.set(\"q\", state.search);\n const query = params.toString();\n const hash = `#${state.tab}${query === \"\" ? \"\" : `?${query}`}`;\n if (location.hash === hash) return;\n history[replace ? \"replaceState\" : \"pushState\"]({ tab: state.tab }, \"\", hash);\n}\n\nfunction readUrl(): { tab: TabName; filter: FilterName; search: string } {\n // And it is not ours to read either: a host page using hash routing would otherwise\n // decide which tab this opens on.\n const raw = isEmbedded() ? \"\" : location.hash.slice(1);\n const split = raw.indexOf(\"?\");\n const name = split === -1 ? raw : raw.slice(0, split);\n const params = new URLSearchParams(split === -1 ? \"\" : raw.slice(split + 1));\n const filter = params.get(\"f\") ?? \"all\";\n return {\n tab: isTab(name) ? name : FIRST,\n filter: filter as FilterName,\n search: params.get(\"q\") ?? \"\",\n };\n}\n\nfunction showTab(name: TabName, options: { focus?: boolean; replace?: boolean; push?: boolean } = {}): void {\n const target = isTab(name) ? name : FIRST;\n state.tab = target;\n for (const [id, tab, enabled] of TABS) {\n const selected = tab === target;\n const node = $(id);\n node.hidden = !enabled;\n node.setAttribute(\"aria-selected\", String(selected));\n // Roving tabindex: the selected tab is the strip's single stop in the tab order.\n node.tabIndex = selected ? 0 : -1;\n $(`view-${tab}`).hidden = !selected || !enabled;\n }\n if (options.focus === true) $(available[tabIndexOf(target)]?.[0] ?? \"tab-live\").focus();\n if (target === \"policy\" && state.policy === undefined) {\n void loadPolicy();\n void loadRanges();\n }\n // The Actors screen is fetched rather than streamed, and polled only while it is the\n // screen somebody is looking at.\n trackActors();\n if (options.push !== false) syncUrl(options.replace !== false);\n drawNow();\n}\n\nfunction initTabs(): void {\n available.forEach(([id, name], index) => {\n const tab = $(id);\n tab.addEventListener(\"click\", () => showTab(name, { replace: false }));\n tab.addEventListener(\"keydown\", (event) => {\n let next = -1;\n if (event.key === \"ArrowRight\") next = (index + 1) % available.length;\n else if (event.key === \"ArrowLeft\") next = (index - 1 + available.length) % available.length;\n else if (event.key === \"Home\") next = 0;\n else if (event.key === \"End\") next = available.length - 1;\n if (next === -1) return;\n event.preventDefault();\n showTab(available[next]?.[1] ?? FIRST, { focus: true, replace: false });\n });\n });\n\n if (isEmbedded()) return;\n addEventListener(\"popstate\", () => {\n const url = readUrl();\n state.filter = url.filter;\n setSearch(url.search);\n reflectFilterButtons();\n showTab(url.tab, { push: false });\n });\n}\n\n// ---- keyboard ---------------------------------------------------------------\n\nfunction initKeyboard(): void {\n // Scoped to this dashboard when embedded: a digit pressed while the host page has\n // focus switched a tab in here, which is somebody else's keyboard being taken.\n eventTarget().addEventListener(\"keydown\", ((event: KeyboardEvent) => {\n const target = event.target as HTMLElement | null;\n const typing = target !== null && (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\" || target.tagName === \"SELECT\");\n\n if (event.key === \"Escape\") {\n if (typing && target?.id === \"search\" && (target as HTMLInputElement).value !== \"\") {\n (target as HTMLInputElement).value = \"\";\n setSearch(\"\");\n syncUrl();\n drawNow();\n return;\n }\n if (typing) {\n target?.blur();\n return;\n }\n // Otherwise close whatever is open, innermost first.\n if (state.actor !== undefined) {\n state.actor = undefined;\n drawNow();\n return;\n }\n if (state.open.size > 0) {\n state.open.clear();\n drawNow();\n }\n return;\n }\n\n if (typing || event.metaKey || event.ctrlKey || event.altKey) return;\n\n if (event.key === \"/\" && SECTIONS.feed) {\n event.preventDefault();\n showTab(\"live\");\n const search = byId<HTMLInputElement>(\"search\");\n search.focus();\n search.select();\n return;\n }\n // 1-2-3-4 for the views, the way every tabbed console does it. They index the tabs\n // this listener actually has, so a dashboard with two of them has two shortcuts.\n const digit = [\"1\", \"2\", \"3\", \"4\"].indexOf(event.key);\n if (digit !== -1 && digit < available.length) {\n event.preventDefault();\n showTab(available[digit]?.[1] ?? FIRST, { focus: true, replace: false });\n }\n }) as EventListener);\n}\n\n// ---- the two range controls --------------------------------------------------\n\nfunction initRanges(): void {\n const ranges: Array<[string, number]> = [\n [\"1m\", 60_000],\n [\"5m\", 300_000],\n [\"15m\", 900_000],\n [\"1h\", 3_600_000],\n ];\n const host = $(\"traffic-range\");\n for (const [label, value] of ranges) {\n const button = el(\"button\", null, label);\n button.setAttribute(\"aria-pressed\", String(value === state.rangeMs));\n button.addEventListener(\"click\", () => {\n state.rangeMs = value;\n for (const other of Array.from(host.children)) other.setAttribute(\"aria-pressed\", \"false\");\n button.setAttribute(\"aria-pressed\", \"true\");\n drawTraffic();\n });\n host.appendChild(button);\n }\n\n const scopes: Array<[string, \"run\" | \"window\"]> = [\n [\"since start\", \"run\"],\n [\"this window\", \"window\"],\n ];\n const scopeHost = $(\"score-scope\");\n for (const [label, value] of scopes) {\n const button = el(\"button\", null, label);\n button.setAttribute(\"aria-pressed\", String(value === state.scoreScope));\n button.addEventListener(\"click\", () => {\n state.scoreScope = value;\n for (const other of Array.from(scopeHost.children)) other.setAttribute(\"aria-pressed\", \"false\");\n button.setAttribute(\"aria-pressed\", \"true\");\n drawScores();\n });\n scopeHost.appendChild(button);\n }\n}\n\n// ---- draw --------------------------------------------------------------------\n\nlet pending = false;\n\nfunction schedule(): void {\n if (state.paused || pending) return;\n pending = true;\n requestAnimationFrame(() => {\n pending = false;\n draw();\n });\n}\n\n/**\n * Draws the view that is on screen, and nothing else.\n *\n * It used to draw all three. Every incoming request rebuilt eight statistics bar\n * panels into a hidden tab, on top of rebuilding three hundred feed rows — which is\n * work nobody could see, done at the frame rate, on the one screen where the work that\n * can be seen matters most.\n */\nfunction draw(): void {\n drawChips();\n drawTiles();\n drawNoticeBadge();\n updateWindowLabels();\n\n if (state.tab === \"live\") {\n drawActor();\n drawFeed();\n drawLivePanels();\n } else if (state.tab === \"actors\") {\n drawActors();\n } else if (state.tab === \"stats\") {\n drawTraffic();\n drawLatency();\n drawScores();\n drawStatsPanels();\n if (SECTIONS.audit) drawAudit();\n } else {\n drawPolicyTab();\n if (SECTIONS.notices) drawNotices();\n if (SECTIONS.changes) drawChanges();\n }\n}\n\nfunction drawNow(): void {\n draw();\n}\n\n// ---- start -------------------------------------------------------------------\n\nfunction applySections(): void {\n // A section that is off is removed from the document rather than hidden, so nothing\n // can tab into it and no panel is left waiting for data that will never arrive.\n const gated: Array<[string, boolean]> = [\n [\"tiles\", SECTIONS.statistics],\n [\"tester-panel\", SECTIONS.tester],\n [\"ranges-panel\", SECTIONS.ranges],\n [\"changes-panel\", SECTIONS.changes],\n [\"actor-panel\", SECTIONS.actors],\n [\"live-actors-panel\", SECTIONS.actors],\n [\"audit-panel\", SECTIONS.audit],\n [\"audit-checks-panel\", SECTIONS.audit],\n [\"notices-panel\", SECTIONS.notices],\n [\"guard-panel\", SECTIONS.guard],\n [\"robots-panel\", SECTIONS.robots],\n [\"identities-panel\", SECTIONS.actors],\n [\"evidence-legend\", SECTIONS.evidence],\n ];\n for (const [id, enabled] of gated) {\n const node = rootNode().querySelector<HTMLElement>(`#${id}`);\n if (node !== null && !enabled) node.remove();\n }\n}\n\nfunction start(): void {\n app.draw = schedule;\n app.drawNow = drawNow;\n app.showTab = showTab;\n app.syncUrl = () => syncUrl();\n\n applySections();\n initHeader();\n drawPeers();\n initTabs();\n initSkipLink();\n initKeyboard();\n initRanges();\n if (SECTIONS.feed) initFeed();\n initActor();\n initTester();\n initPolicy();\n\n let resizeTimer: ReturnType<typeof setTimeout> | undefined;\n addEventListener(\"resize\", () => {\n if (resizeTimer !== undefined) clearTimeout(resizeTimer);\n resizeTimer = setTimeout(() => {\n if (state.tab === \"stats\") {\n drawTraffic();\n drawLatency();\n drawScores();\n }\n }, 120);\n });\n\n // Open on whatever view the URL names, so a link to #policy lands there and a refresh\n // keeps your place — filter and search included. replace:true so the first entry does\n // not add a step the back button has to walk through on the way out.\n const url = readUrl();\n state.filter = url.filter;\n setSearch(url.search);\n if (SECTIONS.feed) reflectFilterButtons();\n showTab(url.tab, { replace: true });\n\n suspendScrollAnchoring();\n // Settled when the snapshot that draws the counters has actually been rendered, rather\n // than a couple of frames after start-up. The counters are the tallest thing script puts\n // above the feed and they wait on a round trip, so a frame count was measuring the wrong\n // thing and the page still opened scrolled.\n void loadInitialSnapshot().finally(settleScrollAnchoring);\n connectStream();\n}\n\n/**\n * Lets the page finish drawing itself before the browser starts defending the scroll\n * position.\n *\n * The counters, the filter chips and the pager are all built by script after the document\n * has laid out, which inserts a few hundred pixels above the feed. Scroll anchoring reads\n * that as \"content appeared above what you were looking at\" and scrolls down by the same\n * amount to compensate — so on a window narrow enough for the counter row to wrap, the\n * dashboard opened with its own counters already off the top of the screen.\n *\n * Off for the first render, then back on, rather than off for good: the feed puts new\n * requests at the *top*, and anchoring is what keeps somebody's place while they read a\n * list that is growing above them.\n */\nfunction htmlElement(): HTMLElement | undefined {\n const root = rootNode();\n return root instanceof Document ? root.documentElement : undefined;\n}\n\nfunction suspendScrollAnchoring(): void {\n htmlElement()?.classList.add(\"settling\");\n}\n\nfunction settleScrollAnchoring(): void {\n const html = htmlElement();\n if (html === undefined) return;\n // Two frames after the snapshot: its own render, and the layout that follows. A third\n // was measured and changed nothing. What remains is about fifteen pixels on a narrow\n // window — the counters are fully clear of the header, which is what mattered.\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n html.classList.remove(\"settling\");\n });\n });\n}\n\nstart();\n","/**\n * `<bot-dashboard>` — the operator dashboard as an element you drop into your own page.\n *\n * ```html\n * <bot-dashboard src=\"/_bots\"></bot-dashboard>\n * <script type=\"module\">\n * import { defineBotDashboard } from \"@osqd/bothandlerjs/element\";\n * defineBotDashboard();\n * </script>\n * ```\n *\n * The data still comes from a mounted handler — `createDashboardHandler(botHandler, {\n * basePath: \"/_bots\" })` — because there is nowhere else for it to come from. What the\n * element removes is having to build, style and route a page around it.\n *\n * ## What running it in your page costs you\n *\n * This renders into a shadow root inside your document, which is what makes it sit in\n * your layout rather than in a frame. The shadow root is a styling boundary and **not a\n * security boundary**: any script that can run on the host page can reach into it, read\n * every visitor address and verdict on screen, and call the dashboard's API with your\n * credentials. On the standalone page an injected script in your application could do\n * none of that.\n *\n * So mount it on a page that is already behind your admin authentication, and treat an\n * XSS on that page as equivalent to handing over the dashboard. If you would rather have\n * the isolation than the layout, serve the standalone page instead — it is the same\n * dashboard, and `createDashboardHandler` already returns it.\n */\n\nimport { cssEscape } from \"../dashboard/client/css.js\";\nimport { cellText, explainStatus, parseMount, resolveSections, shapeOf } from \"./config.js\";\nimport type { BotDashboardConfig, BotDashboardPanel, BotDashboardRows, BotDashboardTab, BotDashboardTabId, BotDashboardTheme } from \"./config.js\";\nimport { DASHBOARD_CSS, DASHBOARD_MARKUP } from \"../dashboard/page.js\";\n\n/** One screen of the dashboard. */\nexport type { BotDashboardConfig, BotDashboardPanel, BotDashboardRows, BotDashboardTab, BotDashboardTabId, BotDashboardTheme };\n\n/**\n * Most rows a panel may put on the page.\n *\n * A panel is a summary, and a source that returns everything it has should not be able to\n * lock up somebody's admin page while fifty thousand rows are laid out — which is exactly\n * what an unbounded version did when asked. Anything past this is dropped and said so, so\n * the number on screen is never quietly wrong.\n */\nconst MAX_PANEL_ROWS = 200;\n\nconst TAB_ELEMENT: Record<BotDashboardTabId, string> = {\n live: \"tab-live\",\n actors: \"tab-actors\",\n stats: \"tab-stats\",\n policy: \"tab-policy\",\n};\n\n\nconst EXTRA_CSS = `\n/* What the body element carries on the standalone page.\n A shadow root has no body, so without this nothing sets the base colour, background or\n type: every element inherited the *host page's* colour instead. On a white page that\n passed for correct; in dark mode it was near-black text on a near-black surface, which\n axe reported as a serious contrast failure and which is exactly the kind of thing this\n library is not supposed to ship. */\n:host {\n display: block;\n background: var(--page);\n color: var(--ink);\n font: 14px/1.55 system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n -webkit-font-smoothing: antialiased;\n}\n:host([hidden]) { display: none; }\n/* The page's own chrome assumes it owns the viewport. Inside somebody else's layout the\n element is the viewport, so the sticky header sticks to the element and the body\n metrics come from the host box. */\n:host { position: relative; }\n:host header { position: sticky; top: 0; }\n.bd-extra { display: grid; gap: 10px; padding: 14px 15px; }\n.bd-notice { font: 14px/1.5 system-ui, sans-serif; padding: 14px; margin: 0; color: var(--crit-text, #8a1c1c); }\n.bd-extra .row { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }\n.bd-extra .row b { font-weight: 600; font-variant-numeric: tabular-nums; }\n.bd-extra .row span { color: var(--muted); font-size: 12px; }\n:host([data-density=\"compact\"]) tbody td,\n:host([data-density=\"compact\"]) thead th { padding-top: 5px; padding-bottom: 5px; }\n:host([data-density=\"compact\"]) .panel > h2 { padding-top: 8px; padding-bottom: 8px; }\n`;\n\n/**\n * The dashboard's rendered subtree, kept across mounts.\n *\n * An SPA route unmounts and remounts the element, and the first version refused the second\n * mount outright — \"a bot dashboard is already running on this page\" — which made the\n * element unusable in React, Vue or anything else with a router. The client is a module\n * graph that evaluates once and holds its own state, so it cannot simply be started again.\n *\n * Keeping the subtree and re-parenting it into the new shadow root solves both halves: the\n * client's element references stay valid because they are the same nodes, and the feed\n * history survives the navigation instead of starting empty. What changes on a remount is\n * the host element the theme hangs off, and the stylesheet, which belongs to the shadow\n * root rather than to the subtree.\n */\nlet container: HTMLElement | undefined;\nlet owner: BotDashboardElement | undefined;\n/**\n * The theme most recently applied, remembered across mounts.\n *\n * A router builds a *new* element on the way back to a route, and that element carries no\n * configuration until the framework sets it — which for an attribute-driven or set-once\n * integration is never. Without this the dashboard came back unthemed: the tokens had been\n * set as inline properties on the previous host, which is gone.\n */\nlet lastTheme: BotDashboardTheme | undefined;\n\n/**\n * Registers `<bot-dashboard>`. Safe to call more than once.\n *\n * @param name - element name, if `bot-dashboard` is taken.\n */\nexport function defineBotDashboard(name = \"bot-dashboard\"): void {\n if (typeof customElements === \"undefined\") return;\n const taken = customElements.get(name);\n if (taken !== undefined) {\n // Calling this twice is ordinary and stays silent — it is the documented way to be\n // safe about ordering. A name held by *something else* is a different situation\n // entirely: this call does nothing, every `<bot-dashboard>` on the page is the other\n // library's element, and nothing about the resulting blank space says so.\n if (taken !== BotDashboardElement && !(taken.prototype instanceof BotDashboardElement) && !clashed.has(name)) {\n clashed.add(name);\n console.warn(`bot-dashboard: <${name}> is already registered on this page by something else, so this did nothing and your <${name}> elements are not this dashboard. Register it under a name of your own instead: defineBotDashboard(\"ops-dashboard\").`);\n }\n return;\n }\n // A constructor may be registered once per registry, so a second name cannot reuse this\n // class — `customElements.define` throws NotSupportedError, uncaught, from inside a\n // function whose whole job is to register a name. A fresh subclass per name is what the\n // platform wants, and it costs nothing: the behaviour is inherited whole.\n //\n // Only one dashboard runs at a time whatever it is called; that is enforced separately,\n // in `boot`.\n customElements.define(name, registered ? class extends BotDashboardElement {} : BotDashboardElement);\n registered = true;\n}\n\n/** Whether the base class has been handed to the registry yet. See {@link defineBotDashboard}. */\nlet registered = false;\n\n/** Names already reported as taken, so calling `defineBotDashboard` twice says it once. */\nconst clashed = new Set<string>();\n\n/**\n * `HTMLElement`, or a stand-in where there is no DOM.\n *\n * `class X extends HTMLElement` is evaluated when the module is *loaded*, not when the\n * element is used — so on a server, where there is no `HTMLElement`, merely importing this\n * file threw `ReferenceError` before a line of anyone's code ran. That is not an exotic\n * case: it is what every framework with server rendering does with a top-level\n * `import { defineBotDashboard } from \"@osqd/bothandlerjs/element\"`, which is exactly the\n * line the documentation tells people to write.\n *\n * `defineBotDashboard` already declines to do anything without a `customElements`\n * registry, so the intent was there; the class declaration simply ran first. With this the\n * module imports anywhere, and does nothing at all until it is in a browser.\n */\nconst ElementBase: typeof HTMLElement = typeof HTMLElement === \"undefined\" ? (class {} as unknown as typeof HTMLElement) : HTMLElement;\n\nexport class BotDashboardElement extends ElementBase {\n private settings: BotDashboardConfig = {};\n private booted = false;\n /**\n * Booted *and* finished drawing. Distinct from `booted`, which is set on the way in:\n * `connectedCallback` replays a config assigned before the upgrade through the setter,\n * and at that moment `booted` is already true — so a warning keyed on it fired on a\n * perfectly ordinary mount, complaining that the config had changed when what had\n * happened was that it had arrived.\n */\n private rendered = false;\n\n /**\n * Set before the element is attached, or pass the same things as attributes.\n *\n * An accessor rather than a field, and the difference is load-bearing. A page that\n * writes `el.config = {...}` before the element is defined puts an *own property* on\n * the instance, and a class field initialiser then runs at upgrade and overwrites it —\n * so the configuration silently disappears and the dashboard renders its defaults. The\n * accessor plus the upgrade dance in `connectedCallback` is the documented way round\n * it, and the order it protects (write the config, then define the element) is the\n * natural one to write.\n */\n get config(): BotDashboardConfig {\n return this.settings;\n }\n\n set config(value: BotDashboardConfig) {\n const before = this.rendered && this.isConnected ? shapeOf(this.settings) : undefined;\n this.settings = value ?? {};\n // Applied immediately when the element is already running, so a framework that sets\n // its props after mount — which most of them do on a re-render — changes the theme\n // rather than being quietly ignored.\n if (this.booted && this.isConnected) {\n this.applyTheme();\n // `tabs` and `panels` are settled at boot, and until now changing them did nothing\n // at all — no effect and no word about it, which is the failure `src` a few lines\n // down already refuses to make. Compared by shape rather than by identity, because\n // a framework hands over a freshly built object on every render and warning about\n // that would be noise nobody could act on.\n if (before !== undefined && shapeOf(this.settings) !== before) {\n console.warn(\"bot-dashboard: `tabs` and `panels` are read once, when the element first mounts. Changing them afterwards has no effect — remount the element instead. (`theme` does update live.)\");\n }\n }\n }\n private timers: number[] = [];\n /** Custom properties this element put on itself, so it can take them off again. */\n private appliedTokens = new Set<string>();\n private warned = new Set<string>();\n\n static get observedAttributes(): string[] {\n return [\"src\", \"scheme\", \"density\"];\n }\n\n connectedCallback(): void {\n if (this.booted) return;\n this.booted = true;\n // Reclaim anything assigned before the upgrade: delete the own property so the write\n // below reaches the accessor rather than sitting in front of it for ever.\n if (Object.prototype.hasOwnProperty.call(this, \"config\")) {\n const preset = (this as unknown as { config: BotDashboardConfig }).config;\n // The own property has to be removed, not emptied: assigning undefined leaves it in\n // place, shadowing the accessor on the prototype for the life of the element, and\n // the configuration never arrives. `Reflect.deleteProperty` rather than `delete`\n // says the same thing without tripping a lint rule aimed at hot loops; this runs\n // once per element.\n Reflect.deleteProperty(this, \"config\");\n this.config = preset;\n }\n void this.boot();\n }\n\n disconnectedCallback(): void {\n for (const timer of this.timers) clearInterval(timer);\n this.timers = [];\n // The subtree stays alive and detached, ready to be re-parented. Releasing ownership\n // is what lets a later element — the same one after a route change, or a different\n // one — pick it up.\n if (owner === this) owner = undefined;\n // And the stream goes with it. Left open it holds a server connection and keeps\n // drawing into a tree nobody can see, for as long as the page lives. Reconnecting\n // resumes from the last id seen, so the cost of a route change is a reconnect.\n void import(\"../dashboard/client/stream.js\").then((stream) => {\n if (!this.isConnected) stream.suspendStream();\n });\n }\n\n attributeChangedCallback(attribute: string, previous: string | null, value: string | null): void {\n // Through the same two checks as the config path, so an attribute cannot set a value\n // the config would have refused.\n if (attribute === \"scheme\") this.applyScheme(value);\n if (attribute === \"density\") this.applyDensity(value);\n // `src` decides where the client fetches from, and the client reads it once as it\n // loads. Changing it later does nothing, and doing nothing quietly is how somebody\n // spends an afternoon on it.\n if (attribute === \"src\" && previous !== null && previous !== value && this.booted) {\n console.warn(\"bot-dashboard: `src` is read once, when the element first mounts. Changing it afterwards has no effect — replace the element instead.\");\n }\n }\n\n /**\n * Give up on this attempt without giving up on the element.\n *\n * Every way a boot can end early — it lost the one-at-a-time race, its `src` is\n * cross-origin, its handler did not answer, it was unmounted mid-flight — is about this\n * moment rather than about this element, and all four are things a remount can fix: the\n * other dashboard has gone, the attribute was corrected, the server finished starting.\n * Releasing ownership without clearing `booted` was the bug: `connectedCallback` returns\n * early on a latched `booted`, so the element stayed dead for the life of the instance\n * and a router that remounts it drew a stale error over a handler that now works.\n */\n private standDown(): void {\n if (owner === this) owner = undefined;\n this.booted = false;\n this.rendered = false;\n }\n\n /**\n * Where the handler is mounted, from `config.src` or the attribute.\n *\n * A trailing slash is dropped, and so are a query string and a fragment — the element\n * appends `/api/bootstrap` to this, so anything after the path cannot survive that\n * concatenation and never could. Left in, `src=\"/_bots?token=x\"` failed with \"it\n * answered text/html — is createDashboardHandler mounted at /_bots?token=x?\", which\n * sends somebody to check the one thing that was right.\n */\n private mountPath(): string {\n const { base, warning } = parseMount(this.config.src ?? this.getAttribute(\"src\") ?? \"\");\n if (warning !== undefined) this.warnOnce(`bot-dashboard: ${warning}`);\n return base;\n }\n\n private async boot(): Promise<void> {\n const base = this.mountPath();\n const shadow = this.shadowRoot ?? this.attachShadow({ mode: \"open\" });\n // A retry after a refusal or a failed boot: drop the message from that attempt, or the\n // dashboard renders underneath a line that is no longer true.\n //\n // Direct children only. The same class marks a panel's own \"could not load\" message,\n // and on a remount the built tree is still hanging in this shadow root — so a plain\n // `querySelectorAll(\".bd-notice\")` reached inside the panels and wiped those too. They\n // came back on the next repaint, which is why it looked harmless; it was still this\n // function deleting other code's output.\n //\n // Walked rather than selected, because `:scope > .bd-notice` matches nothing here: the\n // scoping root is a shadow root, `:scope` only matches an element, and the tidier\n // selector silently removed nothing at all.\n for (const child of Array.from(shadow.children)) {\n if (child.classList.contains(\"bd-notice\")) child.remove();\n }\n\n // Still only one at a time. The client is a module graph with its own state, and two\n // live instances would share it — two feeds writing to one store, and the second\n // element silently drawing the first one's traffic. But *sequentially* is fine, and\n // is what a router does, so the test is whether one is currently connected rather\n // than whether one has ever been.\n if (owner !== undefined && owner !== this && owner.isConnected) {\n shadow.append(notice(\"A bot dashboard is already running on this page. Only one can be.\"));\n this.standDown();\n return;\n }\n owner = this;\n\n // A remount: re-parent what is already built and rendered, and re-point the theme.\n if (container !== undefined) {\n const dom = await import(\"../dashboard/client/dom.js\");\n dom.setThemeHost(this);\n this.applyTheme();\n adoptStyles(shadow);\n shadow.append(container);\n this.renderPanels(container);\n // Pick the feed back up where it was left.\n const stream = await import(\"../dashboard/client/stream.js\");\n stream.connectStream();\n this.rendered = true;\n this.dispatchEvent(new CustomEvent(\"bot-dashboard-ready\", { bubbles: true }));\n return;\n }\n\n // A cross-origin `src` cannot work and should say why rather than failing as a bare\n // \"Failed to fetch\". The dashboard deliberately sends no Access-Control-Allow-Origin —\n // that is what stops another site reading your traffic through a logged-in browser —\n // so it is same-origin only, and pointing at another origin is a configuration\n // mistake rather than something to retry.\n if (base !== \"\" && /^https?:\\/\\//i.test(base)) {\n try {\n if (new URL(base).origin !== location.origin) {\n const why = `src points at ${new URL(base).origin}, which is not this page's origin. The dashboard is same-origin only: it sends no CORS headers, which is what stops another site reading your traffic. Mount it under this origin and use a path, e.g. src=\"/_bots\".`;\n shadow.append(notice(`The bot dashboard could not start: ${why}`));\n console.error(\"bot-dashboard:\", why);\n this.standDown();\n return;\n }\n } catch {\n /* an unparseable src falls through to the fetch, which reports it. */\n }\n }\n\n let boot: Record<string, unknown>;\n try {\n const response = await fetch(`${base}/api/bootstrap`, { credentials: \"same-origin\", headers: { accept: \"application/json\" } });\n if (!response.ok) throw new Error(explainStatus(response.status, base));\n // Checked before parsing, because the failure it catches is the common one and the\n // parser's account of it is useless: with no `src` the element asks its own origin,\n // is handed the host page's HTML, and reports \"Unexpected token '<'\" to a developer\n // whose actual mistake was forgetting an attribute.\n const type = response.headers.get(\"content-type\") ?? \"\";\n if (!type.includes(\"json\")) {\n throw new Error(\n base === \"\"\n ? \"no `src` was given, so it asked this page's own origin and got HTML back. Point `src` at where createDashboardHandler is mounted, e.g. src=\\\"/_bots\\\"\"\n : `it answered ${type || \"no content type\"} rather than JSON. Is createDashboardHandler mounted at ${base}?`,\n );\n }\n boot = (await response.json()) as Record<string, unknown>;\n // Removed while that was in flight — a route change during the round trip. Ownership\n // was already handed back by `disconnectedCallback`, and somebody else may hold it\n // by now, so building into a detached tree here would be a second client racing the\n // live one for the same module state.\n if (!this.isConnected) {\n this.standDown();\n return;\n }\n } catch (error) {\n // A dashboard that cannot reach its handler should say so where somebody will see\n // it, rather than rendering an empty shell that looks like no traffic.\n const why = String(error instanceof Error ? error.message : error);\n shadow.append(notice(`The bot dashboard could not start: ${why}`));\n // Also to the console, because the element may be somewhere nobody is looking and\n // this is always a configuration mistake rather than a condition.\n console.error(\"bot-dashboard:\", why);\n this.standDown();\n return;\n }\n\n // The mount path the element was given wins over whatever the server thinks it is:\n // the element is the thing that knows where it is pointing.\n boot[\"base\"] = base;\n // Which screens survive the server's answer, `hide` and `tabs` — worked out in\n // `config.ts`, which has no document in it and can therefore be tested directly.\n const { sections, warnings } = resolveSections(boot[\"sections\"] as Record<string, boolean>, this.config);\n for (const warning of warnings) this.warnOnce(`bot-dashboard: ${warning}`);\n boot[\"sections\"] = sections;\n\n adoptStyles(shadow);\n\n const frame = document.createElement(\"div\");\n frame.className = \"bd-root\";\n // The markup is this package's own, built from a template literal in `page.ts` and\n // containing no interpolation of anything a request supplied. It is the one place the\n // no-innerHTML rule does not apply, and a test asserts that rather than trusting it.\n //\n // The one placeholder it carries is the title, which the standalone renderer\n // substitutes and this has to as well — the first version did not, and the header\n // read \"__TITLE__\" to anyone who looked at it.\n frame.innerHTML = DASHBOARD_MARKUP;\n shadow.append(frame);\n const title = typeof boot[\"title\"] === \"string\" ? boot[\"title\"] : \"bothandlerjs\";\n\n // The page's own <main> is a landmark, and a landmark inside somebody else's page that\n // already has one is two mains: a screen reader user gets two \"main\" landmarks to\n // choose between, and axe reports landmark-no-duplicate-main and landmark-unique.\n //\n // Replaced rather than re-roled. Setting role=\"region\" on a <main> silences those two\n // and earns aria-allowed-role instead, because <main> permits no role but its own — so\n // the honest fix is to not be a <main>. The class carries the layout, so swapping the\n // tag changes nothing visually.\n const main = frame.querySelector(\"main\");\n if (main !== null) {\n // Swapped rather than re-roled, because <main> permits no role but its own —\n // setting role=\"region\" silences the duplicate-landmark rules and earns\n // aria-allowed-role instead. Its class carries the layout, so the tag is all that\n // changes.\n const region = document.createElement(\"div\");\n region.className = main.className;\n region.setAttribute(\"role\", \"region\");\n region.setAttribute(\"aria-label\", `${title} bot dashboard`);\n while (main.firstChild !== null) region.append(main.firstChild);\n main.replaceWith(region);\n }\n\n // The <header> is a banner landmark for the same reason, and a page may have one\n // banner. Re-roled rather than swapped, because <header> does permit role=\"none\" and\n // because its styling is by element rather than by class — a div here would lose it.\n // `none` drops the element's own semantics and leaves its children where they are.\n frame.querySelector(\"header\")?.setAttribute(\"role\", \"none\");\n for (const node of Array.from(frame.querySelectorAll(\"*\"))) {\n for (const child of Array.from(node.childNodes)) {\n // textContent, so a title containing markup is a title containing markup.\n if (child.nodeType === 3 && child.nodeValue?.includes(\"__TITLE__\") === true) {\n child.nodeValue = child.nodeValue.replace(/__TITLE__/g, title);\n }\n }\n }\n\n this.applyTheme();\n\n // Order matters. `setRoot` has to run before the rest of the client evaluates, since\n // its modules resolve their elements as they initialise — so the dom module is\n // imported and pointed at the shadow root first, and only then the entry.\n container = frame;\n const dom = await import(\"../dashboard/client/dom.js\");\n // The subtree rather than the shadow root, so a remount can carry it to a new one.\n dom.setRoot(frame, this);\n (globalThis as unknown as { __BOOTSTRAP__?: unknown }).__BOOTSTRAP__ = boot;\n\n await import(\"../dashboard/client/index.js\");\n // After the client, because it draws the strip from the boot object and would undo a\n // reorder applied before it ran.\n this.relabelTabs(shadow);\n this.renderPanels(frame);\n\n warnIfFramed();\n\n this.rendered = true;\n this.dispatchEvent(new CustomEvent(\"bot-dashboard-ready\", { bubbles: true }));\n }\n\n private applyTheme(): void {\n const theme = this.config.theme ?? lastTheme ?? {};\n if (this.config.theme !== undefined) lastTheme = this.config.theme;\n this.applyScheme(theme.scheme ?? this.getAttribute(\"scheme\"));\n this.applyDensity(theme.density ?? this.getAttribute(\"density\"));\n\n const next = new Set<string>();\n for (const [token, value] of Object.entries(theme.tokens ?? {})) {\n const name = token.startsWith(\"--\") ? token : `--${token}`;\n next.add(name);\n this.style.setProperty(name, value);\n }\n // A token dropped from the config has to be dropped from the element, or a theme\n // switch leaves the colours it stopped asking for painted on for ever. Removed by\n // name, one at a time, rather than by clearing the style attribute: the client keeps\n // its own measurements there — `--header-h` among them — and clearing it would take\n // the sticky header's layout with it.\n for (const name of this.appliedTokens) {\n if (!next.has(name)) this.style.removeProperty(name);\n }\n this.appliedTokens = next;\n }\n\n /**\n * `scheme` and `density` take two values each, and until now anything else was accepted\n * in silence — a mis-typed `scheme` did nothing, and a mis-typed `density` was written\n * onto the element as `data-density=\"cozy\"`, matching no rule and looking, from the\n * outside, exactly like a dashboard that ignores its configuration.\n *\n * Warned once per bad value per element, because `applyTheme` runs on every re-render\n * and a typo that repeats a hundred times is a typo the console has stopped conveying.\n */\n private applyScheme(value: string | null | undefined): void {\n if (value === null || value === undefined) return;\n if (value === \"light\" || value === \"dark\") {\n this.setAttribute(\"data-theme\", value);\n return;\n }\n this.warnOnce(`bot-dashboard: scheme \"${value}\" is not \"light\" or \"dark\", so it was ignored. Leave it unset to follow the viewer's own setting.`);\n }\n\n private applyDensity(value: string | null | undefined): void {\n if (value === null || value === undefined) return;\n if (value === \"comfortable\" || value === \"compact\") {\n this.setAttribute(\"data-density\", value);\n return;\n }\n this.warnOnce(`bot-dashboard: density \"${value}\" is not \"comfortable\" or \"compact\", so it was ignored.`);\n }\n\n private warnOnce(message: string): void {\n if (this.warned.has(message)) return;\n this.warned.add(message);\n console.warn(message);\n }\n\n /**\n * Reorders and relabels the strip the client has just drawn.\n *\n * Only the tabs that survived: which screens *exist* was settled in the boot object\n * before the client ran, because the client builds its list from there and looks each\n * one up by id — deleting an element here instead crashed it with \"no element\n * #tab-actors\", which is what this comment is standing in for.\n */\n private relabelTabs(shadow: ShadowRoot): void {\n const wanted = this.config.tabs;\n if (wanted === undefined || wanted.length === 0) return;\n const strip = shadow.querySelector(\".tabs\");\n if (strip === null) return;\n\n for (const tab of wanted) {\n const node = shadow.querySelector(`#${TAB_ELEMENT[tab.id]}`);\n if (node === null) continue;\n if (tab.label !== undefined) node.textContent = tab.label;\n // Appending a node already in the strip moves it, which is the reorder.\n strip.append(node);\n }\n }\n\n private renderPanels(within: ParentNode): void {\n const panels = this.config.panels ?? [];\n\n // The rendered tree outlives the element that built it — it is reused across a remount\n // and, on a route change, adopted by a *different* element with a different config. So\n // a panel that this config does not ask for has to go, or the new dashboard shows the\n // old one's panels alongside its own, frozen, because the timer that fed them was\n // cleared when their element was disconnected. Keyed on screen as well as id, so a\n // panel moved to another screen leaves rather than being drawn in both.\n const wanted = new Set(panels.map((panel) => `${panel.id}@${panel.screen}`));\n for (const section of Array.from(within.querySelectorAll(\"[data-bd-panel]\"))) {\n const key = `${section.getAttribute(\"data-bd-panel\")}@${section.getAttribute(\"data-bd-screen\")}`;\n if (!wanted.has(key)) section.remove();\n }\n\n const drawn = new Set<string>();\n for (const panel of panels) {\n // Two panels under one id land on one section: the first builds it, the second finds\n // it and paints over it, and then both refresh timers write into the same body,\n // which flickers between two sources every second. The section keeps the first\n // panel's heading while showing the second one's rows, so the screen actively\n // misdescribes itself.\n const key = `${panel.id}@${panel.screen}`;\n if (drawn.has(key)) {\n this.warnOnce(`bot-dashboard: two panels share the id \"${panel.id}\" on screen \"${panel.screen}\". Ids identify a panel, so the second was ignored — give them different ids.`);\n continue;\n }\n drawn.add(key);\n\n // Escaped, because `screen` and `id` come from whoever wrote the config and a\n // selector built by interpolation is a parser waiting to be handed something it\n // cannot read. An id containing a quote threw `SyntaxError` out of `querySelector`\n // — from here, outside the per-paint try below — which took down the whole loop\n // before the first panel was built, left `bot-dashboard-ready` undispatched, and\n // reported itself as a complaint about a selector nobody wrote.\n const host = within.querySelector(`#view-${cssEscape(panel.screen)}`);\n if (host === null) {\n // Silence here means a panel that simply never appears, and a developer reading\n // their own config for an hour. `src` gets the same treatment a few lines up.\n // The list is read off the rendered markup rather than kept alongside it, so it\n // stays right when a screen is added and cannot drift into naming one that a\n // `tabs` or `hide` setting has already removed.\n const known = Array.from(within.querySelectorAll(\"[id^='view-']\"))\n .map((node) => node.id.slice(\"view-\".length))\n .join(\", \");\n console.warn(`bot-dashboard: panel \"${panel.id}\" asks for screen \"${panel.screen}\", which this dashboard does not have. It has: ${known}.`);\n continue;\n }\n\n // A remount runs this against a subtree that already has the panels, so the section\n // is reused rather than rebuilt — but the timer below is *not*, because\n // `disconnectedCallback` cleared it. Skipping the whole loop here left a refreshing\n // panel frozen on whatever it last drew after the first route change.\n const existing = host.querySelector(`[data-bd-panel=\"${cssEscape(panel.id)}\"]`);\n let body: HTMLElement;\n if (existing !== null) {\n body = existing.querySelector(\".bd-extra\") as HTMLElement;\n } else {\n const section = document.createElement(\"section\");\n section.className = \"panel\";\n section.setAttribute(\"data-bd-panel\", panel.id);\n section.setAttribute(\"data-bd-screen\", panel.screen);\n const heading = document.createElement(\"h2\");\n heading.textContent = panel.title;\n body = document.createElement(\"div\");\n body.className = \"bd-extra\";\n section.append(heading, body);\n host.append(section);\n }\n\n const paint = async (): Promise<void> => {\n try {\n const data = typeof panel.source === \"string\"\n ? ((await (await fetch(panel.source, { credentials: \"same-origin\" })).json()) as BotDashboardRows)\n : await panel.source();\n while (body.firstChild) body.removeChild(body.firstChild);\n // Checked rather than assumed, because a source is somebody else's endpoint and\n // the failures are not hypothetical: `{ rows: \"nope\" }` is *iterable*, so\n // without this it rendered one row per character, each reading \"undefined\".\n if (!Array.isArray(data?.rows)) {\n body.append(notice(`${panel.title} returned no rows.`));\n return;\n }\n const all = data.rows;\n for (const row of all.slice(0, MAX_PANEL_ROWS)) {\n // A row with nothing to say is skipped rather than drawn as \"undefined\".\n if (typeof row !== \"object\" || row === null || row.label === undefined || row.value === undefined) continue;\n const line = document.createElement(\"div\");\n line.className = \"row\";\n const label = document.createElement(\"span\");\n label.textContent = cellText(row.label);\n const value = document.createElement(\"b\");\n // textContent throughout. Whatever your endpoint returns is somebody's data,\n // and this file will not turn it into markup.\n value.textContent = cellText(row.value);\n line.append(label, value);\n if (row.note !== undefined) {\n const note = document.createElement(\"span\");\n note.textContent = cellText(row.note);\n line.append(note);\n }\n body.append(line);\n }\n if (all.length > MAX_PANEL_ROWS) {\n const more = document.createElement(\"div\");\n more.className = \"row\";\n const label = document.createElement(\"span\");\n label.textContent = `${all.length - MAX_PANEL_ROWS} more not shown`;\n more.append(label);\n body.append(more);\n }\n } catch {\n while (body.firstChild) body.removeChild(body.firstChild);\n body.append(notice(`Could not load ${panel.title}.`));\n }\n };\n\n void paint();\n if (panel.refreshMs !== undefined && panel.refreshMs > 0) {\n this.timers.push(setInterval(paint, Math.max(1000, panel.refreshMs)) as unknown as number);\n }\n }\n }\n}\n\n/**\n * The stylesheet, built once and adopted by every shadow root that needs it.\n *\n * A constructable stylesheet rather than a `<style>` element, and the reason is Content\n * Security Policy. An injected `<style>` is inline style, so a host page carrying\n * `style-src 'self'` — which any admin page worth mounting this on does — blocks it, and\n * the dashboard renders with no colours, no radii and no layout at all. A stylesheet built\n * by script that has already satisfied `script-src` is not inline style and is not\n * blocked.\n *\n * It is also the cheaper answer: one parse of forty kilobytes of CSS shared by every\n * mount, rather than one per mount.\n */\nlet sheet: CSSStyleSheet | undefined;\n\nfunction adoptStyles(shadow: ShadowRoot): void {\n if (typeof CSSStyleSheet !== \"undefined\" && \"replaceSync\" in CSSStyleSheet.prototype && \"adoptedStyleSheets\" in shadow) {\n if (sheet === undefined) {\n sheet = new CSSStyleSheet();\n sheet.replaceSync(`${DASHBOARD_CSS}\\n${EXTRA_CSS}`);\n }\n shadow.adoptedStyleSheets = [...shadow.adoptedStyleSheets, sheet];\n return;\n }\n // Older engines without constructable stylesheets. Blocked by a strict style-src, which\n // is the trade those browsers get rather than no dashboard at all.\n const style = document.createElement(\"style\");\n style.textContent = `${DASHBOARD_CSS}\\n${EXTRA_CSS}`;\n shadow.append(style);\n}\n\n\n/**\n * What of a config actually decides the rendered screens: which tabs, and which panels\n * where. Deliberately not the panel `source` functions — a framework rebuilds those\n * closures on every render, and a comparison that counted them would report a change on\n * every render regardless of whether anything meaningful differed.\n */\n\nlet framingWarned = false;\n\n/**\n * The one protection that does not survive being embedded.\n *\n * The standalone dashboard is served by this package and its response carries\n * `frame-ancestors 'none'`, so a page on another origin cannot frame it and trick an\n * operator into clicking a control they cannot see. The element renders into a document\n * this package does not serve and cannot set a header on — so whether the same attack\n * works is decided by the host page's own headers, and the failure mode is silent.\n *\n * A warning rather than a refusal: an admin app framing its own pages is ordinary and\n * legitimate, which is why the check is specifically for a *cross-origin* ancestor —\n * reading `top.location` throws for one and reads fine for one of our own — and why the\n * decision to render anyway is not this element's to make.\n */\nfunction warnIfFramed(): void {\n if (framingWarned) return;\n if (globalThis.top === globalThis.self) return;\n try {\n void (globalThis.top as Window).location.origin;\n return;\n } catch {\n framingWarned = true;\n console.warn(\n \"bot-dashboard: this page is framed by another origin, and a framed dashboard can be clickjacked — an operator clicks a control they cannot see. The standalone page refuses framing with `frame-ancestors 'none'`; a page you serve yourself has to send that header itself, or `X-Frame-Options: DENY`.\",\n );\n }\n}\n\n\nfunction notice(text: string): HTMLElement {\n // A class rather than `style.cssText`. Writing the style attribute is inline style, and\n // a host page carrying `style-src 'self'` blocks it — so the one element whose whole job\n // is to explain a failure would have appeared unstyled, in the situation where somebody\n // most needs to notice it.\n const node = document.createElement(\"p\");\n node.className = \"bd-notice\";\n node.textContent = text;\n return node;\n}\n\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"bot-dashboard\": BotDashboardElement;\n }\n}\n","/**\n * The element's decisions, with no document in sight.\n *\n * `src/element/` is excluded from the coverage report for the same reason\n * `src/dashboard/client/` is — it needs a document and a custom-element registry, and a\n * number collected from a runner that cannot execute it would describe the runner. That\n * exclusion carries a second half in the client's case, and it was missing here: the pure\n * modules inside an excluded directory still get unit tests, they are simply not counted.\n * This is that module for the element.\n *\n * Everything below answers a question with an argument rather than by looking at the page:\n * which screens survive a config, what a status code means, where the handler is mounted,\n * whether two configs describe the same thing. It is also where the fiddly parts live —\n * `tabs` and `hide` naming different vocabularies, a screen the server withheld — which is\n * exactly the logic that had bugs in it and no test able to reach them.\n */\n\nimport type { DashboardSections } from \"../dashboard/sections.js\";\n\nexport type BotDashboardTabId = \"live\" | \"actors\" | \"stats\" | \"policy\";\n\nexport interface BotDashboardTab {\n id: BotDashboardTabId;\n /** Shown in the tab strip. Defaults to the built-in name. */\n label?: string;\n}\n\n/** A panel of your own, rendered beside the built-in ones. */\nexport interface BotDashboardPanel {\n id: string;\n /** Which screen it appears on. */\n screen: BotDashboardTabId;\n title: string;\n /**\n * Where its rows come from: a URL returning `{ rows: [{ label, value, note? }] }`, or a\n * function returning the same shape.\n *\n * Rendered as text, always — the same rule the rest of the client follows, and for the\n * same reason. Nothing here interprets markup, so a value that happens to contain a tag\n * appears as that tag rather than becoming one.\n */\n source: string | (() => Promise<BotDashboardRows> | BotDashboardRows);\n /** How often to refresh, ms. Omit for once on load. */\n refreshMs?: number;\n}\n\nexport interface BotDashboardRows {\n rows: ReadonlyArray<{ label: string; value: string | number; note?: string }>;\n}\n\nexport interface BotDashboardTheme {\n /**\n * Token overrides, by custom property name without the leading dashes:\n * `{ accent: \"#7c3aed\", surface: \"#fff\" }`.\n *\n * Applied to the host, so they cascade into the shadow root the same way the built-in\n * tokens do. Every token the stylesheet defines can be replaced; the contrast\n * guarantees the built-in palette was measured against are yours to keep once you do.\n */\n tokens?: Record<string, string>;\n /** Force a scheme instead of following the host page and the OS. */\n scheme?: \"light\" | \"dark\";\n /** `compact` tightens the row and panel padding. */\n density?: \"comfortable\" | \"compact\";\n}\n\nexport interface BotDashboardConfig {\n /** Where the dashboard handler is mounted. Also settable as the `src` attribute. */\n src?: string;\n /** Which screens appear, in which order, under which labels. */\n tabs?: readonly BotDashboardTab[];\n /** Panels of your own. */\n panels?: readonly BotDashboardPanel[];\n theme?: BotDashboardTheme;\n /**\n * Hide parts of the page the *server* is still serving.\n *\n * Cosmetic, and worth being clear about: a section switched off here is removed from\n * the screen and stays on the wire. The `sections` option on the handler is the one\n * that stops the data leaving the process, and it is the one to use when the point is\n * that somebody should not have it.\n */\n hide?: DashboardSections;\n}\n\n/**\n * The section flag that decides whether each screen exists.\n *\n * Omitting a tab has to be expressed this way rather than by deleting the element: the\n * client builds its tab list from the sections it was booted with and then looks each one\n * up, so a missing element is a crash rather than a smaller strip. Saying it in the boot\n * object means the client removes the screen itself, the way it already does for a\n * section the server switched off.\n */\nexport const TAB_SECTION: Record<BotDashboardTabId, keyof DashboardSections> = {\n live: \"feed\",\n actors: \"registry\",\n stats: \"statistics\",\n policy: \"policy\",\n};\n\n/** `hide: { policy: true }` means the client's `sections.policy` is false. */\nfunction invert(hide: DashboardSections): Record<string, boolean> {\n const out: Record<string, boolean> = {};\n for (const [key, value] of Object.entries(hide)) if (value === true) out[key] = false;\n return out;\n}\n\n/**\n * Which sections the client should be booted with, and what to say about the config.\n *\n * Three inputs settle it, in this order: what the server offered, what `hide` removes, and\n * what `tabs` narrows to. The warnings are returned rather than logged because the reasons\n * a screen goes missing are the interesting part and the caller is the only one that knows\n * whether it has already said them.\n */\nexport function resolveSections(\n fromServer: Readonly<Record<string, boolean>>,\n config: Pick<BotDashboardConfig, \"hide\" | \"tabs\">,\n): { sections: Record<string, boolean>; warnings: string[] } {\n const sections: Record<string, boolean> = { ...fromServer };\n const warnings: string[] = [];\n\n // `tabs` and `hide` do not share a vocabulary — `tabs` names screens (`live`, `actors`,\n // `stats`, `policy`) and `hide` names sections (`feed`, `registry`, `statistics`,\n // `policy`, and the finer-grained ones that are not screens at all). A name from the\n // wrong list is not a type error to anyone writing plain JavaScript, and did nothing\n // whatsoever: `hide: { live: true }` is the natural thing to write after reading about\n // `tabs`, and it left the live feed exactly where it was.\n for (const key of Object.keys(config.hide ?? {})) {\n if (Object.hasOwn(sections, key)) continue;\n const suggestion = TAB_SECTION[key as BotDashboardTabId];\n warnings.push(\n suggestion === undefined\n ? `hide.${key} is not a section this dashboard has, so it did nothing. It has: ${Object.keys(sections).sort().join(\", \")}.`\n : `hide.${key} did nothing — \"${key}\" is a tab name, and \\`hide\\` takes section names. You want hide.${suggestion}, or leave \"${key}\" out of \\`tabs\\`.`,\n );\n }\n for (const [key, value] of Object.entries(invert(config.hide ?? {}))) sections[key] = value;\n\n // A tab left out of `tabs` is a screen the client should not build at all.\n const wanted = config.tabs;\n if (wanted !== undefined && wanted.length > 0) {\n const keep = new Set(wanted.map((tab) => tab.id));\n for (const id of keep) {\n // Dropped in silence until now, so asking for two screens and being given one looked\n // like the element choosing for itself.\n if (TAB_SECTION[id] === undefined) {\n warnings.push(`tabs lists \"${id}\", which is not a screen. The screens are: ${Object.keys(TAB_SECTION).join(\", \")}.`);\n continue;\n }\n // Asked for, and withheld by the server rather than by anything on this page. The\n // element cannot override it and should not pretend to: `sections` is enforced where\n // the data is, which is the whole point of it. But a developer who lists two screens\n // and is given one deserves to be told which file to go and look in.\n if (fromServer[TAB_SECTION[id]] === false && config.hide?.[TAB_SECTION[id]] !== true) {\n warnings.push(\n `tabs lists \"${id}\", but this dashboard's server has the \"${TAB_SECTION[id]}\" section switched off, so that screen does not exist. That is the \\`sections\\` option on createDashboardHandler, and it is enforced there rather than here.`,\n );\n }\n }\n for (const id of Object.keys(TAB_SECTION) as BotDashboardTabId[]) {\n if (!keep.has(id)) sections[TAB_SECTION[id]] = false;\n }\n }\n\n return { sections, warnings };\n}\n\n/**\n * Where the handler is mounted, from a raw `src`.\n *\n * A trailing slash is dropped, and so are a query string and a fragment — the element\n * appends `/api/bootstrap` to this, so anything after the path cannot survive that\n * concatenation and never could. Left in, `src=\"/_bots?token=x\"` failed with \"it answered\n * text/html — is createDashboardHandler mounted at /_bots?token=x?\", which sends somebody\n * to check the one thing that was right.\n */\nexport function parseMount(raw: string): { base: string; warning?: string } {\n const cut = raw.search(/[?#]/);\n if (cut === -1) return { base: raw.replace(/\\/$/, \"\") };\n return {\n base: raw.slice(0, cut).replace(/\\/$/, \"\"),\n warning: `src \"${raw}\" has a ${raw[cut] === \"?\" ? \"query string\" : \"fragment\"} on it. The element asks for \\`<src>/api/bootstrap\\`, so only the path can mean anything here; the rest was ignored.`,\n };\n}\n\n/**\n * What a refusal from the handler actually means to whoever has to fix it.\n *\n * \"it answered 401\" is true and useless, and 401 is not an exotic case: the documented\n * setup is a dashboard with `auth` set, embedded in an admin page, and a background fetch\n * cannot put up the sign-in prompt that a browser would show for a navigation. So the\n * dashboard is simply blank with a number on it, and the number does not say that the\n * credentials the operator already has are the answer.\n */\nexport function explainStatus(status: number, base: string): string {\n if (status === 401) {\n return `it answered 401 Unauthorized. The dashboard has \\`auth\\` set, and the element fetches in the background, where a browser cannot offer the sign-in prompt it would show for a normal navigation. Open ${base || \"the dashboard\"} directly and sign in once — the browser then sends those credentials with the element's requests too — or put this page behind the same authentication.`;\n }\n if (status === 403) {\n return `it answered 403 Forbidden, which is this dashboard refusing the caller rather than the password: check \\`allowedHosts\\` and \\`allowedClients\\`, and that this page is on the same origin as \\`src\\`.`;\n }\n return `it answered ${status}`;\n}\n\n/**\n * What of a config actually decides the rendered screens: which tabs, and which panels\n * where. Deliberately not the panel `source` functions — a framework rebuilds those\n * closures on every render, and a comparison that counted them would report a change on\n * every render regardless of whether anything meaningful differed.\n */\nexport function shapeOf(config: BotDashboardConfig): string {\n const tabs = (config.tabs ?? []).map((tab) => tab.id).join(\",\");\n const panels = (config.panels ?? []).map((panel) => `${panel.id}@${panel.screen}:${panel.title}`).join(\",\");\n return `${tabs}|${panels}`;\n}\n\n/** A panel cell, as text and never as markup, and never longer than a cell should be. */\nexport function cellText(value: unknown): string {\n if (typeof value === \"string\") return value.slice(0, 200);\n if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n if (value === null || value === undefined) return \"\";\n try {\n return JSON.stringify(value)?.slice(0, 200) ?? \"\";\n } catch {\n return \"\";\n }\n}\n","import { CLIENT_SCRIPT } from \"./client.generated.js\";\nimport type { DashboardSections } from \"./types.js\";\n\n/**\n * The dashboard page.\n *\n * Shipped as a template rather than as a file on disk: a bundled library cannot assume\n * anything about what sits next to its own JavaScript, and a dashboard that works from\n * `src` and 404s from `dist` is the worst kind of bug to find. The markup and the CSS\n * are the template below; the behaviour is a real TypeScript module under `client/`,\n * bundled into {@link CLIENT_SCRIPT} at build time by `scripts/build-client.mjs` and\n * stamped in here. It used to be two thousand lines of JavaScript inside this string,\n * where the type-checker could not see it, Biome did not lint it and no test could\n * import a function out of it — which is how a call to a function nobody had written\n * shipped and blanked the whole Statistics tab.\n *\n * Three rules govern everything here, and all three are about the fact that this page\n * renders **attacker-supplied text**. A User-Agent is written by the client. So is the\n * path. So is anything a detector quoted into an evidence summary.\n *\n * 1. **Nothing is ever assembled into HTML.** Every value reaches the document through\n * `textContent`. There is no `innerHTML` in the page or in the bundle, and the\n * build refuses to produce one.\n * 2. **No `style` attributes.** The page is served under a nonce-based CSP, which\n * blocks inline styles; geometry is set through the CSSOM (`el.style.width`), which\n * is not a style attribute and is allowed.\n * 3. **No network access of any kind.** `default-src 'none'` and no external font,\n * script or image. Everything is here.\n */\n\nexport interface DashboardPageOptions {\n title: string;\n basePath: string;\n links: ReadonlyArray<{ label: string; href: string }>;\n allowReset: boolean;\n allowEdit: boolean;\n allowGuardEdit: boolean;\n allowActing: boolean;\n peers: ReadonlyArray<{ label: string; href: string }>;\n /** Which parts of the page this listener has. The client removes the rest of them. */\n sections: Required<DashboardSections>;\n}\n\n/**\n * Builds the page once and returns a function that stamps a per-response nonce into\n * it. The HTML is a few tens of kilobytes and identical on every request; rebuilding\n * it per view would be pure waste.\n */\n/**\n * What the page is told about itself.\n *\n * Built here rather than inline in the renderer so that the embeddable element can be\n * served the identical object from `/api/bootstrap`. The page carries it stamped into its\n * one nonced script; the element has to ask, because it is rendered into somebody else's\n * document and there is nothing to stamp.\n */\nexport function bootFor(options: DashboardPageOptions): Record<string, unknown> {\n return {\n base: options.basePath === \"/\" ? \"\" : options.basePath,\n title: options.title,\n allowReset: options.allowReset,\n allowEdit: options.allowEdit,\n allowGuardEdit: options.allowGuardEdit,\n allowActing: options.allowActing,\n peers: options.peers.map((peer) => ({ label: String(peer.label), href: String(peer.href) })),\n sections: options.sections,\n links: options.links.map((link) => ({ label: String(link.label), href: String(link.href) })),\n };\n}\n\nexport function renderDashboardPage(options: DashboardPageOptions): (nonce: string) => string {\n const bootstrap = escapeForScript(JSON.stringify(JSON.stringify(bootFor(options))));\n\n // Function replacements throughout, never string ones. `String.prototype.replace`\n // reads `$&`, `` $` `` and `$'` out of a *string* replacement and substitutes match\n // context for them — so a title, a link label or a minified bundle containing one of\n // those sequences would otherwise rewrite itself on the way into the page.\n const html = PAGE.replace(\"__BOOT_JSON__\", () => bootstrap)\n .replace(\"__SCRIPT__\", () => CLIENT_SCRIPT)\n .replace(/__TITLE__/g, () => escapeHtml(options.title));\n return (nonce: string) => html.replace(/__NONCE__/g, () => nonce);\n}\n\n/**\n * Makes a JSON literal safe to sit inside a `<script>` element.\n *\n * `JSON.stringify` is not enough, and the gap is not theoretical: HTML ends a script\n * element at the first `</script>` in the source, *including one inside a string\n * literal*. A caller passing that sequence as a dashboard `title` — or a link label —\n * would otherwise close the tag and open one of their own. Escaping the angle brackets\n * as `\\u003c` / `\\u003e` keeps the value identical after `JSON.parse` while making the\n * sequence unrecognisable to the HTML parser. The two line separators are the same\n * class of problem for older parsers.\n */\nfunction escapeForScript(json: string): string {\n return json\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (character) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[character] ?? character);\n}\n\n/**\n * The dashboard's stylesheet, on its own.\n *\n * Separated from the document so the same rules can be adopted into a shadow root by the\n * embeddable element. The selectors are written `:root, :host` throughout for that\n * reason: `:root` matches the document element on the standalone page and matches nothing\n * inside a shadow tree, where `:host` is the element the tokens have to hang off.\n */\nexport const DASHBOARD_CSS = String.raw`/* ---------------------------------------------------------------------------\n Tokens.\n\n The two series colours and the critical status step are the validated data\n palette: blue and orange clear every colour-vision gate against both surfaces as\n an adjacent pair, and every chart that uses them also carries a legend and a\n label, so hue is never the only thing distinguishing anything. Text never wears a\n series colour — the ink tokens below are all at least 4.5:1 on their surface.\n--------------------------------------------------------------------------- */\n:root, :host {\n color-scheme: light;\n --page: #f4f5f7;\n --surface: #ffffff;\n --surface-2: #fafafa;\n --raised: #ffffff;\n --line: #e4e6ea;\n --line-soft: #eef0f3;\n --ink: #0b0d12;\n --ink-2: #3c414b;\n --muted: #5f646e;\n --s1: #2a78d6;\n --s2: #eb6834;\n --crit: #d03b3b;\n /* The series blue carries white text at about 3.9:1, which is under the 4.5 floor —\n fine for a bar, not for a label. Solid buttons get a darker blue of their own\n rather than a lighter series colour, because the series colour is validated for\n charts and moving it would move every chart with it. */\n --accent-solid: #1b5fb0;\n --good-text: #006300;\n /* Darker than it looks like it needs to be, and measured rather than judged. This ink\n appears on a 16%-amber chip, and that chip can sit on the open row's blue tint —\n three layers, each lightening the background under the same text. At #8a5a00 the\n suspected badge measured 3.52:1 there, under the 4.5 floor for 11px bold. */\n --warn-text: #6d4700;\n --crit-text: #b02525;\n --info-text: #1c5cab;\n --proven-text: #5b34a8;\n --grid: #eceef1;\n --shadow: 0 1px 2px rgba(11,13,18,.06), 0 1px 8px rgba(11,13,18,.04);\n --focus: #2a78d6;\n}\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]), :host(:not([data-theme=\"light\"])) {\n color-scheme: dark;\n --page: #0d0f12;\n --surface: #16181d;\n --surface-2: #1b1e24;\n --raised: #1f232a;\n --line: #272b33;\n --line-soft: #202329;\n --ink: #f2f4f7;\n --ink-2: #c4cad3;\n --muted: #8b929c;\n --s1: #3987e5;\n --s2: #d95926;\n --crit: #d03b3b;\n --accent-solid: #2364b4;\n --good-text: #4ec97a;\n --warn-text: #fab219;\n --crit-text: #ff8078;\n --info-text: #86b6ef;\n --proven-text: #b3a4f5;\n --grid: #23272e;\n --shadow: none;\n --focus: #86b6ef;\n }\n}\n:root[data-theme=\"dark\"], :host([data-theme=\"dark\"]) {\n color-scheme: dark;\n --page: #0d0f12;\n --surface: #16181d;\n --surface-2: #1b1e24;\n --raised: #1f232a;\n --line: #272b33;\n --line-soft: #202329;\n --ink: #f2f4f7;\n --ink-2: #c4cad3;\n --muted: #8b929c;\n --s1: #3987e5;\n --s2: #d95926;\n --crit: #d03b3b;\n --accent-solid: #2364b4;\n --good-text: #4ec97a;\n --warn-text: #fab219;\n --crit-text: #ff8078;\n --info-text: #86b6ef;\n --proven-text: #b3a4f5;\n --grid: #23272e;\n --shadow: none;\n --focus: #86b6ef;\n}\n\n* { box-sizing: border-box; }\nhtml, body { height: 100%; }\n/* Restored by the client once the first render has settled. See the note on .tiles. */\nhtml.settling { overflow-anchor: none; }\nbody {\n margin: 0; background: var(--page); color: var(--ink);\n font: 14px/1.55 system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n -webkit-font-smoothing: antialiased;\n}\n.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n.tnum { font-variant-numeric: tabular-nums; }\n:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 6px; }\n\n/* --- header -------------------------------------------------------------\n Two rows, and the split is the whole idea.\n\n The first carries identity, connection state and the controls — everything you\n can *press*. The second carries the tab strip and, at the far end, the handful of\n configuration facts that used to sit up top wearing the same bordered pill as the\n buttons beside them. Ten identical rounded rectangles in a row, five of which did\n nothing when clicked, is not a toolbar; it is a guessing game. The facts are now\n plain text — the one treatment nothing else on the page uses for a control — and\n they fill the tab strip's empty right-hand side rather than needing room of their\n own.\n\n Nothing here has a fixed height. The old row was pinned to 54px with children that\n refused to shrink, so on a narrow window the title and the live indicator drew on\n top of each other rather than wrapping.\n------------------------------------------------------------------------- */\nheader {\n position: sticky; top: 0; z-index: 20;\n background: color-mix(in srgb, var(--surface) 92%, transparent);\n backdrop-filter: saturate(180%) blur(8px);\n border-bottom: 1px solid var(--line);\n padding: 0 20px;\n}\n.head-row { display: flex; align-items: center; gap: 10px 14px; min-height: 54px; flex-wrap: wrap; padding: 7px 0; }\n.brand { display: flex; align-items: baseline; gap: 8px; min-width: 0; }\n/* A real top-level heading, because the page had none: a screen reader's \"jump to the\n heading\" found nothing to jump to. Styled back down to the size it always was — it is\n a name in a toolbar, not a title on a poster. */\n.brand h1 { font-size: 15px; font-weight: 640; letter-spacing: -.015em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin: 0; }\n.brand span { color: var(--muted); font-size: 12.5px; white-space: nowrap; }\n\n/* Status, not a control: a filled pill with no border, so it never reads as pressable. */\n.live {\n display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-2);\n white-space: nowrap; background: color-mix(in srgb, var(--ink) 5%, transparent);\n border-radius: 999px; padding: 3px 10px 3px 8px; font-weight: 520;\n}\n.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex: none; }\n.dot.on { background: var(--good-text); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good-text) 22%, transparent); }\n.dot.off { background: var(--crit-text); box-shadow: 0 0 0 3px color-mix(in srgb, var(--crit-text) 20%, transparent); }\n.grow { flex: 1 1 auto; min-width: 8px; }\n\n.head-links { display: flex; gap: 6px; flex-wrap: wrap; }\n/* A hairline between \"somewhere else\" and \"do something here\". */\n.head-actions { display: flex; gap: 6px; align-items: center; }\n.head-links:not(:empty) + .head-actions { padding-left: 12px; border-left: 1px solid var(--line); }\n\n.nav-row { display: flex; align-items: flex-end; gap: 20px; }\n.facts {\n display: flex; gap: 14px; flex-wrap: wrap; margin-left: auto;\n font-size: 11.5px; color: var(--muted); padding-bottom: 9px; min-width: 0;\n}\n.facts span { white-space: nowrap; }\n/* Sibling instances. A dashboard reports on one process, and this is the honest amount\n of help the page can give with that: a way to reach the others. */\n.peers { display: flex; gap: 6px; align-items: center; padding-bottom: 7px; flex-wrap: wrap; }\n.peers a { font-size: 11.5px; padding: 2px 8px; border-radius: 999px; }\n@media (max-width: 900px) { .peers { display: none; } }\n.facts b { font-weight: 600; color: var(--ink-2); font-variant-numeric: tabular-nums; }\n\n/* Secondary by definition, so it is the first thing to go when room runs out. */\n@media (max-width: 900px) { .facts { display: none; } }\n@media (max-width: 560px) { .brand span { display: none; } }\nbutton, .linkbtn {\n font: inherit; font-size: 12.5px; line-height: 1.4; padding: 6px 11px; border-radius: 8px;\n border: 1px solid var(--line); background: var(--surface); color: var(--ink-2);\n cursor: pointer; white-space: nowrap; text-decoration: none; display: inline-block;\n}\nbutton:hover, .linkbtn:hover { background: var(--surface-2); color: var(--ink); border-color: color-mix(in srgb, var(--ink) 22%, var(--line)); }\nbutton[aria-pressed=\"true\"] { background: color-mix(in srgb, var(--s1) 12%, var(--surface)); border-color: color-mix(in srgb, var(--s1) 45%, var(--line)); color: var(--ink); }\nbutton[disabled] { opacity: .5; cursor: default; }\n\n.tabs { display: flex; gap: 2px; margin-bottom: -1px; flex: none; }\n.tab {\n border: 0; background: none; border-bottom: 2px solid transparent; border-radius: 0;\n padding: 9px 12px; font-size: 13px; color: var(--muted); font-weight: 500;\n}\n.tab:hover { background: none; color: var(--ink); }\n.tab[aria-selected=\"true\"] { color: var(--ink); border-bottom-color: var(--s1); }\n\n/* --- layout ------------------------------------------------------------- */\n/* The top padding is the gap under the sticky header. At 18px the counter row sat almost\n against the header's border and read as part of it; the tiles carry their own border, so\n two lines were meeting with nothing between them. */\nmain { padding: 28px 20px 64px; max-width: 1680px; margin: 0 auto; }\n.stack { display: grid; gap: 16px; }\n/* Everything above the feed is drawn by script once the first snapshot arrives, which\n inserts a block of content above what is already laid out. The browser's scroll\n anchoring compensates for that by scrolling down by its height — so on any window narrow\n enough for the counter row to wrap, the dashboard opened with its own counters already\n off the top of the screen, every time.\n\n Anchoring is switched off for the first render and switched back on once it has settled\n (see settleScrollAnchoring in the client). It is not simply left off: the feed puts new requests at\n the top, and anchoring is exactly what keeps somebody's place while they read a screen\n that grows above them. */\n.tiles { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(136px, 1fr)); }\n.tile {\n background: var(--surface); border: 1px solid var(--line); border-radius: 10px;\n padding: 8px 12px 9px; box-shadow: var(--shadow);\n}\n.tile .v { font-size: 21px; font-weight: 620; letter-spacing: -.025em; line-height: 1.15; }\n.tile .k { font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .055em; margin-top: 2px; font-weight: 560; }\n/* One line, always. These are grid items, so the tallest sets the height of all nine —\n two captions wrapping to a second line was costing every tile forty pixels of nothing.\n The full text stays available on hover rather than being cut from the page. */\n.tile .s { font-size: 11px; color: var(--muted); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n/* A tile that navigates is a real button, so it arrives carrying the browser's own\n font, centring and chrome. Reset to match its inert neighbours exactly, then given\n back the one thing a div must not have: something that says it can be pressed. */\nbutton.tile {\n font: inherit; color: inherit; text-align: left; width: 100%; display: block;\n cursor: pointer; appearance: none; transition: border-color .12s, box-shadow .12s;\n}\nbutton.tile:hover { border-color: var(--focus); }\n/* The badge's companion: fetches the entries the stream skipped. Sits inline with the\n heading, so it is styled to read as part of the sentence rather than as a form control. */\n.load-skipped {\n font: inherit; margin-left: 6px; padding: 1px 8px; border-radius: 6px; cursor: pointer;\n border: 1px solid var(--warn-text); background: transparent; color: var(--warn-text);\n}\n.load-skipped:hover { background: color-mix(in srgb, var(--warn-text) 12%, transparent); }\n.load-skipped:disabled { opacity: .5; cursor: default; }\n\n/* Prev/next under a table. Quiet: it is navigation for a list, not an action on it. */\n.pager {\n display: flex; align-items: center; gap: 10px; flex-wrap: wrap;\n padding: 9px 2px 2px; font-size: 12px; color: var(--muted);\n}\n.pager button {\n font: inherit; padding: 4px 11px; border-radius: 7px;\n border: 1px solid var(--line); background: var(--surface); color: var(--ink); cursor: pointer;\n}\n.pager button:hover:not(:disabled) { border-color: var(--focus); }\n.pager button:disabled { opacity: .45; cursor: default; }\n.pager .where { font-variant-numeric: tabular-nums; }\n.pager.pager-top { padding: 2px 2px 9px; border-bottom: 1px solid var(--line); margin-bottom: 9px; }\n/* The feed's upper pager rides in the toolbar rather than owning a row of its own, which\n was thirty-six pixels of mostly empty rule above every screenful of requests. */\n.pager.pager-inline { padding: 0; margin-left: auto; gap: 8px; }\n.pager.pager-inline .size { margin-left: 0; }\n.pager .step { min-width: 30px; font-size: 15px; line-height: 1; padding: 3px 8px 5px; }\n.pager .size { display: flex; align-items: center; gap: 6px; margin-left: auto; }\n.pager .size select {\n font: inherit; padding: 3px 6px; border-radius: 6px;\n border: 1px solid var(--line); background: var(--surface); color: var(--ink);\n}\n.pager .held { color: var(--warn-text); }\n.tile.good .v { color: var(--good-text); }\n.tile.warn .v { color: var(--warn-text); }\n.tile.crit .v { color: var(--crit-text); }\n.tile.proven .v { color: var(--proven-text); }\n\n/* \"clip\" rather than \"hidden\", and the difference is load-bearing. Both round off the\n corners, but \"hidden\" makes the panel a scroll container — which becomes the\n containing block for anything sticky inside it, so the feed's column headers were\n anchored to a box that never scrolls and simply never stuck. They have not worked\n since they were written; the hard-coded offset above them was dead code. \"clip\"\n clips without creating a scrollport, so the headers stick to the viewport, under\n the page header, where they were always meant to. */\n.panel { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow); overflow: clip; }\n.panel > h2 {\n margin: 0; padding: 9px 14px; font-size: 11.5px; font-weight: 620; color: var(--muted);\n text-transform: uppercase; letter-spacing: .055em; border-bottom: 1px solid var(--line);\n display: flex; align-items: center; gap: 10px;\n}\n.panel > h2 .sub { font-weight: 400; text-transform: none; letter-spacing: 0; font-size: 11.5px; margin-left: auto; }\n/* A container, so the two-column grid below can ask how much room it actually has.\n\n It used to ask the viewport, which is the same mistake the feed table's breakpoints\n made one level down: the constraint is the width of this column, not of the window.\n Embedded in a 320px sidebar on a 1280px screen the media query never fired, the second\n column held its 280px minimum, and the feed was squeezed to twenty-two pixels. */\n.stack { container: dash / inline-size; }\n/* A grid item's min-width is auto, so a panel wrapping a wide table refuses to shrink and\n pushes the whole document sideways instead — which is what the Actors screen did under\n about 600px, where the table is widest and the viewport narrowest. The .two grid already\n says minmax(0, ...) for this reason; .stack needs the same permission. With it the panel\n shrinks and the scroller inside it does its job. */\n.stack > * { min-width: 0; }\n/* Numeric columns shrink to their contents, so the width goes to the two columns that\n carry text — the actor and what is known about it. Six counters of one or two digits\n were each taking about a hundred pixels while the State column was squeezed against the\n buttons. Same trick the feed's time column already uses. */\n#view-actors th.num, #view-actors td.num { width: 1%; white-space: nowrap; }\n.two { display: grid; gap: 16px; grid-template-columns: minmax(0, 1.9fr) minmax(280px, 1fr); align-items: start; }\n.grid3 { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }\n@container dash (max-width: 1080px) { .two { grid-template-columns: minmax(0, 1fr); } }\n\n/* The feed table has a floor — six columns of request text will not go below about\n 940px — and under it the panel was simply amputating the right-hand columns.\n\n These are **container** queries, not media queries, and the difference is the whole\n fix. What the table has to fit inside is the panel, and the panel is one column of a\n two-column grid: at a 1400px viewport it is about 880px wide, so a viewport-based\n breakpoint at 980px never fires and the right-hand columns are clipped on a perfectly\n ordinary desktop. That is exactly the bug a media query was written to fix, and the\n media query was measuring the wrong box the whole time. It only appeared to work\n because five columns happened to fit.\n\n Four things happen on the way down, in the order that costs least. First the\n duration goes: it is the column people narrow a window least to read and it is still\n in the row detail. Then the timestamp, which is also still in the row detail and is\n implied by the order while the stream is live. Then the User-Agent line stops\n ellipsing and wraps, which is what actually buys the room — it takes the table's floor\n from about 100ch to about 56ch, because the cap on that one line is most of what the\n table cannot go below. Only under *that* does the table get a scrollbar of its own.\n\n The scrollbar is deliberately last and deliberately rare, and it is worth being\n precise about why, because it is the rung that gets set too high. A scroll container\n is the containing block for anything sticky inside it, so the moment it turns on the\n column headers stop tracking the viewport — and a row becomes wider than the box it\n sits in, which is enough to make it hard to click and, for a keyboard or a pointer\n being driven by a test, hard to reach at all. It cost both when it fired at 1440px on\n a wider face: the headers scrolled away with the rows and a click on a row never\n landed. Wrapping costs a line of height and nothing else, so it goes first and this\n goes last.\n\n The thresholds are in **ch**, and that is the second half of the same lesson. They\n used to be pixels, which quietly assumed a font: what six columns of request text\n actually need is set by the width of a glyph — the User-Agent below is capped in ch —\n and a hard pixel breakpoint is only correct for the font it was measured on. On this\n machine the six columns needed 951px and the breakpoint sat at 960px, so it passed by\n nine pixels; on a CI runner with a wider default face the same six columns needed more\n than the panel had, the query did not fire, and the right-hand columns were clipped on\n an ordinary 1600px desktop. The five-column rung was already over its own floor by\n 8px and nobody had noticed, because nothing measures a layout that merely looks fine.\n\n In ch, both sides move together: a wider face makes the content wider and makes the\n threshold wider by the same proportion. The floors, measured, are 119ch / 111ch /\n 100ch ellipsing and 56ch wrapped, and each rung sits above its own floor with room to\n spare so that the arithmetic does not have to be exact. */\n.feed-panel { container: feed / inline-size; }\n\n/* The rungs themselves are further down, immediately after the table's own rules, and\n they have to be: a container query adds no specificity, so an override written above\n the declaration it overrides simply loses on source order and does nothing. The wrap\n rung sat here for exactly that reason and was dead the whole time — the table never\n wrapped, its floor never dropped, and nothing said so. See \"the column ladder\" below. */\n\n/* The header's own subtitle, which is a viewport thing rather than a panel thing: it\n sits in the page header and has no container to be measured against. */\n@media (max-width: 700px) {\n .sub { white-space: normal; max-width: none; }\n}\n\n/* --- feed table --------------------------------------------------------- */\n.toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 10px 15px; border-bottom: 1px solid var(--line); }\n.filters { display: flex; gap: 5px; flex-wrap: wrap; }\n.filters button { font-size: 11.5px; padding: 3px 10px; border-radius: 999px; }\ninput[type=\"search\"] {\n font: inherit; font-size: 12.5px; padding: 5px 10px; min-width: 180px; flex: 1 1 180px;\n border: 1px solid var(--line); border-radius: 8px; background: var(--surface-2); color: var(--ink);\n}\ninput[type=\"search\"]::placeholder { color: var(--muted); }\n\ntable { width: 100%; border-collapse: collapse; }\nthead th {\n /* Measured at runtime — see trackHeaderHeight(). The literal is the fallback for\n the instant before the first measurement, and for the tab strip wrapping. */\n position: sticky; top: var(--header-h, 91px); z-index: 2; background: var(--surface);\n text-align: left; font-size: 11px; font-weight: 600; color: var(--muted);\n text-transform: uppercase; letter-spacing: .05em; padding: 9px 15px; border-bottom: 1px solid var(--line);\n}\ntbody td { padding: 7px 14px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }\n/* Narrow, quiet, and never the reason a row wraps: the time is for scanning down, not\n for reading across. */\ntbody td.when { color: var(--muted); font-size: 11.5px; white-space: nowrap; width: 1%; padding-right: 4px; }\ntbody tr.row { cursor: pointer; }\ntbody tr.row:hover { background: color-mix(in srgb, var(--ink) 3.5%, transparent); }\ntbody tr.row.open { background: color-mix(in srgb, var(--s1) 7%, transparent); }\ntd.edge { border-left: 3px solid transparent; padding-left: 12px; }\ntr.a-allow td.edge { border-left-color: var(--good-text); }\ntr.a-mitigate td.edge { border-left-color: var(--s2); }\ntr.a-deny td.edge { border-left-color: var(--crit); }\ntr.a-guard td.edge { border-left-color: var(--warn-text); }\n.req { font-size: 12.5px; overflow-wrap: anywhere; }\n/* The disclosure for a row.\n The whole row used to be role=\"button\", with the actor link inside it — a control\n nested in a control, which leaves a screen reader with two things to announce and no\n way to say which one Enter belongs to. The row still opens on click, because that is\n a convenience rather than a role; the keyboard gets this, which is one control with\n one name and one job. */\n.row-toggle {\n border: 0; background: none; padding: 0; margin: 0; border-radius: 4px;\n font: inherit; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12.5px; color: inherit; text-align: left; cursor: pointer; display: block; width: 100%;\n}\n.row-toggle:hover { background: none; color: inherit; border-color: transparent; }\n.req .ua { color: var(--muted); font-size: 11.5px; display: block; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 64ch; }\n.req .ua a { color: inherit; text-decoration: none; border-bottom: 1px dotted color-mix(in srgb, var(--muted) 60%, transparent); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n.req .ua a:hover { color: var(--ink); border-bottom-color: var(--ink); }\n.num { text-align: right; }\n\n/* The column ladder. Read the note beside .feed-panel above for what it is for and why\n the thresholds are in ch; this is where it has to live, below every declaration it\n overrides. */\n@container feed (max-width: 128ch) {\n thead th:nth-child(6), tbody td:nth-child(6) { display: none; }\n}\n@container feed (max-width: 119ch) {\n thead th:nth-child(1), tbody td.when { display: none; }\n}\n@container feed (max-width: 107ch) {\n .req .ua { white-space: normal; max-width: none; }\n}\n@container feed (max-width: 62ch) {\n .feed-scroll { overflow-x: auto; }\n}\n\n.badge { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; border: 1px solid transparent; }\n.b-proven { background: color-mix(in srgb, var(--proven-text) 14%, transparent); color: var(--proven-text); }\n.b-suspected { background: color-mix(in srgb, var(--warn-text) 16%, transparent); color: var(--warn-text); }\n.b-human { background: color-mix(in srgb, var(--good-text) 14%, transparent); color: var(--good-text); }\n/* The secondary ink rather than the muted one, for the same reason as the weak tier:\n muted ink on its own muted chip is 4.31:1 on a plain row and 3.70:1 on an open one. */\n.b-unknown { background: color-mix(in srgb, var(--muted) 14%, transparent); color: var(--ink-2); }\n.act { font-weight: 600; font-size: 12.5px; }\n.act-allow { color: var(--good-text); }\n.act-mitigate { color: var(--warn-text); }\n.act-deny { color: var(--crit-text); }\n.act-tag { color: var(--ink-2); }\n.sub { color: var(--muted); font-size: 11.5px; display: block; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 22ch; }\n.guard { color: var(--warn-text); font-size: 11.5px; display: block; margin-top: 3px; font-weight: 500; }\n\ntr.detail > td { background: var(--surface-2); padding: 14px 15px 16px; border-bottom: 1px solid var(--line); }\n.ev { display: grid; gap: 9px; }\n.ev-item { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 12px; align-items: start; font-size: 12.5px; }\n.tier { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; padding: 3px 6px; border-radius: 6px; text-align: center; }\n.t-certain { background: color-mix(in srgb, var(--proven-text) 15%, transparent); color: var(--proven-text); }\n.t-strong { background: color-mix(in srgb, var(--crit-text) 14%, transparent); color: var(--crit-text); }\n.t-moderate { background: color-mix(in srgb, var(--warn-text) 16%, transparent); color: var(--warn-text); }\n/* The secondary ink rather than the muted one: muted ink on its own muted chip clears\n 4.5:1 on the light surface and does not on the dark one, and this is 10px bold text\n saying how much a piece of evidence is worth. It still reads as the quiet tier — the\n only one with no hue — without being the one nobody can read at night. */\n.t-weak { background: color-mix(in srgb, var(--muted) 14%, transparent); color: var(--ink-2); }\n.ev-item.human .tier { background: color-mix(in srgb, var(--good-text) 14%, transparent); color: var(--good-text); }\n.ev-meta { color: var(--muted); font-size: 11.5px; margin-top: 2px; }\n.basis { margin-top: 5px; color: var(--ink-2); font-size: 12px; border-left: 2px solid color-mix(in srgb, var(--proven-text) 40%, transparent); padding: 2px 0 2px 9px; }\n.detail-foot { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); color: var(--muted); font-size: 11.5px; display: flex; gap: 14px; flex-wrap: wrap; }\n\n/* --- bars --------------------------------------------------------------- */\n.bars { padding: 12px 15px 14px; display: grid; gap: 8px; }\n.bar { display: grid; grid-template-columns: minmax(0, 1fr) 46px; gap: 10px; align-items: center; font-size: 12.5px; }\n.bar .track { position: relative; height: 20px; background: color-mix(in srgb, var(--ink) 5%, transparent); border-radius: 6px; overflow: hidden; }\n.bar .fill { position: absolute; inset: 0 auto 0 0; background: color-mix(in srgb, var(--s1) 26%, transparent); border-radius: 0 5px 5px 0; }\n.bar .lbl { position: relative; padding: 0 8px; line-height: 20px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.bar .v { text-align: right; color: var(--ink-2); font-size: 12px; }\n\n/* --- charts ------------------------------------------------------------- */\n.chart { padding: 12px 15px 14px; position: relative; }\n.chart svg { display: block; width: 100%; overflow: visible; }\n.legend { display: flex; gap: 14px; flex-wrap: wrap; padding: 0 15px 12px; font-size: 12px; color: var(--ink-2); }\n.legend span { display: inline-flex; align-items: center; gap: 6px; }\n/* The marker for a runtime change. A line rather than a swatch, because it is an\n instant rather than a quantity. */\n.legend .mark { width: 2px; height: 11px; background: var(--proven-text); border-radius: 1px; flex: none; }\n.swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }\n.tip {\n position: absolute; pointer-events: none; opacity: 0; transition: opacity .1s;\n background: var(--raised); border: 1px solid var(--line); border-radius: 8px;\n padding: 8px 10px; font-size: 12px; box-shadow: 0 4px 16px rgba(0,0,0,.16); z-index: 10; min-width: 128px;\n}\n.tip .t { color: var(--muted); font-size: 11px; margin-bottom: 4px; }\n.tip .r { display: flex; align-items: center; gap: 7px; justify-content: space-between; }\n.tip .r em { font-style: normal; color: var(--ink-2); display: inline-flex; align-items: center; gap: 6px; }\n.tip .r b { font-weight: 600; font-variant-numeric: tabular-nums; }\n\n.stat-row { display: flex; justify-content: space-between; gap: 12px; padding: 7px 15px; font-size: 12.5px; border-bottom: 1px solid var(--line-soft); }\n.stat-row:last-child { border-bottom: 0; }\n.stat-row .k { color: var(--muted); }\n.stat-row .v { font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 560; }\n/* The baseline a window is being read against. Quieter than the current value, and\n beside it rather than under it, because the comparison is the whole point. */\n.stat-row .v .was { color: var(--muted); font-weight: 400; font-size: 11.5px; }\n.note { padding: 13px 15px; font-size: 12.5px; color: var(--ink-2); }\n.note p { margin: 0 0 9px; }\n.note p:last-child { margin: 0; }\n.note b { color: var(--ink); }\n.empty { padding: 40px 15px; text-align: center; color: var(--muted); font-size: 13px; }\n.empty code { background: color-mix(in srgb, var(--ink) 7%, transparent); padding: .15em .4em; border-radius: 5px; }\n.detectors { padding: 4px 0; }\n.det { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 10px; padding: 8px 15px; border-bottom: 1px solid var(--line-soft); font-size: 12.5px; }\n.det:last-child { border-bottom: 0; }\n.det .d { color: var(--muted); font-size: 11.5px; margin-top: 2px; }\n.det .n { color: var(--ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; }\n.rules { padding: 10px 15px; display: flex; gap: 6px; flex-wrap: wrap; }\n.rules .chip { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n[hidden] { display: none !important; }\n/* Present to a screen reader, absent to everyone else. Clipped rather than moved\n off-screen or hidden with display:none, both of which take an element out of the\n accessibility tree along with the viewport. */\n.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; white-space: nowrap; clip-path: inset(50%); border: 0; }\n\n/* --- navigation --------------------------------------------------------- */\n/* Present for a keyboard, out of the way of everything else. */\n.skip {\n position: absolute; left: 10px; top: 10px; z-index: 40;\n background: var(--raised); color: var(--ink); border: 1px solid var(--line);\n border-radius: 8px; padding: 8px 13px; font-size: 12.5px; text-decoration: none;\n /* Hidden by clipping rather than by being moved off-screen. An element parked\n above the viewport has its whole border box outside it, and Chrome's sequential\n focus navigation skips those — so a skip link done that way is invisible to the\n one input device it exists for. Clipped to a pixel it stays in the tab order. */\n width: 1px; height: 1px; padding: 0; overflow: hidden; white-space: nowrap; clip-path: inset(50%);\n}\n.skip:focus { width: auto; height: auto; padding: 8px 13px; overflow: visible; clip-path: none; }\n\n/* A row is a control now, so it has to look like one when focused. outline-offset\n is negative because an outline drawn outside a table row is clipped by the cell. */\ntbody tr.row:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }\ntbody tr.row:focus-visible td.edge { border-left-color: var(--focus); }\n\n.search { position: relative; display: flex; flex: 1 1 180px; min-width: 180px; }\n.search input[type=\"search\"] { flex: 1 1 auto; padding-right: 26px; }\n.search kbd {\n position: absolute; right: 7px; top: 50%; transform: translateY(-50%);\n font: 500 10.5px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n color: var(--muted); background: color-mix(in srgb, var(--ink) 6%, transparent);\n border: 1px solid var(--line); border-radius: 4px; padding: 3px 5px; pointer-events: none;\n}\n/* The hint is an affordance for a mouse user who has not found the key yet. Once the\n box is in use it is noise, and it sits where the clear button wants to be. */\n.search input[type=\"search\"]:focus + kbd, .search input[type=\"search\"]:not(:placeholder-shown) + kbd { display: none; }\n/* --- added panels ------------------------------------------------------- */\n.range { display: flex; gap: 4px; }\n.range button { font-size: 11px; padding: 2px 8px; border-radius: 999px; }\n\n.kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; padding: 12px 15px; font-size: 12.5px; }\n.kv dt { color: var(--muted); }\n.kv dd { margin: 0; font-variant-numeric: tabular-nums; }\n\n.hdr { width: 100%; border-collapse: collapse; margin-top: 10px; }\n.hdr td { padding: 3px 8px 3px 0; vertical-align: top; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11.5px; border: 0; }\n.hdr td.n { color: var(--muted); white-space: nowrap; width: 1%; }\n.hdr td.v { overflow-wrap: anywhere; }\n.hdr td.v.red { color: var(--muted); font-style: italic; }\n\n.tools { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; }\n.tools button { font-size: 11.5px; padding: 4px 9px; }\n\n.editor { padding: 12px 15px 14px; display: grid; gap: 10px; }\ntextarea {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5;\n width: 100%; min-height: 320px; resize: vertical; padding: 11px 12px; tab-size: 2;\n border: 1px solid var(--line); border-radius: 9px; background: var(--surface-2); color: var(--ink);\n}\ntextarea[readonly] { opacity: .8; }\n.editor-bar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n.result { font-size: 12.5px; border-radius: 9px; padding: 10px 12px; border: 1px solid var(--line); background: var(--surface-2); }\n.result.ok { border-color: color-mix(in srgb, var(--good-text) 45%, var(--line)); }\n.result.bad { border-color: color-mix(in srgb, var(--crit-text) 45%, var(--line)); color: var(--crit-text); }\n.result.warn { border-color: color-mix(in srgb, var(--warn-text) 45%, var(--line)); }\n.result b { font-weight: 620; }\n.diff { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12px; }\n.diff th { text-align: left; font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); padding: 4px 8px 4px 0; border-bottom: 1px solid var(--line); }\n.diff td { padding: 5px 8px 5px 0; border-bottom: 1px solid var(--line-soft); vertical-align: top; }\n.diff .from { color: var(--muted); }\n.diff .to { font-weight: 600; }\n.diff .to.deny { color: var(--crit-text); }\n.diff .to.allow { color: var(--good-text); }\npre.code {\n margin: 0; padding: 11px 12px; border-radius: 9px; border: 1px solid var(--line);\n background: var(--surface-2); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11.5px; line-height: 1.5; overflow-x: auto; white-space: pre; color: var(--ink-2);\n}\n.notice { display: grid; grid-template-columns: 74px 1fr; gap: 10px; padding: 8px 15px; border-bottom: 1px solid var(--line-soft); font-size: 12.5px; }\n.notice:last-child { border-bottom: 0; }\n.notice .when { color: var(--muted); font-size: 11.5px; font-variant-numeric: tabular-nums; }\n.notice.warning .tag { color: var(--warn-text); font-weight: 600; }\n.notice.error .tag { color: var(--crit-text); font-weight: 600; }\n.dead { color: var(--muted); }\n.dead .lbl { text-decoration: line-through; }\n.pill { font-size: 10.5px; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line); color: var(--muted); margin-left: 6px; }\n.actor-head { display: flex; align-items: center; gap: 10px; padding: 12px 15px; border-bottom: 1px solid var(--line); }\n.actor-head .who { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; font-weight: 600; }\n/* --- the rule editor ----------------------------------------------------- */\n.bar-actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; padding: 10px 15px; border-bottom: 1px solid var(--line); background: var(--surface); }\n.bar-actions + .bar-actions, .rulelist + .bar-actions { border-bottom: 0; border-top: 1px solid var(--line); }\n.bar-actions .grow { flex: 1 1 auto; }\nbutton.primary { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; font-weight: 560; }\nbutton.primary:hover { background: color-mix(in srgb, var(--accent-solid) 86%, #000); border-color: transparent; color: #fff; }\nbutton.danger { color: var(--crit-text); }\nbutton.danger:hover { border-color: color-mix(in srgb, var(--crit-text) 50%, var(--line)); }\nbutton.icon { padding: 3px 8px; font-size: 12px; line-height: 1.2; }\n.dirty { font-size: 11.5px; color: var(--warn-text); font-weight: 560; display: inline-flex; align-items: center; gap: 6px; }\n.dirty::before { content: \"\"; width: 7px; height: 7px; border-radius: 50%; background: var(--warn-text); }\n\n.rulelist { display: grid; gap: 12px; padding: 14px 15px 16px; }\n.rule {\n border: 1px solid var(--line); border-radius: 11px; background: var(--surface-2);\n transition: border-color .12s, box-shadow .12s;\n}\n.rule:focus-within { border-color: color-mix(in srgb, var(--s1) 55%, var(--line)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--s1) 12%, transparent); }\n.rule.locked { background: color-mix(in srgb, var(--ink) 3%, var(--surface)); }\n.rule-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px; flex-wrap: nowrap; }\n.rule:not(.collapsed) .rule-head { border-bottom: 1px solid var(--line); }\n.rule-head .ord { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; min-width: 20px; text-align: right; }\n.rule-head input[type=\"text\"] { flex: 0 1 240px; width: auto; min-width: 130px; }\n.rule-head select { flex: 0 0 auto; }\n.chev { border: 0; background: none; padding: 2px 4px; color: var(--muted); font-size: 11px; line-height: 1; }\n.chev:hover { background: none; color: var(--ink); }\n.rule-summary { flex: 1 1 auto; display: flex; gap: 5px; flex-wrap: wrap; align-items: center; min-width: 0; overflow: hidden; }\n.rule-summary .k { font-size: 10.5px; color: var(--muted); }\n.rule-summary .t { font-size: 10.5px; padding: 1px 6px; border-radius: 999px; background: color-mix(in srgb, var(--ink) 6%, transparent); color: var(--ink-2); white-space: nowrap; }\n.rule-summary .none { font-size: 11px; color: var(--muted); font-style: italic; }\n.act-pill { font-size: 11px; font-weight: 620; padding: 2px 9px; border-radius: 999px; white-space: nowrap; }\n.act-pill.deny { background: color-mix(in srgb, var(--crit-text) 14%, transparent); color: var(--crit-text); }\n.act-pill.mitigate { background: color-mix(in srgb, var(--warn-text) 16%, transparent); color: var(--warn-text); }\n.act-pill.allow { background: color-mix(in srgb, var(--good-text) 14%, transparent); color: var(--good-text); }\n.act-pill.tag { background: color-mix(in srgb, var(--ink) 8%, transparent); color: var(--ink-2); }\n.rule-body { padding: 11px 12px 13px; display: grid; gap: 11px; }\n.rule.collapsed .rule-body { display: none; }\n.field { display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: 10px; align-items: start; font-size: 12.5px; }\n.field > label { color: var(--muted); padding-top: 5px; }\n.field-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n.hint { color: var(--muted); font-size: 11px; }\n\ninput[type=\"text\"], input[type=\"number\"], select {\n font: inherit; font-size: 12.5px; padding: 5px 9px; border-radius: 7px;\n border: 1px solid var(--line); background: var(--surface); color: var(--ink); min-width: 0;\n}\ninput[type=\"text\"] { width: 100%; }\ninput[type=\"number\"] { width: 92px; }\nselect { cursor: pointer; }\ninput::placeholder { color: color-mix(in srgb, var(--muted) 80%, transparent); }\n.mono-input { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n\n.chips-select { display: flex; gap: 4px; flex-wrap: wrap; }\n.chips-select button {\n font-size: 11px; padding: 2px 8px; border-radius: 999px; color: var(--muted);\n background: var(--surface); border: 1px solid var(--line);\n}\n.chips-select button[aria-pressed=\"true\"] { background: color-mix(in srgb, var(--s1) 14%, var(--surface)); border-color: color-mix(in srgb, var(--s1) 50%, var(--line)); color: var(--ink); font-weight: 560; }\n\n.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }\n.seg button { border: 0; border-radius: 0; padding: 4px 10px; font-size: 11.5px; background: var(--surface); }\n.seg button + button { border-left: 1px solid var(--line); }\n.seg button[aria-pressed=\"true\"] { background: color-mix(in srgb, var(--s1) 14%, var(--surface)); color: var(--ink); font-weight: 560; }\n\n.drop { outline: 2px dashed color-mix(in srgb, var(--s1) 60%, transparent); outline-offset: -6px; }\n\n/* --- the actors table, the tester and the range sets --------------------- */\n.warn-text { color: var(--warn-text); }\n#actor-rows td { font-size: 12.5px; }\n#actor-rows td.who { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; overflow-wrap: anywhere; }\n#actor-rows td.acts { text-align: right; white-space: nowrap; }\n#actor-rows td.acts button { font-size: 11px; padding: 3px 8px; margin-left: 4px; }\n/* A tag, not a warning: a cleared actor is a decision somebody made, and a metronomic\n one is a measurement. Neither is a verdict, so neither gets a verdict's colour. */\n.tagline { font-size: 11px; color: var(--muted); }\n.tagline b { color: var(--ink-2); font-weight: 600; }\n\n#test-input { min-height: 82px; }\n#tester-panel .editor { gap: 8px; }\n#tester-panel .field-row { gap: 6px; }\n#tester-panel input[type=\"text\"] { flex: 1 1 120px; min-width: 90px; }\n.assumed { color: var(--muted); font-size: 11px; margin-top: 6px; }\n\n.rangeset { border-bottom: 1px solid var(--line-soft); padding: 10px 15px; }\n.rangeset:last-child { border-bottom: 0; }\n.rangeset h3 { margin: 0 0 6px; font-size: 12.5px; font-weight: 620; display: flex; align-items: baseline; gap: 8px; }\n.rangeset h3 span { font-weight: 400; color: var(--muted); font-size: 11.5px; }\n.cidrs { display: flex; gap: 5px; flex-wrap: wrap; }\n.cidr {\n display: inline-flex; align-items: center; gap: 5px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px; background: color-mix(in srgb, var(--ink) 6%, transparent); border-radius: 999px; padding: 2px 4px 2px 9px;\n}\n.cidr button { border: 0; background: none; padding: 0 4px; font-size: 12px; line-height: 1; color: var(--muted); border-radius: 999px; }\n.cidr button:hover { background: none; color: var(--crit-text); }\n.cidr.readonly { padding-right: 9px; }\n.toasts { position: fixed; right: 18px; bottom: 18px; z-index: 60; display: grid; gap: 8px; max-width: 380px; }\n.toast {\n background: var(--raised); border: 1px solid var(--line); border-left: 3px solid var(--s1);\n border-radius: 9px; padding: 10px 12px; font-size: 12.5px; box-shadow: 0 6px 22px rgba(0,0,0,.18);\n animation: toast-in .16s ease-out;\n}\n.toast.ok { border-left-color: var(--good-text); }\n.toast.bad { border-left-color: var(--crit-text); }\n.toast.warn { border-left-color: var(--warn-text); }\n.toast b { display: block; margin-bottom: 2px; }\n.toast span { color: var(--muted); }\n@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }\n@media (prefers-reduced-motion: reduce) { .toast { animation: none; } * { transition: none !important; } }\n\n/* Forced colours — Windows High Contrast and the like.\n The browser repaints text, backgrounds and borders from the user's palette but leaves\n SVG fill and stroke alone, which is the right outcome for the two series: they stay\n tellable apart instead of collapsing into one system colour. The gridlines are the\n casualty. They are drawn in --grid, a colour picked to recede against *this* dashboard's\n background, and against a forced black one they recede to 1.4:1 — measured, on the\n statistics charts — which is not recessive, it is gone. GrayText is the palette's own\n answer to \"present but secondary\", so the grid follows the user's colours while the data\n keeps the ones that carry meaning. */\n@media (forced-colors: active) {\n .gridline { stroke: GrayText; }\n}`;\n\n/**\n * Everything between `<body>` and the boot script.\n *\n * The standalone page and the embedded element render identical markup; only what they\n * render it *into* differs.\n */\nexport const DASHBOARD_MARKUP = String.raw`\n<a class=\"skip\" href=\"#view-live\">Skip to the feed</a>\n\n<header>\n <div class=\"head-row\">\n <div class=\"brand\"><h1 id=\"title\">__TITLE__</h1> <span>bot dashboard</span></div>\n <output class=\"live\" id=\"live\" aria-live=\"polite\"><span class=\"dot\" id=\"dot\"></span><span id=\"conn\">connecting…</span></output>\n <div class=\"grow\"></div>\n <nav class=\"head-links\" id=\"links\" aria-label=\"Related\"></nav>\n <div class=\"head-actions\">\n <button id=\"theme\" aria-label=\"Switch between light and dark\">Theme</button>\n <button id=\"pause\" aria-pressed=\"false\">Pause</button>\n <button id=\"reset\" class=\"danger\" hidden>Reset</button>\n </div>\n </div>\n <div class=\"nav-row\">\n <div class=\"tabs\" role=\"tablist\" aria-label=\"Dashboard views\">\n <button class=\"tab\" id=\"tab-live\" role=\"tab\" aria-selected=\"true\" aria-controls=\"view-live\">Live feed</button>\n <button class=\"tab\" id=\"tab-actors\" role=\"tab\" aria-selected=\"false\" aria-controls=\"view-actors\">Actors</button>\n <button class=\"tab\" id=\"tab-stats\" role=\"tab\" aria-selected=\"false\" aria-controls=\"view-stats\">Statistics</button>\n <button class=\"tab\" id=\"tab-policy\" role=\"tab\" aria-selected=\"false\" aria-controls=\"view-policy\">Policy<span id=\"notice-badge\" class=\"pill\" hidden></span></button>\n </div>\n <div class=\"facts\" id=\"chips\"></div>\n <div class=\"peers\" id=\"peers\"></div>\n </div>\n</header>\n\n<main class=\"stack\">\n <section class=\"tiles\" id=\"tiles\"></section>\n\n <div id=\"view-live\" role=\"tabpanel\" aria-labelledby=\"tab-live\" class=\"stack\">\n <section class=\"panel\" id=\"actor-panel\" hidden>\n <div class=\"actor-head\">\n <span class=\"who\" id=\"actor-key\"></span>\n <span class=\"grow\"></span>\n <button id=\"actor-close\">Close</button>\n </div>\n <div class=\"two\">\n <dl class=\"kv\" id=\"actor-stats\"></dl>\n <div class=\"bars\" id=\"actor-mix\"></div>\n </div>\n <div class=\"bar-actions\" id=\"actor-actions\"></div>\n </section>\n\n <div class=\"two\">\n <section class=\"panel feed-panel\">\n <h2>Requests <span class=\"sub\" id=\"feed-count\"></span><span class=\"sub win\" id=\"feed-window\"></span><span class=\"sub warn-text\" id=\"feed-skipped\" hidden></span><button class=\"sub load-skipped\" id=\"feed-load-skipped\" type=\"button\" hidden>Load them</button></h2>\n <div class=\"toolbar\">\n <div class=\"filters\" id=\"filters\"></div>\n <div class=\"search\">\n <input type=\"search\" id=\"search\" placeholder=\"path:/api actor:203.0.113.4 -rule:allow-crawlers\" spellcheck=\"false\" autocomplete=\"off\">\n <kbd aria-hidden=\"true\">/</kbd>\n </div>\n <button id=\"feed-export\" title=\"Download every request matching this filter as replay JSONL\">Export</button>\n <div class=\"pager pager-inline\" id=\"feed-pager-top\" hidden></div>\n </div>\n <div class=\"feed-scroll\">\n <table>\n <thead>\n <tr>\n <th class=\"num\">Time</th><th>Request</th><th>Verdict</th><th class=\"num\">Score</th><th>Action</th><th class=\"num\">ms</th>\n </tr>\n </thead>\n <tbody id=\"rows\"></tbody>\n </table>\n </div>\n <div class=\"pager\" id=\"feed-pager\" hidden></div>\n <div class=\"empty\" id=\"empty\">Nothing assessed yet. Send some traffic through the handler and it appears here within a moment.</div>\n </section>\n\n <div class=\"stack\">\n <section class=\"panel\">\n <h2>Detectors firing <span class=\"sub win\"></span></h2>\n <div class=\"bars\" id=\"live-detectors\"></div>\n </section>\n <section class=\"panel\" id=\"live-actors-panel\">\n <h2>Busiest actors <span class=\"sub win\"></span></h2>\n <div class=\"bars\" id=\"live-actors\"></div>\n </section>\n <section class=\"panel\" id=\"tester-panel\">\n <h2>Test a request <span class=\"sub\">nothing is recorded</span></h2>\n <div class=\"editor\">\n <textarea id=\"test-input\" rows=\"4\" spellcheck=\"false\" autocomplete=\"off\" aria-label=\"A User-Agent, a curl command, or a block of request headers\"\n placeholder=\"Paste a User-Agent, a curl command, or a block of request headers\"></textarea>\n <div class=\"field-row\">\n <input type=\"text\" id=\"test-ip\" class=\"mono-input\" placeholder=\"client address (optional)\" aria-label=\"Client address\">\n <input type=\"text\" id=\"test-url\" class=\"mono-input\" placeholder=\"/path (optional)\" aria-label=\"Path\">\n <button id=\"test-run\" class=\"primary\">Assess</button>\n </div>\n <div class=\"result\" id=\"test-result\" hidden></div>\n </div>\n </section>\n\n <section class=\"panel\" id=\"evidence-legend\">\n <h2>Reading this</h2>\n <div class=\"note\">\n <p><b>Purple</b> evidence is <em>proven</em>: a self-declaration, a refuted\n identity, a trap, a protocol violation. Only proven evidence may deny anybody.</p>\n <p><b>Red, amber and grey</b> are probabilistic. They accumulate into a score\n and may tag, delay, rate-limit or challenge — never block.</p>\n <p>An <b>amber left edge</b> is the safety guard stopping a rule: the policy\n asked for a terminal action the evidence could not support, and got the\n fallback instead. Those rows are the library working, not failing.</p>\n </div>\n </section>\n </div>\n </div>\n </div>\n\n <div id=\"view-actors\" role=\"tabpanel\" aria-labelledby=\"tab-actors\" class=\"stack\" hidden>\n <section class=\"panel\">\n <h2>Actors in the registry <span class=\"sub\" id=\"actors-count\"></span></h2>\n <div class=\"note\" id=\"actors-note\">Everyone the engine is currently remembering, busiest first — a far larger\n population than the feed's ring, which holds requests rather than clients. This is\n what <code>cadence</code>, <code>crawl-breadth</code> and <code>rate-anomaly</code> are reading.</div>\n <div class=\"pager pager-top\" id=\"actors-pager-top\" hidden></div>\n <div class=\"feed-scroll\">\n <table>\n <thead>\n <tr>\n <th>Actor</th><th class=\"num\">Requests</th><th class=\"num\">Per min</th><th class=\"num\">Paths</th>\n <th class=\"num\">Cadence</th><th class=\"num\">Proven</th><th class=\"num\">Unsolved</th><th>State</th><th><span class=\"sr-only\">Actions</span></th>\n </tr>\n </thead>\n <tbody id=\"actor-rows\"></tbody>\n </table>\n </div>\n <div class=\"pager\" id=\"actors-pager\" hidden></div>\n <div class=\"empty\" id=\"actors-empty\" hidden>Nothing in the registry yet.</div>\n </section>\n </div>\n\n <div id=\"view-stats\" role=\"tabpanel\" aria-labelledby=\"tab-stats\" class=\"stack\" hidden>\n <div class=\"two\">\n <section class=\"panel\">\n <h2>Traffic <span class=\"range\" id=\"traffic-range\"></span><span class=\"sub\" id=\"traffic-window\"></span></h2>\n <div class=\"chart\" id=\"traffic-chart\"><svg id=\"traffic\" role=\"img\" aria-labelledby=\"traffic-alt\"></svg><div class=\"tip\" id=\"traffic-tip\"></div></div>\n <p class=\"sr-only\" id=\"traffic-alt\"></p>\n <div class=\"legend\" id=\"traffic-legend\"></div>\n </section>\n <section class=\"panel\">\n <h2>Assessment latency <span class=\"sub\">since start</span></h2>\n <div class=\"chart\" id=\"latency-chart\"><svg id=\"latency\" role=\"img\" aria-labelledby=\"latency-alt\"></svg><div class=\"tip\" id=\"latency-tip\"></div></div>\n <p class=\"sr-only\" id=\"latency-alt\"></p>\n <div class=\"legend\"><span id=\"latency-summary\"></span></div>\n </section>\n </div>\n\n <div class=\"two\">\n <section class=\"panel\">\n <h2>Score distribution <span class=\"range\" id=\"score-scope\"></span><span class=\"sub\" id=\"score-window\"></span></h2>\n <div class=\"chart\" id=\"score-chart\"><svg id=\"scores\" role=\"img\" aria-labelledby=\"score-alt\"></svg><div class=\"tip\" id=\"score-tip\"></div></div>\n <p class=\"sr-only\" id=\"score-alt\"></p>\n <div class=\"legend\" id=\"score-legend\"></div>\n </section>\n <section class=\"panel\">\n <h2>Guard stops by rule <span class=\"sub win\"></span></h2>\n <div class=\"bars\" id=\"stat-guard\"></div>\n </section>\n </div>\n\n <div class=\"grid3\">\n <section class=\"panel\"><h2>Verdicts <span class=\"sub\">since start</span></h2><div class=\"bars\" id=\"stat-verdicts\"></div></section>\n <section class=\"panel\"><h2>Actions taken <span class=\"sub\">since start</span></h2><div class=\"bars\" id=\"stat-actions\"></div></section>\n <section class=\"panel\"><h2>Bot classes <span class=\"sub\">since start</span></h2><div class=\"bars\" id=\"stat-classes\"></div></section>\n </div>\n\n <div class=\"grid3\">\n <section class=\"panel\"><h2>Detector firings <span class=\"sub\">since start</span></h2><div class=\"bars\" id=\"stat-detectors\"></div></section>\n <section class=\"panel\">\n <h2>Challenges</h2>\n <div id=\"stat-challenges\"></div>\n </section>\n <section class=\"panel\">\n <h2>Health</h2>\n <div id=\"stat-health\"></div>\n </section>\n </div>\n\n <div class=\"two\">\n <section class=\"panel\" id=\"audit-panel\">\n <h2>Audit <span class=\"sub\" id=\"audit-spans\"></span></h2>\n <div id=\"audit-body\"></div>\n </section>\n <section class=\"panel\" id=\"audit-checks-panel\">\n <h2>Checks installed</h2>\n <div class=\"detectors\" id=\"audit-checks\"></div>\n </section>\n </div>\n\n <div class=\"grid3\">\n <section class=\"panel\" id=\"identities-panel\"><h2>Identities seen <span class=\"sub win\"></span></h2><div class=\"bars\" id=\"stat-identities\"></div></section>\n <section class=\"panel\"><h2>Busiest paths <span class=\"sub win\"></span></h2><div class=\"bars\" id=\"stat-paths\"></div></section>\n <section class=\"panel\"><h2>Denied paths <span class=\"sub win\"></span></h2><div class=\"bars\" id=\"stat-denied-paths\"></div></section>\n </div>\n\n <div class=\"two\">\n <section class=\"panel\">\n <h2>Installed detectors <span class=\"sub\" id=\"detector-count\"></span></h2>\n <div class=\"detectors\" id=\"stat-detector-list\"></div>\n </section>\n <div class=\"stack\">\n <section class=\"panel\">\n <h2>Rules that fired <span class=\"sub win\"></span></h2>\n <div class=\"bars\" id=\"stat-rule-hits\"></div>\n </section>\n <section class=\"panel\">\n <h2>Bypassed <span class=\"sub\">detection never ran</span></h2>\n <div class=\"bars\" id=\"stat-bypassed\"></div>\n </section>\n </div>\n </div>\n </div>\n\n <div id=\"view-policy\" role=\"tabpanel\" aria-labelledby=\"tab-policy\" class=\"stack\" hidden>\n <div class=\"two\">\n <section class=\"panel\" id=\"editor-panel\">\n <h2>Rules <span class=\"sub\" id=\"policy-mode\"></span></h2>\n <div class=\"bar-actions\">\n <div class=\"seg\" id=\"editor-mode\">\n <button id=\"mode-gui\" aria-pressed=\"true\">Editor</button>\n <button id=\"mode-json\" aria-pressed=\"false\">JSON</button>\n </div>\n <button id=\"policy-preview\">Preview</button>\n <button id=\"policy-apply\" class=\"primary\" hidden>Apply</button>\n <button id=\"policy-revert\">Revert</button>\n <span class=\"grow\"></span>\n <button id=\"policy-import\">Import…</button>\n <button id=\"policy-export\">Export</button>\n <span class=\"dirty\" id=\"policy-dirty\" hidden>unsaved</span>\n </div>\n <div id=\"editor-gui\">\n <div class=\"rulelist\" id=\"rulelist\"></div>\n <div class=\"bar-actions\">\n <button id=\"rule-add\">+ Add rule</button>\n <button id=\"rule-expand\">Expand all</button>\n <span class=\"hint\" id=\"policy-note\"></span>\n </div>\n </div>\n <div class=\"editor\" id=\"editor-json\" hidden>\n <textarea id=\"policy-json\" spellcheck=\"false\" autocomplete=\"off\" aria-label=\"Policy rules as JSON\"></textarea>\n </div>\n <div class=\"editor\"><div class=\"result\" id=\"policy-result\" hidden></div></div>\n <input type=\"file\" id=\"policy-file\" accept=\"application/json,.json\" hidden>\n </section>\n\n <div class=\"stack\">\n <section class=\"panel\" id=\"guard-panel\">\n <h2>Guard <span class=\"sub\" id=\"guard-mode\"></span></h2>\n <div id=\"stat-policy\"></div>\n <div class=\"note\" id=\"guard-note\"></div>\n </section>\n <section class=\"panel\" id=\"ranges-panel\">\n <h2>Range sets <span class=\"sub\" id=\"ranges-mode\"></span></h2>\n <div id=\"ranges-body\"></div>\n <div class=\"note\" id=\"ranges-note\"></div>\n </section>\n <section class=\"panel\">\n <h2>Preview a shipped preset</h2>\n <div class=\"rules\" id=\"preset-buttons\"></div>\n <div class=\"note\">Runs the preset against the requests still in the window and shows what would change. Nothing is applied.</div>\n </section>\n <section class=\"panel\" id=\"changes-panel\">\n <h2>Changes <span class=\"sub\" id=\"change-count\"></span></h2>\n <div id=\"stat-changes\"></div>\n <div class=\"note\">What was applied to this process at runtime, and by whom when the dashboard's\n <code>auth</code> could say. Bounded and in memory: the durable copy is the\n <code>policy-change</code>, <code>guard-change</code>, <code>range-change</code> and\n <code>actor-change</code> events.</div>\n </section>\n <section class=\"panel\" id=\"notices-panel\">\n <h2>Notices <span class=\"sub\" id=\"notice-count\"></span></h2>\n <div id=\"stat-notices\"></div>\n </section>\n </div>\n </div>\n\n <div class=\"two\">\n <section class=\"panel\">\n <h2>Rules, in evaluation order</h2>\n <div class=\"rules\" id=\"stat-rules\"></div>\n </section>\n <section class=\"panel\" id=\"robots-panel\">\n <h2>robots.txt this policy implies</h2>\n <div class=\"editor\"><pre class=\"code\" id=\"robots-preview\"></pre><div id=\"robots-notes\"></div></div>\n </section>\n </div>\n </div>\n</main>\n\n<div class=\"toasts\" id=\"toasts\" aria-live=\"polite\"></div>\n`;\n\nconst PAGE = String.raw`<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"robots\" content=\"noindex, nofollow\">\n<title>__TITLE__ · bot dashboard</title>\n<style nonce=\"__NONCE__\">\n${DASHBOARD_CSS}\n</style>\n</head>\n<body>\n${DASHBOARD_MARKUP}\n<script nonce=\"__NONCE__\">\nwindow.__BOOTSTRAP__ = JSON.parse(__BOOT_JSON__);\n__SCRIPT__\n</script>\n</body>\n</html>\n`;\n"],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAssB7B,IAAM,mBAAmB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAA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","names":["field","oldestAt","label","select","clear","label","run","pending","timer","label","label","label","segmented","label","rendered","label","draft","label","notice","label","start","raw","raw","label","root","raw","timer","dom","label"]}
|
|
1
|
+
{"version":3,"sources":["../../src/dashboard/client/css.ts","../../src/dashboard/client/dom.ts","../../src/dashboard/client/boot.ts","../../src/dashboard/client/app.ts","../../src/dashboard/client/outcome.ts","../../src/dashboard/client/query.ts","../../src/dashboard/client/store.ts","../../src/dashboard/client/api.ts","../../src/dashboard/client/saved.ts","../../src/dashboard/client/format.ts","../../src/dashboard/client/pager.ts","../../src/dashboard/client/replay.ts","../../src/dashboard/client/actions.ts","../../src/dashboard/client/bars.ts","../../src/dashboard/client/actor.ts","../../src/dashboard/client/result.ts","../../src/dashboard/client/guard.ts","../../src/dashboard/client/ranges.ts","../../src/dashboard/client/draft.ts","../../src/dashboard/client/policy.ts","../../src/dashboard/client/feed.ts","../../src/dashboard/client/stream.ts","../../src/dashboard/client/panels.ts","../../src/dashboard/client/charts.ts","../../src/dashboard/client/registry.ts","../../src/dashboard/client/tester.ts","../../src/dashboard/client/index.ts","../../src/element/index.ts","../../src/element/config.ts","../../src/dashboard/page.ts"],"sourcesContent":["/**\n * Selector escaping, on its own so both halves can have it.\n *\n * `dom.ts` is the browser client's entry into the document and carries module state that\n * has to be initialised in order; the embeddable element imports it lazily for exactly\n * that reason. But the element also assembles selectors from developer-supplied panel ids\n * *synchronously*, before any of that has happened — so the escape lives here, in a leaf\n * with nothing behind it, rather than being duplicated or dragging the client graph in\n * early.\n */\nexport function cssEscape(value: string): string {\n return typeof CSS !== \"undefined\" && typeof CSS.escape === \"function\" ? CSS.escape(value) : String(value).replace(/[^\\w-]/g, \"\\\\$&\");\n}\n","/**\n * The page's only way of putting something in the document.\n *\n * `textContent`, always. Every string that reaches this file — a User-Agent, a path, a\n * detector's summary, a rule id somebody typed into the editor — was written by\n * somebody else, and one `innerHTML` anywhere below turns a bot's User-Agent into\n * script running in an operator's browser. A test asserts the served page contains\n * none of the four ways to do that, and `scripts/build-client.mjs` refuses to bundle\n * one.\n */\nimport { cssEscape } from \"./css.js\";\n\nexport function el<K extends keyof HTMLElementTagNameMap>(tag: K, className?: string | null, value?: string | number | null): HTMLElementTagNameMap[K] {\n const node = document.createElement(tag);\n if (className !== undefined && className !== null && className !== \"\") node.className = className;\n if (value !== undefined && value !== null) node.textContent = String(value);\n return node;\n}\n\nexport function svgEl(name: string, attributes: Record<string, string | number> = {}): SVGElement {\n const node = document.createElementNS(\"http://www.w3.org/2000/svg\", name);\n for (const [key, value] of Object.entries(attributes)) node.setAttribute(key, String(value));\n return node;\n}\n\nexport function svgText(attributes: Record<string, string | number>, text: string | number): SVGElement {\n const node = svgEl(\"text\", attributes);\n node.textContent = String(text);\n return node;\n}\n\nexport function clear(node: Node): void {\n while (node.firstChild) node.removeChild(node.firstChild);\n}\n\n/**\n * Ids are base64url today, which needs no escaping — but a selector assembled from a\n * value is only safe until somebody widens the alphabet, and `CSS.escape` costs\n * nothing. It lives in its own module because the embeddable element needs it without\n * loading this one; re-exported here so the rest of the client reads unchanged.\n */\nexport { cssEscape };\n\n/**\n * Where this dashboard lives.\n *\n * The standalone page owns the whole document; the embeddable element owns a shadow root\n * inside somebody else's. Everything below looks things up through here rather than\n * through `document`, so the same client code drives both — a shadow root is not\n * reachable from `document.getElementById`, and a document is not reachable from a\n * detached fragment.\n */\nlet root: Document | ShadowRoot | HTMLElement = typeof document === \"undefined\" ? (undefined as never) : document;\n\n/**\n * The element the theme tokens hang off.\n *\n * On the page that is the document element. Inside a shadow tree it is the host, because\n * custom properties inherit *into* a shadow root from the host and `:root` matches\n * nothing in there.\n */\nlet themeHost: HTMLElement = typeof document === \"undefined\" ? (undefined as never) : document.documentElement;\n\n/**\n * True when this dashboard is rendered inside somebody else's page.\n *\n * Three things the standalone page may do and an embedded one may not: write its tab and\n * filter into `location.hash` — which is the host's URL, and whose back button would then\n * walk through somebody's tab changes; listen for keys on the window, which hijacks the\n * host's own shortcuts; and rely on fragment navigation, which does not cross a shadow\n * boundary at all.\n */\nlet embedded = false;\n\n/** Points the client at a shadow root. Call it before the rest of the client loads. */\nexport function setRoot(node: Document | ShadowRoot | HTMLElement, host?: HTMLElement): void {\n root = node as Document | ShadowRoot;\n embedded = node !== (globalThis as unknown as { document?: Document }).document;\n if (host !== undefined) themeHost = host;\n}\n\n/** Updates the element the theme hangs off, without disturbing the rest. */\nexport function setThemeHost(host: HTMLElement): void {\n themeHost = host;\n}\n\nexport function isEmbedded(): boolean {\n return embedded;\n}\n\n/**\n * What global-ish events are listened on.\n *\n * The document when this owns the page. The dashboard's own subtree when it does not, so\n * a digit pressed on the host's page is the host's business and not a tab change here.\n */\nexport function eventTarget(): EventTarget {\n return embedded ? (root as unknown as EventTarget) : (globalThis as unknown as EventTarget);\n}\n\n/** The node every lookup runs against. */\nexport function rootNode(): Document | ShadowRoot {\n return root as Document | ShadowRoot;\n}\n\n/** The element carrying `data-theme` and the custom properties. */\nexport function themeElement(): HTMLElement {\n return themeHost;\n}\n\nexport function $(id: string): HTMLElement {\n // `querySelector` rather than `getElementById`, because the latter is a method of\n // Document and of DocumentFragment but the two are reached differently, and one call\n // that works against both is worth more than the microseconds.\n const node = root.querySelector<HTMLElement>(`#${cssEscape(id)}`);\n // Every id this is called with is one the page's own markup declares, so a miss is a\n // typo in a rename rather than a condition to handle. Failing loudly here beats a\n // null dereference three frames later.\n if (node === null || node === undefined) throw new Error(`dashboard: no element #${id}`);\n return node;\n}\n\nexport function byId<T extends HTMLElement>(id: string): T {\n return $(id) as T;\n}\n\n/** A CSS custom property's value, for the charts — which draw in the theme's own colours. */\nexport function css(name: string): string {\n return getComputedStyle(themeHost).getPropertyValue(name).trim();\n}\n\nlet sequence = 0;\n\n/**\n * Ties a `<label>` to the control it names.\n *\n * A label sitting next to an input is a label to a reader who can see the layout and\n * nothing at all to a screen reader: it announces \"edit, blank\" and moves on. Both form\n * builders on this page had that defect, and both were caught by the automated\n * accessibility pass rather than by anybody looking — which is the argument for having\n * one.\n *\n * Only for the controls a label can point at. A group of chips or a segmented control is\n * several buttons, and those carry their own names.\n */\nexport function label(text: string, control: HTMLElement | HTMLElement[]): HTMLLabelElement {\n const node = document.createElement(\"label\");\n node.textContent = text;\n const single = Array.isArray(control) ? (control.length === 1 ? control[0] : undefined) : control;\n if (single !== undefined && /^(input|select|textarea)$/i.test(single.tagName)) {\n if (single.id === \"\") single.id = `field-${++sequence}`;\n node.htmlFor = single.id;\n } else {\n // Nothing to point at, so the group gets the name instead and the text becomes\n // decoration rather than a promise the page does not keep.\n const group = Array.isArray(control) ? control : [control];\n for (const node_ of group) if (!node_.hasAttribute(\"aria-label\")) node_.setAttribute(\"aria-label\", text);\n }\n return node;\n}\n","import type { Boot } from \"./types.js\";\n\n/**\n * What the server told the page about itself.\n *\n * Stamped into the one nonced `<script>` as a JSON string and parsed here, rather than\n * fetched: the title, the mount path and — the part that matters — which sections and\n * controls this listener has. A page that had to ask for those would spend its first\n * frame drawing tabs it is about to remove.\n */\nconst FALLBACK: Boot = {\n base: \"\",\n title: \"bothandlerjs\",\n allowReset: false,\n allowEdit: false,\n allowGuardEdit: false,\n allowActing: false,\n peers: [],\n sections: { feed: true, evidence: true, actors: true, registry: true, tester: true, statistics: true, audit: true, notices: true, changes: true, policy: true, guard: true, robots: true, ranges: true },\n links: [],\n};\n\n/**\n * Reassignable, and that is the fix for a real bug rather than a style choice.\n *\n * On the served page this module evaluates after the nonced script has stamped the\n * global, so reading it once was right. Embedded, the element sets the global and *then*\n * imports the client — correct, and not something it can actually guarantee, because any\n * other code path that reaches a client module first evaluates this one first.\n *\n * One did. `disconnectedCallback` imports `stream.js` to close the stream, `stream.js`\n * imports this, and a host page that mounts, unmounts and remounts the element before the\n * first bootstrap fetch returns — React 18's development double-mount, exactly — ran that\n * import while the global was still undefined. `base` then froze at `\"\"` for the life of\n * the page, so every request the dashboard made went to the host application's own origin\n * root instead of to `src`: no dashboard, and a stray `/api/stream` arriving at somebody\n * else's router.\n *\n * These are `let` and {@link applyBoot} replaces them, so being evaluated early costs\n * nothing. ES module bindings are live and esbuild keeps them so — importers read the\n * variable, not a copy of it — which is what makes this work without every consumer\n * changing to a getter.\n */\nexport let BOOT: Boot = (globalThis as unknown as { __BOOTSTRAP__?: Boot }).__BOOTSTRAP__ ?? FALLBACK;\n\n/** Every request the page makes is relative to the mount path. */\nexport let API = BOOT.base;\n\nexport let SECTIONS = BOOT.sections;\n\n/**\n * Points this module at the payload the element fetched.\n *\n * Called before the client is imported. Ordering still matters for *drawing* — the client\n * builds its tab strip from these — but no longer for correctness of the mount path,\n * which is the part that was silently wrong.\n */\nexport function applyBoot(next: Boot): void {\n BOOT = next;\n API = next.base;\n SECTIONS = next.sections;\n}\n","import { el, rootNode } from \"./dom.js\";\nimport type { TabName } from \"./types.js\";\n\n/**\n * The handful of things every module needs to be able to do to the page as a whole.\n *\n * A mutable record filled in by `index.ts` at start-up, rather than each module\n * importing the module that owns `draw`. The feed opens the actor panel, the actor\n * panel drafts a rule into the policy editor, the policy editor switches to its own\n * tab: written as direct imports that is a cycle, and a cycle in a bundle is a\n * half-initialised module waiting to be discovered at run time. One indirection costs\n * a property lookup and makes the shape obvious.\n */\nexport interface App {\n /** Redraws the active view on the next frame. Coalesced; safe to call per event. */\n draw: () => void;\n /** Redraws immediately, for the cases where a click has to be reflected before the next frame. */\n drawNow: () => void;\n showTab: (tab: TabName, options?: { focus?: boolean; replace?: boolean; push?: boolean }) => void;\n /**\n * Puts the feed's filter state and the Actors scope into the URL, so a view can be\n * sent to somebody. `replace: false` for a discrete choice a click made, which the\n * back button should undo; the default replaces, because a history entry per keystroke\n * of the search box is not history.\n */\n syncUrl: (options?: { replace?: boolean }) => void;\n}\n\nexport const app: App = {\n draw: () => {},\n drawNow: () => {},\n showTab: () => {},\n syncUrl: () => {},\n};\n\n/**\n * A transient message in the corner.\n *\n * Lives here rather than in the policy editor because the guard form, the feed's\n * exporter and the stream all raise them now, and three copies of a six-line function\n * is how they end up looking different from each other.\n */\nexport function toast(kind: \"ok\" | \"bad\" | \"warn\", title: string, detail = \"\"): void {\n const node = el(\"div\", `toast ${kind}`);\n node.appendChild(el(\"b\", null, title));\n if (detail !== \"\") node.appendChild(el(\"span\", null, detail));\n const host = rootNode().querySelector<HTMLElement>(\"#toasts\");\n if (host === null) return;\n host.appendChild(node);\n setTimeout(() => node.remove(), 6000);\n}\n\n/** Hands the viewer a file. Used by the settings export and the feed export. */\nexport function download(text: string, filename: string, type: string): void {\n const blob = new Blob([text], { type });\n const url = URL.createObjectURL(blob);\n const anchor = el(\"a\");\n anchor.href = url;\n anchor.download = filename;\n anchor.click();\n setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\n/** Today, as a filename fragment. Every export the page produces is stamped with it. */\nexport function today(): string {\n return new Date().toISOString().slice(0, 10);\n}\n","import type { DashboardEntry } from \"./types.js\";\n\n/**\n * One request has one of four outcomes, and every part of the page agrees on which.\n *\n * Served, mitigated (something happened that a legitimate client can still get past),\n * denied, or pending — assessed, with no decision recorded, which is what `assess()`\n * on its own produces and what a row looks like for the moment between the two events.\n *\n * Pure, and exported on its own, because the table, the left edges, the timeline, the\n * actor mix and the statistics panels all classify the same way. Two of them disagreeing\n * about what a denial is would be a bug nobody could see.\n */\nexport type Outcome = \"allow\" | \"mitigate\" | \"deny\" | \"pending\";\n\nconst DENY = new Set([\"block\", \"drop\", \"redirect\"]);\nconst MITIGATE = new Set([\"challenge\", \"rate-limit\", \"delay\"]);\n\nexport function outcome(entry: Pick<DashboardEntry, \"action\">): Outcome {\n return actionKind(entry.action);\n}\n\n/** The same classification for a bare action name, which is what the rule editor has. */\nexport function actionKind(action: string | undefined): Outcome {\n if (action === undefined) return \"pending\";\n if (DENY.has(action)) return \"deny\";\n if (MITIGATE.has(action)) return \"mitigate\";\n return \"allow\";\n}\n\n/** The verdict badge: its class, and the word on it. */\nexport function verdictBadge(entry: Pick<DashboardEntry, \"verdict\" | \"bypass\">): [className: string, label: string] {\n if (entry.verdict === \"verified-bot\" || entry.verdict === \"confirmed-bot\") return [\"b-proven\", entry.verdict];\n if (entry.verdict === \"suspected-bot\") return [\"b-suspected\", \"suspected-bot\"];\n if (entry.verdict === \"human\") return [\"b-human\", \"human\"];\n return [\"b-unknown\", entry.bypass !== undefined ? `skipped · ${entry.bypass}` : \"unknown\"];\n}\n\n/**\n * How many assessments proved a *bot*.\n *\n * Not `metrics.proven`, which is what the tile used to show. That counter is honestly\n * named for what it holds — assessments resting on at least one piece of proven evidence —\n * and evidence has a direction: a client holding an operator or interaction clearance\n * produces *certain human* evidence, so it lands there too. A dashboard watching nothing\n * but cleared humans therefore read \"Proven bots: 100% of traffic\", and the same requests\n * were counted again under Unremarkable.\n *\n * The two proven-bot verdicts are the exact answer. Only the proven path can reach them —\n * the probabilistic path produces `suspected-bot`, `human` or `unknown` and nothing else —\n * so this is a rename of the truth rather than an approximation of it.\n */\nexport function provenBots(verdicts: Record<string, number>): number {\n return (verdicts[\"confirmed-bot\"] ?? 0) + (verdicts[\"verified-bot\"] ?? 0);\n}\n","import { outcome } from \"./outcome.js\";\nimport type { DashboardEntry } from \"./types.js\";\n\n/**\n * The feed's search.\n *\n * It used to be one `indexOf` over every field of a request joined into a string,\n * which answers \"does this word appear anywhere\" and nothing else. That is the wrong\n * question in the two cases somebody actually reaches for the box: an address that\n * also appears inside a User-Agent, and a path that is a prefix of ten others.\n *\n * So terms name a field, and combine:\n *\n * ```text\n * actor:203.0.113.4 -path:/health that address, except its health checks\n * verdict:human $or verdict:unknown either verdict\n * $not path:/health the same as -path:/health, spelled out\n * action:$in(block, drop) one of a set\n * action:$notin(allow, tag) none of a set\n * (verdict:human $or score:<20) $and $not path:/health\n * \"GET /api/v2/orders\" a phrase, spaces and all\n * ```\n *\n * **Adjacent terms still mean AND**, which is what narrowing means and what every\n * existing query and saved filter relies on. `$and` is available for people who would\n * rather write it than rely on juxtaposition; it parses to the same thing. `$or` binds\n * more loosely than `$and`, and `$not` more tightly than either, so\n * `a $or b $and c` reads as `a $or (b $and c)` — the conventional precedence, and the\n * reason parentheses exist for the times it is not what you meant.\n *\n * Operator names are prefixed with `$` for one reason: a bare `or` is a word that\n * appears in User-Agents and paths, and a language where an ordinary search word\n * silently becomes an operator is a language that lies about what it matched.\n *\n * **Nothing here throws.** It backs a live search box, so half-typed input is the\n * normal state rather than an error: an unclosed parenthesis, a dangling `$or`, a\n * `$in(` with nothing after it all parse to the best reading available.\n *\n * Kept free of the DOM so it can be unit-tested, which is the point: this is the piece\n * of the page most likely to be wrong in a way nobody notices.\n */\nexport interface Term {\n /** The field named before the colon, already resolved to a canonical name. */\n field: string | undefined;\n value: string;\n negated: boolean;\n /** For numeric fields: how `value` should be compared. */\n compare?: \"<\" | \">\" | \"=\" | undefined;\n /** For `$in` and `$notin`: the set. `value` holds the first, so a term always has one. */\n values?: readonly string[] | undefined;\n}\n\n/**\n * A parsed query.\n *\n * A tree rather than a list, because `$or` cannot be expressed in a list — which is\n * what the flat version of this quietly told people by ignoring it.\n */\nexport type Filter =\n | { kind: \"all\" }\n | { kind: \"term\"; term: Term }\n | { kind: \"not\"; of: Filter }\n | { kind: \"and\"; parts: readonly Filter[] }\n | { kind: \"or\"; parts: readonly Filter[] };\n\n/** Field names, plus the shorthands people type instead. */\nconst FIELDS: Record<string, string> = {\n path: \"path\",\n url: \"path\",\n actor: \"actor\",\n ip: \"actor\",\n ua: \"userAgent\",\n useragent: \"userAgent\",\n agent: \"userAgent\",\n verdict: \"verdict\",\n action: \"action\",\n rule: \"rule\",\n detector: \"detector\",\n identity: \"identity\",\n method: \"method\",\n class: \"botClass\",\n botclass: \"botClass\",\n id: \"requestId\",\n request: \"requestId\",\n bypass: \"bypass\",\n score: \"score\",\n outcome: \"outcome\",\n certain: \"certain\",\n};\n\nconst NUMERIC = new Set([\"score\"]);\n\n/** Every field name the language accepts, including the aliases. For suggestions. */\nexport const FIELD_NAMES: readonly string[] = Object.keys(FIELDS).sort();\n\n/**\n * Values worth offering for the fields that have a fixed set of them.\n *\n * Only where the set really is closed. `rule`, `identity` and `path` take anything, and\n * offering a guess there would be inventing options rather than completing them.\n */\nexport const FIELD_VALUES: Readonly<Record<string, readonly string[]>> = {\n verdict: [\"confirmed-bot\", \"verified-bot\", \"suspected-bot\", \"human\", \"unknown\"],\n class: [\"scanner\", \"scraper\", \"impersonator\", \"automation\", \"http-client\", \"declared-bot\", \"verified-bot\", \"human\", \"unknown\"],\n botclass: [\"scanner\", \"scraper\", \"impersonator\", \"automation\", \"http-client\", \"declared-bot\", \"verified-bot\", \"human\", \"unknown\"],\n action: [\"allow\", \"tag\", \"log\", \"challenge\", \"rate-limit\", \"delay\", \"block\", \"drop\", \"redirect\"],\n outcome: [\"allow\", \"mitigate\", \"deny\", \"pending\"],\n certain: [\"true\", \"false\"],\n method: [\"GET\", \"POST\", \"HEAD\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"],\n};\n\n/**\n * What to offer for the token the caret is sitting in.\n *\n * Returns the completions and the span they would replace, so the caller can put one in\n * without disturbing the rest of the query. A token that already names a field with a\n * closed value set completes the value; anything else completes the field name.\n */\nexport function suggestFor(input: string, caret: number): { options: readonly string[]; from: number; to: number } {\n const before = input.slice(0, caret);\n const start = Math.max(before.lastIndexOf(\" \") + 1, 0);\n const token = before.slice(start);\n const to = caret;\n const negated = token.startsWith(\"-\") || token.startsWith(\"!\");\n const body = negated ? token.slice(1) : token;\n const colon = body.indexOf(\":\");\n\n if (colon === -1) {\n const partial = body.toLowerCase();\n // A token that has begun with `$` can only be an operator, so nothing else is\n // offered for it — and the field names are never prefixed with one.\n if (partial.startsWith(\"$\")) {\n return { options: OPERATORS.filter((name) => name.startsWith(partial)), from: start, to };\n }\n const options = FIELD_NAMES.filter((name) => name.startsWith(partial)).map((name) => `${negated ? token[0] : \"\"}${name}:`);\n return { options, from: start, to };\n }\n\n const field = body.slice(0, colon).toLowerCase();\n const values = FIELD_VALUES[field];\n if (values === undefined) return { options: [], from: start, to };\n const partial = body.slice(colon + 1).toLowerCase();\n const prefix = `${negated ? token[0] : \"\"}${field}:`;\n // The set forms are offered alongside the values, because a field with a closed set\n // is exactly the field somebody wants two of.\n const forms = partial.startsWith(\"$\") ? [\"$in(\", \"$notin(\"].filter((form) => form.startsWith(partial)).map((form) => `${prefix}${form}`) : [];\n const options = [...forms, ...values.filter((value) => value.toLowerCase().startsWith(partial)).map((value) => `${prefix}${value}`)];\n return { options, from: start, to };\n}\n\n/** The operator words the language understands. Offered by the completions too. */\nexport const OPERATORS: readonly string[] = [\"$and\", \"$or\", \"$not\", \"$in\", \"$notin\"];\n\n/** How deep brackets may nest. Far past anything anyone writes, far short of the stack. */\nconst MAX_GROUP_DEPTH = 32;\n\ntype Token = { kind: \"word\"; text: string } | { kind: \"open\" } | { kind: \"close\" } | { kind: \"op\"; op: \"and\" | \"or\" | \"not\" };\n\n/**\n * Splits the query into words, operators and brackets.\n *\n * Quoted runs stay whole, including their spaces and any bracket inside them. A\n * `field:$in(...)` list is kept as one word rather than being split on its brackets,\n * because its brackets are part of the term and not grouping.\n */\nfunction tokenize(input: string): Token[] {\n const tokens: Token[] = [];\n let current = \"\";\n let quoted = false;\n let depth = 0;\n\n const flush = (): void => {\n if (current === \"\") return;\n const lower = current.toLowerCase();\n if (lower === \"$and\" || lower === \"$or\" || lower === \"$not\") tokens.push({ kind: \"op\", op: lower.slice(1) as \"and\" | \"or\" | \"not\" });\n else tokens.push({ kind: \"word\", text: current });\n current = \"\";\n };\n\n for (let i = 0; i < input.length; i++) {\n const character = input[i] as string;\n if (character === '\"') {\n quoted = !quoted;\n continue;\n }\n if (quoted) {\n current += character;\n continue;\n }\n // Inside a `$in(...)` list every character belongs to the word, brackets included.\n if (depth > 0) {\n current += character;\n if (character === \"(\") depth++;\n else if (character === \")\") {\n depth--;\n if (depth === 0) flush();\n }\n continue;\n }\n if (character === \"(\") {\n // A bracket touching a `$in` / `$notin` opens a set rather than a group.\n if (/\\$(in|notin)$/i.test(current)) {\n current += character;\n depth = 1;\n continue;\n }\n flush();\n tokens.push({ kind: \"open\" });\n continue;\n }\n if (character === \")\") {\n flush();\n tokens.push({ kind: \"close\" });\n continue;\n }\n if (/\\s/.test(character)) {\n flush();\n continue;\n }\n current += character;\n }\n flush();\n return tokens;\n}\n\n/** Turns one word into a leaf. Returns `undefined` for a word with nothing in it. */\nfunction toTerm(word: string): Term | undefined {\n const negated = word.startsWith(\"-\") || word.startsWith(\"!\");\n const body = negated ? word.slice(1) : word;\n if (body === \"\") return undefined;\n\n const colon = body.indexOf(\":\");\n const name = colon === -1 ? \"\" : body.slice(0, colon).toLowerCase();\n const field = FIELDS[name];\n // A colon with an unknown name in front of it is not a field term — a path can\n // contain one, and so can a User-Agent. It falls through to a free term.\n if (colon === -1 || field === undefined) return { field: undefined, value: body.toLowerCase(), negated };\n\n const rest = body.slice(colon + 1);\n // The closing bracket is stripped separately rather than matched here. Written as one\n // pattern with an optional `\\)?` the greedy `.*` swallows the bracket and hands it to\n // the last value, so `$notin(/health, /checkout)` stopped excluding `/checkout`.\n const set = /^\\$(in|notin)\\(([\\s\\S]*)$/i.exec(rest);\n if (set !== null) {\n const inside = (set[2] as string).endsWith(\")\") ? (set[2] as string).slice(0, -1) : (set[2] as string);\n const values = inside\n .split(\",\")\n .map((entry) => entry.trim().toLowerCase())\n .filter((entry) => entry !== \"\");\n // An empty set matches nothing rather than everything: `$in()` is half-typed, and\n // a filter that widens while somebody is still typing it is a filter that lies.\n const inverted = (set[1] as string).toLowerCase() === \"notin\";\n return { field, value: values[0] ?? \"\", negated: negated !== inverted, values };\n }\n\n let value = rest.toLowerCase();\n let compare: Term[\"compare\"];\n if (NUMERIC.has(field)) {\n compare = value.startsWith(\">\") ? \">\" : value.startsWith(\"<\") ? \"<\" : \"=\";\n if (compare !== \"=\") value = value.slice(1);\n }\n if (value === \"\") return undefined;\n return { field, value, negated, compare };\n}\n\n/**\n * Recursive descent over the tokens, with `$not` binding tightest and `$or` loosest.\n *\n * Tolerant by construction. A dangling operator, an unclosed bracket or a stray close\n * is dropped rather than raised, because every one of them is a query somebody is\n * halfway through typing.\n */\nfunction parseTokens(tokens: readonly Token[]): Filter {\n let at = 0;\n let depth = 0;\n\n const parseUnary = (): Filter | undefined => {\n const token = tokens[at];\n if (token === undefined) return undefined;\n if (token.kind === \"op\" && token.op === \"not\") {\n at++;\n const of = parseUnary();\n return of === undefined ? undefined : { kind: \"not\", of };\n }\n if (token.kind === \"open\") {\n at++;\n // Descending on a bracket is recursion over input somebody can paste, and this\n // parser runs on every keystroke *and* on load, because the query lives in the\n // URL. Five thousand nested brackets overflowed the stack and threw a RangeError\n // out of `setSearch` — from a link, that breaks the dashboard for whoever opens\n // it. Past the cap the bracket is simply ignored, which parses the rest at the\n // level above rather than refusing the whole query. No real query nests twice.\n if (depth >= MAX_GROUP_DEPTH) return undefined;\n depth++;\n const inner = parseOr();\n depth--;\n if (tokens[at]?.kind === \"close\") at++;\n return inner.kind === \"all\" ? undefined : inner;\n }\n if (token.kind === \"close\") return undefined;\n if (token.kind === \"op\") {\n // A leading or doubled `$and` / `$or` connects nothing. Skip it.\n at++;\n return parseUnary();\n }\n at++;\n const term = toTerm(token.text);\n return term === undefined ? undefined : { kind: \"term\", term };\n };\n\n const parseAnd = (): Filter => {\n const parts: Filter[] = [];\n while (at < tokens.length) {\n const token = tokens[at];\n if (token === undefined || token.kind === \"close\") break;\n if (token.kind === \"op\" && token.op === \"or\") break;\n if (token.kind === \"op\" && token.op === \"and\") {\n at++;\n continue;\n }\n const before = at;\n const part = parseUnary();\n if (part !== undefined) parts.push(part);\n // Nothing consumed means a token this level cannot use; step over it rather than\n // spin. Reachable from a stray bracket.\n if (at === before) at++;\n }\n return parts.length === 0 ? { kind: \"all\" } : parts.length === 1 ? (parts[0] as Filter) : { kind: \"and\", parts };\n };\n\n const parseOr = (): Filter => {\n const parts: Filter[] = [parseAnd()];\n while (tokens[at]?.kind === \"op\" && (tokens[at] as { op: string }).op === \"or\") {\n at++;\n parts.push(parseAnd());\n }\n const real = parts.filter((part) => part.kind !== \"all\");\n if (real.length === 0) return { kind: \"all\" };\n return real.length === 1 ? (real[0] as Filter) : { kind: \"or\", parts: real };\n };\n\n return parseOr();\n}\n\n/**\n * The longest query this will read.\n *\n * Parsing is linear in the input, so length is not the cliff depth was — twenty thousand\n * terms parse in about fifty milliseconds. But this runs on every keystroke, and the only\n * bound on what arrives is whatever a browser will carry in a URL, which is not a bound\n * this code should be relying on somebody else to enforce. Eight kilobytes is past any\n * query a person writes and matches the ceiling `createFacts` puts on a request target.\n */\nconst MAX_QUERY_CHARS = 8192;\n\n/** Parses a query. An empty or unparseable one matches everything. */\nexport function parseFilter(input: string): Filter {\n const trimmed = input.trim();\n return parseTokens(tokenize(trimmed.length > MAX_QUERY_CHARS ? trimmed.slice(0, MAX_QUERY_CHARS) : trimmed));\n}\n\n/** Everything about a request, as one lower-case string. What a free term is matched against. */\nexport function searchableText(entry: DashboardEntry): string {\n const parts = [\n entry.method,\n entry.path,\n entry.actor,\n entry.userAgent,\n entry.verdict,\n entry.botClass,\n entry.identity ?? \"\",\n entry.action ?? \"\",\n entry.rule ?? \"\",\n entry.requestId,\n ];\n for (const item of entry.evidence) parts.push(item.detector, item.summary);\n return parts.join(\" \").toLowerCase();\n}\n\nfunction fieldValue(entry: DashboardEntry, field: string): string {\n switch (field) {\n case \"path\":\n return entry.path;\n case \"actor\":\n return entry.actor;\n case \"userAgent\":\n return entry.userAgent;\n case \"verdict\":\n return entry.verdict;\n case \"action\":\n return entry.action ?? \"\";\n case \"rule\":\n return entry.rule ?? \"\";\n case \"identity\":\n return entry.identity ?? \"\";\n case \"method\":\n return entry.method;\n case \"botClass\":\n return entry.botClass;\n case \"requestId\":\n return entry.requestId;\n case \"bypass\":\n return entry.bypass ?? \"\";\n case \"outcome\":\n return outcome(entry);\n case \"certain\":\n return String(entry.certain);\n case \"detector\":\n return entry.evidence.map((item) => item.detector).join(\" \");\n default:\n return \"\";\n }\n}\n\nfunction matchesTerm(term: Term, entry: DashboardEntry, haystack: string): boolean {\n if (term.field === undefined) return haystack.includes(term.value);\n if (term.values !== undefined) {\n // The set form. Membership is the same substring test one value would get, so\n // `action:$in(block, drop)` reads exactly like two `action:` terms under `$or`.\n if (term.values.length === 0) return false;\n const actual = fieldValue(entry, term.field).toLowerCase();\n return term.values.some((value) => actual.includes(value));\n }\n if (term.field === \"score\") {\n const wanted = Number(term.value);\n if (Number.isNaN(wanted)) return false;\n if (term.compare === \">\") return entry.score > wanted;\n if (term.compare === \"<\") return entry.score < wanted;\n return entry.score === wanted;\n }\n return fieldValue(entry, term.field).toLowerCase().includes(term.value);\n}\n\n/** True when the query is satisfied. An empty one matches everything. */\nexport function matches(filter: Filter, entry: DashboardEntry, haystack: string): boolean {\n switch (filter.kind) {\n case \"all\":\n return true;\n case \"term\":\n return matchesTerm(filter.term, entry, haystack) !== filter.term.negated;\n case \"not\":\n return !matches(filter.of, entry, haystack);\n case \"and\":\n return filter.parts.every((part) => matches(part, entry, haystack));\n case \"or\":\n return filter.parts.some((part) => matches(part, entry, haystack));\n }\n}\n\n/** The named filter buttons, which are a second, independent narrowing. */\nexport type FilterName = \"all\" | \"proven\" | \"suspected\" | \"human\" | \"guard\" | \"deny\" | \"mitigate\" | \"allow\";\n\nexport function matchesFilter(filter: FilterName, entry: DashboardEntry): boolean {\n switch (filter) {\n case \"proven\":\n return entry.certain;\n case \"suspected\":\n return entry.verdict === \"suspected-bot\";\n case \"human\":\n return entry.verdict === \"human\";\n case \"guard\":\n return entry.downgradedFrom !== undefined;\n case \"deny\":\n return outcome(entry) === \"deny\";\n case \"mitigate\":\n return outcome(entry) === \"mitigate\";\n case \"allow\":\n return outcome(entry) === \"allow\";\n default:\n return true;\n }\n}\n","import { matches as matchesFilterExpression, matchesFilter, parseFilter, searchableText } from \"./query.js\";\nimport { outcome } from \"./outcome.js\";\nimport type { EditorRule } from \"./draft.js\";\nimport type { Filter, FilterName } from \"./query.js\";\nimport type { ActorRow, DashboardEntry, Policy, Row, Snapshot, TabName } from \"./types.js\";\n\n/** Requests the page keeps. The server's ring is smaller; this is the ceiling, not the target. */\nconst MAX_ROWS = 1000;\n\n/** Rows built into the table at once. Beyond this a feed is scrolled past, not read. */\nexport const FEED_LIMIT = 300;\n\nexport interface State {\n rows: Row[];\n byId: Map<string, Row>;\n snapshot: Snapshot | undefined;\n policy: Policy | undefined;\n /** The Actors screen's list, fetched rather than streamed. See `registry.ts`. */\n actors: ActorRow[];\n actorsTracked: number;\n /** Which population the Actors screen is listing. See `registry.feedActors`. */\n actorScope: \"tracked\" | \"feed\";\n paused: boolean;\n filter: FilterName;\n search: string;\n /** The parsed search. A tree, so `$or` means something. */\n query: Filter;\n tab: TabName;\n open: Set<string>;\n actor: string | undefined;\n rangeMs: number;\n /** Which population the score distribution is drawn from. See `charts.drawScores`. */\n scoreScope: \"run\" | \"window\";\n editorRules: EditorRule[];\n editorDirty: boolean;\n editorMode: \"gui\" | \"json\";\n guardDirty: boolean;\n /** Rows that arrived while paused, so the button can say what resuming will show. */\n bufferedWhilePaused: number;\n /** Frames the server dropped because *this* connection was too slow to take them. */\n laggedDrops: number;\n /**\n * Which page of the feed is showing, newest first, zero-based.\n *\n * Zero follows the live feed. Any other page is a position in history, so arriving\n * requests must not shuffle it under the reader — see `feedFrozen`.\n */\n feedPage: number;\n /**\n * The matching rows as they stood when the reader left page zero.\n *\n * Without this, one request arriving while somebody reads page three moves every row\n * down by one and they are silently reading different rows than the ones they were\n * looking at. Frozen on leaving page zero, dropped on returning to it.\n */\n feedFrozen: Row[] | undefined;\n /** Which page of the Actors table is showing. Paged on the server, by offset. */\n actorsPage: number;\n /** How many rows a page of each table holds. Chosen in the page, not configured. */\n feedPageSize: number;\n actorsPageSize: number;\n /**\n * The window somebody asked to look at, as absolute instants.\n *\n * Either end may be left open, which is what makes one control answer all three of the\n * questions people actually ask: from an incident until now, from the beginning until\n * something stopped, or between two moments.\n */\n fromMs: number | undefined;\n toMs: number | undefined;\n /**\n * How many skipped entries have already been fetched back and merged.\n *\n * The server's `skipped` only ever grows, so the badge subtracts this to say how many\n * are *still* missing rather than how many ever were.\n */\n caughtUp: number;\n}\n\nexport const state: State = {\n rows: [],\n byId: new Map(),\n snapshot: undefined,\n policy: undefined,\n actors: [],\n actorsTracked: 0,\n actorScope: \"tracked\",\n paused: false,\n filter: \"all\",\n search: \"\",\n query: { kind: \"all\" },\n tab: \"live\",\n open: new Set(),\n actor: undefined,\n rangeMs: 300_000,\n scoreScope: \"run\",\n editorRules: [],\n editorDirty: false,\n editorMode: \"gui\",\n guardDirty: false,\n bufferedWhilePaused: 0,\n laggedDrops: 0,\n feedPage: 0,\n feedFrozen: undefined,\n actorsPage: 0,\n feedPageSize: 50,\n actorsPageSize: 25,\n fromMs: undefined,\n toMs: undefined,\n caughtUp: 0,\n};\n\n/**\n * Files one entry into the ring.\n *\n * The same request arrives twice — once when it is assessed and again when the\n * decision lands, and again after a reconnect that replays what was missed — so this\n * is an upsert keyed by request id, and `rev` is what tells the renderer that a row on\n * screen is now stale. Rebuilding a row nothing changed about is what used to wipe a\n * text selection every time a request came in.\n */\nexport function ingest(entry: DashboardEntry): void {\n const existing = state.byId.get(entry.requestId);\n if (existing !== undefined) {\n existing.entry = entry;\n existing.rev++;\n existing.text = undefined;\n return;\n }\n const row: Row = { entry, rev: 0 };\n state.byId.set(entry.requestId, row);\n state.rows.push(row);\n if (state.rows.length > MAX_ROWS) {\n for (const dropped of state.rows.splice(0, state.rows.length - MAX_ROWS)) {\n state.byId.delete(dropped.entry.requestId);\n state.open.delete(dropped.entry.requestId);\n }\n }\n}\n\nexport function clearFeed(): void {\n state.rows = [];\n state.byId = new Map();\n state.open.clear();\n state.actor = undefined;\n state.bufferedWhilePaused = 0;\n // The lag count goes with them. It is half of the \"N not streamed\" badge — the server's\n // own `skipped` is the other half, and `FeedRing.clear()` resets that — so leaving this\n // one behind made the badge disagree with itself: a number that survived the feed it\n // described, under a tooltip promising the entries were \"still in the window, the\n // preview and the export\" when the window had just been replaced. Both ways a feed is\n // cleared lead here, and the second is the one that matters: a viewer whose stream was\n // dropped for lagging reconnects with a stale cursor and is sent a fresh backlog, so the\n // gap this counted is exactly what has just been filled in.\n state.laggedDrops = 0;\n // And the frozen page goes with them, for the same reason: it holds rows this store no\n // longer has, so a reader left on page three would be paging through a list of things\n // that are gone.\n resetPaging();\n}\n\nexport function setSearch(value: string): void {\n state.search = value;\n state.query = parseFilter(value);\n // A narrower search over a frozen page-three is somebody reading rows their filter no\n // longer selects. Any change to what matches goes back to the live first page.\n resetPaging();\n}\n\n/** Sets the window somebody is looking at. Either end may be left open. */\nexport function setTimeframe(from: number | undefined, to: number | undefined): void {\n state.fromMs = from;\n state.toMs = to;\n resetPaging();\n}\n\n/** Back to the live first page, thawed. Called whenever what matches changes. */\nexport function resetPaging(): void {\n state.feedPage = 0;\n state.feedFrozen = undefined;\n}\n\nfunction textOf(row: Row): string {\n if (row.text === undefined) row.text = searchableText(row.entry);\n return row.text;\n}\n\n/**\n * Puts the ring back in time order after a bulk merge.\n *\n * `ingest` appends, because the stream delivers in order and appending is what that\n * costs. A backlog fetched over HTTP is not in order relative to what is already held —\n * it is *older* — so merging without this leaves yesterday's requests sitting at the\n * newest end, which is where the feed reads from.\n */\nexport function sortRows(): void {\n state.rows.sort((a, b) => a.entry.at - b.entry.at);\n}\n\nexport function matches(row: Row): boolean {\n // The timeframe first, because it is a number comparison and the cheapest thing here to\n // fail on.\n if (state.fromMs !== undefined && row.entry.at < state.fromMs) return false;\n if (state.toMs !== undefined && row.entry.at > state.toMs) return false;\n return matchesFilter(state.filter, row.entry) && matchesFilterExpression(state.query, row.entry, textOf(row));\n}\n\n/**\n * Every row matching the filter, newest first.\n *\n * The whole set rather than a screenful: the feed pages through it, and the export means\n * \"what I am looking at\" rather than \"the first page of it\".\n */\nexport function matchingRows(limit = Number.POSITIVE_INFINITY): Row[] {\n const shown: Row[] = [];\n for (let i = state.rows.length - 1; i >= 0 && shown.length < limit; i--) {\n const row = state.rows[i];\n if (row !== undefined && matches(row)) shown.push(row);\n }\n return shown;\n}\n\n/** The rows the feed would draw, newest first. Also what \"export what I am looking at\" means. */\nexport function visibleRows(limit = FEED_LIMIT): Row[] {\n return matchingRows(limit);\n}\n\n/**\n * One page of the feed, and what the pager needs to describe itself.\n *\n * Page zero reads live and is recomputed every draw. Any other page reads the list as it\n * was when the reader left page zero, because a feed that renumbers itself under somebody\n * paging through it is a feed they cannot read.\n */\nexport function feedPage(size: number): { rows: Row[]; page: number; pages: number; total: number } {\n const all = state.feedPage === 0 || state.feedFrozen === undefined ? matchingRows() : state.feedFrozen;\n const pages = Math.max(1, Math.ceil(all.length / size));\n // A filter that narrows while somebody is on the last page must not leave them past the\n // end looking at nothing.\n const page = Math.min(Math.max(0, state.feedPage), pages - 1);\n if (page !== state.feedPage) state.feedPage = page;\n return { rows: all.slice(page * size, page * size + size), page, pages, total: all.length };\n}\n\n/** Moves to a page, freezing the list on the way off page zero and thawing on the way back. */\nexport function goToFeedPage(page: number): void {\n const next = Math.max(0, page);\n if (next === 0) {\n state.feedFrozen = undefined;\n } else if (state.feedFrozen === undefined) {\n state.feedFrozen = matchingRows();\n }\n state.feedPage = next;\n}\n\nexport function matchingCount(): number {\n let count = 0;\n for (const row of state.rows) if (matches(row)) count++;\n return count;\n}\n\nexport function oldestAt(): number | undefined {\n return state.rows[0]?.entry.at;\n}\n\nexport interface Aggregates {\n detectors: Map<string, number>;\n actors: Map<string, number>;\n identities: Map<string, number>;\n paths: Map<string, number>;\n deniedPaths: Map<string, number>;\n guardStops: Map<string, number>;\n ruleHits: Map<string, number>;\n bypassed: Map<string, number>;\n}\n\nfunction bump(counter: Map<string, number>, key: string): void {\n counter.set(key, (counter.get(key) ?? 0) + 1);\n}\n\n/**\n * Everything the window-scoped panels count, in one pass over the ring.\n *\n * One pass rather than eight, and — more to the point — it is now called only by the\n * screen that draws them. It used to run on every frame of the live feed to fill in\n * panels on a tab nobody was looking at.\n */\nexport function aggregate(rows: readonly Row[]): Aggregates {\n const totals: Aggregates = {\n detectors: new Map(),\n actors: new Map(),\n identities: new Map(),\n paths: new Map(),\n deniedPaths: new Map(),\n guardStops: new Map(),\n ruleHits: new Map(),\n bypassed: new Map(),\n };\n\n for (const { entry } of rows) {\n for (const item of entry.evidence) bump(totals.detectors, item.detector);\n bump(totals.actors, entry.actor);\n\n if (entry.bypass !== undefined) {\n bump(totals.bypassed, `${entry.path} (${entry.bypass})`);\n continue;\n }\n bump(totals.paths, entry.path);\n if (entry.identity !== undefined && entry.identity !== \"\") {\n // Claimed and confirmed are different facts about the same name, and a panel\n // that merged them would report a forged Googlebot as Googlebot.\n bump(totals.identities, `${entry.identity} · ${entry.verdict === \"verified-bot\" ? \"verified\" : \"claimed\"}`);\n }\n if (outcome(entry) === \"deny\") bump(totals.deniedPaths, entry.path);\n if (entry.downgradedFrom !== undefined && entry.rule !== undefined) bump(totals.guardStops, `${entry.rule} → ${entry.downgradedFrom}`);\n if (entry.rule !== undefined) bump(totals.ruleHits, entry.rule);\n }\n\n return totals;\n}\n","import { API } from \"./boot.js\";\n\n/**\n * The page's two ways of talking to its server.\n *\n * Every write is JSON with a JSON content type, and that is not a style choice: the\n * server refuses a body it did not get as `application/json` precisely because an HTML\n * form cannot send one, which is what stops somebody else's page from posting here\n * with an operator's credentials attached.\n */\nexport async function getJson<T>(path: string): Promise<T> {\n const response = await fetch(API + path);\n if (!response.ok) throw new Error(await errorFrom(response));\n return (await response.json()) as T;\n}\n\nexport interface PostResult<T> {\n ok: boolean;\n data: T;\n error?: string;\n}\n\nexport async function postJson<T>(path: string, body: unknown): Promise<PostResult<T>> {\n const response = await fetch(API + path, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n const data = (await response.json().catch(() => ({}))) as T & { error?: string };\n return response.ok ? { ok: true, data } : { ok: false, data, error: String(data.error ?? \"The server refused this.\") };\n}\n\nasync function errorFrom(response: Response): Promise<string> {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n return body.error ?? `${response.status} ${response.statusText}`;\n}\n","import { isEmbedded } from \"./dom.js\";\n\n/**\n * Filters somebody saved.\n *\n * They live in this browser rather than on the handler, and that is a decision worth\n * stating. A saved filter is a personal working note — the view one operator returns to\n * during an incident — not configuration the whole team shares. Keeping it here means it\n * needs no write endpoint, no auth decision about who may change whose, and no migration\n * when the handler restarts. The cost is that it does not follow you to another machine,\n * which is the right trade for a bookmark.\n *\n * Hidden traffic used to live here too, as a second list. It does not any more: `$not`\n * in the query says the same thing and, unlike a list in one person's browser, travels\n * in the URL — so a view with the noise taken out is something you can send somebody.\n *\n * Every read and write is wrapped: a private window, cleared site data or a browser set to\n * refuse storage all throw on access rather than returning nothing, and a dashboard that\n * cannot save a bookmark must still show the traffic.\n */\nexport interface SavedFilter {\n name: string;\n /** The query text, exactly as typed. */\n query: string;\n /** The chip it was saved with, because a filter is usually both. */\n filter: string;\n}\n\nconst FILTERS_KEY = \"bothandler.filters\";\n/** Enough for a working set; past this it is a list nobody reads. */\nconst MAX_SAVED = 50;\n\nfunction read<T>(key: string, fallback: T): T {\n // Embedded, the storage belongs to the page around us and is shared with whatever else\n // that page keeps there. Reading is harmless; writing somebody else's origin is not, so\n // neither happens.\n if (isEmbedded()) return fallback;\n try {\n const raw = localStorage.getItem(key);\n if (raw === null) return fallback;\n const parsed: unknown = JSON.parse(raw);\n return Array.isArray(parsed) ? (parsed as T) : fallback;\n } catch {\n return fallback;\n }\n}\n\nfunction write(key: string, value: unknown): void {\n if (isEmbedded()) return;\n try {\n localStorage.setItem(key, JSON.stringify(value));\n } catch {\n /* A full or refused store is not a reason to stop showing traffic. */\n }\n}\n\nexport function savedFilters(): SavedFilter[] {\n return read<SavedFilter[]>(FILTERS_KEY, []).filter((entry) => typeof entry?.name === \"string\");\n}\n\n/** Saves under a name, replacing any filter already using it. */\nexport function saveFilter(entry: SavedFilter): SavedFilter[] {\n const kept = savedFilters().filter((existing) => existing.name !== entry.name);\n const next = [entry, ...kept].slice(0, MAX_SAVED);\n write(FILTERS_KEY, next);\n return next;\n}\n\nexport function deleteFilter(name: string): SavedFilter[] {\n const next = savedFilters().filter((entry) => entry.name !== name);\n write(FILTERS_KEY, next);\n return next;\n}\n\n\n","/** Number, duration and window formatting. Pure, so the awkward cases can be tested. */\n\nconst numbers = new Intl.NumberFormat();\n\nexport function n(value: number | undefined): string {\n return numbers.format(value ?? 0);\n}\n\nexport function pct(part: number, whole: number): string {\n return whole > 0 ? `${Math.round((part / whole) * 100)}%` : \"—\";\n}\n\nexport function ms(value: number): string {\n return value >= 10 ? `${value.toFixed(1)}ms` : `${value.toFixed(2)}ms`;\n}\n\nexport function uptime(milliseconds: number): string {\n const seconds = Math.max(0, Math.round(milliseconds / 1000));\n if (seconds < 90) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n if (minutes < 90) return `${minutes}m`;\n return `${Math.round(minutes / 60)}h`;\n}\n\nexport function rangeLabel(milliseconds: number): string {\n const seconds = Math.round(milliseconds / 1000);\n if (seconds < 90) return `${seconds}s`;\n const minutes = Math.round(seconds / 60);\n return minutes < 90 ? `${minutes} min` : `${Math.round(minutes / 60)}h`;\n}\n\n/** Wall-clock time for a feed row. Local, seconds included, because a feed moves in seconds. */\n/**\n * A time, always as 24-hour HH:MM:SS.\n *\n * Built from the local components rather than handed to `toLocaleTimeString`, which\n * answers in whatever the viewer's locale prefers — so the same feed read \"4:40:46 PM\" on\n * one operator's screen and \"16:40:46\" on the next, and a dashboard two people look at\n * together should not disagree with itself about what time it is. Local time, not UTC:\n * this is the clock on the wall next to the server somebody is watching.\n */\nexport function clockTime(at: number): string {\n const when = new Date(at);\n return `${pad(when.getHours())}:${pad(when.getMinutes())}:${pad(when.getSeconds())}`;\n}\n\n/** A date, always as DD-MM-YYYY. */\nexport function clockDate(at: number): string {\n const when = new Date(at);\n return `${pad(when.getDate())}-${pad(when.getMonth() + 1)}-${when.getFullYear()}`;\n}\n\n/**\n * Both, for the places where the time alone is ambiguous.\n *\n * An actor's first sighting can be days back, and \"first seen 09:14:02\" invites the reader\n * to assume it was this morning.\n */\nexport function clockStamp(at: number): string {\n return `${clockDate(at)} ${clockTime(at)}`;\n}\n\nfunction pad(value: number): string {\n return String(value).padStart(2, \"0\");\n}\n\n/**\n * What \"this window\" actually holds, said out loud.\n *\n * Half the panels on the Statistics screen count the retained ring and half count\n * since the process started, and until now both wore the same quiet grey subtitle.\n * They are different populations — on a busy server the ring can be ninety seconds of\n * a three-week run — so comparing a panel from one against a panel from the other is a\n * mistake the page was inviting. Every \"this window\" label now says how much window\n * there is.\n */\nexport function windowLabel(count: number, oldestAt: number | undefined, now: number): string {\n if (count === 0) return \"this window · empty\";\n const span = oldestAt === undefined ? 0 : Math.max(0, now - oldestAt);\n return `last ${n(count)} requests · ${rangeLabel(span)}`;\n}\n","import { clear, el } from \"./dom.js\";\nimport { n } from \"./format.js\";\n\n/**\n * The pager both tables use.\n *\n * One renderer rather than two, because the feed pages in the browser and the Actors\n * table pages on the server and *neither of those facts belongs in the control*. What it\n * needs to know is where you are and what it should do when you press something.\n *\n * Rendered twice per table, above and below. A table of fifty rows is taller than the\n * window on most screens, and a pager only at the bottom means scrolling to the end to go\n * back to the top of the next page.\n */\nexport interface PagerModel {\n /** Zero-based. */\n page: number;\n /** What the range reads: `1–50 of 212`. Total omitted where the total is not known. */\n from: number;\n to: number;\n total?: number;\n atStart: boolean;\n atEnd: boolean;\n /** Shown next to the range when the list is being held still. */\n held?: string;\n go: (page: number) => void;\n size?: {\n current: number;\n choices: readonly number[];\n set: (size: number) => void;\n };\n}\n\n/** Which way a step goes, in words a screen reader can use. */\ninterface Step {\n to: number;\n glyph: string;\n label: string;\n disabled: boolean;\n}\n\n/**\n * What the last render of each pager was showing.\n *\n * The tables redraw on every arriving request, and rebuilding this control each time\n * replaces the very nodes somebody is reaching for: a select loses its open list mid-choice\n * and a click lands on a node that has just been detached. So it is rebuilt only when what\n * it says changes — the same argument the counter tiles already make for themselves.\n */\nconst painted = new WeakMap<HTMLElement, string>();\n\nexport function renderPager(host: HTMLElement, model: PagerModel, options: { withSize: boolean }): void {\n const signature = JSON.stringify([model.page, model.from, model.to, model.total, model.atStart, model.atEnd, model.held ?? \"\", options.withSize, model.size?.current]);\n if (painted.get(host) === signature && host.childElementCount > 0) return;\n painted.set(host, signature);\n clear(host);\n\n const steps: Step[] = [\n { to: model.page - 1, glyph: \"‹\", label: \"Previous page\", disabled: model.atStart },\n { to: model.page + 1, glyph: \"›\", label: \"Next page\", disabled: model.atEnd },\n ];\n\n const [previous, next] = steps as [Step, Step];\n host.appendChild(stepButton(previous, model));\n\n const range = model.total === undefined ? `${n(model.from)}–${n(model.to)}` : `${n(model.from)}–${n(model.to)} of ${n(model.total)}`;\n const where = el(\"span\", \"where\", range);\n // Announced on change, because the rows themselves are not a live region: somebody\n // paging with the keyboard has to hear that the page moved.\n where.setAttribute(\"aria-live\", \"polite\");\n host.appendChild(where);\n\n host.appendChild(stepButton(next, model));\n\n if (model.held !== undefined) {\n const held = el(\"span\", \"held\", model.held);\n held.title = \"New requests are still arriving and are still counted. They appear when you return to the first page.\";\n host.appendChild(held);\n }\n\n // The size chooser goes on one of the two pagers only. Two of them would be two\n // controls for one setting, which is a thing to keep in sync and a second stop in the\n // tab order that does nothing new.\n if (options.withSize && model.size !== undefined) {\n const size = model.size;\n const label = el(\"label\", \"size\");\n label.appendChild(document.createTextNode(\"Per page\"));\n const select = document.createElement(\"select\");\n for (const choice of size.choices) {\n const option = document.createElement(\"option\");\n option.value = String(choice);\n option.textContent = String(choice);\n option.selected = choice === size.current;\n select.appendChild(option);\n }\n select.addEventListener(\"change\", () => {\n const chosen = Number(select.value);\n if (Number.isFinite(chosen) && chosen > 0) size.set(chosen);\n });\n label.appendChild(select);\n host.appendChild(label);\n }\n}\n\nfunction stepButton(step: Step, model: PagerModel): HTMLElement {\n const button = el(\"button\", \"step\", step.glyph);\n const element = button as HTMLButtonElement;\n element.type = \"button\";\n element.disabled = step.disabled;\n // The glyph is decoration; the name is the label. A button whose accessible name is\n // \"›\" tells somebody using a screen reader nothing at all.\n button.setAttribute(\"aria-label\", step.label);\n button.title = step.label;\n button.addEventListener(\"click\", () => model.go(Math.max(0, step.to)));\n return button;\n}\n","import type { DashboardEntry } from \"./types.js\";\n\n/**\n * A request as a line `bothandlerjs replay` can read.\n *\n * The point of the button this backs: a verdict you disagree with on screen becomes a\n * fixture you can re-run offline, and then a corpus case that stops it coming back.\n * Values the dashboard redacted stay redacted — the shape is what replays, and a\n * session cookie is not part of the shape.\n */\nexport function replayLine(entry: DashboardEntry): string {\n const headers: Record<string, string> = {};\n for (const [name, value] of entry.headers ?? []) headers[name] = value;\n const query = Object.keys(entry.query)\n .map((name) => `${encodeURIComponent(name)}=${encodeURIComponent(entry.query[name] ?? \"\")}`)\n .join(\"&\");\n return JSON.stringify({\n method: entry.method,\n url: entry.path + (query === \"\" ? \"\" : `?${query}`),\n headers,\n ip: entry.actor,\n timestamp: new Date(entry.at).toISOString(),\n protocol: entry.protocol ?? \"https\",\n httpVersion: entry.httpVersion ?? \"1.1\",\n });\n}\n\n/** Every shown request as replay JSONL, oldest first — the whole filtered window at once. */\nexport function replayFile(entries: readonly DashboardEntry[]): string {\n return entries.map(replayLine).join(\"\\n\");\n}\n\n/** The same request as a traffic-corpus case, ready to paste into a fixture file. */\nexport function corpusCase(entry: DashboardEntry): string {\n const headers = (entry.headers ?? []).map((pair) => ` [${JSON.stringify(pair[0])}, ${JSON.stringify(pair[1])}]`).join(\",\\n\");\n return [\n \"bot({\",\n ` id: ${JSON.stringify(`case-${entry.requestId.toLowerCase().replace(/[^a-z0-9]+/g, \"-\")}`)},`,\n ` title: ${JSON.stringify(`${entry.method} ${entry.path} from ${entry.userAgent.slice(0, 60)}`)},`,\n ' audience: \"unwanted-bot\", // human | benign-bot | declared-bot | unwanted-bot | hostile | infrastructure',\n ' category: \"observed\",',\n ` provenance: \"Captured from the live dashboard on ${new Date(entry.at).toISOString().slice(0, 10)}\",`,\n \" requests: [\",\n \" {\",\n \" headers: [\",\n headers,\n \" ],\",\n ` protocol: ${JSON.stringify(entry.protocol ?? \"https\")},`,\n ` httpVersion: ${JSON.stringify(entry.httpVersion ?? \"1.1\")},`,\n ` path: ${JSON.stringify(entry.path)},`,\n \" },\",\n \" ],\",\n ` expect: { verdict: ${JSON.stringify(entry.verdict)}, certain: ${String(entry.certain)} },`,\n \"}),\",\n ].join(\"\\n\");\n}\n","import { clear, el } from \"./dom.js\";\nimport { BOOT } from \"./boot.js\";\nimport { postJson } from \"./api.js\";\nimport { toast } from \"./app.js\";\n\n/**\n * The three things you can do to one client, rather than to a class of request.\n *\n * Shared by the actor drill-down and the Actors screen, which offer the same three and\n * would otherwise offer them slightly differently.\n *\n * Allowlisting is the consequential one and it is the one that asks twice. **An\n * allowlisted address is not judged leniently; it is not judged at all** — detection\n * does not run, no evidence is produced, no rule sees it — so the button states the\n * address it is about to exempt and waits for a second click. That is deliberately not\n * a dialog: a confirmation you can dismiss without reading is a click with extra steps,\n * whereas a button that changes into the sentence it is about to enact has to be read\n * to be pressed.\n */\nexport type AfterAction = () => void;\n\n/**\n * How many controls in the Actors table are mid-interaction right now.\n *\n * The Actors screen repaints on every counters frame — every two seconds — and a repaint\n * builds new buttons. An operator who clicked \"Allowlist\", read the address it offered\n * back and reached for the second click could therefore have the button quietly reset\n * under their cursor, so that the second click armed it again instead of doing anything.\n * Which is the worst outcome available for a confirmation: it teaches people that the\n * first click does nothing and the way through is to click twice, quickly.\n *\n * The open label editor counts for the same reason and is the worse case of the two: a\n * repaint does not merely reset it, it removes the input and takes whatever had been\n * typed with it, on a timer, while somebody is still typing. That is why this counter is\n * about interaction rather than about confirmation specifically.\n *\n * So the list holds still while somebody is deciding. See `drawActors`.\n */\nlet armed = 0;\n\nexport function isConfirming(): boolean {\n return armed > 0;\n}\n\nexport function actorActions(key: string, after: AfterAction, current?: string): HTMLElement[] {\n if (!BOOT.allowActing) return [];\n\n const forget = el(\"button\", null, \"Forget\");\n forget.title = \"Discard this actor's history — the cure for a false positive that has stuck\";\n forget.addEventListener(\"click\", () => {\n void act({ key, action: \"forget\" }, `Forgot ${key}`, \"Its next request is assessed as a first request.\", after);\n });\n\n const clear = el(\"button\", null, \"Clear as human\");\n clear.title = \"Grant this actor human clearance for an hour, as though it had solved a challenge\";\n clear.addEventListener(\"click\", () => {\n void act({ key, action: \"clear\", forMs: 60 * 60_000 }, `Cleared ${key}`, \"Held as human for an hour, then reassessed.\", after);\n });\n\n // A note rather than an action: nothing in detection reads a label, which is why it sits\n // beside the buttons that change what happens rather than looking like one of them.\n const label = labelControl(key, current, after);\n\n return [confirmingButton(\"Allowlist\", `Allowlist ${key} — it stops being assessed at all`, () => allowlist(key, after)), forget, clear, label];\n}\n\n/**\n * The Label control: a button that becomes a text box, a Save and a Cancel in place.\n *\n * It used to call `prompt()`, which is the smallest amount of code that could possibly\n * work and does not work here. A sandboxed iframe blocks `prompt()` outright, and the\n * dashboard is embeddable — so on an embedded page the button did nothing at all, with\n * no error and no way to tell. Editing in place also keeps the actor's own row on screen\n * while you name it, which is the row you are naming it after.\n */\nfunction labelControl(key: string, current: string | undefined, after: AfterAction): HTMLElement {\n const host = el(\"span\", \"label-edit\");\n const button = el(\"button\", null, current === undefined ? \"Label\" : \"Relabel\");\n button.title = \"Give this actor a name, for whoever reads this next. It never changes a verdict.\";\n\n const commit = (value: string): void => {\n const trimmed = value.trim().slice(0, 120);\n void act(\n { key, action: \"label\", ...(trimmed === \"\" ? {} : { label: trimmed }) },\n trimmed === \"\" ? `Cleared the label on ${key}` : `Labelled ${key}`,\n trimmed === \"\" ? \"It shows as its address again.\" : `Shown as \"${trimmed}\" wherever it appears.`,\n after,\n );\n };\n\n button.addEventListener(\"click\", () => {\n // Whatever holds the row's actions: the table cell on the Actors screen, the button\n // bar in the drill-down. The editor takes it over for as long as it is open.\n const container = host.parentElement;\n clear(host);\n\n const input = el(\"input\", \"label-input\") as HTMLInputElement;\n input.type = \"text\";\n input.value = current ?? \"\";\n input.placeholder = \"A name for this client\";\n input.setAttribute(\"aria-label\", `Name for ${key}`);\n input.maxLength = 120;\n\n // Save and Cancel, on screen, where a `prompt()` used to put them.\n //\n // Three earlier attempts at this put the two buttons beside the row's existing four\n // and the \"In feed\" link. The cell does not wrap, so the row grew wider than the\n // panel and Save ended up past its right edge, underneath the page — visible and\n // impossible to click. The fix is not narrower buttons: it is that an open editor\n // has no business showing Allowlist and Forget at all. Somebody reaching for a name\n // is not reaching for those, and hiding them leaves more room than the editor needs.\n const save = el(\"button\", \"label-save\", \"Save\");\n save.title = \"Save this name\";\n const cancel = el(\"button\", null, \"Cancel\");\n cancel.title = \"Leave the name as it was\";\n // Still true, and still worth saying — the buttons are for people who do not know it.\n input.title = \"Enter to save, Escape to cancel\";\n\n // The table must not repaint this editor out from under the person using it.\n armed++;\n container?.classList.add(\"editing\");\n\n let settled = false;\n const finish = (accept: boolean): void => {\n if (settled) return;\n settled = true;\n armed--;\n container?.classList.remove(\"editing\");\n if (accept) commit(input.value);\n // Restoring the button is only right when nothing was saved: a save repaints the\n // whole table from the server, and touching a detached node would be a no-op that\n // looks like a bug the next time somebody reads this.\n else {\n clear(host);\n host.appendChild(button);\n }\n };\n\n for (const control of [input, save, cancel]) {\n control.addEventListener(\"keydown\", (event) => {\n const pressed = (event as KeyboardEvent).key;\n if (pressed === \"Escape\") {\n event.preventDefault();\n finish(false);\n } else if (pressed === \"Enter\" && control === input) {\n event.preventDefault();\n finish(true);\n }\n });\n }\n\n // A press inside the editor must not blur the input on its way to the click, because\n // a blur is a cancel and the cancel would land first. `mousedown` rather than\n // `pointerdown`: an earlier version used the latter, which does not fire at all when\n // a button is activated from the keyboard — so Save worked with a mouse and silently\n // did nothing with Tab and Enter.\n for (const control of [save, cancel]) control.addEventListener(\"mousedown\", (event) => event.preventDefault());\n save.addEventListener(\"click\", () => finish(true));\n cancel.addEventListener(\"click\", () => finish(false));\n\n // Clicking away is a cancel rather than a save. Naming something is deliberate, and\n // a stray click should not commit a half-typed name — but tabbing from the box to\n // Save is not clicking away, which is what `relatedTarget` is here to tell apart.\n host.addEventListener(\"focusout\", (event) => {\n const next = (event as FocusEvent).relatedTarget as Node | null;\n if (next !== null && host.contains(next)) return;\n finish(false);\n });\n\n host.append(input, save, cancel);\n input.focus();\n input.select();\n });\n\n host.appendChild(button);\n return host;\n}\n\n/**\n * A button that has to be pressed twice, and says what it will do in between.\n *\n * Reverts after a few seconds, so a half-pressed destructive action does not sit there\n * waiting for an accidental second click.\n */\nfunction confirmingButton(label: string, confirmation: string, run: () => void): HTMLButtonElement {\n const button = el(\"button\", \"danger\", label);\n let pending = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const disarm = (): void => {\n if (!pending) return;\n pending = false;\n armed--;\n button.textContent = label;\n button.className = \"danger\";\n };\n\n button.addEventListener(\"click\", () => {\n if (pending) {\n if (timer !== undefined) clearTimeout(timer);\n disarm();\n run();\n return;\n }\n pending = true;\n armed++;\n button.textContent = confirmation;\n button.className = \"danger primary\";\n timer = setTimeout(disarm, 5000);\n });\n return button;\n}\n\nasync function allowlist(key: string, after: AfterAction): Promise<void> {\n const result = await postJson<{ entries?: string[]; error?: string }>(\"/api/ranges\", { name: \"allowlist\", add: [key] });\n if (!result.ok) {\n toast(\"bad\", \"Not allowlisted\", result.error ?? \"\");\n return;\n }\n toast(\"warn\", `Allowlisted ${key}`, \"Requests from it are no longer assessed at all.\");\n after();\n}\n\nasync function act(body: { key: string; action: string; forMs?: number; label?: string }, title: string, detail: string, after: AfterAction): Promise<void> {\n const result = await postJson<{ error?: string }>(\"/api/actor\", body);\n if (!result.ok) {\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n toast(\"ok\", title, detail);\n after();\n}\n","import { clear, el } from \"./dom.js\";\nimport { n } from \"./format.js\";\n\n/** A horizontal bar list, sorted by value, capped at what a panel can show without scrolling. */\nexport function drawBars(target: HTMLElement, rows: Iterable<[string, number]>, emptyText: string): void {\n clear(target);\n const filtered = [...rows].filter(([, value]) => value > 0);\n if (filtered.length === 0) {\n target.appendChild(el(\"div\", \"note\", emptyText));\n return;\n }\n filtered.sort((a, b) => b[1] - a[1]);\n const max = filtered[0]?.[1] ?? 1;\n for (const [label, value] of filtered.slice(0, 14)) {\n const bar = el(\"div\", \"bar\");\n const track = el(\"div\", \"track\");\n const fill = el(\"div\", \"fill\");\n fill.style.width = `${Math.max(2, Math.round((value / max) * 100))}%`;\n track.appendChild(fill);\n track.appendChild(el(\"div\", \"lbl\", label));\n bar.appendChild(track);\n bar.appendChild(el(\"div\", \"v tnum\", n(value)));\n target.appendChild(bar);\n }\n}\n\n/** A record of counters as bar rows. */\nexport function pairs(record: Record<string, number> | undefined): Array<[string, number]> {\n return Object.entries(record ?? {});\n}\n","import { $, clear, el } from \"./dom.js\";\nimport { actorActions } from \"./actions.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { app } from \"./app.js\";\nimport { clockStamp, n } from \"./format.js\";\nimport { drawBars } from \"./bars.js\";\nimport { state } from \"./store.js\";\n\n/** Wires the panel's own Close button. Called once, and only when the panel exists. */\nexport function initActor(): void {\n if (!SECTIONS.actors) return;\n $(\"actor-close\").addEventListener(\"click\", closeActor);\n}\n\n/** Opens the drill-down above the feed and scrolls it into view. */\nexport function openActor(key: string): void {\n state.actor = key;\n app.drawNow();\n $(\"actor-panel\").scrollIntoView({ block: \"nearest\" });\n}\n\nfunction closeActor(): void {\n state.actor = undefined;\n app.drawNow();\n}\n\nexport function drawActor(): void {\n // The whole panel is removed when the actors section is off, so nothing below here\n // has anything to draw into.\n if (!SECTIONS.actors) return;\n const panel = $(\"actor-panel\");\n if (state.actor === undefined) {\n panel.hidden = true;\n return;\n }\n panel.hidden = false;\n $(\"actor-key\").textContent = state.actor;\n\n const mine = state.rows.filter((row) => row.entry.actor === state.actor).map((row) => row.entry);\n const latest = mine[mine.length - 1];\n const stats = latest?.actorStats;\n\n const gaps: number[] = [];\n for (let i = 1; i < mine.length; i++) gaps.push((mine[i] as { at: number }).at - (mine[i - 1] as { at: number }).at);\n const meanGap = gaps.length > 0 ? gaps.reduce((a, b) => a + b, 0) / gaps.length : 0;\n\n const box = $(\"actor-stats\");\n clear(box);\n const rows: Array<[string, string]> = [\n [\"In this window\", `${n(mine.length)} requests`],\n [\"Engine sees\", stats !== undefined ? `${n(stats.requests)} requests, ${n(stats.distinctPaths)} distinct paths` : \"—\"],\n [\"Prior confirmations\", stats !== undefined ? n(stats.priorConfirmations) : \"—\"],\n [\"Holds clearance\", stats !== undefined ? (stats.cleared ? \"yes\" : \"no\") : \"—\"],\n [\"First seen\", stats !== undefined ? clockStamp(stats.firstSeen) : \"—\"],\n [\"Mean gap\", gaps.length > 0 ? `${Math.round(meanGap)}ms over ${n(gaps.length)} gaps` : \"one request only\"],\n ];\n for (const [key, value] of rows) {\n box.appendChild(el(\"dt\", null, key));\n box.appendChild(el(\"dd\", null, value));\n }\n\n const verdicts = new Map<string, number>();\n const actions = new Map<string, number>();\n for (const entry of mine) {\n verdicts.set(entry.verdict, (verdicts.get(entry.verdict) ?? 0) + 1);\n if (entry.action !== undefined) actions.set(entry.action, (actions.get(entry.action) ?? 0) + 1);\n }\n drawBars($(\"actor-mix\"), [...verdicts, ...actions], \"Nothing yet.\");\n\n // The same three operations the Actors screen offers, on the actor already in front\n // of you. Absent entirely without `controls.editRanges`.\n const bar = $(\"actor-actions\");\n clear(bar);\n const buttons = actorActions(state.actor, () => app.drawNow());\n bar.hidden = buttons.length === 0;\n for (const button of buttons) bar.appendChild(button);\n}\n","import { $, clear, el } from \"./dom.js\";\nimport { n } from \"./format.js\";\nimport type { Preview } from \"./types.js\";\n\n/** The result box under the editor. One box, whichever of the two forms wrote to it. */\nexport function showResult(kind: \"ok\" | \"bad\" | \"warn\", build: (box: HTMLElement) => void): void {\n const box = $(\"policy-result\");\n box.hidden = false;\n box.className = `result ${kind}`;\n clear(box);\n build(box);\n}\n\n/**\n * What a candidate would have done to the traffic still in the window.\n *\n * `newDenials` is called out on its own because it is the one direction of change that\n * costs somebody their access, and it is easy to miss in a table of counts.\n */\nexport function renderPreview(preview: Preview, notes: readonly string[] = []): void {\n showResult(preview.newDenials > 0 ? \"warn\" : \"ok\", (box) => {\n const head = el(\"div\");\n head.appendChild(el(\"b\", null, `${n(preview.changed)} of ${n(preview.evaluated)} requests would be treated differently.`));\n box.appendChild(head);\n // Whatever led to this preview, kept beside its result. A draft explains what it\n // matched on and why it went last, and that explanation is worth more next to the\n // numbers than it was in the two seconds before they arrived.\n for (const note of notes) box.appendChild(el(\"div\", \"ev-meta\", note));\n if (preview.newDenials > 0) {\n box.appendChild(el(\"div\", null, `${n(preview.newDenials)} request(s) that are served today would be denied. Read the samples before applying this.`));\n }\n for (const warning of preview.warnings) box.appendChild(el(\"div\", \"ev-meta\", warning));\n\n const dead = preview.ruleHits.filter((row) => row.hits === 0 && row.rule !== \"default\");\n if (dead.length > 0) box.appendChild(el(\"div\", \"ev-meta\", `Never matched in this window: ${dead.map((row) => row.rule).join(\", \")}`));\n\n if (preview.samples.length > 0) {\n const table = el(\"table\", \"diff\");\n const head2 = el(\"tr\");\n for (const label of [\"Request\", \"Now\", \"Would be\"]) head2.appendChild(el(\"th\", null, label));\n table.appendChild(head2);\n for (const sample of preview.samples) {\n const row = el(\"tr\");\n const what = el(\"td\");\n what.appendChild(el(\"div\", \"mono\", sample.path));\n what.appendChild(el(\"div\", \"ev-meta\", `${sample.verdict} · ${sample.userAgent.slice(0, 48)}`));\n row.appendChild(what);\n const from = el(\"td\", \"from\");\n from.appendChild(el(\"div\", null, sample.from));\n from.appendChild(el(\"div\", \"ev-meta\", sample.fromRule));\n row.appendChild(from);\n const kind = sample.to === \"block\" || sample.to === \"drop\" || sample.to === \"redirect\" ? \"deny\" : sample.to === \"allow\" ? \"allow\" : \"\";\n const to = el(\"td\", `to ${kind}`);\n to.appendChild(el(\"div\", null, sample.to));\n to.appendChild(el(\"div\", \"ev-meta\", sample.toRule));\n row.appendChild(to);\n table.appendChild(row);\n }\n box.appendChild(table);\n } else if (preview.evaluated === 0) {\n box.appendChild(el(\"div\", \"ev-meta\", \"No traffic in the window to preview against — send some requests first.\"));\n }\n });\n}\n","import { $, clear, el, label } from \"./dom.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { app, toast } from \"./app.js\";\nimport { postJson } from \"./api.js\";\nimport { renderPreview, showResult } from \"./result.js\";\nimport { state } from \"./store.js\";\nimport type { Preview } from \"./types.js\";\n\n/** The guard as the form holds it. The same five fields the server accepts back. */\ninterface GuardDraft {\n falsePositivePolicy: string;\n fallbackAction: string;\n defaultAction: string;\n terminalScoreThreshold: number;\n suspectThreshold: number;\n}\n\nlet draft: GuardDraft | undefined;\n\n/**\n * What each mode actually does, in the place somebody is about to change it.\n *\n * The words `strict`, `balanced` and `aggressive` sound like a slider from cautious to\n * effective. They are not: they are three different answers to \"may a guess close the\n * door\", and the third one is \"yes\". Anybody moving this setting should read what it\n * means without going to find the documentation, so the documentation comes to them.\n */\nconst MODE_NOTES: Record<string, string> = {\n strict: \"A terminal action survives only on proven evidence. Nothing is ever denied on a guess. This is the default, and it is the claim this library makes about itself.\",\n balanced:\n \"A terminal action also survives on a probabilistic verdict that clears the score threshold with at least two independent strong signals. Real people do trip two signals — a hardened browser behind a corporate proxy is the usual pair — so this setting will eventually deny somebody who should have been served.\",\n aggressive: \"The guard is off. Every rule does exactly what it says, on proof or on suspicion alike, and the people it turns away first are the ones with the most unusual and most legitimate setups.\",\n};\n\nexport function drawGuard(): void {\n // Removed with its section, which can be off while the Policy tab as a whole is on.\n if (!SECTIONS.guard) return;\n const document_ = state.policy;\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n\n const editable = document_?.guardEditable === true;\n const panel = $(\"stat-policy\");\n clear(panel);\n\n if (!editable || document_ === undefined) {\n const rows: Array<[string, string]> = [\n [\"False-positive policy\", snapshot.policy.falsePositivePolicy],\n [\"Fallback when the guard stops a rule\", snapshot.policy.fallbackAction],\n [\"Terminal score threshold\", String(snapshot.policy.terminalScoreThreshold)],\n [\"Suspect threshold\", String(snapshot.policy.suspectThreshold)],\n [\"Action when no rule matches\", snapshot.policy.defaultAction],\n [\"Challenge configured\", snapshot.policy.challengeEnabled ? \"yes\" : \"no\"],\n [\"Range sets\", snapshot.ranges.length === 0 ? \"none\" : snapshot.ranges.map((range) => `${range.name} (${range.size})`).join(\", \")],\n ];\n for (const [key, value] of rows) {\n const line = el(\"div\", \"stat-row\");\n line.appendChild(el(\"span\", \"k\", key));\n line.appendChild(el(\"span\", \"v\", value));\n panel.appendChild(line);\n }\n $(\"guard-mode\").textContent = \"not editable here\";\n $(\"guard-note\").textContent =\n \"These are fixed at construction on this dashboard. It can change which rules exist; it cannot change how far a rule is allowed to go, because relaxing that is the one edit that can start denying people. Enable it deliberately with controls: { editGuard: true }.\";\n return;\n }\n\n if (draft === undefined) draft = { ...document_.guard };\n const vocabulary = document_.vocabulary;\n $(\"guard-mode\").textContent = state.guardDirty ? \"unsaved changes\" : \"editable\";\n $(\"guard-note\").textContent =\n \"Changing these changes what every rule is allowed to do, on the next request. Preview it against the traffic in the window first — that is the only view you get of who it would start turning away.\";\n\n panel.appendChild(\n guardField(\n // \"Mode\" rather than \"Guard\": the panel is already called that, and a field\n // repeating its own panel's name reads as a heading rather than as a control.\n \"Mode\",\n segmented(\n vocabulary.falsePositivePolicies.map((mode) => [mode, mode] as [string, string]),\n draft.falsePositivePolicy,\n (value) => {\n setDraft({ falsePositivePolicy: value });\n drawGuard();\n },\n ),\n ),\n );\n panel.appendChild(el(\"div\", \"note guard-explains\", MODE_NOTES[draft.falsePositivePolicy] ?? \"\"));\n\n panel.appendChild(\n guardField(\n \"Fallback\",\n select(vocabulary.fallbackActions, draft.fallbackAction, (value) => setDraft({ fallbackAction: value })),\n \"what a stopped rule becomes — the terminal actions are absent because a terminal fallback would deny the request the guard just protected\",\n ),\n );\n panel.appendChild(\n guardField(\n \"Default action\",\n select(vocabulary.actions, draft.defaultAction, (value) => setDraft({ defaultAction: value })),\n \"when no rule matches\",\n ),\n );\n panel.appendChild(\n guardField(\n \"Terminal score\",\n number(draft.terminalScoreThreshold, (value) => setDraft({ terminalScoreThreshold: value })),\n \"balanced mode only: the score a probabilistic verdict must clear\",\n ),\n );\n panel.appendChild(\n guardField(\n \"Suspect at\",\n number(draft.suspectThreshold, (value) => setDraft({ suspectThreshold: value })),\n \"the score at which a request becomes suspected-bot\",\n ),\n );\n\n const bar = el(\"div\", \"bar-actions\");\n const preview = el(\"button\", null, \"Preview\");\n preview.addEventListener(\"click\", () => {\n void previewGuard();\n });\n const apply = el(\"button\", \"primary\", \"Apply\");\n apply.addEventListener(\"click\", () => {\n void applyGuard();\n });\n const revert = el(\"button\", null, \"Revert\");\n revert.addEventListener(\"click\", () => {\n draft = { ...document_.guard };\n state.guardDirty = false;\n drawGuard();\n });\n bar.appendChild(preview);\n bar.appendChild(apply);\n bar.appendChild(revert);\n panel.appendChild(bar);\n}\n\nfunction setDraft(change: Partial<GuardDraft>): void {\n if (draft === undefined) return;\n draft = { ...draft, ...change };\n state.guardDirty = true;\n $(\"guard-mode\").textContent = \"unsaved changes\";\n}\n\n/** Forgets an in-progress edit, so a reload of the policy document is reflected. */\nexport function resetGuardDraft(): void {\n draft = undefined;\n state.guardDirty = false;\n}\n\n/**\n * The rules a guard preview is run against.\n *\n * The *running* rules, not whatever is sitting unsaved in the editor, so the answer is\n * about the guard alone. The server splices its predicate rules back in by position, so\n * this is the policy in force.\n */\nfunction liveRules(): unknown[] {\n return (state.policy?.rules ?? []).filter((row) => row.editable && row.rule !== undefined).map((row) => row.rule);\n}\n\nasync function previewGuard(): Promise<void> {\n if (draft === undefined) return;\n const result = await postJson<Preview & { error?: string }>(\"/api/policy/preview\", { rules: liveRules(), guard: draft });\n if (!result.ok) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, result.error ?? \"\")));\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n renderPreview(result.data, state.editorDirty ? [\"Previewed against the rules currently in force, not the unsaved edits in the editor.\"] : []);\n app.showTab(\"policy\");\n}\n\nasync function applyGuard(): Promise<void> {\n if (draft === undefined) return;\n const result = await postJson<{ guard: GuardDraft; error?: string }>(\"/api/guard\", draft);\n if (!result.ok) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, result.error ?? \"\")));\n toast(\"bad\", \"Guard unchanged\", result.error ?? \"\");\n return;\n }\n state.guardDirty = false;\n draft = { ...result.data.guard };\n if (state.policy !== undefined) state.policy.guard = { ...result.data.guard };\n toast(\"ok\", \"Guard changed\", `${result.data.guard.falsePositivePolicy}, falling back to ${result.data.guard.fallbackAction}.`);\n showResult(\"warn\", (box) => {\n box.appendChild(el(\"div\", null, \"The guard changed. It applies from the next request, and it is in the notices panel and in your logs.\"));\n if (result.data.guard.falsePositivePolicy !== \"strict\") {\n box.appendChild(\n el(\n \"div\",\n \"ev-meta\",\n \"Requests can now be denied without proof. The guard-stop count is the series to watch: every stop that no longer happens is a request that used to be recoverable and is not any more.\",\n ),\n );\n }\n });\n app.draw();\n}\n\nfunction guardField(name: string, control: HTMLElement, hint?: string): HTMLElement {\n const row = el(\"div\", \"field\");\n row.appendChild(label(name, control));\n const right = el(\"div\", \"field-row\");\n right.appendChild(control);\n if (hint !== undefined) right.appendChild(el(\"span\", \"hint\", hint));\n row.appendChild(right);\n return row;\n}\n\nfunction segmented(options: Array<[string, string]>, value: string, onChange: (value: string) => void): HTMLElement {\n const box = el(\"div\", \"seg\");\n for (const [label, option] of options) {\n const button = el(\"button\", null, label);\n button.type = \"button\";\n button.setAttribute(\"aria-pressed\", String(option === value));\n button.addEventListener(\"click\", () => onChange(option));\n box.appendChild(button);\n }\n return box;\n}\n\nfunction select(options: readonly string[], value: string, onChange: (value: string) => void): HTMLElement {\n const node = el(\"select\");\n for (const option of options) {\n const item = el(\"option\", null, option);\n item.value = option;\n if (option === value) item.selected = true;\n node.appendChild(item);\n }\n node.addEventListener(\"change\", () => onChange(node.value));\n return node;\n}\n\nfunction number(value: number, onChange: (value: number) => void): HTMLElement {\n const input = el(\"input\");\n input.type = \"number\";\n input.min = \"1\";\n input.max = \"100\";\n input.value = String(value);\n input.addEventListener(\"input\", () => {\n if (input.value !== \"\") onChange(Number(input.value));\n });\n return input;\n}\n","import { $, clear, el } from \"./dom.js\";\nimport { BOOT, SECTIONS } from \"./boot.js\";\nimport { getJson, postJson } from \"./api.js\";\nimport { n } from \"./format.js\";\nimport { toast } from \"./app.js\";\n\n/**\n * The range sets, and what is in them.\n *\n * The Guard panel used to report these as `allowlist (3)` — a count, which tells you\n * that something is exempt without telling you what. These are the lists that decide\n * which traffic is never assessed and which crawler ranges are believed, so \"what is\n * actually in it\" is the whole question.\n *\n * Editing is behind `controls.editRanges`, and the allowlist carries a warning wherever\n * it appears: an address on it is not judged leniently, it is not judged at all.\n */\ninterface RangeSet {\n name: string;\n size: number;\n entries: string[];\n}\n\ninterface RangesBody {\n ranges: RangeSet[];\n editable: boolean;\n}\n\nlet sets: RangeSet[] = [];\n\nexport async function loadRanges(): Promise<void> {\n if (!SECTIONS.ranges) return;\n try {\n const body = await getJson<RangesBody>(\"/api/ranges\");\n sets = body.ranges;\n drawRanges();\n } catch {\n /* the panel simply stays as it was */\n }\n}\n\nexport function drawRanges(): void {\n if (!SECTIONS.ranges) return;\n const body = $(\"ranges-body\");\n clear(body);\n\n $(\"ranges-mode\").textContent = BOOT.allowActing ? \"editable\" : \"read-only\";\n $(\"ranges-note\").textContent = BOOT.allowActing\n ? \"An address on the allowlist is not judged leniently — it is not judged at all. Detection does not run on it, no evidence is produced, and no rule sees it.\"\n : \"These come from the code that constructed the handler. Enable controls.editRanges to add an address from here.\";\n\n if (sets.length === 0) {\n body.appendChild(el(\"div\", \"note\", \"No range sets are configured. Add an address to the allowlist and one appears.\"));\n }\n\n for (const set of sets) {\n const block = el(\"div\", \"rangeset\");\n const heading = el(\"h3\");\n heading.appendChild(document.createTextNode(set.name));\n heading.appendChild(el(\"span\", null, `${n(set.size)} entr${set.size === 1 ? \"y\" : \"ies\"}`));\n block.appendChild(heading);\n\n const list = el(\"div\", \"cidrs\");\n for (const entry of set.entries) {\n const chip = el(\"span\", BOOT.allowActing ? \"cidr\" : \"cidr readonly\");\n chip.appendChild(document.createTextNode(entry));\n if (BOOT.allowActing) {\n const remove = el(\"button\", null, \"×\");\n remove.title = `Remove ${entry} from ${set.name}`;\n remove.setAttribute(\"aria-label\", `Remove ${entry} from ${set.name}`);\n remove.addEventListener(\"click\", () => void update(set.name, { remove: [entry] }));\n chip.appendChild(remove);\n }\n list.appendChild(chip);\n }\n if (set.entries.length === 0) list.appendChild(el(\"span\", \"hint\", \"empty\"));\n block.appendChild(list);\n body.appendChild(block);\n }\n\n if (!BOOT.allowActing) return;\n\n const form = el(\"div\", \"rangeset\");\n const row = el(\"div\", \"field-row\");\n const name = el(\"input\", \"mono-input\");\n name.type = \"text\";\n name.value = \"allowlist\";\n name.setAttribute(\"aria-label\", \"Range set\");\n name.style.maxWidth = \"150px\";\n const value = el(\"input\", \"mono-input\");\n value.type = \"text\";\n value.placeholder = \"203.0.113.0/24\";\n value.setAttribute(\"aria-label\", \"Address or CIDR to add\");\n const add = el(\"button\", null, \"Add\");\n const submit = (): void => {\n const entry = value.value.trim();\n if (entry === \"\") return;\n value.value = \"\";\n void update(name.value.trim(), { add: [entry] });\n };\n add.addEventListener(\"click\", submit);\n value.addEventListener(\"keydown\", (event) => {\n if (event.key === \"Enter\") submit();\n });\n row.appendChild(name);\n row.appendChild(value);\n row.appendChild(add);\n form.appendChild(row);\n body.appendChild(form);\n}\n\nasync function update(name: string, change: { add?: string[]; remove?: string[] }): Promise<void> {\n const result = await postJson<{ entries?: string[]; error?: string }>(\"/api/ranges\", { name, ...change });\n if (!result.ok) {\n toast(\"bad\", \"Ranges unchanged\", result.error ?? \"\");\n return;\n }\n toast(name === \"allowlist\" && change.add !== undefined ? \"warn\" : \"ok\", `“${name}” updated`, `${n(result.data.entries?.length ?? 0)} entr${result.data.entries?.length === 1 ? \"y\" : \"ies\"} now.`);\n await loadRanges();\n}\n","import type { DashboardEntry } from \"./types.js\";\n\n/** A rule as the editor holds it: plain JSON, plus the card's open/closed state. */\nexport interface EditorRule {\n id: string;\n match: Record<string, unknown>;\n action: string;\n params?: Record<string, unknown>;\n reason?: string;\n /** Editor-only: whether this card is expanded. Stripped before anything is submitted. */\n _open?: boolean;\n}\n\nexport interface Draft {\n rule: EditorRule;\n /** One line saying what the draft matched on, and why that and not something else. */\n because: string;\n}\n\n/**\n * Turns a request you are looking at into a rule you can preview.\n *\n * This is the half of the loop the dashboard was missing. The row detail could already\n * turn a request into a replay line and a corpus case — the offline loop, where a\n * verdict you disagree with becomes a test. The online loop ran the other way and had\n * no help at all: you saw an actor worth acting on, then went and hand-wrote a rule in\n * a different tab, guessing at which field would catch it.\n *\n * Three decisions in here, and all three are about not being clever on somebody's\n * behalf:\n *\n * **The action is always `tag`.** Never `block`, never `challenge`, whatever the\n * request looks like. A drafted rule is a starting point that has not been reviewed by\n * anyone, and the dashboard picking a terminal action for a request that annoyed you is\n * exactly the reflex this library exists to interrupt. The operator picks the action;\n * the guard still checks it afterwards either way.\n *\n * **It matches on the strongest thing the request actually proves**, in a fixed order:\n * a claimed-and-checked identity, else the detectors whose evidence was proven, else\n * the verdict with a score floor. Each of those is a fact about the client. A path\n * would narrow it further and is deliberately left out — the request happens to have\n * one, which is not the same as the rule being about it.\n *\n * **It goes last.** Appending is the only position that cannot change what any\n * existing rule does, because first match wins. A rule that never fires shows up as\n * \"never matched\" in the preview, which is a better way to learn it is shadowed than\n * discovering it silently took over from something else.\n */\nexport function draftRule(entry: DashboardEntry, existingIds: readonly string[] = []): Draft {\n const match: Record<string, unknown> = {};\n let because: string;\n let stem: string;\n\n const proven = entry.evidence.filter((item) => item.certainty === \"certain\" && item.direction !== \"human\");\n const detectors = [...new Set((proven.length > 0 ? proven : entry.evidence.filter((item) => item.direction !== \"human\")).map((item) => item.detector))];\n\n if (entry.identity !== undefined && entry.identity !== \"\") {\n match[\"identity\"] = [entry.identity];\n if (entry.certain) match[\"certain\"] = true;\n stem = entry.identity;\n because = entry.certain\n ? `Matched on the identity “${entry.identity}”, and on proof — so a client merely claiming that name does not match.`\n : `Matched on the claimed identity “${entry.identity}”. Nothing has verified it, so this matches anything that says so.`;\n } else if (proven.length > 0) {\n match[\"detector\"] = detectors;\n match[\"certain\"] = true;\n stem = detectors[0] ?? \"proven\";\n because = `Matched on proof from ${detectors.join(\", \")}. Only requests that carry the same proof match.`;\n } else if (detectors.length > 0) {\n match[\"verdict\"] = [entry.verdict];\n match[\"detector\"] = detectors;\n match[\"minScore\"] = Math.max(0, Math.floor(entry.score / 10) * 10);\n stem = detectors[0] ?? entry.verdict;\n because = `Matched on ${entry.verdict} at score ${String(match[\"minScore\"])} or more, from ${detectors.join(\", \")}. Every one of those is probabilistic, so the guard will not let this rule deny anybody.`;\n } else {\n match[\"verdict\"] = [entry.verdict];\n stem = entry.verdict;\n because = `Nothing fired on this request, so there is nothing sharper to match on than the verdict itself. Narrow it before you use it.`;\n }\n\n return {\n rule: {\n id: uniqueId(`from-${slug(stem)}`, existingIds),\n match,\n action: \"tag\",\n reason: \"Drafted from a request on the dashboard.\",\n _open: true,\n },\n because,\n };\n}\n\nfunction slug(value: string): string {\n const cleaned = value\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n return cleaned === \"\" ? \"request\" : cleaned.slice(0, 40);\n}\n\n/** Rule ids name every decision and every log line, so two rules sharing one is a real problem. */\nfunction uniqueId(wanted: string, taken: readonly string[]): string {\n if (!taken.includes(wanted)) return wanted;\n for (let suffix = 2; suffix < 1000; suffix++) {\n const candidate = `${wanted}-${suffix}`;\n if (!taken.includes(candidate)) return candidate;\n }\n return `${wanted}-${Date.now()}`;\n}\n","import { $, byId, clear, el, label } from \"./dom.js\";\nimport { API, SECTIONS } from \"./boot.js\";\nimport { app, download, today, toast } from \"./app.js\";\nimport { getJson, postJson } from \"./api.js\";\nimport { actionKind } from \"./outcome.js\";\nimport { drawGuard, resetGuardDraft } from \"./guard.js\";\nimport { drawRanges } from \"./ranges.js\";\nimport { n } from \"./format.js\";\nimport { renderPreview, showResult } from \"./result.js\";\nimport { state } from \"./store.js\";\nimport { draftRule } from \"./draft.js\";\nimport type { EditorRule } from \"./draft.js\";\nimport type { DashboardEntry, Policy, Preview } from \"./types.js\";\n\n/**\n * The rule editor works on a model — an array of plain rule objects — and both views\n * render from it. The GUI mutates the model in place on each keystroke, which is what\n * keeps focus and cursor position while somebody types; only structural changes (add,\n * remove, move, a different action) re-render a card.\n */\nexport async function loadPolicy(): Promise<void> {\n if (!SECTIONS.policy) return;\n try {\n const document_ = await getJson<Policy>(\"/api/policy\");\n state.policy = document_;\n if (!state.editorDirty) {\n setEditorRules(document_.rules.filter((row) => row.editable).map((row) => row.rule as unknown as EditorRule));\n }\n if (!state.guardDirty) resetGuardDraft();\n drawPolicyTab();\n } catch {\n /* the page still works without it */\n }\n}\n\nfunction setEditorRules(rules: readonly EditorRule[]): void {\n state.editorRules = JSON.parse(JSON.stringify(rules ?? [])) as EditorRule[];\n renderEditor();\n}\n\nfunction markDirty(): void {\n state.editorDirty = true;\n $(\"policy-dirty\").hidden = false;\n}\n\nfunction editorRules(): { rules?: EditorRule[]; error?: string } {\n if (state.editorMode === \"json\") {\n try {\n const parsed: unknown = JSON.parse(byId<HTMLTextAreaElement>(\"policy-json\").value);\n if (!Array.isArray(parsed)) return { error: \"The JSON must be an array of rules.\" };\n return { rules: parsed as EditorRule[] };\n } catch (error) {\n return { error: `The editor does not contain valid JSON: ${String(error)}` };\n }\n }\n return { rules: state.editorRules };\n}\n\n/** Strips the empty fields a form inevitably leaves behind, so the JSON stays readable. */\nfunction cleanRule(rule: EditorRule): Record<string, unknown> {\n const match: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(rule.match ?? {})) {\n if (value === undefined || value === null || value === \"\") continue;\n if (Array.isArray(value) && value.length === 0) continue;\n match[key] = value;\n }\n const params: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(rule.params ?? {})) {\n if (value === undefined || value === null || value === \"\") continue;\n if (key === \"limit\") {\n const limit = value as { max?: unknown; windowMs?: unknown };\n if (limit.max === undefined || limit.windowMs === undefined) continue;\n }\n params[key] = value;\n }\n const out: Record<string, unknown> = { id: rule.id, match, action: rule.action };\n if (Object.keys(params).length > 0) out[\"params\"] = params;\n if (rule.reason !== undefined && rule.reason !== \"\") out[\"reason\"] = rule.reason;\n return out;\n}\n\nfunction cleanRules(rules: readonly EditorRule[]): Array<Record<string, unknown>> {\n return rules.map(cleanRule);\n}\n\n// ---- field widgets ---------------------------------------------------------\n\nfunction field(name: string, control: HTMLElement | HTMLElement[], hint?: string): HTMLElement {\n const row = el(\"div\", \"field\");\n row.appendChild(label(name, control));\n const right = el(\"div\", \"field-row\");\n for (const node of Array.isArray(control) ? control : [control]) right.appendChild(node);\n if (hint !== undefined) right.appendChild(el(\"span\", \"hint\", hint));\n row.appendChild(right);\n return row;\n}\n\nfunction chipSelect(options: readonly string[], selected: unknown, onChange: (value: string[] | undefined) => void): HTMLElement {\n const box = el(\"div\", \"chips-select\");\n const chosen: string[] = Array.isArray(selected) ? [...(selected as string[])] : selected === undefined ? [] : [String(selected)];\n for (const option of options) {\n const button = el(\"button\", null, option);\n button.type = \"button\";\n button.setAttribute(\"aria-pressed\", String(chosen.includes(option)));\n button.addEventListener(\"click\", () => {\n const at = chosen.indexOf(option);\n if (at === -1) chosen.push(option);\n else chosen.splice(at, 1);\n button.setAttribute(\"aria-pressed\", String(at === -1));\n onChange(chosen.length === 0 ? undefined : [...chosen]);\n });\n box.appendChild(button);\n }\n return box;\n}\n\nfunction segmented<T>(options: Array<[string, T]>, value: T, onChange: (value: T) => void): HTMLElement {\n const box = el(\"div\", \"seg\");\n for (const [label, option] of options) {\n const button = el(\"button\", null, label);\n button.type = \"button\";\n button.setAttribute(\"aria-pressed\", String(option === value));\n button.addEventListener(\"click\", () => {\n for (const other of Array.from(box.children)) other.setAttribute(\"aria-pressed\", \"false\");\n button.setAttribute(\"aria-pressed\", \"true\");\n onChange(option);\n });\n box.appendChild(button);\n }\n return box;\n}\n\nfunction textInput(value: unknown, placeholder: string, onChange: (value: string | undefined) => void, mono = false): HTMLInputElement {\n const input = el(\"input\", mono ? \"mono-input\" : null);\n input.type = \"text\";\n input.value = value === undefined || value === null ? \"\" : String(value);\n input.placeholder = placeholder;\n input.addEventListener(\"input\", () => onChange(input.value.trim() === \"\" ? undefined : input.value));\n return input;\n}\n\n/** A comma-separated list, because a tag widget is a lot of keyboard surface for one path. */\nfunction listInput(value: unknown, placeholder: string, onChange: (value: string | string[] | undefined) => void): HTMLInputElement {\n const current = value === undefined ? \"\" : Array.isArray(value) ? (value as string[]).join(\", \") : String(value);\n const input = textInput(current, placeholder, () => {}, true);\n input.addEventListener(\"input\", () => {\n const parts = input.value\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n onChange(parts.length === 0 ? undefined : parts.length === 1 ? parts[0] : parts);\n });\n return input;\n}\n\nfunction numberInput(value: unknown, placeholder: string, onChange: (value: number | undefined) => void): HTMLInputElement {\n const input = el(\"input\");\n input.type = \"number\";\n input.value = value === undefined || value === null ? \"\" : String(value);\n input.placeholder = placeholder;\n input.addEventListener(\"input\", () => onChange(input.value === \"\" ? undefined : Number(input.value)));\n return input;\n}\n\n// ---- one rule --------------------------------------------------------------\n\n/** What a collapsed card says about itself: the match, in the order a reader scans it. */\nfunction matchSummary(rule: EditorRule): HTMLElement {\n const box = el(\"div\", \"rule-summary\");\n const match = rule.match ?? {};\n const parts: Array<[string, string]> = [];\n for (const key of [\"verdict\", \"botClass\", \"category\", \"identity\", \"detector\", \"method\", \"path\"]) {\n const value = match[key];\n if (value === undefined) continue;\n parts.push([key, Array.isArray(value) ? (value as string[]).join(\", \") : String(value)]);\n }\n if (match[\"certain\"] !== undefined) parts.push([\"certain\", String(match[\"certain\"])]);\n if (match[\"minScore\"] !== undefined || match[\"maxScore\"] !== undefined) {\n parts.push([\"score\", `${String(match[\"minScore\"] ?? 0)}–${String(match[\"maxScore\"] ?? 99)}`]);\n }\n if (match[\"minPriorConfirmations\"] !== undefined) parts.push([\"prior\", String(match[\"minPriorConfirmations\"])]);\n if (match[\"minUnsolvedChallenges\"] !== undefined) parts.push([\"unsolved\", String(match[\"minUnsolvedChallenges\"])]);\n\n if (parts.length === 0) {\n box.appendChild(el(\"span\", \"none\", \"matches everything\"));\n return box;\n }\n for (const [key, value] of parts.slice(0, 4)) box.appendChild(el(\"span\", \"t\", `${key}: ${value}`));\n if (parts.length > 4) box.appendChild(el(\"span\", \"k\", `+${parts.length - 4} more`));\n return box;\n}\n\nfunction ruleCard(rule: EditorRule, index: number): HTMLElement {\n const vocabulary = state.policy?.vocabulary;\n const open = rule._open === true;\n const card = el(\"div\", `rule${open ? \"\" : \" collapsed\"}`);\n\n const head = el(\"div\", \"rule-head\");\n\n // Collapsed by default: eight rules with every field on screen is a wall, and what a\n // reader wants first is the order and what each one matches.\n const chevron = el(\"button\", \"chev\", open ? \"▾\" : \"▸\");\n chevron.title = open ? \"Collapse\" : \"Expand\";\n chevron.setAttribute(\"aria-expanded\", String(open));\n chevron.addEventListener(\"click\", () => {\n rule._open = !open;\n renderEditor();\n });\n head.appendChild(chevron);\n head.appendChild(el(\"span\", \"ord\", index + 1));\n\n const id = textInput(rule.id, \"rule-id\", (value) => {\n rule.id = value ?? \"\";\n markDirty();\n }, true);\n id.setAttribute(\"aria-label\", \"Rule id\");\n head.appendChild(id);\n\n if (open) {\n const action = el(\"select\");\n action.setAttribute(\"aria-label\", \"Action\");\n for (const name of vocabulary?.actions ?? []) {\n const option = el(\"option\", null, name);\n option.value = name;\n if (name === rule.action) option.selected = true;\n action.appendChild(option);\n }\n action.addEventListener(\"change\", () => {\n rule.action = action.value;\n rule.params = {};\n markDirty();\n renderEditor();\n });\n head.appendChild(action);\n } else {\n head.appendChild(matchSummary(rule));\n head.appendChild(el(\"span\", `act-pill ${actionKind(rule.action)}`, rule.action));\n }\n\n const up = el(\"button\", \"icon\", \"↑\");\n up.title = \"Move earlier — the first matching rule wins\";\n up.addEventListener(\"click\", () => moveRule(index, -1));\n const down = el(\"button\", \"icon\", \"↓\");\n down.title = \"Move later\";\n down.addEventListener(\"click\", () => moveRule(index, 1));\n const remove = el(\"button\", \"icon danger\", \"Remove\");\n remove.addEventListener(\"click\", () => {\n state.editorRules.splice(index, 1);\n markDirty();\n renderEditor();\n });\n head.appendChild(up);\n head.appendChild(down);\n head.appendChild(remove);\n card.appendChild(head);\n\n if (!open) return card;\n\n const body = el(\"div\", \"rule-body\");\n rule.match = rule.match ?? {};\n const match = rule.match;\n\n body.appendChild(field(\"Verdict\", chipSelect(vocabulary?.verdicts ?? [], match[\"verdict\"], (value) => { match[\"verdict\"] = value; markDirty(); })));\n body.appendChild(field(\"Bot class\", chipSelect(vocabulary?.botClasses ?? [], match[\"botClass\"], (value) => { match[\"botClass\"] = value; markDirty(); })));\n body.appendChild(field(\"Category\", chipSelect(vocabulary?.categories ?? [], match[\"category\"], (value) => { match[\"category\"] = value; markDirty(); })));\n body.appendChild(field(\"Detector\", chipSelect(vocabulary?.detectors ?? [], match[\"detector\"], (value) => { match[\"detector\"] = value; markDirty(); })));\n body.appendChild(field(\"Method\", chipSelect(vocabulary?.methods ?? [], match[\"method\"], (value) => { match[\"method\"] = value; markDirty(); })));\n\n body.appendChild(\n field(\n \"Evidence\",\n segmented<boolean | undefined>(\n [\n [\"any\", undefined],\n [\"proven\", true],\n [\"unproven\", false],\n ],\n match[\"certain\"] as boolean | undefined,\n (value) => {\n match[\"certain\"] = value;\n markDirty();\n },\n ),\n \"proven means at least one piece of certain evidence — including a proven human\",\n ),\n );\n\n body.appendChild(\n field(\"Score\", [\n numberInput(match[\"minScore\"], \"min\", (value) => { match[\"minScore\"] = value; markDirty(); }),\n el(\"span\", \"hint\", \"to\"),\n numberInput(match[\"maxScore\"], \"max\", (value) => { match[\"maxScore\"] = value; markDirty(); }),\n ]),\n );\n\n body.appendChild(field(\"Identity\", listInput(match[\"identity\"], \"googlebot, gptbot\", (value) => { match[\"identity\"] = value; markDirty(); })));\n body.appendChild(field(\"Path\", listInput(match[\"path\"], \"/api/, /search\", (value) => { match[\"path\"] = value; markDirty(); }), \"a string matches as a prefix\"));\n body.appendChild(\n field(\n \"Prior bots\",\n numberInput(match[\"minPriorConfirmations\"], \"0\", (value) => { match[\"minPriorConfirmations\"] = value; markDirty(); }),\n \"times this actor was already proven a bot\",\n ),\n );\n body.appendChild(\n field(\n \"Unsolved\",\n numberInput(match[\"minUnsolvedChallenges\"], \"0\", (value) => { match[\"minUnsolvedChallenges\"] = value; markDirty(); }),\n \"challenges issued to this actor that were never answered — solving one clears the count\",\n ),\n );\n\n rule.params = rule.params ?? {};\n const params = rule.params;\n for (const node of paramFields(rule.action, params)) body.appendChild(node);\n\n body.appendChild(field(\"Reason\", textInput(rule.reason, \"shown in the decision and in your logs\", (value) => { rule.reason = value ?? \"\"; markDirty(); })));\n\n card.appendChild(body);\n return card;\n}\n\nfunction paramFields(action: string, params: Record<string, unknown>): HTMLElement[] {\n switch (action) {\n case \"block\":\n return [\n field(\"Status\", numberInput(params[\"status\"], \"403\", (value) => { params[\"status\"] = value; markDirty(); })),\n field(\"Body\", textInput(params[\"body\"], \"Automated traffic is not served here.\", (value) => { params[\"body\"] = value; markDirty(); })),\n ];\n case \"redirect\":\n return [field(\"Location\", textInput(params[\"location\"], \"/too-fast\", (value) => { params[\"location\"] = value; markDirty(); }, true))];\n case \"delay\":\n return [field(\"Delay\", numberInput(params[\"delayMs\"], \"250\", (value) => { params[\"delayMs\"] = value; markDirty(); }), \"milliseconds\")];\n case \"rate-limit\": {\n const limit = (params[\"limit\"] ?? {}) as Record<string, unknown>;\n params[\"limit\"] = limit;\n return [\n field(\"Limit\", [\n numberInput(limit[\"max\"], \"60\", (value) => { limit[\"max\"] = value; markDirty(); }),\n el(\"span\", \"hint\", \"requests per\"),\n numberInput(limit[\"windowMs\"], \"60000\", (value) => { limit[\"windowMs\"] = value; markDirty(); }),\n el(\"span\", \"hint\", \"ms\"),\n ]),\n ];\n }\n case \"custom\":\n return [field(\"Handler\", textInput(params[\"handler\"], \"handler-id\", (value) => { params[\"handler\"] = value; markDirty(); }, true), \"id of a handler you registered\")];\n default:\n return [];\n }\n}\n\nfunction lockedCard(row: { id: string; index: number }): HTMLElement {\n const card = el(\"div\", \"rule locked\");\n const head = el(\"div\", \"rule-head\");\n head.appendChild(el(\"span\", \"ord\", `#${row.index + 1}`));\n head.appendChild(el(\"span\", \"mono\", row.id));\n head.appendChild(el(\"span\", \"grow\"));\n head.appendChild(el(\"span\", \"pill\", \"predicate — locked\"));\n card.appendChild(head);\n card.appendChild(\n el(\"div\", \"rule-body\", \"This rule matches with a function, which cannot be represented here or sent over HTTP. It stays exactly as it is, at this position, whatever else you change.\"),\n );\n return card;\n}\n\nfunction moveRule(index: number, delta: number): void {\n const target = index + delta;\n if (target < 0 || target >= state.editorRules.length) return;\n const moved = state.editorRules.splice(index, 1)[0];\n if (moved === undefined) return;\n state.editorRules.splice(target, 0, moved);\n markDirty();\n renderEditor();\n}\n\nfunction renderEditor(): void {\n if (!SECTIONS.policy) return;\n const list = $(\"rulelist\");\n clear(list);\n const locked = (state.policy?.rules ?? []).filter((row) => !row.editable);\n\n if (state.editorRules.length === 0 && locked.length === 0) {\n list.appendChild(el(\"div\", \"note\", \"No rules. Every request takes the default action — add one, or import a set.\"));\n }\n\n // Locked rules are drawn at the index they hold, because that is where the server\n // will splice them back in and therefore what decides whether they run first.\n const rendered: HTMLElement[] = state.editorRules.map((rule, index) => ruleCard(rule, index));\n for (const row of locked) rendered.splice(Math.min(row.index, rendered.length), 0, lockedCard(row));\n for (const node of rendered) list.appendChild(node);\n\n if (state.editorMode === \"json\") byId<HTMLTextAreaElement>(\"policy-json\").value = JSON.stringify(cleanRules(state.editorRules), null, 2);\n}\n\nexport function drawPolicyTab(): void {\n const document_ = state.policy;\n if (document_ === undefined) return;\n\n const editable = document_.editable;\n $(\"policy-apply\").hidden = !editable;\n byId<HTMLTextAreaElement>(\"policy-json\").readOnly = !editable;\n $(\"policy-mode\").textContent = editable ? \"editable\" : \"read-only\";\n $(\"rule-add\").hidden = !editable;\n $(\"policy-import\").hidden = !editable;\n\n const preserved = document_.rules.filter((row) => !row.editable);\n let note = editable ? \"First match wins — order matters.\" : \"Read-only. Enable controls.editPolicy to change these.\";\n if (preserved.length > 0) note += ` ${preserved.length} rule(s) use a predicate function and are locked.`;\n $(\"policy-note\").textContent = note;\n\n renderPresetButtons();\n drawGuard();\n drawRanges();\n\n if (SECTIONS.robots) {\n $(\"robots-preview\").textContent = document_.robots === \"\" ? \"(this policy declines no crawler by name)\" : document_.robots;\n const notes = $(\"robots-notes\");\n clear(notes);\n for (const note_ of document_.robotsNotes) notes.appendChild(el(\"div\", \"ev-meta\", `${note_.rule}: ${note_.reason}`));\n }\n\n const rules = $(\"stat-rules\");\n clear(rules);\n const installed = state.snapshot?.rules ?? [];\n if (installed.length === 0) rules.appendChild(el(\"div\", \"note\", \"No rules configured — every request takes the default action.\"));\n else installed.forEach((rule, index) => rules.appendChild(el(\"span\", \"chip\", `${index + 1}. ${rule}`)));\n}\n\n/**\n * Starts a rule from a request the operator was looking at.\n *\n * Appended, expanded, and previewed straight away — the three things somebody would\n * otherwise do by hand between noticing an actor and having a rule they can judge.\n * Nothing is applied: this fills the editor, exactly like an import.\n *\n * It loads the policy document first, and that `await` is load-bearing rather than\n * tidy. Drafting is reachable from the Live tab, which somebody can use for an hour\n * without ever opening the Policy one — and appending to an editor that has not been\n * filled yet leaves it holding the draft *and nothing else*, while marking it dirty so\n * the load that follows will not correct it. Apply that and the running policy becomes\n * one drafted rule.\n */\nexport async function draftIntoEditor(entry: DashboardEntry): Promise<void> {\n if (state.policy === undefined) await loadPolicy();\n const drafted = draftRule(entry, state.editorRules.map((rule) => rule.id));\n state.editorRules.push(drafted.rule);\n markDirty();\n if (state.editorMode === \"json\") byId<HTMLTextAreaElement>(\"policy-json\").value = JSON.stringify(cleanRules(state.editorRules), null, 2);\n renderEditor();\n app.showTab(\"policy\");\n\n const notes = [\n `Drafted “${drafted.rule.id}”, tagging only. Nothing is applied — read it, choose the action, then apply.`,\n drafted.because,\n \"It was added last, which is the only position that cannot change what an existing rule does. Move it up with ↑ if it needs to win.\",\n ];\n // Said immediately, because the preview is a round trip and an empty box in the\n // meantime reads as nothing having happened.\n showResult(\"warn\", (box) => {\n for (const note of notes) box.appendChild(el(\"div\", note === notes[0] ? null : \"ev-meta\", note));\n });\n toast(\"ok\", \"Rule drafted\", \"In the editor, tagging only, not applied.\");\n await runPreview(notes);\n}\n\n// ---- mode switch, add, expand ----------------------------------------------\n\nfunction switchToGui(): void {\n if (state.editorMode === \"gui\") return;\n const parsed = editorRules();\n if (parsed.error !== undefined) {\n toast(\"bad\", \"That JSON will not parse\", parsed.error);\n return;\n }\n state.editorRules = parsed.rules ?? [];\n state.editorMode = \"gui\";\n $(\"mode-gui\").setAttribute(\"aria-pressed\", \"true\");\n $(\"mode-json\").setAttribute(\"aria-pressed\", \"false\");\n $(\"editor-gui\").hidden = false;\n $(\"editor-json\").hidden = true;\n renderEditor();\n}\n\nfunction switchToJson(): void {\n state.editorMode = \"json\";\n $(\"mode-gui\").setAttribute(\"aria-pressed\", \"false\");\n $(\"mode-json\").setAttribute(\"aria-pressed\", \"true\");\n $(\"editor-gui\").hidden = true;\n $(\"editor-json\").hidden = false;\n byId<HTMLTextAreaElement>(\"policy-json\").value = JSON.stringify(cleanRules(state.editorRules), null, 2);\n}\n\n// ---- import and export ------------------------------------------------------\n\nasync function exportSettings(): Promise<void> {\n // The whole settings document, not just the rules: a file that records the detectors,\n // ranges and guard alongside them is the one you want when comparing two deployments\n // six months from now. Only the rules half is importable, and the file says so.\n try {\n const response = await fetch(`${API}/api/settings`);\n const text = await response.text();\n if (!response.ok) throw new Error(text);\n download(text, `bothandler-settings-${today()}.json`, \"application/json\");\n toast(\"ok\", \"Settings exported\", \"Rules, plus a record of the configuration around them.\");\n } catch (error) {\n toast(\"bad\", \"Export failed\", String(error));\n }\n}\n\nfunction readSettingsFile(file: File): void {\n const reader = new FileReader();\n reader.onload = (): void => {\n try {\n applyImported(JSON.parse(String(reader.result)) as Record<string, unknown>, file.name);\n } catch (error) {\n toast(\"bad\", \"That file is not JSON\", String(error));\n }\n };\n reader.onerror = (): void => toast(\"bad\", \"Could not read that file\", \"\");\n reader.readAsText(file);\n}\n\n/**\n * Loads a file into the editor — and only into the editor.\n *\n * Nothing is applied: an import that took effect on drop would be a policy change made\n * by a mis-drag. What it does is fill the editor and say what it ignored, so the next\n * step is the same Preview and Apply as any other edit.\n */\nfunction applyImported(document_: Record<string, unknown> | unknown[], name: string): void {\n const rules = Array.isArray(document_)\n ? (document_ as EditorRule[])\n : Array.isArray((document_ as { rules?: unknown }).rules)\n ? ((document_ as { rules: EditorRule[] }).rules)\n : undefined;\n if (rules === undefined) {\n toast(\"bad\", \"Nothing to import\", \"Expected an array of rules, or a settings file with a rules array.\");\n return;\n }\n setEditorRules(rules);\n markDirty();\n switchToGui();\n\n const ignored: string[] = [];\n const readOnly = (document_ as { readOnly?: { lockedRules?: unknown[] } }).readOnly;\n if (readOnly !== undefined) {\n ignored.push(\"the guard, the detectors, the ranges and the audit — those come from the code that built the handler, not from a file\");\n if (Array.isArray(readOnly.lockedRules) && readOnly.lockedRules.length > 0) {\n ignored.push(`${readOnly.lockedRules.length} predicate rule(s), which stay as they are`);\n }\n }\n showResult(\"warn\", (box) => {\n box.appendChild(el(\"div\", null, `Loaded ${rules.length} rule(s) from ${name}. Nothing has been applied yet — preview it first.`));\n for (const line of ignored) box.appendChild(el(\"div\", \"ev-meta\", `Ignored: ${line}`));\n });\n toast(\"ok\", `Imported ${rules.length} rule(s)`, \"Review, preview, then apply.\");\n}\n\n// ---- preview and apply ------------------------------------------------------\n\nfunction collectForSubmit(): Array<Record<string, unknown>> | undefined {\n const parsed = editorRules();\n if (parsed.error !== undefined) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, parsed.error ?? \"\")));\n toast(\"bad\", \"That JSON will not parse\", parsed.error);\n return undefined;\n }\n const cleaned = cleanRules(parsed.rules ?? []);\n const blank = cleaned.filter((rule) => rule[\"id\"] === undefined || rule[\"id\"] === \"\").length;\n if (blank > 0) {\n toast(\"bad\", \"Every rule needs an id\", \"It is what every decision and log line names.\");\n return undefined;\n }\n return cleaned;\n}\n\nasync function runPreview(notes: readonly string[] = []): Promise<void> {\n const rules = collectForSubmit();\n if (rules === undefined) return;\n const result = await postJson<Preview & { error?: string }>(\"/api/policy/preview\", { rules });\n if (!result.ok) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, result.error ?? \"\")));\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n renderPreview(result.data, notes);\n}\n\nasync function applyPolicy(): Promise<void> {\n const rules = collectForSubmit();\n if (rules === undefined) return;\n const result = await postJson<{ rules: number; warnings?: string[]; error?: string }>(\"/api/policy/apply\", { rules });\n if (!result.ok) {\n showResult(\"bad\", (box) => box.appendChild(el(\"div\", null, result.error ?? \"\")));\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n state.editorDirty = false;\n $(\"policy-dirty\").hidden = true;\n await loadPolicy();\n toast(\"ok\", \"Applied\", `${n(result.data.rules)} rule(s) now in force.`);\n showResult(\"ok\", (box) => {\n box.appendChild(el(\"div\", null, `Applied. ${n(result.data.rules)} rule(s) are now in force.`));\n for (const warning of result.data.warnings ?? []) box.appendChild(el(\"div\", \"ev-meta\", warning));\n });\n}\n\n/** Built from the registry the server sends, so a new preset appears here on its own. */\nfunction renderPresetButtons(): void {\n const box = $(\"preset-buttons\");\n const presets = state.policy?.vocabulary.presets ?? [];\n if (box.childElementCount === presets.length) return;\n clear(box);\n for (const preset of presets) {\n const button = el(\"button\", null, preset);\n button.addEventListener(\"click\", () => {\n void (async () => {\n const result = await postJson<Preview & { error?: string }>(\"/api/policy/preview\", { preset });\n if (!result.ok) {\n toast(\"bad\", \"Refused\", result.error ?? \"\");\n return;\n }\n renderPreview(result.data);\n })();\n });\n box.appendChild(button);\n }\n}\n\nexport function initPolicy(): void {\n if (!SECTIONS.policy) return;\n $(\"mode-gui\").addEventListener(\"click\", switchToGui);\n $(\"mode-json\").addEventListener(\"click\", switchToJson);\n\n $(\"rule-add\").addEventListener(\"click\", () => {\n state.editorRules.push({ id: `new-rule-${state.editorRules.length + 1}`, match: {}, action: \"tag\", params: {}, _open: true });\n markDirty();\n renderEditor();\n });\n\n $(\"rule-expand\").addEventListener(\"click\", () => {\n const anyClosed = state.editorRules.some((rule) => rule._open !== true);\n for (const rule of state.editorRules) rule._open = anyClosed;\n $(\"rule-expand\").textContent = anyClosed ? \"Collapse all\" : \"Expand all\";\n renderEditor();\n });\n\n byId<HTMLTextAreaElement>(\"policy-json\").addEventListener(\"input\", markDirty);\n $(\"policy-preview\").addEventListener(\"click\", () => void runPreview());\n $(\"policy-apply\").addEventListener(\"click\", () => void applyPolicy());\n $(\"policy-revert\").addEventListener(\"click\", () => {\n state.editorDirty = false;\n $(\"policy-dirty\").hidden = true;\n $(\"policy-result\").hidden = true;\n resetGuardDraft();\n void loadPolicy();\n });\n\n $(\"policy-export\").addEventListener(\"click\", () => void exportSettings());\n $(\"policy-import\").addEventListener(\"click\", () => byId<HTMLInputElement>(\"policy-file\").click());\n byId<HTMLInputElement>(\"policy-file\").addEventListener(\"change\", () => {\n const input = byId<HTMLInputElement>(\"policy-file\");\n const file = input.files?.[0];\n if (file !== undefined) readSettingsFile(file);\n input.value = \"\";\n });\n\n const panel = $(\"editor-panel\");\n for (const name of [\"dragenter\", \"dragover\"]) {\n panel.addEventListener(name, (event) => {\n if (state.policy?.editable !== true) return;\n event.preventDefault();\n panel.classList.add(\"drop\");\n });\n }\n for (const name of [\"dragleave\", \"drop\"]) {\n panel.addEventListener(name, (event) => {\n panel.classList.remove(\"drop\");\n if (name !== \"drop\") return;\n event.preventDefault();\n const file = (event as DragEvent).dataTransfer?.files?.[0];\n if (file !== undefined) readSettingsFile(file);\n });\n }\n}\n","import { $, byId, clear, cssEscape, el, rootNode } from \"./dom.js\";\nimport { feedPage, goToFeedPage, ingest, matchingCount, matchingRows, resetPaging, setSearch, setTimeframe, sortRows, state } from \"./store.js\";\nimport { deleteFilter, saveFilter, savedFilters } from \"./saved.js\";\nimport { suggestFor } from \"./query.js\";\nimport { getJson } from \"./api.js\";\nimport { renderPager } from \"./pager.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { app, download, today, toast } from \"./app.js\";\nimport { clockTime, n } from \"./format.js\";\nimport { corpusCase, replayFile, replayLine } from \"./replay.js\";\nimport { openActor } from \"./actor.js\";\nimport { outcome, verdictBadge } from \"./outcome.js\";\nimport { draftIntoEditor } from \"./policy.js\";\nimport type { FilterName } from \"./query.js\";\nimport type { DashboardEntry } from \"./types.js\";\n\nconst FILTERS: Array<[FilterName, string]> = [\n [\"all\", \"All\"],\n [\"proven\", \"Proven\"],\n [\"suspected\", \"Suspected\"],\n [\"human\", \"Human\"],\n [\"guard\", \"Guard stops\"],\n [\"deny\", \"Denied\"],\n [\"mitigate\", \"Mitigated\"],\n [\"allow\", \"Served\"],\n];\n\n/** A row currently in the table, and what it was built from. */\ninterface Rendered {\n row: HTMLTableRowElement;\n detail: HTMLTableRowElement | undefined;\n rev: number;\n open: boolean;\n}\n\nconst rendered = new Map<string, Rendered>();\n\nexport function initFeed(): void {\n const loadThem = byId<HTMLButtonElement>(\"feed-load-skipped\");\n loadThem.addEventListener(\"click\", () => {\n // Disabled while it is in flight, because the fetch is the whole ring and a second\n // press would ask for it again.\n loadThem.disabled = true;\n void loadSkipped().finally(() => {\n loadThem.disabled = false;\n });\n });\n\n const filters = $(\"filters\");\n for (const [name, label] of FILTERS) {\n const button = el(\"button\", null, label);\n button.setAttribute(\"aria-pressed\", String(name === state.filter));\n button.dataset[\"filter\"] = name;\n button.addEventListener(\"click\", () => {\n state.filter = name;\n resetPaging();\n for (const other of Array.from(filters.children)) other.setAttribute(\"aria-pressed\", \"false\");\n button.setAttribute(\"aria-pressed\", \"true\");\n app.syncUrl();\n app.drawNow();\n });\n filters.appendChild(button);\n }\n\n const search = byId<HTMLInputElement>(\"search\");\n search.addEventListener(\"input\", () => {\n setSearch(search.value.trim());\n app.syncUrl();\n app.drawNow();\n showSuggestions(search);\n });\n initSuggestions(search);\n initTimeframe();\n initSavedFilters(search);\n\n // Downloading the window is the bulk half of what the per-row buttons do one request\n // at a time. It exports what is on screen rather than everything held, because the\n // filter is how you said which requests you meant.\n const exportShown = byId<HTMLButtonElement>(\"feed-export\");\n exportShown.hidden = !SECTIONS.evidence;\n exportShown.addEventListener(\"click\", () => {\n const rows = matchingRows();\n if (rows.length === 0) {\n toast(\"warn\", \"Nothing to export\", \"No request in the window matches this filter.\");\n return;\n }\n // Oldest first, so the file replays in the order the traffic happened.\n const entries = rows.map((row) => row.entry).reverse();\n download(`${replayFile(entries)}\\n`, `bothandler-feed-${today()}.jsonl`, \"application/x-ndjson\");\n toast(\"ok\", `Exported ${n(entries.length)} request(s)`, \"Replay them with `bothandlerjs replay`.\");\n });\n}\n\n/** Applies filter state that arrived in the URL rather than from a click. */\nexport function reflectFilterButtons(): void {\n for (const button of Array.from($(\"filters\").children)) {\n button.setAttribute(\"aria-pressed\", String(button instanceof HTMLElement && button.dataset[\"filter\"] === state.filter));\n }\n const search = byId<HTMLInputElement>(\"search\");\n if (search.value !== state.search) search.value = state.search;\n}\n\n/**\n * Fetches the entries this viewer never received, and merges them.\n *\n * They were never lost. The rate cap keeps a burst off the *stream* and the lag guard\n * drops frames a slow socket cannot take, but both leave the ring alone — and the opening\n * replay is droppable too, so a first load of a busy dashboard can arrive with most of the\n * backlog missing. `/api/feed` serves that ring whole, and `ingest` is keyed on the request\n * id, so merging it is idempotent: what is already held is refreshed, what is missing is\n * added.\n *\n * On demand rather than on a timer. Skips happen exactly when the origin is busiest, and a\n * dashboard that answered every skip by re-fetching the whole ring would be a load\n * amplifier pointed at the process it is meant to be watching — which is the thing\n * `maxEventsPerSecond` exists to prevent.\n */\nexport async function loadSkipped(): Promise<void> {\n const before = state.rows.length;\n let served: number | undefined;\n try {\n const body = await getJson<{ entries: DashboardEntry[]; skipped?: number }>(\"/api/feed\");\n for (const entry of body.entries) ingest(entry);\n served = body.skipped;\n sortRows();\n } catch {\n toast(\"bad\", \"Could not load them\", \"The dashboard did not answer. The entries are still in the window; try again.\");\n return;\n }\n // What the badge counted has now been asked for, whether or not the ring still had all\n // of it — anything it no longer holds is gone and saying so forever helps nobody.\n //\n // Counted from the response that closed the gap rather than from `state.snapshot`,\n // which is refreshed on a timer and was therefore usually a few seconds out of date. A\n // snapshot arriving afterwards with a larger count reopened a gap that had just been\n // closed, so the badge came back on a feed holding everything there was — you pressed\n // the button, it said how many it had loaded, and the thing the button exists to clear\n // stayed on screen. Older handlers do not send the field; theirs is the previous\n // behaviour rather than an error.\n state.caughtUp = (served ?? state.snapshot?.skipped ?? 0) + state.laggedDrops;\n const added = state.rows.length - before;\n resetPaging();\n app.drawNow();\n toast(\n added > 0 ? \"ok\" : \"warn\",\n added > 0 ? `Loaded ${n(added)}` : \"Nothing left to load\",\n added > 0 ? \"They are in the feed now, in the order they happened.\" : \"The window no longer holds them; the ring had already rotated past.\",\n );\n}\n\n\n/**\n * Completion for the filter box.\n *\n * The options come from the parser's own field map rather than a list kept beside it, so\n * a field added to the language is offered the day it exists and one removed stops being\n * offered. Values are completed only where the set is genuinely closed — `rule`,\n * `identity` and `path` take anything, and guessing there would be inventing options\n * rather than completing them.\n */\nlet highlighted = -1;\n\nfunction suggestionList(): HTMLElement {\n return $(\"search-suggest\");\n}\n\nfunction closeSuggestions(input: HTMLInputElement): void {\n const list = suggestionList();\n list.hidden = true;\n clear(list);\n highlighted = -1;\n input.setAttribute(\"aria-expanded\", \"false\");\n}\n\nfunction showSuggestions(input: HTMLInputElement): void {\n const caret = input.selectionStart ?? input.value.length;\n const { options, from, to } = suggestFor(input.value, caret);\n const list = suggestionList();\n if (options.length === 0 || input.value.slice(from, to) === options[0]) {\n closeSuggestions(input);\n return;\n }\n clear(list);\n highlighted = -1;\n options.slice(0, 12).forEach((option, index) => {\n const item = el(\"li\", null, option);\n item.setAttribute(\"role\", \"option\");\n item.setAttribute(\"aria-selected\", \"false\");\n // `mousedown` rather than `click`: the input loses focus first on a click, and the\n // blur handler closes the list out from under the pointer.\n item.addEventListener(\"mousedown\", (event) => {\n event.preventDefault();\n apply(input, option, from, to);\n });\n item.dataset[\"index\"] = String(index);\n list.appendChild(item);\n });\n list.hidden = false;\n input.setAttribute(\"aria-expanded\", \"true\");\n}\n\nfunction apply(input: HTMLInputElement, option: string, from: number, to: number): void {\n const before = input.value.slice(0, from);\n const after = input.value.slice(to);\n input.value = `${before}${option}${after}`;\n const caret = before.length + option.length;\n input.setSelectionRange(caret, caret);\n setSearch(input.value.trim());\n app.syncUrl();\n app.drawNow();\n closeSuggestions(input);\n // Completing a field name leaves the caret after the colon, where the values are — so\n // offer them straight away rather than making somebody type a character to see them.\n showSuggestions(input);\n input.focus();\n}\n\nfunction initSuggestions(input: HTMLInputElement): void {\n input.addEventListener(\"keydown\", (event) => {\n const list = suggestionList();\n const items = Array.from(list.querySelectorAll(\"li\"));\n if (list.hidden || items.length === 0) return;\n if (event.key === \"Escape\") {\n closeSuggestions(input);\n return;\n }\n if (event.key === \"ArrowDown\" || event.key === \"ArrowUp\") {\n event.preventDefault();\n highlighted = (highlighted + (event.key === \"ArrowDown\" ? 1 : items.length - 1) + (highlighted === -1 && event.key === \"ArrowUp\" ? 1 : 0)) % items.length;\n items.forEach((item, index) => item.setAttribute(\"aria-selected\", String(index === highlighted)));\n return;\n }\n if ((event.key === \"Enter\" || event.key === \"Tab\") && highlighted >= 0) {\n const chosen = items[highlighted]?.textContent ?? \"\";\n const caret = input.selectionStart ?? input.value.length;\n const { from, to } = suggestFor(input.value, caret);\n event.preventDefault();\n apply(input, chosen, from, to);\n }\n });\n input.addEventListener(\"blur\", () => {\n setTimeout(() => closeSuggestions(input), 120);\n });\n input.addEventListener(\"focus\", () => showSuggestions(input));\n}\n\n/** The saved-filter control: a list to load from, and buttons to add and remove. */\nfunction initSavedFilters(input: HTMLInputElement): void {\n const host = $(\"saved-filters\");\n const redraw = (): void => {\n clear(host);\n const entries = savedFilters();\n const select = document.createElement(\"select\");\n select.setAttribute(\"aria-label\", \"Saved filters\");\n const first = document.createElement(\"option\");\n first.value = \"\";\n first.textContent = entries.length === 0 ? \"No saved filters\" : \"Saved filters…\";\n select.appendChild(first);\n for (const entry of entries) {\n const option = document.createElement(\"option\");\n option.value = entry.name;\n option.textContent = entry.name;\n select.appendChild(option);\n }\n select.addEventListener(\"change\", () => {\n const chosen = entries.find((entry) => entry.name === select.value);\n if (chosen === undefined) return;\n input.value = chosen.query;\n setSearch(chosen.query);\n state.filter = chosen.filter as typeof state.filter;\n reflectFilterButtons();\n resetPaging();\n app.syncUrl();\n app.drawNow();\n });\n host.appendChild(select);\n\n const save = el(\"button\", null, \"Save\");\n (save as HTMLButtonElement).type = \"button\";\n save.title = \"Save this filter, in this browser, under a name\";\n save.addEventListener(\"click\", () => {\n const name = prompt(\"Save this filter as:\")?.trim();\n if (name === undefined || name === \"\") return;\n saveFilter({ name, query: input.value.trim(), filter: state.filter });\n redraw();\n toast(\"ok\", \"Filter saved\", `\"${name}\" is in this browser. It is not shared with anybody else.`);\n });\n host.appendChild(save);\n\n if (select.value !== \"\") {\n const remove = el(\"button\", null, \"Delete\");\n (remove as HTMLButtonElement).type = \"button\";\n remove.addEventListener(\"click\", () => {\n deleteFilter(select.value);\n redraw();\n });\n host.appendChild(remove);\n }\n\n };\n redraw();\n}\n\n\n\n\n/**\n * The window somebody is looking at.\n *\n * `datetime-local` reads and writes local wall-clock time, which is what the feed's own\n * timestamps show — so the value in the box means the same thing as the value in the\n * rows. Either end may be left empty, and that is the whole design: one control answers\n * \"from the incident until now\", \"everything up to when it stopped\" and \"between these\n * two moments\" without three sets of buttons.\n */\nfunction initTimeframe(): void {\n const from = byId<HTMLInputElement>(\"from-at\");\n const to = byId<HTMLInputElement>(\"to-at\");\n const clearButton = byId<HTMLButtonElement>(\"timeframe-clear\");\n\n const read = (input: HTMLInputElement): number | undefined => {\n if (input.value === \"\") return undefined;\n const parsed = new Date(input.value).getTime();\n return Number.isFinite(parsed) ? parsed : undefined;\n };\n\n const apply = (): void => {\n const start = read(from);\n const end = read(to);\n // A backwards range selects nothing and looks like a broken dashboard, so say what\n // happened rather than showing an empty feed.\n if (start !== undefined && end !== undefined && end < start) {\n toast(\"warn\", \"That window runs backwards\", \"The end is before the start, so nothing can fall inside it.\");\n }\n setTimeframe(start, end);\n clearButton.hidden = start === undefined && end === undefined;\n app.drawNow();\n };\n\n from.addEventListener(\"change\", apply);\n to.addEventListener(\"change\", apply);\n clearButton.addEventListener(\"click\", () => {\n from.value = \"\";\n to.value = \"\";\n apply();\n });\n}\n\n/** Page sizes the feed offers. */\nconst FEED_PAGE_SIZES = [25, 50, 100, 200] as const;\n\n/**\n * The pager, above the table and below it.\n *\n * Hidden outright on a single page, because a control that can only say \"1 of 1\" is\n * furniture — and with it the size chooser goes too, which is the one thing lost by that\n * rule and not worth a permanent row of chrome to keep.\n */\nfunction drawPager(paged: { page: number; pages: number; total: number }): void {\n const size = state.feedPageSize;\n const hidden = paged.pages <= 1;\n const model = {\n page: paged.page,\n from: paged.page * size + 1,\n to: Math.min(paged.total, (paged.page + 1) * size),\n total: paged.total,\n atStart: paged.page === 0,\n atEnd: paged.page >= paged.pages - 1,\n ...(paged.page > 0 ? { held: \"held while you read\" } : {}),\n go: (page: number): void => {\n goToFeedPage(page);\n app.drawNow();\n },\n size: {\n current: size,\n choices: FEED_PAGE_SIZES,\n set: (next: number): void => {\n state.feedPageSize = next;\n // Back to the front: page four of fifty is not page four of two hundred, and\n // keeping the number while changing what it counts moves the reader somewhere\n // they did not ask to go.\n resetPaging();\n app.drawNow();\n },\n },\n };\n for (const [id, withSize] of [\n [\"feed-pager-top\", true],\n [\"feed-pager\", false],\n ] as const) {\n const host = $(id);\n host.hidden = hidden;\n if (hidden) clear(host);\n else renderPager(host, model, { withSize });\n }\n}\n\nexport function drawFeed(): void {\n const body = byId<HTMLTableSectionElement>(\"rows\");\n const paged = feedPage(state.feedPageSize);\n const shown = paged.rows;\n\n // Keyed reconciliation against what is already in the table.\n //\n // The old renderer emptied the tbody and rebuilt three hundred rows on every frame\n // that carried a new request. Two things were wrong with that, and the invisible one\n // is not the expensive one: rebuilding a node destroys any text selection inside it,\n // so a live feed could not be *read* — drag across a User-Agent to copy it and the\n // next request wiped the selection. Rows are now rebuilt only when the entry behind\n // them changed or they were opened, which on a busy feed is one row in three hundred.\n let index = 0;\n const place = (node: Node): void => {\n const current = body.childNodes[index] ?? null;\n // insertBefore moves a node that is already in the document, so this handles a\n // reorder as well as an insert.\n if (current !== node) body.insertBefore(node, current);\n index++;\n };\n\n for (const row of shown) {\n const id = row.entry.requestId;\n const open = state.open.has(id);\n let cached = rendered.get(id);\n if (cached === undefined || cached.rev !== row.rev || cached.open !== open) {\n cached = {\n row: buildRow(row.entry, open),\n detail: open ? buildDetail(row.entry) : undefined,\n rev: row.rev,\n open,\n };\n rendered.set(id, cached);\n }\n place(cached.row);\n if (cached.detail !== undefined) place(cached.detail);\n }\n\n while (body.childNodes.length > index) body.removeChild(body.childNodes[index] as Node);\n\n // The cache outlives the table — a row scrolled past the filter is likely to come\n // back — but not indefinitely. Anything not on screen goes once it has grown past\n // twice the visible set.\n if (rendered.size > shown.length * 2 + 100) {\n const live = new Set(shown.map((row) => row.entry.requestId));\n for (const id of Array.from(rendered.keys())) if (!live.has(id)) rendered.delete(id);\n }\n\n const total = state.rows.length;\n const matching = matchingCount();\n $(\"empty\").hidden = total > 0;\n // No \"showing 300\" any more: the pager reaches the rest, so the count says what is in\n // the window and the pager says where in it you are.\n $(\"feed-count\").textContent = matching === total ? `${n(total)} in this window` : `${n(matching)} of ${n(total)}`;\n drawPager(paged);\n // Redrawn with the feed rather than once at start-up: its whole message is a count of\n // what is being hidden *now*, and drawn once it said \"hiding 0 of 0\" for the rest of\n // the session because the rows had not arrived yet.\n // `#feed-window` carries the `win` class, so `updateWindowLabels` fills it — one\n // owner for a label that appears on eight panels.\n\n // A thinned feed must never look like a quiet one. The ring still has these — they\n // are in the preview, in the export and in whatever a reconnect replays — but they\n // were kept off the stream to stop a busy origin handing every open browser a\n // megabyte a second.\n // Minus what has already been fetched back: the server's own count only ever grows, so\n // without this the badge goes on reporting a gap that has been filled.\n const skipped = Math.max(0, (state.snapshot?.skipped ?? 0) + state.laggedDrops - state.caughtUp);\n const note = $(\"feed-skipped\");\n note.hidden = skipped === 0;\n note.textContent = `${n(skipped)} not streamed`;\n // The badge used to state the gap and leave it there. It is now next to the button that\n // closes it.\n byId<HTMLButtonElement>(\"feed-load-skipped\").hidden = skipped === 0;\n note.title =\n state.laggedDrops > 0\n ? `${n(state.laggedDrops)} were skipped because this connection could not keep up, and the rest by the rate cap. All of them are still in the window, the preview and the export.`\n : \"Entries the rate cap kept off this stream. They are still in the window, the preview and the export — raise maxEventsPerSecond to see them live.\";\n}\n\n/** Drops every cached node. Used when the feed is cleared under the page's feet. */\nexport function resetFeedCache(): void {\n rendered.clear();\n}\n\nfunction buildRow(entry: DashboardEntry, open: boolean): HTMLTableRowElement {\n const out = outcome(entry);\n const tr = el(\"tr\", `row a-${entry.downgradedFrom !== undefined ? \"guard\" : out}${open ? \" open\" : \"\"}`);\n\n // When it happened, which the feed never used to say. Ordering implies it while the\n // stream is live and stops implying it the moment you type into the filter box.\n tr.appendChild(el(\"td\", \"num mono tnum when\", clockTime(entry.at)));\n\n const request = el(\"td\", \"edge req\");\n // The row's one control. It carries the name, the state and the keys; the row itself\n // keeps a click handler for the mouse, which needs no role to be useful.\n const toggle = el(\"button\", \"row-toggle\", `${entry.method} ${entry.path}`);\n toggle.type = \"button\";\n toggle.setAttribute(\"aria-expanded\", String(open));\n toggle.setAttribute(\"aria-label\", `${entry.method} ${entry.path}, ${entry.verdict}. Evidence.`);\n toggle.dataset[\"request\"] = entry.requestId;\n request.appendChild(toggle);\n const ua = el(\"span\", \"ua\");\n if (SECTIONS.actors) {\n const actorLink = el(\"a\", null, entry.actor);\n actorLink.href = \"#actor\";\n actorLink.title = \"Show everything from this actor\";\n actorLink.addEventListener(\"click\", (event) => {\n event.preventDefault();\n event.stopPropagation();\n openActor(entry.actor);\n });\n ua.appendChild(actorLink);\n ua.appendChild(document.createTextNode(` · ${entry.userAgent}`));\n } else {\n ua.appendChild(document.createTextNode(`${entry.actor} · ${entry.userAgent}`));\n }\n ua.title = `${entry.actor} · ${entry.userAgent}`;\n request.appendChild(ua);\n tr.appendChild(request);\n\n const verdictCell = el(\"td\");\n const [badgeClass, badgeLabel] = verdictBadge(entry);\n verdictCell.appendChild(el(\"span\", `badge ${badgeClass}`, badgeLabel));\n if (entry.identity !== undefined) verdictCell.appendChild(el(\"span\", \"sub\", entry.identity));\n tr.appendChild(verdictCell);\n\n tr.appendChild(el(\"td\", \"num mono tnum\", entry.certain ? \"proven\" : String(entry.score)));\n\n const actionCell = el(\"td\");\n if (entry.action !== undefined) {\n const kind = out === \"deny\" ? \"act-deny\" : out === \"mitigate\" ? \"act-mitigate\" : out === \"allow\" ? \"act-allow\" : \"act-tag\";\n actionCell.appendChild(el(\"span\", `act ${kind}`, entry.action));\n if (entry.rule !== undefined) {\n const ruleLabel = el(\"span\", \"sub\", entry.rule);\n ruleLabel.title = entry.rule;\n actionCell.appendChild(ruleLabel);\n }\n if (entry.downgradedFrom !== undefined) actionCell.appendChild(el(\"span\", \"guard\", `guard stopped ${entry.downgradedFrom}`));\n } else {\n actionCell.appendChild(el(\"span\", \"sub\", \"assessed only\"));\n }\n tr.appendChild(actionCell);\n\n tr.appendChild(el(\"td\", \"num mono tnum\", entry.durationMs.toFixed(2)));\n\n tr.dataset[\"request\"] = entry.requestId;\n const flip = (): void => {\n if (state.open.has(entry.requestId)) state.open.delete(entry.requestId);\n else state.open.add(entry.requestId);\n app.drawNow();\n // The row was rebuilt, so focus has to be put back or a keyboard user is returned\n // to the top of the document every time they open one.\n rootNode().querySelector<HTMLElement>(`button.row-toggle[data-request=\"${cssEscape(entry.requestId)}\"]`)?.focus();\n };\n toggle.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n flip();\n });\n // Clicking anywhere in the row is the mouse affordance people expect from a feed, and\n // it needs no role to work — a click is not an accessibility contract.\n tr.addEventListener(\"click\", flip);\n return tr;\n}\n\nfunction buildDetail(entry: DashboardEntry): HTMLTableRowElement {\n const tr = el(\"tr\", \"detail\");\n const cell = el(\"td\");\n cell.colSpan = 6;\n\n if (!SECTIONS.evidence) {\n cell.appendChild(\n el(\n \"div\",\n \"ev-meta\",\n \"The evidence section is switched off on this dashboard, so the reasons behind this verdict are not sent to it. What fired, and why, is on a dashboard that has `sections: { evidence: true }`.\",\n ),\n );\n } else if (entry.evidence.length === 0) {\n cell.appendChild(\n el(\n \"div\",\n \"ev-meta\",\n entry.bypass !== undefined\n ? `Detection was skipped for this request: ${entry.bypass}.`\n : \"No detector produced any evidence. This is what ordinary traffic looks like.\",\n ),\n );\n } else {\n const list = el(\"div\", \"ev\");\n for (const item of entry.evidence) {\n const row = el(\"div\", `ev-item${item.direction === \"human\" ? \" human\" : \"\"}${item.shadow === true ? \" shadow\" : \"\"}`);\n row.appendChild(el(\"div\", `tier t-${item.certainty}`, item.certainty));\n const body = el(\"div\");\n body.appendChild(el(\"div\", null, item.summary));\n let meta = `${item.detector} · points to ${item.direction}`;\n if (item.family !== undefined) meta += ` · family “${item.family}”, counted once with its siblings`;\n // Said on every shadowed line rather than once at the top. A reader scanning the\n // list is looking at one row at a time, and a row that reads like evidence and was\n // not evidence is the single most misleading thing this page could show.\n if (item.shadow === true) meta += \" · shadowed: counted, and part of no decision\";\n body.appendChild(el(\"div\", \"ev-meta\", meta));\n if (item.deterministicBasis !== undefined) body.appendChild(el(\"div\", \"basis\", item.deterministicBasis));\n row.appendChild(body);\n list.appendChild(row);\n }\n cell.appendChild(list);\n }\n\n // The counterfactual, in the one place somebody is already asking \"why this verdict\".\n if (entry.shadowVerdict !== undefined) {\n const would = entry.shadowVerdict;\n const changed = would.verdict !== entry.verdict;\n cell.appendChild(\n el(\n \"div\",\n `ev-meta shadow-verdict${changed ? \" changed\" : \"\"}`,\n changed\n ? `With the shadowed detectors counted, this request would have been ${would.verdict} at ${would.score} instead of ${entry.verdict} at ${entry.score}.`\n : `With the shadowed detectors counted, this request would still have been ${would.verdict}${would.score === entry.score ? \"\" : `, at ${would.score} rather than ${entry.score}`}.`,\n ),\n );\n }\n\n for (const failure of entry.failures) {\n cell.appendChild(el(\"div\", \"ev-meta\", `Detector ${failure.detector} ${failure.reason}: ${failure.message}`));\n }\n if (entry.downgradeReason !== undefined) cell.appendChild(el(\"div\", \"basis\", `Guard: ${entry.downgradeReason}`));\n\n const queryNames = Object.keys(entry.query);\n if (queryNames.length > 0) {\n const queryTable = el(\"table\", \"hdr\");\n for (const name of queryNames) {\n const value = entry.query[name] ?? \"\";\n const row = el(\"tr\");\n row.appendChild(el(\"td\", \"n\", `?${name}`));\n row.appendChild(el(\"td\", `v${value === \"[redacted]\" ? \" red\" : \"\"}`, value));\n queryTable.appendChild(row);\n }\n cell.appendChild(queryTable);\n }\n\n // The header set in wire order: what half the detectors are actually reading, and the\n // first thing worth looking at when a verdict seems wrong.\n if (entry.headers !== undefined && entry.headers.length > 0) {\n const table = el(\"table\", \"hdr\");\n for (const [name, value] of entry.headers) {\n const row = el(\"tr\");\n row.appendChild(el(\"td\", \"n\", `${name}:`));\n row.appendChild(el(\"td\", `v${value === \"[redacted]\" ? \" red\" : \"\"}`, value));\n table.appendChild(row);\n }\n cell.appendChild(table);\n }\n\n const tools = el(\"div\", \"tools\");\n if (SECTIONS.evidence) {\n tools.appendChild(copyButton(\"Copy replay line\", () => replayLine(entry)));\n tools.appendChild(downloadButton(\"Download replay line\", () => replayLine(entry), `request-${entry.requestId}.jsonl`));\n tools.appendChild(copyButton(\"Copy corpus case\", () => corpusCase(entry)));\n }\n if (SECTIONS.policy) {\n const draft = el(\"button\", null, \"Draft a rule\");\n draft.title = \"Start a rule from this request, in the policy editor\";\n draft.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n void draftIntoEditor(entry);\n });\n tools.appendChild(draft);\n }\n if (SECTIONS.actors) {\n const actorButton = el(\"button\", null, \"Show this actor\");\n actorButton.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n openActor(entry.actor);\n });\n tools.appendChild(actorButton);\n }\n cell.appendChild(tools);\n\n const foot = el(\"div\", \"detail-foot\");\n foot.appendChild(el(\"span\", null, clockTime(entry.at)));\n foot.appendChild(el(\"span\", null, `actor ${entry.actor}`));\n if (entry.rule !== undefined) foot.appendChild(el(\"span\", null, `rule “${entry.rule}”`));\n foot.appendChild(el(\"span\", null, `assessed in ${entry.durationMs.toFixed(3)}ms`));\n foot.appendChild(el(\"span\", \"mono\", entry.requestId));\n cell.appendChild(foot);\n\n tr.appendChild(cell);\n return tr;\n}\n\nfunction copyButton(label: string, produce: () => string): HTMLButtonElement {\n const button = el(\"button\", null, label);\n button.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n const text = produce();\n const done = (): void => {\n button.textContent = \"Copied\";\n setTimeout(() => {\n button.textContent = label;\n }, 1200);\n };\n // navigator.clipboard needs a secure context, which a dashboard on a plain HTTP LAN\n // address is not. The fallback selects the text so a keyboard copy still works\n // rather than leaving a button that does nothing.\n if (navigator.clipboard?.writeText !== undefined) navigator.clipboard.writeText(text).then(done, () => showText(text));\n else showText(text);\n });\n return button;\n}\n\nfunction downloadButton(label: string, produce: () => string, filename: string): HTMLButtonElement {\n const button = el(\"button\", null, label);\n button.addEventListener(\"click\", (event) => {\n event.stopPropagation();\n download(`${produce()}\\n`, filename, \"application/x-ndjson\");\n });\n return button;\n}\n\nfunction showText(text: string): void {\n const box = el(\"pre\", \"code\", text);\n const host = $(\"policy-result\");\n host.hidden = false;\n host.className = \"result\";\n clear(host);\n host.appendChild(el(\"div\", \"ev-meta\", \"Copying needs a secure context; here is the text.\"));\n host.appendChild(box);\n const selection = getSelection();\n if (selection !== null) {\n const range = document.createRange();\n range.selectNodeContents(box);\n selection.removeAllRanges();\n selection.addRange(range);\n }\n}\n","import { $ } from \"./dom.js\";\nimport { API, SECTIONS } from \"./boot.js\";\nimport { app } from \"./app.js\";\nimport { clearFeed, ingest, state } from \"./store.js\";\nimport { getJson } from \"./api.js\";\nimport { resetFeedCache } from \"./feed.js\";\nimport type { DashboardEntry, Snapshot } from \"./types.js\";\n\n/**\n * The event stream.\n *\n * `EventSource` reconnects on its own and resends the last `id:` it saw as\n * `Last-Event-ID`, so the server can answer a reconnect with the handful of frames\n * that were missed instead of the whole ring — five hundred entries with their\n * headers, evidence and actor history attached, every time a laptop lid closes. The\n * page's side of that bargain is this: nothing here tracks the cursor, because the\n * browser already does, and a second copy would be the one that is wrong.\n */\n/**\n * The open connection, so it can be closed again.\n *\n * It used to be a local, which meant nothing could stop it. Embedded in somebody's page\n * that mattered: removing the element left the stream open and the client drawing into a\n * detached tree for the life of the page — one held server connection and a steady trickle\n * of work for a dashboard nobody was looking at.\n */\nlet source: EventSource | undefined;\n\n/**\n * Closes the stream, keeping everything the page has drawn.\n *\n * Reconnecting resumes from where this left off: the server is told the last id seen and\n * sends only what was missed, so suspending across a route change costs a reconnect rather\n * than a reload.\n */\nexport function suspendStream(): void {\n source?.close();\n source = undefined;\n}\n\nexport function connectStream(): void {\n if (!SECTIONS.feed) {\n $(\"dot\").className = \"dot\";\n $(\"conn\").textContent = \"feed off\";\n return;\n }\n if (source !== undefined) return;\n\n source = new EventSource(`${API}/api/stream`);\n\n source.addEventListener(\"open\", () => {\n $(\"dot\").className = \"dot on\";\n $(\"conn\").textContent = \"live\";\n });\n\n // A full backlog is on its way and it replaces what the page holds. Sent when the\n // server cannot honour the cursor — a restarted process, a cleared feed, or a gap\n // longer than the ring — so keeping the old rows would mean showing requests\n // nothing will ever correct.\n source.addEventListener(\"sync\", (event) => {\n const detail = JSON.parse((event as MessageEvent<string>).data) as { replace: boolean };\n if (!detail.replace) return;\n clearFeed();\n resetFeedCache();\n });\n\n source.addEventListener(\"entry\", (event) => {\n ingest(JSON.parse((event as MessageEvent<string>).data) as DashboardEntry);\n if (state.paused) {\n // Paused means the page stops redrawing, so the only thing that may move is the\n // button that says how much you are not being shown.\n state.bufferedWhilePaused++;\n $(\"pause\").textContent = `Resume (${state.bufferedWhilePaused})`;\n }\n app.draw();\n });\n\n source.addEventListener(\"update\", (event) => {\n ingest(JSON.parse((event as MessageEvent<string>).data) as DashboardEntry);\n app.draw();\n });\n\n // Somebody pressed Reset — possibly in another browser. Without this a second viewer\n // goes on showing a feed of requests the server has forgotten.\n source.addEventListener(\"reset\", () => {\n clearFeed();\n resetFeedCache();\n app.draw();\n });\n\n // The server could not keep up with this connection and skipped part of the feed\n // rather than queueing it in its own memory. Said out loud: a gap the viewer knows\n // about is a different thing from one it does not.\n source.addEventListener(\"lagged\", (event) => {\n const detail = JSON.parse((event as MessageEvent<string>).data) as { dropped: number };\n state.laggedDrops += detail.dropped;\n app.draw();\n });\n\n source.addEventListener(\"stats\", (event) => {\n state.snapshot = JSON.parse((event as MessageEvent<string>).data) as Snapshot;\n app.draw();\n });\n\n source.addEventListener(\"error\", () => {\n $(\"dot\").className = \"dot off\";\n $(\"conn\").textContent = \"reconnecting…\";\n // EventSource reconnects on its own; this only reports it. A stream closed by the\n // server on shutdown ends up here too, which is why the dot matters.\n });\n}\n\n/**\n * A first fetch, so the page has counters before the stream opens — and so a browser\n * with `EventSource` blocked still shows something useful.\n */\nexport async function loadInitialSnapshot(): Promise<void> {\n try {\n state.snapshot = await getJson<Snapshot>(\"/api/stats\");\n app.drawNow();\n } catch {\n /* the stream is the primary path */\n }\n}\n","import { $, clear, el, rootNode } from \"./dom.js\";\nimport { BOOT, SECTIONS } from \"./boot.js\";\nimport { aggregate, oldestAt, state } from \"./store.js\";\nimport { drawBars, pairs } from \"./bars.js\";\nimport { clockTime, ms, n, pct, rangeLabel, uptime, windowLabel } from \"./format.js\";\nimport { provenBots } from \"./outcome.js\";\nimport { app } from \"./app.js\";\nimport type { Aggregates } from \"./store.js\";\nimport type { TabName } from \"./types.js\";\n\n/**\n * The counter tiles.\n *\n * Redrawn only when the snapshot behind them changes, which is every two seconds\n * rather than every frame. They sit above the tab strip, so they are the one part of\n * the page that is on screen whichever view is showing, and they were being rebuilt on\n * every incoming request for numbers that could not have moved.\n */\nlet paintedSnapshot: unknown;\n\n/** A screen a counter tile can send you to. */\ninterface Destination {\n tab: TabName;\n label: string;\n}\n\nexport function drawTiles(force = false): void {\n // The tiles are the statistics section's, and `applySections` removes the node when\n // that section is off — so this has to check before it looks.\n if (!SECTIONS.statistics) return;\n const box = $(\"tiles\");\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n if (!force && paintedSnapshot === snapshot) return;\n paintedSnapshot = snapshot;\n\n const metrics = snapshot.metrics;\n clear(box);\n if (metrics === undefined) {\n box.appendChild(el(\"div\", \"note\", \"Counters are switched off on this handler (metrics: false). The live feed still works.\"));\n return;\n }\n\n const actions = metrics.actions;\n const denied = actions.block + actions.drop + actions.redirect;\n const mitigated = actions.challenge + actions[\"rate-limit\"] + actions.delay;\n const served = actions.allow + actions.tag + actions.log;\n const total = metrics.requests;\n const unremarkable = metrics.verdicts.unknown + metrics.verdicts.human;\n // From the verdicts rather than from `metrics.proven`: that counter includes proven\n // *humans* — a clearance token is certain evidence too — so a dashboard watching a\n // logged-in audience reported all of them as proven bots. See `provenBots`.\n const provenBotCount = provenBots(metrics.verdicts);\n\n // A tile that counts what a screen shows can take you to it. Only where that screen\n // exists: `registry` off means there is no Actors tab to reach, and a control that\n // navigates nowhere is worse than no control.\n const actors: Destination | undefined = SECTIONS.registry ? { tab: \"actors\", label: \"Actors\" } : undefined;\n\n const tiles: Array<[string, string, string, string, Destination | undefined]> = [\n [\"\", n(total), \"Requests\", \"since start\", undefined],\n [\"proven\", n(provenBotCount), \"Proven bots\", `${pct(provenBotCount, total)} of traffic`, undefined],\n [\"warn\", n(metrics.verdicts[\"suspected-bot\"]), \"Suspected\", \"never denied alone\", undefined],\n [\"\", n(unremarkable), \"Unremarkable\", `${pct(unremarkable, total)} of traffic`, undefined],\n [\"warn\", n(metrics.downgrades), \"Guard stops\", metrics.downgrades > 0 ? \"a rule over-reached\" : \"no rule overreached\", undefined],\n [\"crit\", n(denied), \"Denied\", `${pct(denied, total)} of traffic`, undefined],\n [\n \"\",\n n(mitigated),\n \"Mitigated\",\n metrics.challenges.issued > 0 ? `${n(metrics.challenges.solved)} of ${n(metrics.challenges.issued)} challenges solved` : \"challenged or limited\",\n undefined,\n ],\n [\"good\", n(served), \"Served\", `${pct(served, total)} of traffic`, undefined],\n [\"\", n(metrics.actorsTracked), \"Actors tracked\", \"in the registry now\", actors],\n ];\n for (const [kind, value, key, sub, goes] of tiles) {\n // A real button where it navigates, so it is in the tab order, answers Enter and\n // Space, and is announced as something to press — none of which a div with a click\n // handler gets, and all of which somebody expects from a thing that moves the page.\n const tile = goes === undefined ? el(\"div\", `tile ${kind}`) : el(\"button\", `tile ${kind} go`);\n tile.appendChild(el(\"div\", \"v tnum\", value));\n tile.appendChild(el(\"div\", \"k\", key));\n tile.appendChild(el(\"div\", \"s\", sub));\n if (goes !== undefined) {\n (tile as HTMLButtonElement).type = \"button\";\n // Appended rather than an `aria-label`, which would replace the count and the\n // caption a screen reader is here to hear. This adds to them.\n tile.appendChild(el(\"span\", \"sr-only\", `. Show the ${goes.label} screen`));\n tile.addEventListener(\"click\", () => app.showTab(goes.tab, { replace: false }));\n }\n box.appendChild(tile);\n }\n}\n\n/**\n * The configuration facts at the end of the tab strip.\n *\n * Label first, value second, every time — \"suspect at 60\", not \"60 suspect at\" — and\n * as plain text, which is the one treatment nothing else on the page uses for a\n * control.\n */\nexport function drawChips(): void {\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n const box = $(\"chips\");\n clear(box);\n const facts: Array<[string, string]> = [\n // Which process this is, first, because everything after it is a fact about this\n // process and nothing else. Behind a load balancer there are as many of these\n // dashboards as there are pods, each showing its own share of the traffic.\n [\"instance\", snapshot.instance],\n [\"guard\", snapshot.policy.falsePositivePolicy],\n [\"suspect at\", String(snapshot.policy.suspectThreshold)],\n ];\n if (SECTIONS.statistics) facts.push([\"detectors\", String(snapshot.detectors.length)]);\n if (SECTIONS.policy) facts.push([\"rules\", String(snapshot.rules.length)]);\n facts.push([\"uptime\", uptime(snapshot.now - snapshot.startedAt)]);\n\n for (const [label, value] of facts) {\n const fact = el(\"span\");\n fact.appendChild(document.createTextNode(`${label} `));\n fact.appendChild(el(\"b\", null, value));\n box.appendChild(fact);\n }\n}\n\n/**\n * Every panel that counts the retained ring says how much ring there is.\n *\n * Half the Statistics screen counts the window and half counts since the process\n * started; they were wearing the same grey subtitle, which invited exactly the\n * comparison that does not hold.\n */\nexport function updateWindowLabels(): void {\n const label = windowLabel(state.rows.length, oldestAt(), Date.now());\n for (const node of Array.from(rootNode().querySelectorAll<HTMLElement>(\".win\"))) node.textContent = label;\n}\n\nexport function drawLivePanels(): void {\n if (!SECTIONS.statistics) return;\n const totals = aggregate(state.rows);\n drawBars($(\"live-detectors\"), totals.detectors, \"Nothing has fired in this window.\");\n if (SECTIONS.actors) drawBars($(\"live-actors\"), totals.actors, \"No traffic in this window.\");\n}\n\nexport function drawStatsPanels(): void {\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n const metrics = snapshot.metrics;\n\n if (metrics !== undefined) {\n drawBars($(\"stat-verdicts\"), pairs(metrics.verdicts), \"Nothing assessed yet.\");\n drawBars($(\"stat-actions\"), pairs(metrics.actions), \"No decisions yet.\");\n drawBars($(\"stat-classes\"), pairs(metrics.botClasses), \"Nothing classified yet.\");\n drawBars($(\"stat-detectors\"), pairs(metrics.detectorFirings), \"No detector has produced evidence yet.\");\n\n const challenges = $(\"stat-challenges\");\n clear(challenges);\n if (!snapshot.policy.challengeEnabled) {\n challenges.appendChild(el(\"div\", \"note\", \"No challenge is configured, so a rule asking for one degrades to a tag. Set challenge.secrets to enable it.\"));\n } else {\n const funnel: Array<[string, string]> = [\n [\"Issued\", n(metrics.challenges.issued)],\n [\"Solved\", n(metrics.challenges.solved)],\n [\"Rejected\", n(metrics.challenges.rejected)],\n [\"Solve rate\", metrics.challenges.issued > 0 ? pct(metrics.challenges.solved, metrics.challenges.issued) : \"—\"],\n ];\n for (const [key, value] of funnel) challenges.appendChild(statRow(key, value));\n }\n\n const health = $(\"stat-health\");\n clear(health);\n const bypassed = metrics.bypassed.allowlist + metrics.bypassed[\"ignored-path\"];\n const rows: Array<[string, string]> = [\n [\"Requests assessed\", n(metrics.requests)],\n [\"Bypassed — allowlist\", n(metrics.bypassed.allowlist)],\n [\"Bypassed — ignored path\", n(metrics.bypassed[\"ignored-path\"])],\n [\"Detection ran on\", pct(metrics.requests - bypassed, metrics.requests)],\n [\"Actors tracked\", n(metrics.actorsTracked)],\n [\"Guard stops\", n(metrics.downgrades)],\n ];\n // What the shadowed detectors would have done, next to what was actually done. The\n // row is absent entirely when nothing is shadowed, rather than showing a row of zeros\n // that invites somebody to wonder what it means.\n const shadowed = pairs(metrics.shadowFirings);\n if (shadowed.length > 0) {\n const moved = Object.entries(metrics.shadowChanges).filter(([, count]) => count > 0);\n rows.push([\"Shadowed findings\", n(shadowed.reduce((total, [, count]) => total + count, 0))]);\n for (const [verdict, count] of moved) rows.push([`Would have become ${verdict}`, n(count)]);\n if (moved.length === 0) rows.push([\"Verdicts they would have changed\", \"none\"]);\n }\n const failures = pairs(metrics.detectorFailures);\n for (const [detector, count] of failures) rows.push([`Detector failures — ${detector}`, n(count)]);\n for (const [key, value] of rows) health.appendChild(statRow(key, value));\n if (failures.length === 0) health.appendChild(el(\"div\", \"note\", \"No detector has thrown or timed out.\"));\n }\n\n const totals = aggregate(state.rows);\n if (SECTIONS.actors) drawBars($(\"stat-identities\"), totals.identities, \"No client has named itself in this window.\");\n drawBars($(\"stat-paths\"), totals.paths, \"No traffic in this window.\");\n drawBars($(\"stat-denied-paths\"), totals.deniedPaths, \"Nothing has been denied in this window.\");\n drawBars($(\"stat-guard\"), totals.guardStops, \"No rule has asked for more than its evidence supports.\");\n drawBars($(\"stat-bypassed\"), totals.bypassed, \"Nothing bypassed detection.\");\n drawRuleHits(totals);\n\n const list = $(\"stat-detector-list\");\n clear(list);\n $(\"detector-count\").textContent = `${snapshot.detectors.length} installed`;\n for (const detector of snapshot.detectors) {\n const row = el(\"div\", `det${detector.shadow === true ? \" shadow\" : \"\"}`);\n const left = el(\"div\");\n left.appendChild(el(\"div\", \"mono\", detector.id));\n left.appendChild(el(\"div\", \"d\", detector.description));\n row.appendChild(left);\n // A shadowed detector's firings are counted elsewhere and mean something else, so\n // they are read from elsewhere and labelled. Showing them in the same column as the\n // rest would say this detector had been deciding things, which is the one claim the\n // whole feature exists to avoid making. Read off the detector rather than off the\n // counter, so one that is shadowed and has never fired still says so — \"0\" under a\n // heading that means \"times this decided something\" is exactly the wrong reassurance.\n const fires = (detector.shadow === true ? metrics?.shadowFirings[detector.id] : metrics?.detectorFirings[detector.id]) ?? 0;\n const timing = metrics?.detectorTimings[detector.id];\n let right = `${n(fires)}${detector.shadow === true ? \" shadowed\" : \"\"} · ${detector.cost} · ${detector.stage}`;\n // Only when the operator asked for timing; the field is empty otherwise.\n if (timing !== undefined && timing.count > 0) right += ` · ${ms(timing.totalMs / timing.count)} avg`;\n row.appendChild(el(\"div\", \"n\", right));\n list.appendChild(row);\n }\n}\n\nfunction statRow(key: string, value: string): HTMLElement {\n const line = el(\"div\", \"stat-row\");\n line.appendChild(el(\"span\", \"k\", key));\n line.appendChild(el(\"span\", \"v\", value));\n return line;\n}\n\n/**\n * Rule hit counts, including the rules that never fire.\n *\n * The zeros are the point. A rule that has matched nothing is either dead\n * configuration or a rule sitting behind a broader one that swallows its traffic, and\n * both are invisible in a chart that only draws what happened.\n */\nfunction drawRuleHits(totals: Aggregates): void {\n const target = $(\"stat-rule-hits\");\n clear(target);\n const rules = state.snapshot?.rules ?? [];\n if (rules.length === 0) {\n target.appendChild(el(\"div\", \"note\", \"No rules configured.\"));\n return;\n }\n\n let max = 1;\n for (const rule of rules) max = Math.max(max, totals.ruleHits.get(rule) ?? 0);\n for (const rule of rules) {\n const value = totals.ruleHits.get(rule) ?? 0;\n const bar = el(\"div\", `bar${value === 0 ? \" dead\" : \"\"}`);\n const track = el(\"div\", \"track\");\n if (value > 0) {\n const fill = el(\"div\", \"fill\");\n fill.style.width = `${Math.max(2, Math.round((value / max) * 100))}%`;\n track.appendChild(fill);\n }\n track.appendChild(el(\"div\", \"lbl\", rule));\n bar.appendChild(track);\n bar.appendChild(el(\"div\", \"v tnum\", value === 0 ? \"never\" : n(value)));\n target.appendChild(bar);\n }\n}\n\n/**\n * The audit panel: the window against its baseline.\n *\n * Both, side by side, because that is the only way the audit's output means anything.\n * A bot share of 60% is a number; a bot share of 60% against a baseline of 12% is an\n * incident.\n */\nexport function drawAudit(): void {\n const snapshot = state.snapshot;\n if (snapshot === undefined) return;\n const body = $(\"audit-body\");\n const checks = $(\"audit-checks\");\n clear(body);\n clear(checks);\n\n const audit = snapshot.audit;\n if (audit === undefined) {\n $(\"audit-spans\").textContent = \"\";\n body.appendChild(\n el(\n \"div\",\n \"note\",\n \"The traffic audit is switched off on this handler (audit: false). It watches the shape of your traffic rather than any one request — a spike in automation, a collapse in human traffic, a policy suddenly denying far more than usual.\",\n ),\n );\n return;\n }\n\n const current = audit.window;\n const baseline = audit.baseline;\n $(\"audit-spans\").textContent = `last ${rangeLabel(current.spanMs)} against the ${rangeLabel(baseline.spanMs)} before`;\n\n // A share is only legible next to the one it is being compared with, so every row\n // carries both. The delta is left to the reader: an arrow implying \"worse\" would be\n // the dashboard editorialising about traffic it cannot see the purpose of.\n const rows: Array<[string, string, string]> = [\n [\"Requests\", n(current.requests), n(baseline.requests)],\n [\"Rate\", `${current.rate.toFixed(1)}/min`, `${baseline.rate.toFixed(1)}/min`],\n [\"Bot share\", `${Math.round(current.botShare * 100)}%`, `${Math.round(baseline.botShare * 100)}%`],\n [\"Bots\", n(current.bots), n(baseline.bots)],\n [\"Humans\", n(current.humans), n(baseline.humans)],\n [\"Denials\", n(current.denials), n(baseline.denials)],\n [\"Challenges\", n(current.challenges), n(baseline.challenges)],\n [\"Guard stops\", n(current.downgrades), n(baseline.downgrades)],\n [\"Detector failures\", n(current.failures), n(baseline.failures)],\n [\"Bypassed\", n(current.bypassed), n(baseline.bypassed)],\n ];\n for (const [key, now, was] of rows) {\n const line = el(\"div\", \"stat-row\");\n line.appendChild(el(\"span\", \"k\", key));\n const values = el(\"span\", \"v\");\n values.appendChild(el(\"b\", null, now));\n values.appendChild(el(\"span\", \"was\", ` was ${was}`));\n line.appendChild(values);\n body.appendChild(line);\n }\n\n if (audit.checks.length === 0) {\n checks.appendChild(el(\"div\", \"note\", \"No checks are installed, so nothing here will ever raise an anomaly.\"));\n return;\n }\n for (const check of audit.checks) {\n const row = el(\"div\", \"det\");\n const left = el(\"div\");\n left.appendChild(el(\"div\", \"mono\", check.id));\n left.appendChild(el(\"div\", \"d\", check.description));\n row.appendChild(left);\n checks.appendChild(row);\n }\n}\n\n/** The count on the Policy tab. Cheap, so it runs on every frame; the panel itself does not. */\nexport function drawNoticeBadge(): void {\n const notices = state.snapshot?.notices ?? [];\n const badge = $(\"notice-badge\");\n badge.hidden = notices.length === 0 || !SECTIONS.notices;\n badge.textContent = String(notices.length);\n}\n\nexport function drawNotices(): void {\n const box = $(\"stat-notices\");\n const notices = state.snapshot?.notices ?? [];\n clear(box);\n $(\"notice-count\").textContent = notices.length > 0 ? `${notices.length} total` : \"\";\n if (notices.length === 0) {\n box.appendChild(el(\"div\", \"note\", \"Nothing to report: no startup warnings, no detector errors.\"));\n return;\n }\n for (const notice of notices.slice().reverse().slice(0, 40)) {\n const row = el(\"div\", `notice ${notice.kind}`);\n const when = el(\"div\", \"when\");\n when.appendChild(el(\"div\", \"tag\", notice.kind));\n when.appendChild(el(\"div\", null, clockTime(notice.at)));\n row.appendChild(when);\n const body = el(\"div\");\n body.appendChild(el(\"div\", null, notice.message));\n if (notice.source !== undefined) body.appendChild(el(\"div\", \"ev-meta\", notice.source));\n row.appendChild(body);\n box.appendChild(row);\n }\n}\n\n/**\n * What somebody did to this process, and when.\n *\n * The same list the traffic timeline marks, written out — because a marker answers\n * \"was there a change here?\" and a person auditing wants \"what were they, in order\".\n * Bounded and in memory, which the panel says: the durable copy is the change events.\n */\nexport function drawChanges(): void {\n if (!SECTIONS.changes) return;\n const box = $(\"stat-changes\");\n const changes = state.snapshot?.changes ?? [];\n clear(box);\n $(\"change-count\").textContent = changes.length > 0 ? `${changes.length} this run` : \"\";\n\n if (changes.length === 0) {\n box.appendChild(el(\"div\", \"note\", \"Nothing has been changed at runtime. Rules, guard and ranges are as the code that built this handler left them.\"));\n return;\n }\n\n for (const change of changes.slice().reverse()) {\n const row = el(\"div\", \"notice\");\n const when = el(\"div\", \"when\");\n when.appendChild(el(\"div\", \"tag\", change.kind));\n when.appendChild(el(\"div\", null, clockTime(change.at)));\n row.appendChild(when);\n const body = el(\"div\");\n body.appendChild(el(\"div\", null, change.summary));\n // \"by nobody in particular\" is a fact about the dashboard's `auth`, not a gap to\n // hide: a bearer token is a credential and not an identity.\n body.appendChild(el(\"div\", \"ev-meta\", change.by === undefined || change.by === \"\" ? \"by an unnamed viewer — this listener's auth carries no identity\" : `by ${change.by}`));\n row.appendChild(body);\n box.appendChild(row);\n }\n}\n\n/** Sibling instances, when the operator listed any. */\nexport function drawPeers(): void {\n const box = $(\"peers\");\n if (BOOT.peers.length === 0) {\n box.hidden = true;\n return;\n }\n if (box.childElementCount > 0) return;\n box.appendChild(el(\"span\", \"hint\", \"also:\"));\n for (const peer of BOOT.peers) {\n const link = el(\"a\", \"linkbtn\", peer.label);\n link.href = peer.href;\n link.rel = \"noreferrer noopener\";\n box.appendChild(link);\n }\n}\n","import { $, clear, css, el, svgEl, svgText } from \"./dom.js\";\nimport { clockTime, n, pct, ms, rangeLabel, windowLabel } from \"./format.js\";\nimport { oldestAt, state } from \"./store.js\";\nimport { outcome } from \"./outcome.js\";\n\nconst BUCKETS = 60;\nconst LATENCY_BOUNDS = [0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10, 25, 50, 100];\nconst SCORE_BOUNDS = 10;\n\ninterface Bucket {\n at: number;\n served: number;\n mitigated: number;\n denied: number;\n total: number;\n}\n\n/** Puts a tooltip beside the pointer without letting it hang off the panel. */\nfunction positionTip(tip: HTMLElement, host: HTMLElement, clientX: number, boxLeft: number): void {\n tip.style.opacity = \"1\";\n tip.style.left = `${Math.min(host.clientWidth - 150, Math.max(4, clientX - boxLeft - 60))}px`;\n tip.style.top = \"14px\";\n}\n\nfunction tipRow(label: string, value: string, colour?: string): HTMLElement {\n const line = el(\"div\", \"r\");\n const left = el(\"em\");\n if (colour !== undefined) {\n const swatch = el(\"span\", \"swatch\");\n swatch.style.background = colour;\n left.appendChild(swatch);\n }\n left.appendChild(document.createTextNode(label));\n line.appendChild(left);\n line.appendChild(el(\"b\", null, value));\n return line;\n}\n\nfunction timeline(): Bucket[] {\n const bucketMs = state.rangeMs / BUCKETS;\n const now = Date.now();\n const start = now - state.rangeMs;\n const buckets: Bucket[] = [];\n for (let i = 0; i < BUCKETS; i++) buckets.push({ at: start + i * bucketMs, served: 0, mitigated: 0, denied: 0, total: 0 });\n for (const { entry } of state.rows) {\n const index = Math.floor((entry.at - start) / bucketMs);\n if (index < 0 || index >= BUCKETS) continue;\n const bucket = buckets[index];\n if (bucket === undefined) continue;\n bucket.total++;\n const out = outcome(entry);\n if (out === \"deny\") bucket.denied++;\n else if (out === \"mitigate\") bucket.mitigated++;\n else bucket.served++;\n }\n return buckets;\n}\n\n/**\n * Stacked bars, two series, plus a marker row for denials.\n *\n * Two series rather than three because served/mitigated/denied as three fills would\n * need a third hue that survives colour-vision simulation beside these two, and none\n * does; a denial is a status rather than a series, so it gets a status marker and a\n * label instead.\n */\nexport function drawTraffic(): void {\n const host = $(\"traffic-chart\");\n const svg = $(\"traffic\");\n const width = Math.max(320, host.clientWidth - 30);\n const height = 190;\n const padBottom = 26;\n const markerRow = 8;\n const plot = height - padBottom - markerRow;\n const buckets = timeline();\n const now = Date.now();\n const start = now - state.rangeMs;\n let peak = 1;\n for (const bucket of buckets) if (bucket.total > peak) peak = bucket.total;\n // Round the ceiling up to an even number so the mid gridline lands on a whole\n // request rather than on 1.5 rendered as \"2\".\n const max = Math.max(2, Math.ceil(peak / 2) * 2);\n\n clear(svg);\n svg.setAttribute(\"viewBox\", `0 0 ${width} ${height}`);\n svg.setAttribute(\"height\", String(height));\n\n const s1 = css(\"--s1\");\n const s2 = css(\"--s2\");\n const crit = css(\"--crit\");\n const grid = css(\"--grid\");\n const muted = css(\"--muted\");\n const step = width / BUCKETS;\n const barWidth = Math.max(2, step - 2);\n\n // Three gridlines: enough to read a magnitude, few enough to stay recessive.\n for (const fraction of [0, 0.5, 1]) {\n const y = markerRow + plot - fraction * plot;\n svg.appendChild(svgEl(\"line\", { class: \"gridline\", x1: 0, x2: width, y1: y, y2: y, stroke: grid, \"stroke-width\": 1 }));\n if (fraction > 0) svg.appendChild(svgText({ x: 2, y: y - 3, fill: muted, \"font-size\": 10 }, Math.round(max * fraction)));\n }\n\n buckets.forEach((bucket, index) => {\n const x = index * step + 1;\n const servedHeight = (bucket.served / max) * plot;\n const mitigatedHeight = (bucket.mitigated / max) * plot;\n let y = markerRow + plot;\n\n if (servedHeight > 0) {\n y -= servedHeight;\n svg.appendChild(svgEl(\"rect\", { x, y, width: barWidth, height: servedHeight, fill: s1, rx: 2 }));\n }\n if (mitigatedHeight > 0) {\n // A 2px gap between stacked segments, so the boundary is a surface line rather\n // than two colours meeting.\n y -= mitigatedHeight + (servedHeight > 0 ? 2 : 0);\n svg.appendChild(svgEl(\"rect\", { x, y: Math.max(markerRow, y), width: barWidth, height: mitigatedHeight, fill: s2, rx: 2 }));\n }\n if (bucket.denied > 0) svg.appendChild(svgEl(\"rect\", { x, y: 0, width: barWidth, height: 5, fill: crit, rx: 2 }));\n });\n\n svg.appendChild(svgEl(\"line\", { x1: 0, x2: width, y1: markerRow + plot, y2: markerRow + plot, stroke: css(\"--line\"), \"stroke-width\": 1 }));\n const span = rangeLabel(state.rangeMs);\n const labels: Array<[number, string]> = [\n [0, `${span} ago`],\n [BUCKETS / 2, rangeLabel(state.rangeMs / 2)],\n [BUCKETS - 1, \"now\"],\n ];\n for (const [position, text] of labels) {\n svg.appendChild(svgText({ x: Math.min(width - 26, Math.max(0, position * step)), y: height - 8, fill: muted, \"font-size\": 10 }, text));\n }\n\n // Runtime changes, on the same axis as the traffic they changed.\n //\n // A preview says \"44 of 151 requests would be treated differently\", which is a\n // prediction. Nothing on the page used to tell you whether it held: the moment of the\n // change left no trace, so the traffic before and after it were the same undifferentiated\n // line. A marker per change turns the chart into the answer.\n const changes = (state.snapshot?.changes ?? []).filter((change) => change.at >= start && change.at <= now);\n const markColour = css(\"--proven-text\");\n for (const change of changes) {\n const x = ((change.at - start) / state.rangeMs) * width;\n svg.appendChild(svgEl(\"line\", { x1: x, x2: x, y1: 0, y2: markerRow + plot, stroke: markColour, \"stroke-width\": 1.5, \"stroke-dasharray\": \"3 2\", opacity: 0.85 }));\n const dot = svgEl(\"circle\", { cx: x, cy: 3, r: 3, fill: markColour });\n const title = svgEl(\"title\");\n title.textContent = `${clockTime(change.at)} · ${change.kind}: ${change.summary}${change.by === undefined ? \"\" : ` (by ${change.by})`}`;\n dot.appendChild(title);\n svg.appendChild(dot);\n }\n\n const hover = svgEl(\"rect\", { x: 0, y: 0, width: 0, height: markerRow + plot, fill: css(\"--ink\"), opacity: 0.06 });\n svg.appendChild(hover);\n\n const tip = $(\"traffic-tip\");\n host.onmousemove = (event: MouseEvent): void => {\n const box = svg.getBoundingClientRect();\n const index = Math.floor(((event.clientX - box.left) / box.width) * BUCKETS);\n const bucket = buckets[index];\n if (bucket === undefined) {\n tip.style.opacity = \"0\";\n hover.setAttribute(\"width\", \"0\");\n return;\n }\n hover.setAttribute(\"x\", String(index * step));\n hover.setAttribute(\"width\", String(step));\n clear(tip);\n tip.appendChild(el(\"div\", \"t\", `${clockTime(bucket.at)} · ${Math.round(state.rangeMs / BUCKETS / 1000)}s`));\n tip.appendChild(tipRow(\"Served\", n(bucket.served), s1));\n tip.appendChild(tipRow(\"Mitigated\", n(bucket.mitigated), s2));\n tip.appendChild(tipRow(\"Denied\", n(bucket.denied), crit));\n positionTip(tip, host, event.clientX, box.left);\n };\n host.onmouseleave = (): void => {\n tip.style.opacity = \"0\";\n hover.setAttribute(\"width\", \"0\");\n };\n\n const total = buckets.reduce((sum, bucket) => sum + bucket.total, 0);\n $(\"traffic-window\").textContent = `last ${rangeLabel(state.rangeMs)}`;\n\n // What the chart says, in words.\n //\n // `role=\"img\"` with a label told a screen reader that a picture is here and its name.\n // It did not tell anybody what is *in* it, so every number on this screen was\n // unreachable to a reader who cannot see the bars. The chart is the presentation; this\n // is the data, and it is the same data.\n const served = buckets.reduce((sum, bucket) => sum + bucket.served, 0);\n const mitigated = buckets.reduce((sum, bucket) => sum + bucket.mitigated, 0);\n const denied = buckets.reduce((sum, bucket) => sum + bucket.denied, 0);\n const busiest = buckets.reduce((best, bucket) => (bucket.total > best.total ? bucket : best), buckets[0] ?? { at: now, total: 0, served: 0, mitigated: 0, denied: 0 });\n $(\"traffic-alt\").textContent =\n `Traffic over the last ${rangeLabel(state.rangeMs)}: ${n(total)} requests — ${n(served)} served, ${n(mitigated)} mitigated, ${n(denied)} denied. ` +\n (total === 0\n ? \"No traffic in this range.\"\n : `Busiest ${Math.round(state.rangeMs / BUCKETS / 1000)}-second interval: ${n(busiest.total)} requests at ${clockTime(busiest.at)}.`) +\n (changes.length === 0 ? \"\" : ` ${n(changes.length)} runtime change${changes.length === 1 ? \"\" : \"s\"} in this range: ${changes.map((change) => `${change.kind}, ${change.summary}`).join(\"; \")}.`);\n\n const legend = $(\"traffic-legend\");\n clear(legend);\n legend.appendChild(el(\"span\", null, `${n(total)} requests in the last ${rangeLabel(state.rangeMs)} ·`));\n // The ring is finite. Saying so beats drawing an empty half-hour that only means the\n // feed forgot it.\n const oldest = oldestAt();\n if (oldest !== undefined && Date.now() - oldest < state.rangeMs * 0.9) {\n legend.appendChild(el(\"span\", null, `window holds ${rangeLabel(Date.now() - oldest)} ·`));\n }\n for (const [label, colour] of [\n [\"Served\", s1],\n [\"Mitigated — challenged, limited or delayed\", s2],\n [\"Denied\", crit],\n ] as Array<[string, string]>) {\n const item = el(\"span\");\n const swatch = el(\"span\", \"swatch\");\n swatch.style.background = colour;\n item.appendChild(swatch);\n item.appendChild(document.createTextNode(label));\n legend.appendChild(item);\n }\n if (changes.length > 0) {\n const item = el(\"span\");\n item.appendChild(el(\"span\", \"mark\"));\n item.appendChild(document.createTextNode(`${n(changes.length)} runtime change${changes.length === 1 ? \"\" : \"s\"} — hover a marker`));\n legend.appendChild(item);\n }\n}\n\n/** Cumulative bucket counts to per-bucket ones. Prometheus wants the first; a histogram needs the second. */\nfunction differences(cumulative: readonly number[]): number[] {\n const counts: number[] = [];\n for (let i = 0; i < cumulative.length; i++) counts.push((cumulative[i] ?? 0) - (i > 0 ? (cumulative[i - 1] ?? 0) : 0));\n return counts;\n}\n\n/**\n * The score distribution: how close ordinary traffic runs to the line.\n *\n * Two populations, and the panel now says which one it is drawing rather than leaving\n * the reader to assume. **Since start** comes from the same counters as the Prometheus\n * endpoint, so it covers the whole run and agrees with whatever your alerting says.\n * **This window** counts the few hundred requests the page is still holding, which is\n * the right answer to \"what is happening right now\" and the wrong answer to \"where\n * should the threshold be\" — and it was the only answer available before, sitting\n * beside panels counting since start under an identical grey subtitle.\n *\n * Proven requests are excluded from both rather than piled into the last bucket: their\n * score is 100 by definition and plays no part in any decision.\n */\nexport function drawScores(): void {\n const host = $(\"score-chart\");\n const svg = $(\"scores\");\n clear(svg);\n\n const metrics = state.snapshot?.metrics;\n const fromRun = state.scoreScope === \"run\" && metrics !== undefined;\n\n let buckets: number[];\n let scored: number;\n let proven: number;\n if (fromRun && metrics !== undefined) {\n buckets = differences(metrics.scores.buckets);\n scored = metrics.scores.count;\n proven = metrics.proven;\n } else {\n buckets = new Array<number>(SCORE_BOUNDS).fill(0);\n scored = 0;\n proven = 0;\n for (const { entry } of state.rows) {\n if (entry.bypass !== undefined) continue;\n if (entry.certain) {\n proven++;\n continue;\n }\n const index = Math.min(SCORE_BOUNDS - 1, Math.floor(entry.score / 10));\n buckets[index] = (buckets[index] ?? 0) + 1;\n scored++;\n }\n }\n\n const width = Math.max(280, host.clientWidth - 30);\n const height = 190;\n const padBottom = 30;\n const plot = height - padBottom;\n let max = 1;\n for (const value of buckets) if (value > max) max = value;\n\n svg.setAttribute(\"viewBox\", `0 0 ${width} ${height}`);\n svg.setAttribute(\"height\", String(height));\n\n const fill = css(\"--s1\");\n const muted = css(\"--muted\");\n const grid = css(\"--grid\");\n const crit = css(\"--crit\");\n svg.appendChild(svgEl(\"line\", { class: \"gridline\", x1: 0, x2: width, y1: plot, y2: plot, stroke: grid, \"stroke-width\": 1 }));\n\n const step = width / SCORE_BOUNDS;\n buckets.forEach((value, index) => {\n const barHeight = (value / max) * (plot - 8);\n // Capped and centred: ten buckets across a wide panel would otherwise be ten slabs,\n // and a histogram reads better as marks than as a wall.\n const barWidth = Math.max(2, Math.min(step - 6, 56));\n if (barHeight > 0) {\n svg.appendChild(svgEl(\"rect\", { x: index * step + (step - barWidth) / 2, y: plot - barHeight, width: barWidth, height: barHeight, fill, rx: 3 }));\n }\n svg.appendChild(svgText({ x: index * step + 1, y: height - 14, fill: muted, \"font-size\": 9.5 }, index * 10));\n });\n\n const threshold = state.snapshot?.policy.suspectThreshold ?? 60;\n const x = (threshold / 100) * width;\n svg.appendChild(svgEl(\"line\", { x1: x, x2: x, y1: 0, y2: plot, stroke: crit, \"stroke-width\": 2, \"stroke-dasharray\": \"4 3\" }));\n svg.appendChild(svgText({ x: Math.min(width - 92, x + 5), y: 11, fill: crit, \"font-size\": 10 }, `suspect at ${threshold}`));\n svg.appendChild(svgText({ x: 0, y: height - 2, fill: muted, \"font-size\": 10 }, \"score (probabilistic requests only)\"));\n\n const tip = $(\"score-tip\");\n host.onmousemove = (event: MouseEvent): void => {\n const box = svg.getBoundingClientRect();\n const index = Math.floor(((event.clientX - box.left) / box.width) * SCORE_BOUNDS);\n const value = buckets[index];\n if (value === undefined) {\n tip.style.opacity = \"0\";\n return;\n }\n clear(tip);\n tip.appendChild(el(\"div\", \"t\", `score ${index * 10}–${index * 10 + 9}`));\n tip.appendChild(tipRow(\"requests\", n(value)));\n tip.appendChild(tipRow(\"share\", pct(value, scored)));\n positionTip(tip, host, event.clientX, box.left);\n };\n host.onmouseleave = (): void => {\n tip.style.opacity = \"0\";\n };\n\n let over = 0;\n for (let bucket = 0; bucket < SCORE_BOUNDS; bucket++) if (bucket * 10 >= threshold) over += buckets[bucket] ?? 0;\n\n const legend = $(\"score-legend\");\n clear(legend);\n legend.appendChild(el(\"span\", null, `${n(scored)} scored · ${n(over)} at or over the threshold · ${n(proven)} proven, which carry no score`));\n\n $(\"score-alt\").textContent =\n `Distribution of probabilistic scores ${fromRun ? \"since start\" : \"in the retained window\"}, with the suspect threshold at ${threshold}. ` +\n `${n(scored)} scored requests, ${n(over)} at or over the threshold, ${n(proven)} proven and therefore unscored. ` +\n (scored === 0 ? \"Nothing scored yet.\" : `By ten-point band: ${buckets.map((value, index) => `${index * 10}–${index * 10 + 9}: ${n(value)}`).join(\", \")}.`);\n $(\"score-window\").textContent = fromRun ? \"since start\" : windowLabel(state.rows.length, oldestAt(), Date.now());\n if (state.scoreScope === \"run\" && metrics === undefined) {\n legend.appendChild(el(\"span\", null, \"· counters are off on this handler, so this is the retained window\"));\n }\n}\n\nexport function drawLatency(): void {\n const host = $(\"latency-chart\");\n const svg = $(\"latency\");\n clear(svg);\n const metrics = state.snapshot?.metrics;\n if (metrics === undefined || metrics.duration.count === 0) {\n $(\"latency-summary\").textContent = \"No assessments yet.\";\n $(\"latency-alt\").textContent = \"Assessment latency: no assessments yet.\";\n svg.setAttribute(\"viewBox\", \"0 0 100 40\");\n svg.setAttribute(\"height\", \"40\");\n svg.appendChild(svgText({ x: 0, y: 20, fill: css(\"--muted\"), \"font-size\": 11 }, \"No assessments yet.\"));\n return;\n }\n\n const cumulative = metrics.duration.buckets;\n const counts = differences(cumulative);\n\n const width = Math.max(280, host.clientWidth - 30);\n const height = 190;\n const padBottom = 30;\n const plot = height - padBottom;\n let max = 1;\n for (const value of counts) if (value > max) max = value;\n\n svg.setAttribute(\"viewBox\", `0 0 ${width} ${height}`);\n svg.setAttribute(\"height\", String(height));\n\n const fill = css(\"--s1\");\n const muted = css(\"--muted\");\n const grid = css(\"--grid\");\n svg.appendChild(svgEl(\"line\", { class: \"gridline\", x1: 0, x2: width, y1: plot, y2: plot, stroke: grid, \"stroke-width\": 1 }));\n\n const step = width / counts.length;\n counts.forEach((value, index) => {\n const barHeight = (value / max) * (plot - 6);\n if (barHeight > 0) {\n svg.appendChild(svgEl(\"rect\", { x: index * step + 1, y: plot - barHeight, width: Math.max(2, step - 3), height: barHeight, fill, rx: 3 }));\n }\n if (index % 2 === 0) {\n svg.appendChild(svgText({ x: index * step + 1, y: height - 14, fill: muted, \"font-size\": 9.5 }, index < LATENCY_BOUNDS.length ? String(LATENCY_BOUNDS[index]) : \"more\"));\n }\n });\n svg.appendChild(svgText({ x: 0, y: height - 2, fill: muted, \"font-size\": 10 }, \"milliseconds (upper bound of each bucket)\"));\n\n const tip = $(\"latency-tip\");\n host.onmousemove = (event: MouseEvent): void => {\n const box = svg.getBoundingClientRect();\n const index = Math.floor(((event.clientX - box.left) / box.width) * counts.length);\n const value = counts[index];\n if (value === undefined) {\n tip.style.opacity = \"0\";\n return;\n }\n clear(tip);\n tip.appendChild(el(\"div\", \"t\", index < LATENCY_BOUNDS.length ? `≤ ${LATENCY_BOUNDS[index]}ms` : \"over 100ms\"));\n tip.appendChild(tipRow(\"requests\", n(value)));\n tip.appendChild(tipRow(\"share\", pct(value, metrics.duration.count)));\n positionTip(tip, host, event.clientX, box.left);\n };\n host.onmouseleave = (): void => {\n tip.style.opacity = \"0\";\n };\n\n const mean = metrics.duration.totalMs / metrics.duration.count;\n const p95 = percentile(cumulative, metrics.duration.count, 0.95);\n $(\"latency-summary\").textContent = `Time spent in detection, per request · mean ${ms(mean)} · p95 ${ms(p95)} · max ${ms(metrics.duration.maxMs)}`;\n $(\"latency-alt\").textContent =\n `Assessment latency since start over ${n(metrics.duration.count)} requests: mean ${ms(mean)}, 95th percentile ${ms(p95)}, maximum ${ms(metrics.duration.maxMs)}. ` +\n `By bucket: ${counts.map((value, index) => `${index < LATENCY_BOUNDS.length ? `up to ${LATENCY_BOUNDS[index]}ms` : \"over 100ms\"}: ${n(value)}`).join(\", \")}.`;\n}\n\n/** Bucket-boundary percentile. Coarse by construction — it is a histogram, not a series. */\nfunction percentile(cumulative: readonly number[], count: number, fraction: number): number {\n const target = count * fraction;\n const last = LATENCY_BOUNDS[LATENCY_BOUNDS.length - 1] ?? 100;\n for (let i = 0; i < cumulative.length; i++) {\n if ((cumulative[i] ?? 0) >= target) return i < LATENCY_BOUNDS.length ? (LATENCY_BOUNDS[i] ?? last) : last;\n }\n return last;\n}\n","import { $, byId, clear, el } from \"./dom.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { actorActions, isConfirming } from \"./actions.js\";\nimport { app } from \"./app.js\";\nimport { clockStamp, n } from \"./format.js\";\nimport { getJson } from \"./api.js\";\nimport { matchingRows, setSearch, state } from \"./store.js\";\nimport { renderPager } from \"./pager.js\";\nimport type { ActorRow } from \"./types.js\";\n\n/**\n * The Actors screen.\n *\n * The feed is a ring of *requests* — five hundred of them, which on a busy origin is a\n * few seconds. The registry is a bounded map of *clients*, up to twenty thousand of\n * them, each carrying the rate series, path breadth and confirmation count that\n * `cadence`, `crawl-breadth` and `rate-anomaly` are reading. Until this screen existed\n * the page could only show you the actors that happened to appear in the last few\n * hundred requests, which is a different and much smaller question than \"who is hitting\n * me hardest right now\".\n *\n * It is fetched rather than streamed, and refreshed while it is the visible tab.\n * Streaming twenty thousand actors to every viewer to keep a sorted list of fifty\n * fresh would be a great deal of traffic to answer a question nobody asks continuously.\n */\n\ninterface ActorsBody {\n actors: ActorRow[];\n tracked: number;\n actionable: boolean;\n}\n\nlet timer: ReturnType<typeof setInterval> | undefined;\n\nexport async function loadActors(): Promise<void> {\n if (!SECTIONS.registry) return;\n try {\n const body = await getJson<ActorsBody>(`/api/actors?limit=${state.actorsPageSize}&offset=${state.actorsPage * state.actorsPageSize}`);\n state.actors = body.actors;\n state.actorsTracked = body.tracked;\n drawActors();\n } catch {\n /* the rest of the page is unaffected */\n }\n}\n\n/** Polls while the screen is showing, and stops the moment it is not. */\nexport function trackActors(): void {\n if (timer !== undefined) clearInterval(timer);\n timer = undefined;\n if (state.tab !== \"actors\") return;\n void loadActors();\n timer = setInterval(() => {\n if (state.tab !== \"actors\" || state.paused || isConfirming()) return;\n // The feed's actors are derived from rows the page already has, and the draw loop\n // redraws them. Fetching the registry to render a list that does not come from it\n // would be a request every four seconds for nothing.\n if (state.actorScope === \"feed\") return;\n void loadActors();\n }, 4000);\n}\n\n/** Page sizes the Actors table offers. The endpoint will not serve more than 200 at once. */\nconst ACTORS_PAGE_SIZES = [25, 50, 100, 200] as const;\n\n/**\n * The pager, above the table and below it.\n *\n * Paged on the server by offset, because the registry holds far more clients than the\n * feed's ring holds requests — `maxActors` of them — and the point of this screen is the\n * population the feed cannot show. Without paging the dashboard could only ever see the\n * busiest handful.\n *\n * The end of the list is \"a short page came back\". There is no count of how many rank\n * below the current page that is cheaper than asking for it, and asking in order to grey\n * out a button is not worth a request.\n */\nfunction drawActorsPager(full: boolean): void {\n const page = state.actorsPage;\n const hidden = page === 0 && !full;\n const from = page * state.actorsPageSize + 1;\n const model = {\n page,\n from,\n to: from + state.actors.length - 1,\n total: state.actorsTracked,\n atStart: page === 0,\n atEnd: !full,\n go: (next: number): void => {\n state.actorsPage = Math.max(0, next);\n void loadActors();\n },\n size: {\n current: state.actorsPageSize,\n choices: ACTORS_PAGE_SIZES,\n set: (next: number): void => {\n state.actorsPageSize = next;\n state.actorsPage = 0;\n void loadActors();\n },\n },\n };\n for (const [id, withSize] of [\n [\"actors-pager-top\", true],\n [\"actors-pager\", false],\n ] as const) {\n const host = $(id);\n host.hidden = hidden;\n if (hidden) clear(host);\n else renderPager(host, model, { withSize });\n }\n}\n\n/**\n * The actors visible in the feed you are looking at.\n *\n * The registry answers \"who is hitting me hardest\", which is a different question from\n * \"who is in *this*\" — and once a filter is on, the second one is usually what somebody\n * has in mind. Derived from the rows the feed is already showing, so it narrows with the\n * filter, the timeframe and the search without another request.\n *\n * Three columns are left blank on purpose. `Per min`, `Cadence` and `Unsolved` are\n * properties of the whole actor as the engine sees it, and the feed's ring holds a few\n * hundred requests rather than a client's history — computing them from that slice would\n * put a confident number under a heading that means something else. A dash says \"ask the\n * registry\", which is the button next to it.\n */\nexport function feedActors(): ActorRow[] {\n const byKey = new Map<string, { rows: number; paths: Set<string>; agents: Set<string>; first: number; last: number; stats?: { requests: number; distinctPaths: number; priorConfirmations: number; cleared: boolean; firstSeen: number } | undefined }>();\n for (const row of matchingRows()) {\n const entry = row.entry;\n let seen = byKey.get(entry.actor);\n if (seen === undefined) {\n seen = { rows: 0, paths: new Set(), agents: new Set(), first: entry.at, last: entry.at };\n byKey.set(entry.actor, seen);\n }\n seen.rows++;\n seen.paths.add(entry.path);\n seen.agents.add(entry.userAgent);\n if (entry.at < seen.first) seen.first = entry.at;\n if (entry.at > seen.last) seen.last = entry.at;\n // The newest row's snapshot is the closest thing the feed has to the engine's view.\n seen.stats = entry.actorStats ?? seen.stats;\n }\n\n // A feed entry does not carry the actor's label, so it is taken from the registry list\n // when that actor happens to be on it. Better a name where one is known than none.\n const labels = new Map(state.actors.filter((actor) => actor.label !== undefined).map((actor) => [actor.key, actor.label as string]));\n const out: ActorRow[] = [];\n for (const [key, seen] of byKey) {\n const label = labels.get(key);\n out.push({\n key,\n ...(label === undefined ? {} : { label }),\n requests: seen.rows,\n recentRate: Number.NaN,\n distinctPaths: seen.paths.size,\n distinctUserAgents: seen.agents.size,\n cadenceCv: undefined,\n // A dash where the feed cannot know, like the three columns below it. A confident\n // zero under a heading that means \"how many times has this client been proven a bot\"\n // is worse than an admission, and it was the only column here still guessing.\n priorConfirmations: seen.stats?.priorConfirmations ?? Number.NaN,\n unsolvedChallenges: Number.NaN,\n cleared: seen.stats?.cleared ?? false,\n firstSeen: seen.stats?.firstSeen ?? seen.first,\n lastSeen: seen.last,\n });\n }\n // Busiest first, like the registry, so the two lists read the same way.\n return out.sort((a, b) => b.requests - a.requests);\n}\n\n/**\n * Moves the toggle to `scope` and redraws, without touching the URL.\n *\n * Separate from the click handler because the URL is also an input: opening a link and\n * pressing Back both arrive here, and neither should write the address they just read.\n */\nexport function applyActorScope(scope: \"tracked\" | \"feed\"): void {\n if (!SECTIONS.registry) return;\n state.actorScope = scope;\n for (const [id, on] of [\n [\"actors-scope-tracked\", scope === \"tracked\"],\n [\"actors-scope-feed\", scope === \"feed\"],\n ] as const) {\n byId<HTMLButtonElement>(id).className = on ? \"on\" : \"\";\n byId<HTMLButtonElement>(id).setAttribute(\"aria-pressed\", String(on));\n }\n drawActors();\n}\n\n/** Wires the tracked/shown toggle. Called once. */\nexport function initActorScope(): void {\n if (!SECTIONS.registry) return;\n const choose = (scope: \"tracked\" | \"feed\"): void => {\n applyActorScope(scope);\n // Pushed rather than replaced: this is a deliberate switch between two screens, the\n // same shape of act as clicking a tab, and the back button should undo it.\n app.syncUrl({ replace: false });\n };\n $(\"actors-scope-tracked\").addEventListener(\"click\", () => choose(\"tracked\"));\n $(\"actors-scope-feed\").addEventListener(\"click\", () => choose(\"feed\"));\n}\n\nexport function drawActors(): void {\n if (!SECTIONS.registry) return;\n // A repaint replaces every button in the table, including a confirmation somebody is\n // halfway through. The list holds still while they decide.\n if (isConfirming()) return;\n const body = byId<HTMLTableSectionElement>(\"actor-rows\");\n clear(body);\n\n const fromFeed = state.actorScope === \"feed\";\n const actors = fromFeed ? feedActors() : state.actors;\n $(\"actors-count\").textContent = fromFeed\n ? `${n(actors.length)} in the feed you are looking at · ${n(state.actorsTracked)} tracked`\n : `${n(actors.length)} shown · ${n(state.actorsTracked)} tracked`;\n // The pager belongs to the registry's server-side paging. The feed's actors are a\n // client-side list of whatever is on screen, so there is nothing to page through.\n drawActorsPager(!fromFeed && actors.length === state.actorsPageSize);\n if (fromFeed) for (const id of [\"actors-pager-top\", \"actors-pager\"]) byId<HTMLElement>(id).hidden = true;\n byId<HTMLElement>(\"actors-empty\").hidden = actors.length > 0;\n\n for (const actor of actors) {\n const row = el(\"tr\");\n // The label first when there is one, with the key beneath it: somebody who named this\n // actor did so because the key was not the useful part.\n const who = el(\"td\", \"who\");\n if (actor.label === undefined) who.textContent = actor.key;\n else {\n who.appendChild(el(\"div\", \"label\", actor.label));\n who.appendChild(el(\"div\", \"sub\", actor.key));\n }\n row.appendChild(who);\n row.appendChild(el(\"td\", \"num tnum\", n(actor.requests)));\n row.appendChild(el(\"td\", \"num tnum\", Number.isNaN(actor.recentRate) ? \"—\" : n(actor.recentRate)));\n row.appendChild(el(\"td\", \"num tnum\", n(actor.distinctPaths)));\n\n // The coefficient of variation of the gaps between requests. Near zero is a\n // metronome, which no person is; a dash is \"too few gaps to say\", which is not the\n // same as regular and must not look like it.\n const cadence = el(\"td\", \"num tnum\", actor.cadenceCv === undefined ? \"—\" : actor.cadenceCv.toFixed(2));\n if (actor.cadenceCv !== undefined && actor.cadenceCv < 0.15) cadence.className += \" warn-text\";\n row.appendChild(cadence);\n\n row.appendChild(el(\"td\", \"num tnum\", Number.isNaN(actor.priorConfirmations) ? \"—\" : n(actor.priorConfirmations)));\n\n // Outstanding challenges. Amber past two, because one abandoned challenge is a\n // person having a moment and three is a client that does not answer.\n const unsolved = el(\"td\", \"num tnum\", Number.isNaN(actor.unsolvedChallenges) ? \"—\" : n(actor.unsolvedChallenges));\n if (actor.unsolvedChallenges >= 3) unsolved.className += \" warn-text\";\n row.appendChild(unsolved);\n\n const stateCell = el(\"td\");\n const tags: string[] = [];\n if (actor.cleared) tags.push(\"cleared as human\");\n if (actor.distinctUserAgents > 1) tags.push(`${actor.distinctUserAgents} User-Agents`);\n tags.push(`first seen ${clockStamp(actor.firstSeen)}`);\n stateCell.appendChild(el(\"div\", \"tagline\", tags.join(\" · \")));\n row.appendChild(stateCell);\n\n const actions = el(\"td\", \"acts\");\n const inFeed = el(\"button\", null, \"In feed\");\n inFeed.title = \"Show this actor's requests in the live feed\";\n inFeed.addEventListener(\"click\", () => {\n // Reuses the feed's own search rather than a second mechanism, which means the\n // result is a shareable URL like every other view of the feed.\n setSearch(`actor:${actor.key}`);\n const search = byId<HTMLInputElement>(\"search\");\n search.value = state.search;\n app.showTab(\"live\");\n app.syncUrl();\n });\n actions.appendChild(inFeed);\n for (const button of actorActions(actor.key, () => void loadActors(), actor.label)) actions.appendChild(button);\n row.appendChild(actions);\n\n body.appendChild(row);\n }\n}\n","import { $, byId, clear, el } from \"./dom.js\";\nimport { SECTIONS } from \"./boot.js\";\nimport { ms } from \"./format.js\";\nimport { outcome, verdictBadge } from \"./outcome.js\";\nimport { postJson } from \"./api.js\";\nimport { toast } from \"./app.js\";\nimport type { DashboardEntry } from \"./types.js\";\n\n/**\n * The request tester.\n *\n * The page's answer to the question that arrives by ticket rather than by traffic: *why\n * is this client being challenged?* Until now the only way to find out was to wait for\n * them to come back and hope you were watching, or to reproduce their request by hand\n * against the live site — which puts it in the feed, in the counters and in that\n * actor's history, so investigating a complaint changed the thing being investigated.\n *\n * This runs a **dry run** on the server: every detector runs and the decision is real,\n * including the guard, but nothing is recorded anywhere. The one thing it cannot know\n * is history — it gets an actor with no past — so what it answers precisely is \"what\n * would this look like as a first request\", which is what a ticket is asking.\n */\ninterface TestResult {\n entry: DashboardEntry;\n reason: string;\n assumed: string[];\n error?: string;\n}\n\nexport function initTester(): void {\n if (!SECTIONS.tester) return;\n $(\"test-run\").addEventListener(\"click\", () => void run());\n byId<HTMLTextAreaElement>(\"test-input\").addEventListener(\"keydown\", (event) => {\n // Enter is a newline in a textarea, so the shortcut is the one every console uses\n // for \"send this\".\n if (event.key === \"Enter\" && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n void run();\n }\n });\n}\n\nasync function run(): Promise<void> {\n const raw = byId<HTMLTextAreaElement>(\"test-input\").value;\n if (raw.trim() === \"\") {\n toast(\"warn\", \"Nothing to assess\", \"Paste a User-Agent, a curl command, or a header block.\");\n return;\n }\n const result = await postJson<TestResult>(\"/api/test\", {\n raw,\n ip: byId<HTMLInputElement>(\"test-ip\").value.trim(),\n url: byId<HTMLInputElement>(\"test-url\").value.trim(),\n });\n\n const box = $(\"test-result\");\n box.hidden = false;\n clear(box);\n\n if (!result.ok) {\n box.className = \"result bad\";\n box.appendChild(el(\"div\", null, result.error ?? \"The server could not read that.\"));\n return;\n }\n\n const { entry, reason, assumed } = result.data;\n const out = outcome(entry);\n box.className = `result ${out === \"deny\" ? \"bad\" : out === \"mitigate\" ? \"warn\" : \"ok\"}`;\n\n const head = el(\"div\");\n const [badgeClass, badgeLabel] = verdictBadge(entry);\n head.appendChild(el(\"span\", `badge ${badgeClass}`, badgeLabel));\n head.appendChild(document.createTextNode(\" \"));\n head.appendChild(el(\"b\", null, entry.action ?? \"no decision\"));\n if (entry.rule !== undefined) head.appendChild(el(\"span\", \"ev-meta\", ` via ${entry.rule}`));\n box.appendChild(head);\n\n box.appendChild(el(\"div\", \"ev-meta\", `${entry.certain ? \"proven\" : `score ${entry.score}`} · assessed in ${ms(entry.durationMs)}`));\n if (entry.downgradedFrom !== undefined) {\n box.appendChild(el(\"div\", \"guard\", `The guard stopped ${entry.downgradedFrom} here.`));\n if (entry.downgradeReason !== undefined) box.appendChild(el(\"div\", \"basis\", entry.downgradeReason));\n }\n\n if (entry.evidence.length === 0) {\n box.appendChild(el(\"div\", \"ev-meta\", entry.bypass !== undefined ? `Detection was skipped: ${entry.bypass}.` : \"No detector produced any evidence.\"));\n } else {\n const list = el(\"div\", \"ev\");\n for (const item of entry.evidence) {\n const row = el(\"div\", `ev-item${item.direction === \"human\" ? \" human\" : \"\"}`);\n row.appendChild(el(\"div\", `tier t-${item.certainty}`, item.certainty));\n const detail = el(\"div\");\n detail.appendChild(el(\"div\", null, item.summary));\n detail.appendChild(el(\"div\", \"ev-meta\", `${item.detector} · points to ${item.direction}`));\n if (item.deterministicBasis !== undefined) detail.appendChild(el(\"div\", \"basis\", item.deterministicBasis));\n row.appendChild(detail);\n list.appendChild(row);\n }\n box.appendChild(list);\n }\n\n box.appendChild(el(\"div\", \"ev-meta\", reason));\n\n // Said every time, not only when it matters. A tester that silently invents a client\n // address is a tester whose answer about `ip-intelligence` cannot be trusted, and the\n // reader has no way to know which run was which.\n const notes = [...assumed, \"no history: this is assessed as a first request, so cadence and crawl breadth have nothing to read\"];\n box.appendChild(el(\"div\", \"assumed\", `Assumed — ${notes.join(\"; \")}.`));\n}\n","import { $, byId, el, eventTarget, isEmbedded, rootNode, themeElement } from \"./dom.js\";\nimport { API, BOOT, SECTIONS } from \"./boot.js\";\nimport { app, toast } from \"./app.js\";\nimport { clearFeed, setSearch, state } from \"./store.js\";\nimport { drawActor, initActor } from \"./actor.js\";\nimport { connectStream, loadInitialSnapshot } from \"./stream.js\";\nimport { drawAudit, drawChanges, drawChips, drawLivePanels, drawNoticeBadge, drawNotices, drawPeers, drawStatsPanels, drawTiles, updateWindowLabels } from \"./panels.js\";\nimport { drawFeed, initFeed, reflectFilterButtons, resetFeedCache } from \"./feed.js\";\nimport { drawLatency, drawScores, drawTraffic } from \"./charts.js\";\nimport { applyActorScope, drawActors, initActorScope, trackActors } from \"./registry.js\";\nimport { drawPolicyTab, initPolicy, loadPolicy } from \"./policy.js\";\nimport { loadRanges } from \"./ranges.js\";\nimport { initTester } from \"./tester.js\";\nimport type { FilterName } from \"./query.js\";\nimport type { TabName } from \"./types.js\";\n\n/** The views this listener has, in tab-strip order. Sections decide which exist. */\nconst TABS: Array<[id: string, name: TabName, enabled: boolean]> = [\n [\"tab-live\", \"live\", SECTIONS.feed],\n [\"tab-actors\", \"actors\", SECTIONS.registry],\n [\"tab-stats\", \"stats\", SECTIONS.statistics],\n [\"tab-policy\", \"policy\", SECTIONS.policy],\n];\n\nconst available = TABS.filter(([, , enabled]) => enabled);\nconst FIRST: TabName = available[0]?.[1] ?? \"live\";\n\nfunction tabIndexOf(name: TabName): number {\n const at = available.findIndex(([, tab]) => tab === name);\n return at === -1 ? 0 : at;\n}\n\nfunction isTab(value: string): value is TabName {\n return available.some(([, name]) => name === value);\n}\n\n// ---- header ----------------------------------------------------------------\n\n/**\n * The skip link, which is a plain fragment anchor and therefore inert inside a shadow\n * root: fragment navigation does not cross the boundary, so the one affordance that lets\n * a keyboard past the header did nothing at all when embedded. Given the same behaviour\n * by hand.\n */\nfunction initSkipLink(): void {\n if (!isEmbedded()) return;\n const skip = rootNode().querySelector<HTMLAnchorElement>(\"a.skip\");\n if (skip === null) return;\n skip.addEventListener(\"click\", (event) => {\n event.preventDefault();\n const target = rootNode().querySelector<HTMLElement>(`#view-${state.tab}`) ?? rootNode().querySelector<HTMLElement>(\"#view-live\");\n if (target === null) return;\n target.tabIndex = -1;\n target.focus();\n target.scrollIntoView({ block: \"start\" });\n });\n}\n\nfunction initHeader(): void {\n const box = $(\"links\");\n for (const link of BOOT.links) {\n const anchor = el(\"a\", \"linkbtn\", link.label);\n anchor.href = link.href;\n anchor.rel = \"noreferrer noopener\";\n box.appendChild(anchor);\n }\n\n let stored: string | null = null;\n try {\n stored = localStorage.getItem(\"bothandler-dashboard-theme\");\n } catch {\n stored = null;\n }\n if (stored === \"dark\" || stored === \"light\") themeElement().setAttribute(\"data-theme\", stored);\n $(\"theme\").addEventListener(\"click\", () => {\n let current = themeElement().getAttribute(\"data-theme\");\n if (current === null) current = matchMedia(\"(prefers-color-scheme: dark)\").matches ? \"dark\" : \"light\";\n const next = current === \"dark\" ? \"light\" : \"dark\";\n themeElement().setAttribute(\"data-theme\", next);\n try {\n localStorage.setItem(\"bothandler-dashboard-theme\", next);\n } catch {\n /* private mode */\n }\n // The charts read their colours from the CSS custom properties, so they have to be\n // redrawn rather than restyled.\n drawNow();\n });\n\n const pause = byId<HTMLButtonElement>(\"pause\");\n pause.hidden = !SECTIONS.feed;\n pause.addEventListener(\"click\", () => {\n state.paused = !state.paused;\n pause.setAttribute(\"aria-pressed\", String(state.paused));\n pause.textContent = state.paused ? `Resume${state.bufferedWhilePaused > 0 ? ` (${state.bufferedWhilePaused})` : \"\"}` : \"Pause\";\n if (!state.paused) {\n state.bufferedWhilePaused = 0;\n drawNow();\n }\n });\n\n if (BOOT.allowReset) {\n const reset = byId<HTMLButtonElement>(\"reset\");\n reset.hidden = false;\n reset.addEventListener(\"click\", () => {\n reset.disabled = true;\n void fetch(`${API}/api/reset`, { method: \"POST\", headers: { \"content-type\": \"application/json\" }, body: \"{}\" })\n .then(async (response) => {\n if (!response.ok) {\n const body = (await response.json().catch(() => ({}))) as { error?: string };\n toast(\"bad\", \"Reset refused\", body.error ?? String(response.status));\n return;\n }\n clearFeed();\n resetFeedCache();\n drawNow();\n })\n .catch(() => {\n /* the stream will resync */\n })\n .finally(() => {\n reset.disabled = false;\n });\n });\n }\n\n /**\n * The sticky column headers used to sit at a hard-coded 54px — the height of the top\n * row alone — so the tab strip covered them the moment the feed scrolled. The header\n * is not a fixed height: the chip row wraps on a narrow window and grows. Measure it,\n * and let the CSS read the measurement.\n */\n const header = rootNode().querySelector(\"header\");\n if (header !== null) {\n const apply = (): void => {\n themeElement().style.setProperty(\"--header-h\", `${header.getBoundingClientRect().height}px`);\n };\n apply();\n if (typeof ResizeObserver === \"function\") new ResizeObserver(apply).observe(header);\n else addEventListener(\"resize\", apply);\n }\n}\n\n// ---- navigation -------------------------------------------------------------\n\n/**\n * The view, the filter, the search and the Actors scope live in the URL.\n *\n * The view already did. The rest are what make a screen shareable — \"look at the\n * guard stops on /export\" is a link now rather than a set of instructions — and what\n * survives the refresh that is everybody's reflex when a live feed looks stuck.\n * `replaceState` for the filter and the search, because a back button that walks\n * backwards through every keystroke is not a back button.\n *\n * The scope is the same argument one step on: \"the actors in *this* feed\" and \"every\n * actor being tracked\" are two different screens under one tab name, and a link that\n * cannot say which of them you meant is a link to the wrong one half the time.\n */\nfunction syncUrl(replace = true): void {\n // Embedded, the URL belongs to the page around us. Writing a tab into it rewrites\n // somebody else's address bar and puts a history entry between them and wherever they\n // were going.\n if (isEmbedded()) return;\n const params = new URLSearchParams();\n if (state.filter !== \"all\") params.set(\"f\", state.filter);\n if (state.search !== \"\") params.set(\"q\", state.search);\n if (state.actorScope !== \"tracked\") params.set(\"a\", state.actorScope);\n const query = params.toString();\n const hash = `#${state.tab}${query === \"\" ? \"\" : `?${query}`}`;\n if (location.hash === hash) return;\n history[replace ? \"replaceState\" : \"pushState\"]({ tab: state.tab }, \"\", hash);\n}\n\nfunction readUrl(): { tab: TabName; filter: FilterName; search: string; actorScope: \"tracked\" | \"feed\" } {\n // And it is not ours to read either: a host page using hash routing would otherwise\n // decide which tab this opens on.\n const raw = isEmbedded() ? \"\" : location.hash.slice(1);\n const split = raw.indexOf(\"?\");\n const name = split === -1 ? raw : raw.slice(0, split);\n const params = new URLSearchParams(split === -1 ? \"\" : raw.slice(split + 1));\n const filter = params.get(\"f\") ?? \"all\";\n return {\n tab: isTab(name) ? name : FIRST,\n filter: filter as FilterName,\n search: params.get(\"q\") ?? \"\",\n // Anything but the one alternative reads as the default rather than as an error:\n // a hand-edited URL should land somewhere, and this is the somewhere it lands.\n actorScope: params.get(\"a\") === \"feed\" ? \"feed\" : \"tracked\",\n };\n}\n\nfunction showTab(name: TabName, options: { focus?: boolean; replace?: boolean; push?: boolean } = {}): void {\n const target = isTab(name) ? name : FIRST;\n state.tab = target;\n for (const [id, tab, enabled] of TABS) {\n const selected = tab === target;\n const node = $(id);\n node.hidden = !enabled;\n node.setAttribute(\"aria-selected\", String(selected));\n // Roving tabindex: the selected tab is the strip's single stop in the tab order.\n node.tabIndex = selected ? 0 : -1;\n $(`view-${tab}`).hidden = !selected || !enabled;\n }\n if (options.focus === true) $(available[tabIndexOf(target)]?.[0] ?? \"tab-live\").focus();\n if (target === \"policy\" && state.policy === undefined) {\n void loadPolicy();\n void loadRanges();\n }\n // The Actors screen is fetched rather than streamed, and polled only while it is the\n // screen somebody is looking at.\n trackActors();\n if (options.push !== false) syncUrl(options.replace !== false);\n drawNow();\n}\n\nfunction initTabs(): void {\n available.forEach(([id, name], index) => {\n const tab = $(id);\n tab.addEventListener(\"click\", () => showTab(name, { replace: false }));\n tab.addEventListener(\"keydown\", (event) => {\n let next = -1;\n if (event.key === \"ArrowRight\") next = (index + 1) % available.length;\n else if (event.key === \"ArrowLeft\") next = (index - 1 + available.length) % available.length;\n else if (event.key === \"Home\") next = 0;\n else if (event.key === \"End\") next = available.length - 1;\n if (next === -1) return;\n event.preventDefault();\n showTab(available[next]?.[1] ?? FIRST, { focus: true, replace: false });\n });\n });\n\n if (isEmbedded()) return;\n addEventListener(\"popstate\", () => {\n const url = readUrl();\n state.filter = url.filter;\n setSearch(url.search);\n reflectFilterButtons();\n applyActorScope(url.actorScope);\n showTab(url.tab, { push: false });\n });\n}\n\n// ---- keyboard ---------------------------------------------------------------\n\nfunction initKeyboard(): void {\n // Scoped to this dashboard when embedded: a digit pressed while the host page has\n // focus switched a tab in here, which is somebody else's keyboard being taken.\n eventTarget().addEventListener(\"keydown\", ((event: KeyboardEvent) => {\n const target = event.target as HTMLElement | null;\n const typing = target !== null && (target.tagName === \"INPUT\" || target.tagName === \"TEXTAREA\" || target.tagName === \"SELECT\");\n\n if (event.key === \"Escape\") {\n if (typing && target?.id === \"search\" && (target as HTMLInputElement).value !== \"\") {\n (target as HTMLInputElement).value = \"\";\n setSearch(\"\");\n syncUrl();\n drawNow();\n return;\n }\n if (typing) {\n target?.blur();\n return;\n }\n // Otherwise close whatever is open, innermost first.\n if (state.actor !== undefined) {\n state.actor = undefined;\n drawNow();\n return;\n }\n if (state.open.size > 0) {\n state.open.clear();\n drawNow();\n }\n return;\n }\n\n if (typing || event.metaKey || event.ctrlKey || event.altKey) return;\n\n if (event.key === \"/\" && SECTIONS.feed) {\n event.preventDefault();\n showTab(\"live\");\n const search = byId<HTMLInputElement>(\"search\");\n search.focus();\n search.select();\n return;\n }\n // 1-2-3-4 for the views, the way every tabbed console does it. They index the tabs\n // this listener actually has, so a dashboard with two of them has two shortcuts.\n const digit = [\"1\", \"2\", \"3\", \"4\"].indexOf(event.key);\n if (digit !== -1 && digit < available.length) {\n event.preventDefault();\n showTab(available[digit]?.[1] ?? FIRST, { focus: true, replace: false });\n }\n }) as EventListener);\n}\n\n// ---- the two range controls --------------------------------------------------\n\nfunction initRanges(): void {\n const ranges: Array<[string, number]> = [\n [\"1m\", 60_000],\n [\"5m\", 300_000],\n [\"15m\", 900_000],\n [\"1h\", 3_600_000],\n ];\n const host = $(\"traffic-range\");\n for (const [label, value] of ranges) {\n const button = el(\"button\", null, label);\n button.setAttribute(\"aria-pressed\", String(value === state.rangeMs));\n button.addEventListener(\"click\", () => {\n state.rangeMs = value;\n for (const other of Array.from(host.children)) other.setAttribute(\"aria-pressed\", \"false\");\n button.setAttribute(\"aria-pressed\", \"true\");\n drawTraffic();\n });\n host.appendChild(button);\n }\n\n const scopes: Array<[string, \"run\" | \"window\"]> = [\n [\"since start\", \"run\"],\n [\"this window\", \"window\"],\n ];\n const scopeHost = $(\"score-scope\");\n for (const [label, value] of scopes) {\n const button = el(\"button\", null, label);\n button.setAttribute(\"aria-pressed\", String(value === state.scoreScope));\n button.addEventListener(\"click\", () => {\n state.scoreScope = value;\n for (const other of Array.from(scopeHost.children)) other.setAttribute(\"aria-pressed\", \"false\");\n button.setAttribute(\"aria-pressed\", \"true\");\n drawScores();\n });\n scopeHost.appendChild(button);\n }\n}\n\n// ---- draw --------------------------------------------------------------------\n\nlet pending = false;\n\nfunction schedule(): void {\n if (state.paused || pending) return;\n pending = true;\n requestAnimationFrame(() => {\n pending = false;\n draw();\n });\n}\n\n/**\n * Draws the view that is on screen, and nothing else.\n *\n * It used to draw all three. Every incoming request rebuilt eight statistics bar\n * panels into a hidden tab, on top of rebuilding three hundred feed rows — which is\n * work nobody could see, done at the frame rate, on the one screen where the work that\n * can be seen matters most.\n */\nfunction draw(): void {\n drawChips();\n drawTiles();\n drawNoticeBadge();\n updateWindowLabels();\n\n if (state.tab === \"live\") {\n drawActor();\n drawFeed();\n drawLivePanels();\n } else if (state.tab === \"actors\") {\n drawActors();\n } else if (state.tab === \"stats\") {\n drawTraffic();\n drawLatency();\n drawScores();\n drawStatsPanels();\n if (SECTIONS.audit) drawAudit();\n } else {\n drawPolicyTab();\n if (SECTIONS.notices) drawNotices();\n if (SECTIONS.changes) drawChanges();\n }\n}\n\nfunction drawNow(): void {\n draw();\n}\n\n// ---- start -------------------------------------------------------------------\n\nfunction applySections(): void {\n // A section that is off is removed from the document rather than hidden, so nothing\n // can tab into it and no panel is left waiting for data that will never arrive.\n const gated: Array<[string, boolean]> = [\n [\"tiles\", SECTIONS.statistics],\n [\"tester-panel\", SECTIONS.tester],\n [\"ranges-panel\", SECTIONS.ranges],\n [\"changes-panel\", SECTIONS.changes],\n [\"actor-panel\", SECTIONS.actors],\n [\"live-actors-panel\", SECTIONS.actors],\n [\"audit-panel\", SECTIONS.audit],\n [\"audit-checks-panel\", SECTIONS.audit],\n [\"notices-panel\", SECTIONS.notices],\n [\"guard-panel\", SECTIONS.guard],\n [\"robots-panel\", SECTIONS.robots],\n [\"identities-panel\", SECTIONS.actors],\n [\"evidence-legend\", SECTIONS.evidence],\n ];\n for (const [id, enabled] of gated) {\n const node = rootNode().querySelector<HTMLElement>(`#${id}`);\n if (node !== null && !enabled) node.remove();\n }\n}\n\nfunction start(): void {\n app.draw = schedule;\n app.drawNow = drawNow;\n app.showTab = showTab;\n app.syncUrl = (options) => syncUrl(options?.replace !== false);\n\n applySections();\n initHeader();\n drawPeers();\n initTabs();\n initSkipLink();\n initKeyboard();\n initRanges();\n if (SECTIONS.feed) initFeed();\n initActor();\n initActorScope();\n initTester();\n initPolicy();\n\n let resizeTimer: ReturnType<typeof setTimeout> | undefined;\n addEventListener(\"resize\", () => {\n if (resizeTimer !== undefined) clearTimeout(resizeTimer);\n resizeTimer = setTimeout(() => {\n if (state.tab === \"stats\") {\n drawTraffic();\n drawLatency();\n drawScores();\n }\n }, 120);\n });\n\n // Open on whatever view the URL names, so a link to #policy lands there and a refresh\n // keeps your place — filter and search included. replace:true so the first entry does\n // not add a step the back button has to walk through on the way out.\n const url = readUrl();\n state.filter = url.filter;\n setSearch(url.search);\n if (SECTIONS.feed) reflectFilterButtons();\n applyActorScope(url.actorScope);\n showTab(url.tab, { replace: true });\n\n suspendScrollAnchoring();\n // Settled when the snapshot that draws the counters has actually been rendered, rather\n // than a couple of frames after start-up. The counters are the tallest thing script puts\n // above the feed and they wait on a round trip, so a frame count was measuring the wrong\n // thing and the page still opened scrolled.\n void loadInitialSnapshot().finally(settleScrollAnchoring);\n connectStream();\n}\n\n/**\n * Lets the page finish drawing itself before the browser starts defending the scroll\n * position.\n *\n * The counters, the filter chips and the pager are all built by script after the document\n * has laid out, which inserts a few hundred pixels above the feed. Scroll anchoring reads\n * that as \"content appeared above what you were looking at\" and scrolls down by the same\n * amount to compensate — so on a window narrow enough for the counter row to wrap, the\n * dashboard opened with its own counters already off the top of the screen.\n *\n * Off for the first render, then back on, rather than off for good: the feed puts new\n * requests at the *top*, and anchoring is what keeps somebody's place while they read a\n * list that is growing above them.\n */\nfunction htmlElement(): HTMLElement | undefined {\n const root = rootNode();\n return root instanceof Document ? root.documentElement : undefined;\n}\n\nfunction suspendScrollAnchoring(): void {\n htmlElement()?.classList.add(\"settling\");\n}\n\nfunction settleScrollAnchoring(): void {\n const html = htmlElement();\n if (html === undefined) return;\n // Two frames after the snapshot: its own render, and the layout that follows. A third\n // was measured and changed nothing. What remains is about fifteen pixels on a narrow\n // window — the counters are fully clear of the header, which is what mattered.\n requestAnimationFrame(() => {\n requestAnimationFrame(() => {\n html.classList.remove(\"settling\");\n });\n });\n}\n\nstart();\n","/**\n * `<bot-dashboard>` — the operator dashboard as an element you drop into your own page.\n *\n * ```html\n * <bot-dashboard src=\"/_bots\"></bot-dashboard>\n * <script type=\"module\">\n * import { defineBotDashboard } from \"@osqd/bothandlerjs/element\";\n * defineBotDashboard();\n * </script>\n * ```\n *\n * The data still comes from a mounted handler — `createDashboardHandler(botHandler, {\n * basePath: \"/_bots\" })` — because there is nowhere else for it to come from. What the\n * element removes is having to build, style and route a page around it.\n *\n * ## What running it in your page costs you\n *\n * This renders into a shadow root inside your document, which is what makes it sit in\n * your layout rather than in a frame. The shadow root is a styling boundary and **not a\n * security boundary**: any script that can run on the host page can reach into it, read\n * every visitor address and verdict on screen, and call the dashboard's API with your\n * credentials. On the standalone page an injected script in your application could do\n * none of that.\n *\n * So mount it on a page that is already behind your admin authentication, and treat an\n * XSS on that page as equivalent to handing over the dashboard. If you would rather have\n * the isolation than the layout, serve the standalone page instead — it is the same\n * dashboard, and `createDashboardHandler` already returns it.\n */\n\nimport { cssEscape } from \"../dashboard/client/css.js\";\nimport { cellText, explainStatus, parseMount, resolveSections, shapeOf } from \"./config.js\";\nimport type { BotDashboardConfig, BotDashboardPanel, BotDashboardRows, BotDashboardTab, BotDashboardTabId, BotDashboardTheme } from \"./config.js\";\nimport { DASHBOARD_CSS, DASHBOARD_MARKUP } from \"../dashboard/page.js\";\n\n/** One screen of the dashboard. */\nexport type { BotDashboardConfig, BotDashboardPanel, BotDashboardRows, BotDashboardTab, BotDashboardTabId, BotDashboardTheme };\n\n/**\n * Most rows a panel may put on the page.\n *\n * A panel is a summary, and a source that returns everything it has should not be able to\n * lock up somebody's admin page while fifty thousand rows are laid out — which is exactly\n * what an unbounded version did when asked. Anything past this is dropped and said so, so\n * the number on screen is never quietly wrong.\n */\nconst MAX_PANEL_ROWS = 200;\n\nconst TAB_ELEMENT: Record<BotDashboardTabId, string> = {\n live: \"tab-live\",\n actors: \"tab-actors\",\n stats: \"tab-stats\",\n policy: \"tab-policy\",\n};\n\n\nconst EXTRA_CSS = `\n/* What the body element carries on the standalone page.\n A shadow root has no body, so without this nothing sets the base colour, background or\n type: every element inherited the *host page's* colour instead. On a white page that\n passed for correct; in dark mode it was near-black text on a near-black surface, which\n axe reported as a serious contrast failure and which is exactly the kind of thing this\n library is not supposed to ship. */\n:host {\n display: block;\n background: var(--page);\n color: var(--ink);\n font: 14px/1.55 system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n -webkit-font-smoothing: antialiased;\n}\n:host([hidden]) { display: none; }\n/* The page's own chrome assumes it owns the viewport. Inside somebody else's layout the\n element is the viewport, so the sticky header sticks to the element and the body\n metrics come from the host box. */\n:host { position: relative; }\n:host header { position: sticky; top: 0; }\n.bd-extra { display: grid; gap: 10px; padding: 14px 15px; }\n.bd-notice { font: 14px/1.5 system-ui, sans-serif; padding: 14px; margin: 0; color: var(--crit-text, #8a1c1c); }\n.bd-extra .row { display: flex; justify-content: space-between; gap: 12px; align-items: baseline; }\n.bd-extra .row b { font-weight: 600; font-variant-numeric: tabular-nums; }\n.bd-extra .row span { color: var(--muted); font-size: 12px; }\n:host([data-density=\"compact\"]) tbody td,\n:host([data-density=\"compact\"]) thead th { padding-top: 5px; padding-bottom: 5px; }\n:host([data-density=\"compact\"]) .panel > h2 { padding-top: 8px; padding-bottom: 8px; }\n`;\n\n/**\n * The dashboard's rendered subtree, kept across mounts.\n *\n * An SPA route unmounts and remounts the element, and the first version refused the second\n * mount outright — \"a bot dashboard is already running on this page\" — which made the\n * element unusable in React, Vue or anything else with a router. The client is a module\n * graph that evaluates once and holds its own state, so it cannot simply be started again.\n *\n * Keeping the subtree and re-parenting it into the new shadow root solves both halves: the\n * client's element references stay valid because they are the same nodes, and the feed\n * history survives the navigation instead of starting empty. What changes on a remount is\n * the host element the theme hangs off, and the stylesheet, which belongs to the shadow\n * root rather than to the subtree.\n */\nlet container: HTMLElement | undefined;\nlet owner: BotDashboardElement | undefined;\n/**\n * The theme most recently applied, remembered across mounts.\n *\n * A router builds a *new* element on the way back to a route, and that element carries no\n * configuration until the framework sets it — which for an attribute-driven or set-once\n * integration is never. Without this the dashboard came back unthemed: the tokens had been\n * set as inline properties on the previous host, which is gone.\n */\nlet lastTheme: BotDashboardTheme | undefined;\n\n/**\n * Registers `<bot-dashboard>`. Safe to call more than once.\n *\n * @param name - element name, if `bot-dashboard` is taken.\n */\nexport function defineBotDashboard(name = \"bot-dashboard\"): void {\n if (typeof customElements === \"undefined\") return;\n const taken = customElements.get(name);\n if (taken !== undefined) {\n // Calling this twice is ordinary and stays silent — it is the documented way to be\n // safe about ordering. A name held by *something else* is a different situation\n // entirely: this call does nothing, every `<bot-dashboard>` on the page is the other\n // library's element, and nothing about the resulting blank space says so.\n if (taken !== BotDashboardElement && !(taken.prototype instanceof BotDashboardElement) && !clashed.has(name)) {\n clashed.add(name);\n console.warn(`bot-dashboard: <${name}> is already registered on this page by something else, so this did nothing and your <${name}> elements are not this dashboard. Register it under a name of your own instead: defineBotDashboard(\"ops-dashboard\").`);\n }\n return;\n }\n // A constructor may be registered once per registry, so a second name cannot reuse this\n // class — `customElements.define` throws NotSupportedError, uncaught, from inside a\n // function whose whole job is to register a name. A fresh subclass per name is what the\n // platform wants, and it costs nothing: the behaviour is inherited whole.\n //\n // Only one dashboard runs at a time whatever it is called; that is enforced separately,\n // in `boot`.\n customElements.define(name, registered ? class extends BotDashboardElement {} : BotDashboardElement);\n registered = true;\n}\n\n/** Whether the base class has been handed to the registry yet. See {@link defineBotDashboard}. */\nlet registered = false;\n\n/** Names already reported as taken, so calling `defineBotDashboard` twice says it once. */\nconst clashed = new Set<string>();\n\n/**\n * `HTMLElement`, or a stand-in where there is no DOM.\n *\n * `class X extends HTMLElement` is evaluated when the module is *loaded*, not when the\n * element is used — so on a server, where there is no `HTMLElement`, merely importing this\n * file threw `ReferenceError` before a line of anyone's code ran. That is not an exotic\n * case: it is what every framework with server rendering does with a top-level\n * `import { defineBotDashboard } from \"@osqd/bothandlerjs/element\"`, which is exactly the\n * line the documentation tells people to write.\n *\n * `defineBotDashboard` already declines to do anything without a `customElements`\n * registry, so the intent was there; the class declaration simply ran first. With this the\n * module imports anywhere, and does nothing at all until it is in a browser.\n */\nconst ElementBase: typeof HTMLElement = typeof HTMLElement === \"undefined\" ? (class {} as unknown as typeof HTMLElement) : HTMLElement;\n\nexport class BotDashboardElement extends ElementBase {\n private settings: BotDashboardConfig = {};\n private booted = false;\n /**\n * Booted *and* finished drawing. Distinct from `booted`, which is set on the way in:\n * `connectedCallback` replays a config assigned before the upgrade through the setter,\n * and at that moment `booted` is already true — so a warning keyed on it fired on a\n * perfectly ordinary mount, complaining that the config had changed when what had\n * happened was that it had arrived.\n */\n private rendered = false;\n\n /**\n * Set before the element is attached, or pass the same things as attributes.\n *\n * An accessor rather than a field, and the difference is load-bearing. A page that\n * writes `el.config = {...}` before the element is defined puts an *own property* on\n * the instance, and a class field initialiser then runs at upgrade and overwrites it —\n * so the configuration silently disappears and the dashboard renders its defaults. The\n * accessor plus the upgrade dance in `connectedCallback` is the documented way round\n * it, and the order it protects (write the config, then define the element) is the\n * natural one to write.\n */\n get config(): BotDashboardConfig {\n return this.settings;\n }\n\n set config(value: BotDashboardConfig) {\n const before = this.rendered && this.isConnected ? shapeOf(this.settings) : undefined;\n this.settings = value ?? {};\n // Applied immediately when the element is already running, so a framework that sets\n // its props after mount — which most of them do on a re-render — changes the theme\n // rather than being quietly ignored.\n if (this.booted && this.isConnected) {\n this.applyTheme();\n // `tabs` and `panels` are settled at boot, and until now changing them did nothing\n // at all — no effect and no word about it, which is the failure `src` a few lines\n // down already refuses to make. Compared by shape rather than by identity, because\n // a framework hands over a freshly built object on every render and warning about\n // that would be noise nobody could act on.\n if (before !== undefined && shapeOf(this.settings) !== before) {\n console.warn(\"bot-dashboard: `tabs` and `panels` are read once, when the element first mounts. Changing them afterwards has no effect — remount the element instead. (`theme` does update live.)\");\n }\n }\n }\n private timers: number[] = [];\n /** Custom properties this element put on itself, so it can take them off again. */\n private appliedTokens = new Set<string>();\n private warned = new Set<string>();\n\n static get observedAttributes(): string[] {\n return [\"src\", \"scheme\", \"density\"];\n }\n\n connectedCallback(): void {\n if (this.booted) return;\n this.booted = true;\n // Reclaim anything assigned before the upgrade: delete the own property so the write\n // below reaches the accessor rather than sitting in front of it for ever.\n if (Object.prototype.hasOwnProperty.call(this, \"config\")) {\n const preset = (this as unknown as { config: BotDashboardConfig }).config;\n // The own property has to be removed, not emptied: assigning undefined leaves it in\n // place, shadowing the accessor on the prototype for the life of the element, and\n // the configuration never arrives. `Reflect.deleteProperty` rather than `delete`\n // says the same thing without tripping a lint rule aimed at hot loops; this runs\n // once per element.\n Reflect.deleteProperty(this, \"config\");\n this.config = preset;\n }\n void this.boot();\n }\n\n disconnectedCallback(): void {\n for (const timer of this.timers) clearInterval(timer);\n this.timers = [];\n // The subtree stays alive and detached, ready to be re-parented. Releasing ownership\n // is what lets a later element — the same one after a route change, or a different\n // one — pick it up.\n if (owner === this) owner = undefined;\n // And the stream goes with it. Left open it holds a server connection and keeps\n // drawing into a tree nobody can see, for as long as the page lives. Reconnecting\n // resumes from the last id seen, so the cost of a route change is a reconnect.\n void import(\"../dashboard/client/stream.js\").then((stream) => {\n if (!this.isConnected) stream.suspendStream();\n });\n }\n\n attributeChangedCallback(attribute: string, previous: string | null, value: string | null): void {\n // Through the same two checks as the config path, so an attribute cannot set a value\n // the config would have refused.\n if (attribute === \"scheme\") this.applyScheme(value);\n if (attribute === \"density\") this.applyDensity(value);\n // `src` decides where the client fetches from, and the client reads it once as it\n // loads. Changing it later does nothing, and doing nothing quietly is how somebody\n // spends an afternoon on it.\n if (attribute === \"src\" && previous !== null && previous !== value && this.booted) {\n console.warn(\"bot-dashboard: `src` is read once, when the element first mounts. Changing it afterwards has no effect — replace the element instead.\");\n }\n }\n\n /**\n * Give up on this attempt without giving up on the element.\n *\n * Every way a boot can end early — it lost the one-at-a-time race, its `src` is\n * cross-origin, its handler did not answer, it was unmounted mid-flight — is about this\n * moment rather than about this element, and all four are things a remount can fix: the\n * other dashboard has gone, the attribute was corrected, the server finished starting.\n * Releasing ownership without clearing `booted` was the bug: `connectedCallback` returns\n * early on a latched `booted`, so the element stayed dead for the life of the instance\n * and a router that remounts it drew a stale error over a handler that now works.\n */\n private standDown(): void {\n if (owner === this) owner = undefined;\n this.booted = false;\n this.rendered = false;\n }\n\n /**\n * Where the handler is mounted, from `config.src` or the attribute.\n *\n * A trailing slash is dropped, and so are a query string and a fragment — the element\n * appends `/api/bootstrap` to this, so anything after the path cannot survive that\n * concatenation and never could. Left in, `src=\"/_bots?token=x\"` failed with \"it\n * answered text/html — is createDashboardHandler mounted at /_bots?token=x?\", which\n * sends somebody to check the one thing that was right.\n */\n private mountPath(): string {\n const { base, warning } = parseMount(this.config.src ?? this.getAttribute(\"src\") ?? \"\");\n if (warning !== undefined) this.warnOnce(`bot-dashboard: ${warning}`);\n return base;\n }\n\n private async boot(): Promise<void> {\n const base = this.mountPath();\n const shadow = this.shadowRoot ?? this.attachShadow({ mode: \"open\" });\n // A retry after a refusal or a failed boot: drop the message from that attempt, or the\n // dashboard renders underneath a line that is no longer true.\n //\n // Direct children only. The same class marks a panel's own \"could not load\" message,\n // and on a remount the built tree is still hanging in this shadow root — so a plain\n // `querySelectorAll(\".bd-notice\")` reached inside the panels and wiped those too. They\n // came back on the next repaint, which is why it looked harmless; it was still this\n // function deleting other code's output.\n //\n // Walked rather than selected, because `:scope > .bd-notice` matches nothing here: the\n // scoping root is a shadow root, `:scope` only matches an element, and the tidier\n // selector silently removed nothing at all.\n for (const child of Array.from(shadow.children)) {\n if (child.classList.contains(\"bd-notice\")) child.remove();\n }\n\n // Still only one at a time. The client is a module graph with its own state, and two\n // live instances would share it — two feeds writing to one store, and the second\n // element silently drawing the first one's traffic. But *sequentially* is fine, and\n // is what a router does, so the test is whether one is currently connected rather\n // than whether one has ever been.\n if (owner !== undefined && owner !== this && owner.isConnected) {\n shadow.append(notice(\"A bot dashboard is already running on this page. Only one can be.\"));\n this.standDown();\n return;\n }\n owner = this;\n\n // A remount: re-parent what is already built and rendered, and re-point the theme.\n if (container !== undefined) {\n const dom = await import(\"../dashboard/client/dom.js\");\n dom.setThemeHost(this);\n this.applyTheme();\n adoptStyles(shadow);\n shadow.append(container);\n this.renderPanels(container);\n // Pick the feed back up where it was left.\n const stream = await import(\"../dashboard/client/stream.js\");\n stream.connectStream();\n this.rendered = true;\n this.dispatchEvent(new CustomEvent(\"bot-dashboard-ready\", { bubbles: true }));\n return;\n }\n\n // A cross-origin `src` cannot work and should say why rather than failing as a bare\n // \"Failed to fetch\". The dashboard deliberately sends no Access-Control-Allow-Origin —\n // that is what stops another site reading your traffic through a logged-in browser —\n // so it is same-origin only, and pointing at another origin is a configuration\n // mistake rather than something to retry.\n if (base !== \"\" && /^https?:\\/\\//i.test(base)) {\n try {\n if (new URL(base).origin !== location.origin) {\n const why = `src points at ${new URL(base).origin}, which is not this page's origin. The dashboard is same-origin only: it sends no CORS headers, which is what stops another site reading your traffic. Mount it under this origin and use a path, e.g. src=\"/_bots\".`;\n shadow.append(notice(`The bot dashboard could not start: ${why}`));\n console.error(\"bot-dashboard:\", why);\n this.standDown();\n return;\n }\n } catch {\n /* an unparseable src falls through to the fetch, which reports it. */\n }\n }\n\n let boot: Record<string, unknown>;\n try {\n const response = await fetch(`${base}/api/bootstrap`, { credentials: \"same-origin\", headers: { accept: \"application/json\" } });\n if (!response.ok) throw new Error(explainStatus(response.status, base));\n // Checked before parsing, because the failure it catches is the common one and the\n // parser's account of it is useless: with no `src` the element asks its own origin,\n // is handed the host page's HTML, and reports \"Unexpected token '<'\" to a developer\n // whose actual mistake was forgetting an attribute.\n const type = response.headers.get(\"content-type\") ?? \"\";\n if (!type.includes(\"json\")) {\n throw new Error(\n base === \"\"\n ? \"no `src` was given, so it asked this page's own origin and got HTML back. Point `src` at where createDashboardHandler is mounted, e.g. src=\\\"/_bots\\\"\"\n : `it answered ${type || \"no content type\"} rather than JSON. Is createDashboardHandler mounted at ${base}?`,\n );\n }\n boot = (await response.json()) as Record<string, unknown>;\n // Removed while that was in flight — a route change during the round trip. Ownership\n // was already handed back by `disconnectedCallback`, and somebody else may hold it\n // by now, so building into a detached tree here would be a second client racing the\n // live one for the same module state.\n if (!this.isConnected) {\n this.standDown();\n return;\n }\n } catch (error) {\n // A dashboard that cannot reach its handler should say so where somebody will see\n // it, rather than rendering an empty shell that looks like no traffic.\n const why = String(error instanceof Error ? error.message : error);\n shadow.append(notice(`The bot dashboard could not start: ${why}`));\n // Also to the console, because the element may be somewhere nobody is looking and\n // this is always a configuration mistake rather than a condition.\n console.error(\"bot-dashboard:\", why);\n this.standDown();\n return;\n }\n\n // The mount path the element was given wins over whatever the server thinks it is:\n // the element is the thing that knows where it is pointing.\n boot[\"base\"] = base;\n // Which screens survive the server's answer, `hide` and `tabs` — worked out in\n // `config.ts`, which has no document in it and can therefore be tested directly.\n const { sections, warnings } = resolveSections(boot[\"sections\"] as Record<string, boolean>, this.config);\n for (const warning of warnings) this.warnOnce(`bot-dashboard: ${warning}`);\n boot[\"sections\"] = sections;\n\n adoptStyles(shadow);\n\n const frame = document.createElement(\"div\");\n frame.className = \"bd-root\";\n // The markup is this package's own, built from a template literal in `page.ts` and\n // containing no interpolation of anything a request supplied. It is the one place the\n // no-innerHTML rule does not apply, and a test asserts that rather than trusting it.\n //\n // The one placeholder it carries is the title, which the standalone renderer\n // substitutes and this has to as well — the first version did not, and the header\n // read \"__TITLE__\" to anyone who looked at it.\n frame.innerHTML = DASHBOARD_MARKUP;\n shadow.append(frame);\n const title = typeof boot[\"title\"] === \"string\" ? boot[\"title\"] : \"bothandlerjs\";\n\n // The page's own <main> is a landmark, and a landmark inside somebody else's page that\n // already has one is two mains: a screen reader user gets two \"main\" landmarks to\n // choose between, and axe reports landmark-no-duplicate-main and landmark-unique.\n //\n // Replaced rather than re-roled. Setting role=\"region\" on a <main> silences those two\n // and earns aria-allowed-role instead, because <main> permits no role but its own — so\n // the honest fix is to not be a <main>. The class carries the layout, so swapping the\n // tag changes nothing visually.\n const main = frame.querySelector(\"main\");\n if (main !== null) {\n // Swapped rather than re-roled, because <main> permits no role but its own —\n // setting role=\"region\" silences the duplicate-landmark rules and earns\n // aria-allowed-role instead. Its class carries the layout, so the tag is all that\n // changes.\n const region = document.createElement(\"div\");\n region.className = main.className;\n region.setAttribute(\"role\", \"region\");\n region.setAttribute(\"aria-label\", `${title} bot dashboard`);\n while (main.firstChild !== null) region.append(main.firstChild);\n main.replaceWith(region);\n }\n\n // The <header> is a banner landmark for the same reason, and a page may have one\n // banner. Re-roled rather than swapped, because <header> does permit role=\"none\" and\n // because its styling is by element rather than by class — a div here would lose it.\n // `none` drops the element's own semantics and leaves its children where they are.\n frame.querySelector(\"header\")?.setAttribute(\"role\", \"none\");\n for (const node of Array.from(frame.querySelectorAll(\"*\"))) {\n for (const child of Array.from(node.childNodes)) {\n // textContent, so a title containing markup is a title containing markup.\n if (child.nodeType === 3 && child.nodeValue?.includes(\"__TITLE__\") === true) {\n child.nodeValue = child.nodeValue.replace(/__TITLE__/g, title);\n }\n }\n }\n\n this.applyTheme();\n\n // Order matters. `setRoot` has to run before the rest of the client evaluates, since\n // its modules resolve their elements as they initialise — so the dom module is\n // imported and pointed at the shadow root first, and only then the entry.\n container = frame;\n const dom = await import(\"../dashboard/client/dom.js\");\n // The subtree rather than the shadow root, so a remount can carry it to a new one.\n dom.setRoot(frame, this);\n // Handed over explicitly rather than left on the global for the client to find. The\n // global is still set, because the served page uses it and anything reading it should\n // see the truth — but this call is what the client actually reads, and it works\n // however early some other path happened to evaluate that module. See `applyBoot`.\n (globalThis as unknown as { __BOOTSTRAP__?: unknown }).__BOOTSTRAP__ = boot;\n const bootModule = await import(\"../dashboard/client/boot.js\");\n bootModule.applyBoot(boot as unknown as Parameters<typeof bootModule.applyBoot>[0]);\n\n await import(\"../dashboard/client/index.js\");\n // After the client, because it draws the strip from the boot object and would undo a\n // reorder applied before it ran.\n this.relabelTabs(shadow);\n this.renderPanels(frame);\n\n warnIfFramed();\n\n this.rendered = true;\n this.dispatchEvent(new CustomEvent(\"bot-dashboard-ready\", { bubbles: true }));\n }\n\n private applyTheme(): void {\n const theme = this.config.theme ?? lastTheme ?? {};\n if (this.config.theme !== undefined) lastTheme = this.config.theme;\n this.applyScheme(theme.scheme ?? this.getAttribute(\"scheme\"));\n this.applyDensity(theme.density ?? this.getAttribute(\"density\"));\n\n const next = new Set<string>();\n for (const [token, value] of Object.entries(theme.tokens ?? {})) {\n const name = token.startsWith(\"--\") ? token : `--${token}`;\n next.add(name);\n this.style.setProperty(name, value);\n }\n // A token dropped from the config has to be dropped from the element, or a theme\n // switch leaves the colours it stopped asking for painted on for ever. Removed by\n // name, one at a time, rather than by clearing the style attribute: the client keeps\n // its own measurements there — `--header-h` among them — and clearing it would take\n // the sticky header's layout with it.\n for (const name of this.appliedTokens) {\n if (!next.has(name)) this.style.removeProperty(name);\n }\n this.appliedTokens = next;\n }\n\n /**\n * `scheme` and `density` take two values each, and until now anything else was accepted\n * in silence — a mis-typed `scheme` did nothing, and a mis-typed `density` was written\n * onto the element as `data-density=\"cozy\"`, matching no rule and looking, from the\n * outside, exactly like a dashboard that ignores its configuration.\n *\n * Warned once per bad value per element, because `applyTheme` runs on every re-render\n * and a typo that repeats a hundred times is a typo the console has stopped conveying.\n */\n private applyScheme(value: string | null | undefined): void {\n if (value === null || value === undefined) return;\n if (value === \"light\" || value === \"dark\") {\n this.setAttribute(\"data-theme\", value);\n return;\n }\n this.warnOnce(`bot-dashboard: scheme \"${value}\" is not \"light\" or \"dark\", so it was ignored. Leave it unset to follow the viewer's own setting.`);\n }\n\n private applyDensity(value: string | null | undefined): void {\n if (value === null || value === undefined) return;\n if (value === \"comfortable\" || value === \"compact\") {\n this.setAttribute(\"data-density\", value);\n return;\n }\n this.warnOnce(`bot-dashboard: density \"${value}\" is not \"comfortable\" or \"compact\", so it was ignored.`);\n }\n\n private warnOnce(message: string): void {\n if (this.warned.has(message)) return;\n this.warned.add(message);\n console.warn(message);\n }\n\n /**\n * Reorders and relabels the strip the client has just drawn.\n *\n * Only the tabs that survived: which screens *exist* was settled in the boot object\n * before the client ran, because the client builds its list from there and looks each\n * one up by id — deleting an element here instead crashed it with \"no element\n * #tab-actors\", which is what this comment is standing in for.\n */\n private relabelTabs(shadow: ShadowRoot): void {\n const wanted = this.config.tabs;\n if (wanted === undefined || wanted.length === 0) return;\n const strip = shadow.querySelector(\".tabs\");\n if (strip === null) return;\n\n for (const tab of wanted) {\n const node = shadow.querySelector(`#${TAB_ELEMENT[tab.id]}`);\n if (node === null) continue;\n if (tab.label !== undefined) node.textContent = tab.label;\n // Appending a node already in the strip moves it, which is the reorder.\n strip.append(node);\n }\n }\n\n private renderPanels(within: ParentNode): void {\n const panels = this.config.panels ?? [];\n\n // The rendered tree outlives the element that built it — it is reused across a remount\n // and, on a route change, adopted by a *different* element with a different config. So\n // a panel that this config does not ask for has to go, or the new dashboard shows the\n // old one's panels alongside its own, frozen, because the timer that fed them was\n // cleared when their element was disconnected. Keyed on screen as well as id, so a\n // panel moved to another screen leaves rather than being drawn in both.\n const wanted = new Set(panels.map((panel) => `${panel.id}@${panel.screen}`));\n for (const section of Array.from(within.querySelectorAll(\"[data-bd-panel]\"))) {\n const key = `${section.getAttribute(\"data-bd-panel\")}@${section.getAttribute(\"data-bd-screen\")}`;\n if (!wanted.has(key)) section.remove();\n }\n\n const drawn = new Set<string>();\n for (const panel of panels) {\n // Two panels under one id land on one section: the first builds it, the second finds\n // it and paints over it, and then both refresh timers write into the same body,\n // which flickers between two sources every second. The section keeps the first\n // panel's heading while showing the second one's rows, so the screen actively\n // misdescribes itself.\n const key = `${panel.id}@${panel.screen}`;\n if (drawn.has(key)) {\n this.warnOnce(`bot-dashboard: two panels share the id \"${panel.id}\" on screen \"${panel.screen}\". Ids identify a panel, so the second was ignored — give them different ids.`);\n continue;\n }\n drawn.add(key);\n\n // Escaped, because `screen` and `id` come from whoever wrote the config and a\n // selector built by interpolation is a parser waiting to be handed something it\n // cannot read. An id containing a quote threw `SyntaxError` out of `querySelector`\n // — from here, outside the per-paint try below — which took down the whole loop\n // before the first panel was built, left `bot-dashboard-ready` undispatched, and\n // reported itself as a complaint about a selector nobody wrote.\n const host = within.querySelector(`#view-${cssEscape(panel.screen)}`);\n if (host === null) {\n // Silence here means a panel that simply never appears, and a developer reading\n // their own config for an hour. `src` gets the same treatment a few lines up.\n // The list is read off the rendered markup rather than kept alongside it, so it\n // stays right when a screen is added and cannot drift into naming one that a\n // `tabs` or `hide` setting has already removed.\n const known = Array.from(within.querySelectorAll(\"[id^='view-']\"))\n .map((node) => node.id.slice(\"view-\".length))\n .join(\", \");\n console.warn(`bot-dashboard: panel \"${panel.id}\" asks for screen \"${panel.screen}\", which this dashboard does not have. It has: ${known}.`);\n continue;\n }\n\n // A remount runs this against a subtree that already has the panels, so the section\n // is reused rather than rebuilt — but the timer below is *not*, because\n // `disconnectedCallback` cleared it. Skipping the whole loop here left a refreshing\n // panel frozen on whatever it last drew after the first route change.\n const existing = host.querySelector(`[data-bd-panel=\"${cssEscape(panel.id)}\"]`);\n let body: HTMLElement;\n if (existing !== null) {\n body = existing.querySelector(\".bd-extra\") as HTMLElement;\n } else {\n const section = document.createElement(\"section\");\n section.className = \"panel\";\n section.setAttribute(\"data-bd-panel\", panel.id);\n section.setAttribute(\"data-bd-screen\", panel.screen);\n const heading = document.createElement(\"h2\");\n heading.textContent = panel.title;\n body = document.createElement(\"div\");\n body.className = \"bd-extra\";\n section.append(heading, body);\n host.append(section);\n }\n\n const paint = async (): Promise<void> => {\n try {\n const data = typeof panel.source === \"string\"\n ? ((await (await fetch(panel.source, { credentials: \"same-origin\" })).json()) as BotDashboardRows)\n : await panel.source();\n while (body.firstChild) body.removeChild(body.firstChild);\n // Checked rather than assumed, because a source is somebody else's endpoint and\n // the failures are not hypothetical: `{ rows: \"nope\" }` is *iterable*, so\n // without this it rendered one row per character, each reading \"undefined\".\n if (!Array.isArray(data?.rows)) {\n body.append(notice(`${panel.title} returned no rows.`));\n return;\n }\n const all = data.rows;\n for (const row of all.slice(0, MAX_PANEL_ROWS)) {\n // A row with nothing to say is skipped rather than drawn as \"undefined\".\n if (typeof row !== \"object\" || row === null || row.label === undefined || row.value === undefined) continue;\n const line = document.createElement(\"div\");\n line.className = \"row\";\n const label = document.createElement(\"span\");\n label.textContent = cellText(row.label);\n const value = document.createElement(\"b\");\n // textContent throughout. Whatever your endpoint returns is somebody's data,\n // and this file will not turn it into markup.\n value.textContent = cellText(row.value);\n line.append(label, value);\n if (row.note !== undefined) {\n const note = document.createElement(\"span\");\n note.textContent = cellText(row.note);\n line.append(note);\n }\n body.append(line);\n }\n if (all.length > MAX_PANEL_ROWS) {\n const more = document.createElement(\"div\");\n more.className = \"row\";\n const label = document.createElement(\"span\");\n label.textContent = `${all.length - MAX_PANEL_ROWS} more not shown`;\n more.append(label);\n body.append(more);\n }\n } catch {\n while (body.firstChild) body.removeChild(body.firstChild);\n body.append(notice(`Could not load ${panel.title}.`));\n }\n };\n\n void paint();\n if (panel.refreshMs !== undefined && panel.refreshMs > 0) {\n this.timers.push(setInterval(paint, Math.max(1000, panel.refreshMs)) as unknown as number);\n }\n }\n }\n}\n\n/**\n * The stylesheet, built once and adopted by every shadow root that needs it.\n *\n * A constructable stylesheet rather than a `<style>` element, and the reason is Content\n * Security Policy. An injected `<style>` is inline style, so a host page carrying\n * `style-src 'self'` — which any admin page worth mounting this on does — blocks it, and\n * the dashboard renders with no colours, no radii and no layout at all. A stylesheet built\n * by script that has already satisfied `script-src` is not inline style and is not\n * blocked.\n *\n * It is also the cheaper answer: one parse of forty kilobytes of CSS shared by every\n * mount, rather than one per mount.\n */\nlet sheet: CSSStyleSheet | undefined;\n\nfunction adoptStyles(shadow: ShadowRoot): void {\n if (typeof CSSStyleSheet !== \"undefined\" && \"replaceSync\" in CSSStyleSheet.prototype && \"adoptedStyleSheets\" in shadow) {\n if (sheet === undefined) {\n sheet = new CSSStyleSheet();\n sheet.replaceSync(`${DASHBOARD_CSS}\\n${EXTRA_CSS}`);\n }\n shadow.adoptedStyleSheets = [...shadow.adoptedStyleSheets, sheet];\n return;\n }\n // Older engines without constructable stylesheets. Blocked by a strict style-src, which\n // is the trade those browsers get rather than no dashboard at all.\n const style = document.createElement(\"style\");\n style.textContent = `${DASHBOARD_CSS}\\n${EXTRA_CSS}`;\n shadow.append(style);\n}\n\n\n/**\n * What of a config actually decides the rendered screens: which tabs, and which panels\n * where. Deliberately not the panel `source` functions — a framework rebuilds those\n * closures on every render, and a comparison that counted them would report a change on\n * every render regardless of whether anything meaningful differed.\n */\n\nlet framingWarned = false;\n\n/**\n * The one protection that does not survive being embedded.\n *\n * The standalone dashboard is served by this package and its response carries\n * `frame-ancestors 'none'`, so a page on another origin cannot frame it and trick an\n * operator into clicking a control they cannot see. The element renders into a document\n * this package does not serve and cannot set a header on — so whether the same attack\n * works is decided by the host page's own headers, and the failure mode is silent.\n *\n * A warning rather than a refusal: an admin app framing its own pages is ordinary and\n * legitimate, which is why the check is specifically for a *cross-origin* ancestor —\n * reading `top.location` throws for one and reads fine for one of our own — and why the\n * decision to render anyway is not this element's to make.\n */\nfunction warnIfFramed(): void {\n if (framingWarned) return;\n if (globalThis.top === globalThis.self) return;\n try {\n void (globalThis.top as Window).location.origin;\n return;\n } catch {\n framingWarned = true;\n console.warn(\n \"bot-dashboard: this page is framed by another origin, and a framed dashboard can be clickjacked — an operator clicks a control they cannot see. The standalone page refuses framing with `frame-ancestors 'none'`; a page you serve yourself has to send that header itself, or `X-Frame-Options: DENY`.\",\n );\n }\n}\n\n\nfunction notice(text: string): HTMLElement {\n // A class rather than `style.cssText`. Writing the style attribute is inline style, and\n // a host page carrying `style-src 'self'` blocks it — so the one element whose whole job\n // is to explain a failure would have appeared unstyled, in the situation where somebody\n // most needs to notice it.\n const node = document.createElement(\"p\");\n node.className = \"bd-notice\";\n node.textContent = text;\n return node;\n}\n\n\ndeclare global {\n interface HTMLElementTagNameMap {\n \"bot-dashboard\": BotDashboardElement;\n }\n}\n","/**\n * The element's decisions, with no document in sight.\n *\n * `src/element/` is excluded from the coverage report for the same reason\n * `src/dashboard/client/` is — it needs a document and a custom-element registry, and a\n * number collected from a runner that cannot execute it would describe the runner. That\n * exclusion carries a second half in the client's case, and it was missing here: the pure\n * modules inside an excluded directory still get unit tests, they are simply not counted.\n * This is that module for the element.\n *\n * Everything below answers a question with an argument rather than by looking at the page:\n * which screens survive a config, what a status code means, where the handler is mounted,\n * whether two configs describe the same thing. It is also where the fiddly parts live —\n * `tabs` and `hide` naming different vocabularies, a screen the server withheld — which is\n * exactly the logic that had bugs in it and no test able to reach them.\n */\n\nimport type { DashboardSections } from \"../dashboard/sections.js\";\n\nexport type BotDashboardTabId = \"live\" | \"actors\" | \"stats\" | \"policy\";\n\nexport interface BotDashboardTab {\n id: BotDashboardTabId;\n /** Shown in the tab strip. Defaults to the built-in name. */\n label?: string;\n}\n\n/** A panel of your own, rendered beside the built-in ones. */\nexport interface BotDashboardPanel {\n id: string;\n /** Which screen it appears on. */\n screen: BotDashboardTabId;\n title: string;\n /**\n * Where its rows come from: a URL returning `{ rows: [{ label, value, note? }] }`, or a\n * function returning the same shape.\n *\n * Rendered as text, always — the same rule the rest of the client follows, and for the\n * same reason. Nothing here interprets markup, so a value that happens to contain a tag\n * appears as that tag rather than becoming one.\n */\n source: string | (() => Promise<BotDashboardRows> | BotDashboardRows);\n /** How often to refresh, ms. Omit for once on load. */\n refreshMs?: number;\n}\n\nexport interface BotDashboardRows {\n rows: ReadonlyArray<{ label: string; value: string | number; note?: string }>;\n}\n\nexport interface BotDashboardTheme {\n /**\n * Token overrides, by custom property name without the leading dashes:\n * `{ accent: \"#7c3aed\", surface: \"#fff\" }`.\n *\n * Applied to the host, so they cascade into the shadow root the same way the built-in\n * tokens do. Every token the stylesheet defines can be replaced; the contrast\n * guarantees the built-in palette was measured against are yours to keep once you do.\n */\n tokens?: Record<string, string>;\n /** Force a scheme instead of following the host page and the OS. */\n scheme?: \"light\" | \"dark\";\n /** `compact` tightens the row and panel padding. */\n density?: \"comfortable\" | \"compact\";\n}\n\nexport interface BotDashboardConfig {\n /** Where the dashboard handler is mounted. Also settable as the `src` attribute. */\n src?: string;\n /** Which screens appear, in which order, under which labels. */\n tabs?: readonly BotDashboardTab[];\n /** Panels of your own. */\n panels?: readonly BotDashboardPanel[];\n theme?: BotDashboardTheme;\n /**\n * Hide parts of the page the *server* is still serving.\n *\n * Cosmetic, and worth being clear about: a section switched off here is removed from\n * the screen and stays on the wire. The `sections` option on the handler is the one\n * that stops the data leaving the process, and it is the one to use when the point is\n * that somebody should not have it.\n */\n hide?: DashboardSections;\n}\n\n/**\n * The section flag that decides whether each screen exists.\n *\n * Omitting a tab has to be expressed this way rather than by deleting the element: the\n * client builds its tab list from the sections it was booted with and then looks each one\n * up, so a missing element is a crash rather than a smaller strip. Saying it in the boot\n * object means the client removes the screen itself, the way it already does for a\n * section the server switched off.\n */\nexport const TAB_SECTION: Record<BotDashboardTabId, keyof DashboardSections> = {\n live: \"feed\",\n actors: \"registry\",\n stats: \"statistics\",\n policy: \"policy\",\n};\n\n/** `hide: { policy: true }` means the client's `sections.policy` is false. */\nfunction invert(hide: DashboardSections): Record<string, boolean> {\n const out: Record<string, boolean> = {};\n for (const [key, value] of Object.entries(hide)) if (value === true) out[key] = false;\n return out;\n}\n\n/**\n * Which sections the client should be booted with, and what to say about the config.\n *\n * Three inputs settle it, in this order: what the server offered, what `hide` removes, and\n * what `tabs` narrows to. The warnings are returned rather than logged because the reasons\n * a screen goes missing are the interesting part and the caller is the only one that knows\n * whether it has already said them.\n */\nexport function resolveSections(\n fromServer: Readonly<Record<string, boolean>>,\n config: Pick<BotDashboardConfig, \"hide\" | \"tabs\">,\n): { sections: Record<string, boolean>; warnings: string[] } {\n const sections: Record<string, boolean> = { ...fromServer };\n const warnings: string[] = [];\n\n // `tabs` and `hide` do not share a vocabulary — `tabs` names screens (`live`, `actors`,\n // `stats`, `policy`) and `hide` names sections (`feed`, `registry`, `statistics`,\n // `policy`, and the finer-grained ones that are not screens at all). A name from the\n // wrong list is not a type error to anyone writing plain JavaScript, and did nothing\n // whatsoever: `hide: { live: true }` is the natural thing to write after reading about\n // `tabs`, and it left the live feed exactly where it was.\n for (const key of Object.keys(config.hide ?? {})) {\n if (Object.hasOwn(sections, key)) continue;\n const suggestion = TAB_SECTION[key as BotDashboardTabId];\n warnings.push(\n suggestion === undefined\n ? `hide.${key} is not a section this dashboard has, so it did nothing. It has: ${Object.keys(sections).sort().join(\", \")}.`\n : `hide.${key} did nothing — \"${key}\" is a tab name, and \\`hide\\` takes section names. You want hide.${suggestion}, or leave \"${key}\" out of \\`tabs\\`.`,\n );\n }\n for (const [key, value] of Object.entries(invert(config.hide ?? {}))) sections[key] = value;\n\n // A tab left out of `tabs` is a screen the client should not build at all.\n const wanted = config.tabs;\n if (wanted !== undefined && wanted.length > 0) {\n const keep = new Set(wanted.map((tab) => tab.id));\n for (const id of keep) {\n // Dropped in silence until now, so asking for two screens and being given one looked\n // like the element choosing for itself.\n if (TAB_SECTION[id] === undefined) {\n warnings.push(`tabs lists \"${id}\", which is not a screen. The screens are: ${Object.keys(TAB_SECTION).join(\", \")}.`);\n continue;\n }\n // Asked for, and withheld by the server rather than by anything on this page. The\n // element cannot override it and should not pretend to: `sections` is enforced where\n // the data is, which is the whole point of it. But a developer who lists two screens\n // and is given one deserves to be told which file to go and look in.\n if (fromServer[TAB_SECTION[id]] === false && config.hide?.[TAB_SECTION[id]] !== true) {\n warnings.push(\n `tabs lists \"${id}\", but this dashboard's server has the \"${TAB_SECTION[id]}\" section switched off, so that screen does not exist. That is the \\`sections\\` option on createDashboardHandler, and it is enforced there rather than here.`,\n );\n }\n }\n for (const id of Object.keys(TAB_SECTION) as BotDashboardTabId[]) {\n if (!keep.has(id)) sections[TAB_SECTION[id]] = false;\n }\n }\n\n return { sections, warnings };\n}\n\n/**\n * Where the handler is mounted, from a raw `src`.\n *\n * A trailing slash is dropped, and so are a query string and a fragment — the element\n * appends `/api/bootstrap` to this, so anything after the path cannot survive that\n * concatenation and never could. Left in, `src=\"/_bots?token=x\"` failed with \"it answered\n * text/html — is createDashboardHandler mounted at /_bots?token=x?\", which sends somebody\n * to check the one thing that was right.\n */\nexport function parseMount(raw: string): { base: string; warning?: string } {\n const cut = raw.search(/[?#]/);\n if (cut === -1) return { base: raw.replace(/\\/$/, \"\") };\n return {\n base: raw.slice(0, cut).replace(/\\/$/, \"\"),\n warning: `src \"${raw}\" has a ${raw[cut] === \"?\" ? \"query string\" : \"fragment\"} on it. The element asks for \\`<src>/api/bootstrap\\`, so only the path can mean anything here; the rest was ignored.`,\n };\n}\n\n/**\n * What a refusal from the handler actually means to whoever has to fix it.\n *\n * \"it answered 401\" is true and useless, and 401 is not an exotic case: the documented\n * setup is a dashboard with `auth` set, embedded in an admin page, and a background fetch\n * cannot put up the sign-in prompt that a browser would show for a navigation. So the\n * dashboard is simply blank with a number on it, and the number does not say that the\n * credentials the operator already has are the answer.\n */\nexport function explainStatus(status: number, base: string): string {\n if (status === 401) {\n return `it answered 401 Unauthorized. The dashboard has \\`auth\\` set, and the element fetches in the background, where a browser cannot offer the sign-in prompt it would show for a normal navigation. Open ${base || \"the dashboard\"} directly and sign in once — the browser then sends those credentials with the element's requests too — or put this page behind the same authentication.`;\n }\n if (status === 403) {\n return `it answered 403 Forbidden, which is this dashboard refusing the caller rather than the password: check \\`allowedHosts\\` and \\`allowedClients\\`, and that this page is on the same origin as \\`src\\`.`;\n }\n return `it answered ${status}`;\n}\n\n/**\n * What of a config actually decides the rendered screens: which tabs, and which panels\n * where. Deliberately not the panel `source` functions — a framework rebuilds those\n * closures on every render, and a comparison that counted them would report a change on\n * every render regardless of whether anything meaningful differed.\n */\nexport function shapeOf(config: BotDashboardConfig): string {\n const tabs = (config.tabs ?? []).map((tab) => tab.id).join(\",\");\n const panels = (config.panels ?? []).map((panel) => `${panel.id}@${panel.screen}:${panel.title}`).join(\",\");\n return `${tabs}|${panels}`;\n}\n\n/** A panel cell, as text and never as markup, and never longer than a cell should be. */\nexport function cellText(value: unknown): string {\n if (typeof value === \"string\") return value.slice(0, 200);\n if (typeof value === \"number\" || typeof value === \"boolean\") return String(value);\n if (value === null || value === undefined) return \"\";\n try {\n return JSON.stringify(value)?.slice(0, 200) ?? \"\";\n } catch {\n return \"\";\n }\n}\n","import { CLIENT_SCRIPT } from \"./client.generated.js\";\nimport type { DashboardSections } from \"./types.js\";\n\n/**\n * The dashboard page.\n *\n * Shipped as a template rather than as a file on disk: a bundled library cannot assume\n * anything about what sits next to its own JavaScript, and a dashboard that works from\n * `src` and 404s from `dist` is the worst kind of bug to find. The markup and the CSS\n * are the template below; the behaviour is a real TypeScript module under `client/`,\n * bundled into {@link CLIENT_SCRIPT} at build time by `scripts/build-client.mjs` and\n * stamped in here. It used to be two thousand lines of JavaScript inside this string,\n * where the type-checker could not see it, Biome did not lint it and no test could\n * import a function out of it — which is how a call to a function nobody had written\n * shipped and blanked the whole Statistics tab.\n *\n * Three rules govern everything here, and all three are about the fact that this page\n * renders **attacker-supplied text**. A User-Agent is written by the client. So is the\n * path. So is anything a detector quoted into an evidence summary.\n *\n * 1. **Nothing is ever assembled into HTML.** Every value reaches the document through\n * `textContent`. There is no `innerHTML` in the page or in the bundle, and the\n * build refuses to produce one.\n * 2. **No `style` attributes.** The page is served under a nonce-based CSP, which\n * blocks inline styles; geometry is set through the CSSOM (`el.style.width`), which\n * is not a style attribute and is allowed.\n * 3. **No network access of any kind.** `default-src 'none'` and no external font,\n * script or image. Everything is here.\n */\n\nexport interface DashboardPageOptions {\n title: string;\n basePath: string;\n links: ReadonlyArray<{ label: string; href: string }>;\n allowReset: boolean;\n allowEdit: boolean;\n allowGuardEdit: boolean;\n allowActing: boolean;\n peers: ReadonlyArray<{ label: string; href: string }>;\n /** Which parts of the page this listener has. The client removes the rest of them. */\n sections: Required<DashboardSections>;\n}\n\n/**\n * Builds the page once and returns a function that stamps a per-response nonce into\n * it. The HTML is a few tens of kilobytes and identical on every request; rebuilding\n * it per view would be pure waste.\n */\n/**\n * What the page is told about itself.\n *\n * Built here rather than inline in the renderer so that the embeddable element can be\n * served the identical object from `/api/bootstrap`. The page carries it stamped into its\n * one nonced script; the element has to ask, because it is rendered into somebody else's\n * document and there is nothing to stamp.\n */\nexport function bootFor(options: DashboardPageOptions): Record<string, unknown> {\n return {\n base: options.basePath === \"/\" ? \"\" : options.basePath,\n title: options.title,\n allowReset: options.allowReset,\n allowEdit: options.allowEdit,\n allowGuardEdit: options.allowGuardEdit,\n allowActing: options.allowActing,\n peers: options.peers.map((peer) => ({ label: String(peer.label), href: String(peer.href) })),\n sections: options.sections,\n links: options.links.map((link) => ({ label: String(link.label), href: String(link.href) })),\n };\n}\n\nexport function renderDashboardPage(options: DashboardPageOptions): (nonce: string) => string {\n const bootstrap = escapeForScript(JSON.stringify(JSON.stringify(bootFor(options))));\n\n // Function replacements throughout, never string ones. `String.prototype.replace`\n // reads `$&`, `` $` `` and `$'` out of a *string* replacement and substitutes match\n // context for them — so a title, a link label or a minified bundle containing one of\n // those sequences would otherwise rewrite itself on the way into the page.\n const html = PAGE.replace(\"__BOOT_JSON__\", () => bootstrap)\n .replace(\"__SCRIPT__\", () => CLIENT_SCRIPT)\n .replace(/__TITLE__/g, () => escapeHtml(options.title));\n return (nonce: string) => html.replace(/__NONCE__/g, () => nonce);\n}\n\n/**\n * Makes a JSON literal safe to sit inside a `<script>` element.\n *\n * `JSON.stringify` is not enough, and the gap is not theoretical: HTML ends a script\n * element at the first `</script>` in the source, *including one inside a string\n * literal*. A caller passing that sequence as a dashboard `title` — or a link label —\n * would otherwise close the tag and open one of their own. Escaping the angle brackets\n * as `\\u003c` / `\\u003e` keeps the value identical after `JSON.parse` while making the\n * sequence unrecognisable to the HTML parser. The two line separators are the same\n * class of problem for older parsers.\n */\nfunction escapeForScript(json: string): string {\n return json\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(/[&<>\"']/g, (character) => ({ \"&\": \"&\", \"<\": \"<\", \">\": \">\", '\"': \""\", \"'\": \"'\" })[character] ?? character);\n}\n\n/**\n * The dashboard's stylesheet, on its own.\n *\n * Separated from the document so the same rules can be adopted into a shadow root by the\n * embeddable element. The selectors are written `:root, :host` throughout for that\n * reason: `:root` matches the document element on the standalone page and matches nothing\n * inside a shadow tree, where `:host` is the element the tokens have to hang off.\n */\nexport const DASHBOARD_CSS = String.raw`/* ---------------------------------------------------------------------------\n Tokens.\n\n The two series colours and the critical status step are the validated data\n palette: blue and orange clear every colour-vision gate against both surfaces as\n an adjacent pair, and every chart that uses them also carries a legend and a\n label, so hue is never the only thing distinguishing anything. Text never wears a\n series colour — the ink tokens below are all at least 4.5:1 on their surface.\n--------------------------------------------------------------------------- */\n:root, :host {\n color-scheme: light;\n --page: #f4f5f7;\n --surface: #ffffff;\n --surface-2: #fafafa;\n --raised: #ffffff;\n --line: #e4e6ea;\n --line-soft: #eef0f3;\n --ink: #0b0d12;\n --ink-2: #3c414b;\n --muted: #5f646e;\n --s1: #2a78d6;\n --s2: #eb6834;\n --crit: #d03b3b;\n /* The series blue carries white text at about 3.9:1, which is under the 4.5 floor —\n fine for a bar, not for a label. Solid buttons get a darker blue of their own\n rather than a lighter series colour, because the series colour is validated for\n charts and moving it would move every chart with it. */\n --accent-solid: #1b5fb0;\n --good-text: #006300;\n /* Darker than it looks like it needs to be, and measured rather than judged. This ink\n appears on a 16%-amber chip, and that chip can sit on the open row's blue tint —\n three layers, each lightening the background under the same text. At #8a5a00 the\n suspected badge measured 3.52:1 there, under the 4.5 floor for 11px bold. */\n --warn-text: #6d4700;\n --crit-text: #b02525;\n --info-text: #1c5cab;\n --proven-text: #5b34a8;\n --grid: #eceef1;\n --shadow: 0 1px 2px rgba(11,13,18,.06), 0 1px 8px rgba(11,13,18,.04);\n --focus: #2a78d6;\n}\n@media (prefers-color-scheme: dark) {\n :root:not([data-theme=\"light\"]), :host(:not([data-theme=\"light\"])) {\n color-scheme: dark;\n --page: #0d0f12;\n --surface: #16181d;\n --surface-2: #1b1e24;\n --raised: #1f232a;\n --line: #272b33;\n --line-soft: #202329;\n --ink: #f2f4f7;\n --ink-2: #c4cad3;\n --muted: #8b929c;\n --s1: #3987e5;\n --s2: #d95926;\n --crit: #d03b3b;\n --accent-solid: #2364b4;\n --good-text: #4ec97a;\n --warn-text: #fab219;\n --crit-text: #ff8078;\n --info-text: #86b6ef;\n --proven-text: #b3a4f5;\n --grid: #23272e;\n --shadow: none;\n --focus: #86b6ef;\n }\n}\n:root[data-theme=\"dark\"], :host([data-theme=\"dark\"]) {\n color-scheme: dark;\n --page: #0d0f12;\n --surface: #16181d;\n --surface-2: #1b1e24;\n --raised: #1f232a;\n --line: #272b33;\n --line-soft: #202329;\n --ink: #f2f4f7;\n --ink-2: #c4cad3;\n --muted: #8b929c;\n --s1: #3987e5;\n --s2: #d95926;\n --crit: #d03b3b;\n --accent-solid: #2364b4;\n --good-text: #4ec97a;\n --warn-text: #fab219;\n --crit-text: #ff8078;\n --info-text: #86b6ef;\n --proven-text: #b3a4f5;\n --grid: #23272e;\n --shadow: none;\n --focus: #86b6ef;\n}\n\n* { box-sizing: border-box; }\nhtml, body { height: 100%; }\n/* Restored by the client once the first render has settled. See the note on .tiles. */\nhtml.settling { overflow-anchor: none; }\nbody {\n margin: 0; background: var(--page); color: var(--ink);\n font: 14px/1.55 system-ui, -apple-system, \"Segoe UI\", Roboto, sans-serif;\n -webkit-font-smoothing: antialiased;\n}\n.mono { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n.tnum { font-variant-numeric: tabular-nums; }\n:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; border-radius: 6px; }\n\n/* --- header -------------------------------------------------------------\n Two rows, and the split is the whole idea.\n\n The first carries identity, connection state and the controls — everything you\n can *press*. The second carries the tab strip and, at the far end, the handful of\n configuration facts that used to sit up top wearing the same bordered pill as the\n buttons beside them. Ten identical rounded rectangles in a row, five of which did\n nothing when clicked, is not a toolbar; it is a guessing game. The facts are now\n plain text — the one treatment nothing else on the page uses for a control — and\n they fill the tab strip's empty right-hand side rather than needing room of their\n own.\n\n Nothing here has a fixed height. The old row was pinned to 54px with children that\n refused to shrink, so on a narrow window the title and the live indicator drew on\n top of each other rather than wrapping.\n------------------------------------------------------------------------- */\nheader {\n position: sticky; top: 0; z-index: 20;\n background: color-mix(in srgb, var(--surface) 92%, transparent);\n backdrop-filter: saturate(180%) blur(8px);\n border-bottom: 1px solid var(--line);\n padding: 0 20px;\n}\n.head-row { display: flex; align-items: center; gap: 10px 14px; min-height: 54px; flex-wrap: wrap; padding: 7px 0; }\n.brand { display: flex; align-items: baseline; gap: 8px; min-width: 0; }\n/* A real top-level heading, because the page had none: a screen reader's \"jump to the\n heading\" found nothing to jump to. Styled back down to the size it always was — it is\n a name in a toolbar, not a title on a poster. */\n.brand h1 { font-size: 15px; font-weight: 640; letter-spacing: -.015em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; margin: 0; }\n.brand span { color: var(--muted); font-size: 12.5px; white-space: nowrap; }\n\n/* Status, not a control: a filled pill with no border, so it never reads as pressable. */\n.live {\n display: inline-flex; align-items: center; gap: 6px; font-size: 12px; color: var(--ink-2);\n white-space: nowrap; background: color-mix(in srgb, var(--ink) 5%, transparent);\n border-radius: 999px; padding: 3px 10px 3px 8px; font-weight: 520;\n}\n.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--muted); flex: none; }\n.dot.on { background: var(--good-text); box-shadow: 0 0 0 3px color-mix(in srgb, var(--good-text) 22%, transparent); }\n.dot.off { background: var(--crit-text); box-shadow: 0 0 0 3px color-mix(in srgb, var(--crit-text) 20%, transparent); }\n.grow { flex: 1 1 auto; min-width: 8px; }\n\n.head-links { display: flex; gap: 6px; flex-wrap: wrap; }\n/* A hairline between \"somewhere else\" and \"do something here\". */\n.head-actions { display: flex; gap: 6px; align-items: center; }\n.head-links:not(:empty) + .head-actions { padding-left: 12px; border-left: 1px solid var(--line); }\n\n.nav-row { display: flex; align-items: flex-end; gap: 20px; }\n.facts {\n display: flex; gap: 14px; flex-wrap: wrap; margin-left: auto;\n font-size: 11.5px; color: var(--muted); padding-bottom: 9px; min-width: 0;\n}\n.facts span { white-space: nowrap; }\n/* Sibling instances. A dashboard reports on one process, and this is the honest amount\n of help the page can give with that: a way to reach the others. */\n.peers { display: flex; gap: 6px; align-items: center; padding-bottom: 7px; flex-wrap: wrap; }\n.peers a { font-size: 11.5px; padding: 2px 8px; border-radius: 999px; }\n@media (max-width: 900px) { .peers { display: none; } }\n.facts b { font-weight: 600; color: var(--ink-2); font-variant-numeric: tabular-nums; }\n\n/* Secondary by definition, so it is the first thing to go when room runs out. */\n@media (max-width: 900px) { .facts { display: none; } }\n@media (max-width: 560px) { .brand span { display: none; } }\nbutton, .linkbtn {\n font: inherit; font-size: 12.5px; line-height: 1.4; padding: 6px 11px; border-radius: 8px;\n border: 1px solid var(--line); background: var(--surface); color: var(--ink-2);\n cursor: pointer; white-space: nowrap; text-decoration: none; display: inline-block;\n}\nbutton:hover, .linkbtn:hover { background: var(--surface-2); color: var(--ink); border-color: color-mix(in srgb, var(--ink) 22%, var(--line)); }\nbutton[aria-pressed=\"true\"] { background: color-mix(in srgb, var(--s1) 12%, var(--surface)); border-color: color-mix(in srgb, var(--s1) 45%, var(--line)); color: var(--ink); }\nbutton[disabled] { opacity: .5; cursor: default; }\n\n.tabs { display: flex; gap: 2px; margin-bottom: -1px; flex: none; }\n.tab {\n border: 0; background: none; border-bottom: 2px solid transparent; border-radius: 0;\n padding: 9px 12px; font-size: 13px; color: var(--muted); font-weight: 500;\n}\n.tab:hover { background: none; color: var(--ink); }\n.tab[aria-selected=\"true\"] { color: var(--ink); border-bottom-color: var(--s1); }\n\n/* --- layout ------------------------------------------------------------- */\n/* The top padding is the gap under the sticky header. At 18px the counter row sat almost\n against the header's border and read as part of it; the tiles carry their own border, so\n two lines were meeting with nothing between them. */\nmain { padding: 28px 20px 64px; max-width: 1680px; margin: 0 auto; }\n.stack { display: grid; gap: 16px; }\n/* Everything above the feed is drawn by script once the first snapshot arrives, which\n inserts a block of content above what is already laid out. The browser's scroll\n anchoring compensates for that by scrolling down by its height — so on any window narrow\n enough for the counter row to wrap, the dashboard opened with its own counters already\n off the top of the screen, every time.\n\n Anchoring is switched off for the first render and switched back on once it has settled\n (see settleScrollAnchoring in the client). It is not simply left off: the feed puts new requests at\n the top, and anchoring is exactly what keeps somebody's place while they read a screen\n that grows above them. */\n.tiles { display: grid; gap: 10px; grid-template-columns: repeat(auto-fit, minmax(136px, 1fr)); }\n.tile {\n background: var(--surface); border: 1px solid var(--line); border-radius: 10px;\n padding: 8px 12px 9px; box-shadow: var(--shadow);\n}\n.tile .v { font-size: 21px; font-weight: 620; letter-spacing: -.025em; line-height: 1.15; }\n.tile .k { font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .055em; margin-top: 2px; font-weight: 560; }\n/* One line, always. These are grid items, so the tallest sets the height of all nine —\n two captions wrapping to a second line was costing every tile forty pixels of nothing.\n The full text stays available on hover rather than being cut from the page. */\n.tile .s { font-size: 11px; color: var(--muted); margin-top: 1px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n/* A tile that navigates is a real button, so it arrives carrying the browser's own\n font, centring and chrome. Reset to match its inert neighbours exactly, then given\n back the one thing a div must not have: something that says it can be pressed. */\nbutton.tile {\n font: inherit; color: inherit; text-align: left; width: 100%; display: block;\n cursor: pointer; appearance: none; transition: border-color .12s, box-shadow .12s;\n}\nbutton.tile:hover { border-color: var(--focus); }\n/* The suggestion list under the search box, positioned against the search wrapper. */\n.search { position: relative; }\n.suggest {\n position: absolute; top: calc(100% + 4px); left: 0; z-index: 30; margin: 0; padding: 4px;\n list-style: none; min-width: 220px; max-height: 260px; overflow-y: auto;\n background: var(--surface); border: 1px solid var(--line); border-radius: 9px; box-shadow: var(--shadow);\n}\n.suggest li { padding: 4px 9px; border-radius: 6px; cursor: pointer; font-size: 12px; }\n.suggest li[aria-selected=\"true\"] { background: color-mix(in srgb, var(--focus) 18%, transparent); }\n\n/* Saved filters: a list of small removable things. Quiet, because it is not what\n somebody came to the page to look at. */\n.saved { display: flex; align-items: center; gap: 6px; }\n/* Two open-ended bounds rather than a list of durations: \"from the incident until now\",\n \"everything up to when it stopped\" and \"between these two moments\" are the same control\n with one end left empty. */\n.timeframe { display: flex; align-items: center; gap: 8px; font-size: 11.5px; color: var(--muted); }\n.timeframe label { display: inline-flex; align-items: center; gap: 4px; }\n.timeframe input {\n font: inherit; font-size: 11.5px; padding: 2px 5px; border-radius: 6px;\n border: 1px solid var(--line); background: var(--surface); color: var(--ink);\n}\n.timeframe button { font: inherit; font-size: 11.5px; padding: 3px 9px; border-radius: 7px; border: 1px solid var(--line); background: var(--surface); color: var(--ink); cursor: pointer; }\n.saved select { font: inherit; font-size: 11.5px; padding: 3px 6px; border-radius: 7px; border: 1px solid var(--line); background: var(--surface); color: var(--ink); max-width: 170px; }\n.saved button { font: inherit; font-size: 11.5px; padding: 3px 9px; border-radius: 7px; border: 1px solid var(--line); background: var(--surface); color: var(--ink); cursor: pointer; }\n.saved button:hover { border-color: var(--focus); }\n\n/* The badge's companion: fetches the entries the stream skipped. Sits inline with the\n heading, so it is styled to read as part of the sentence rather than as a form control. */\n.load-skipped {\n font: inherit; margin-left: 6px; padding: 1px 8px; border-radius: 6px; cursor: pointer;\n border: 1px solid var(--warn-text); background: transparent; color: var(--warn-text);\n}\n.load-skipped:hover { background: color-mix(in srgb, var(--warn-text) 12%, transparent); }\n.load-skipped:disabled { opacity: .5; cursor: default; }\n\n/* Prev/next under a table. Quiet: it is navigation for a list, not an action on it. */\n.pager {\n display: flex; align-items: center; gap: 10px; flex-wrap: wrap;\n padding: 9px 2px 2px; font-size: 12px; color: var(--muted);\n}\n.pager button {\n font: inherit; padding: 4px 11px; border-radius: 7px;\n border: 1px solid var(--line); background: var(--surface); color: var(--ink); cursor: pointer;\n}\n.pager button:hover:not(:disabled) { border-color: var(--focus); }\n.pager button:disabled { opacity: .45; cursor: default; }\n.pager .where { font-variant-numeric: tabular-nums; }\n/* A labelled actor leads with its name and keeps the key underneath: whoever named it did\n so because the key was not the useful part, and the key is still what you search for. */\ntd.who .label { font-weight: 560; }\ntd.who .sub { color: var(--muted); font-size: 11px; }\n.pager.pager-top { padding: 2px 2px 9px; border-bottom: 1px solid var(--line); margin-bottom: 9px; }\n/* The feed's upper pager rides in the toolbar rather than owning a row of its own, which\n was thirty-six pixels of mostly empty rule above every screenful of requests. */\n.pager.pager-inline { padding: 0; margin-left: auto; gap: 8px; }\n.pager.pager-inline .size { margin-left: 0; }\n.pager .step { min-width: 30px; font-size: 15px; line-height: 1; padding: 3px 8px 5px; }\n.pager .size { display: flex; align-items: center; gap: 6px; margin-left: auto; }\n.pager .size select {\n font: inherit; padding: 3px 6px; border-radius: 6px;\n border: 1px solid var(--line); background: var(--surface); color: var(--ink);\n}\n.pager .held { color: var(--warn-text); }\n.tile.good .v { color: var(--good-text); }\n.tile.warn .v { color: var(--warn-text); }\n.tile.crit .v { color: var(--crit-text); }\n.tile.proven .v { color: var(--proven-text); }\n\n/* \"clip\" rather than \"hidden\", and the difference is load-bearing. Both round off the\n corners, but \"hidden\" makes the panel a scroll container — which becomes the\n containing block for anything sticky inside it, so the feed's column headers were\n anchored to a box that never scrolls and simply never stuck. They have not worked\n since they were written; the hard-coded offset above them was dead code. \"clip\"\n clips without creating a scrollport, so the headers stick to the viewport, under\n the page header, where they were always meant to. */\n.panel { background: var(--surface); border: 1px solid var(--line); border-radius: 12px; box-shadow: var(--shadow); overflow: clip; }\n.panel > h2 {\n margin: 0; padding: 9px 14px; font-size: 11.5px; font-weight: 620; color: var(--muted);\n text-transform: uppercase; letter-spacing: .055em; border-bottom: 1px solid var(--line);\n display: flex; align-items: center; gap: 10px;\n}\n.panel > h2 .sub { font-weight: 400; text-transform: none; letter-spacing: 0; font-size: 11.5px; margin-left: auto; }\n/* A container, so the two-column grid below can ask how much room it actually has.\n\n It used to ask the viewport, which is the same mistake the feed table's breakpoints\n made one level down: the constraint is the width of this column, not of the window.\n Embedded in a 320px sidebar on a 1280px screen the media query never fired, the second\n column held its 280px minimum, and the feed was squeezed to twenty-two pixels. */\n.stack { container: dash / inline-size; }\n/* A grid item's min-width is auto, so a panel wrapping a wide table refuses to shrink and\n pushes the whole document sideways instead — which is what the Actors screen did under\n about 600px, where the table is widest and the viewport narrowest. The .two grid already\n says minmax(0, ...) for this reason; .stack needs the same permission. With it the panel\n shrinks and the scroller inside it does its job. */\n.stack > * { min-width: 0; }\n/* Numeric columns shrink to their contents, so the width goes to the two columns that\n carry text — the actor and what is known about it. Six counters of one or two digits\n were each taking about a hundred pixels while the State column was squeezed against the\n buttons. Same trick the feed's time column already uses. */\n#view-actors th.num, #view-actors td.num { width: 1%; white-space: nowrap; }\n.two { display: grid; gap: 16px; grid-template-columns: minmax(0, 1.9fr) minmax(280px, 1fr); align-items: start; }\n.grid3 { display: grid; gap: 16px; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }\n@container dash (max-width: 1080px) { .two { grid-template-columns: minmax(0, 1fr); } }\n\n/* The feed table has a floor — six columns of request text will not go below about\n 940px — and under it the panel was simply amputating the right-hand columns.\n\n These are **container** queries, not media queries, and the difference is the whole\n fix. What the table has to fit inside is the panel, and the panel is one column of a\n two-column grid: at a 1400px viewport it is about 880px wide, so a viewport-based\n breakpoint at 980px never fires and the right-hand columns are clipped on a perfectly\n ordinary desktop. That is exactly the bug a media query was written to fix, and the\n media query was measuring the wrong box the whole time. It only appeared to work\n because five columns happened to fit.\n\n Four things happen on the way down, in the order that costs least. First the\n duration goes: it is the column people narrow a window least to read and it is still\n in the row detail. Then the timestamp, which is also still in the row detail and is\n implied by the order while the stream is live. Then the User-Agent line stops\n ellipsing and wraps, which is what actually buys the room — it takes the table's floor\n from about 100ch to about 56ch, because the cap on that one line is most of what the\n table cannot go below. Only under *that* does the table get a scrollbar of its own.\n\n The scrollbar is deliberately last and deliberately rare, and it is worth being\n precise about why, because it is the rung that gets set too high. A scroll container\n is the containing block for anything sticky inside it, so the moment it turns on the\n column headers stop tracking the viewport — and a row becomes wider than the box it\n sits in, which is enough to make it hard to click and, for a keyboard or a pointer\n being driven by a test, hard to reach at all. It cost both when it fired at 1440px on\n a wider face: the headers scrolled away with the rows and a click on a row never\n landed. Wrapping costs a line of height and nothing else, so it goes first and this\n goes last.\n\n The thresholds are in **ch**, and that is the second half of the same lesson. They\n used to be pixels, which quietly assumed a font: what six columns of request text\n actually need is set by the width of a glyph — the User-Agent below is capped in ch —\n and a hard pixel breakpoint is only correct for the font it was measured on. On this\n machine the six columns needed 951px and the breakpoint sat at 960px, so it passed by\n nine pixels; on a CI runner with a wider default face the same six columns needed more\n than the panel had, the query did not fire, and the right-hand columns were clipped on\n an ordinary 1600px desktop. The five-column rung was already over its own floor by\n 8px and nobody had noticed, because nothing measures a layout that merely looks fine.\n\n In ch, both sides move together: a wider face makes the content wider and makes the\n threshold wider by the same proportion. The floors, measured, are 119ch / 111ch /\n 100ch ellipsing and 56ch wrapped, and each rung sits above its own floor with room to\n spare so that the arithmetic does not have to be exact. */\n.feed-panel { container: feed / inline-size; }\n\n/* The rungs themselves are further down, immediately after the table's own rules, and\n they have to be: a container query adds no specificity, so an override written above\n the declaration it overrides simply loses on source order and does nothing. The wrap\n rung sat here for exactly that reason and was dead the whole time — the table never\n wrapped, its floor never dropped, and nothing said so. See \"the column ladder\" below. */\n\n/* The header's own subtitle, which is a viewport thing rather than a panel thing: it\n sits in the page header and has no container to be measured against. */\n@media (max-width: 700px) {\n .sub { white-space: normal; max-width: none; }\n}\n\n/* --- feed table --------------------------------------------------------- */\n.toolbar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; padding: 10px 15px; border-bottom: 1px solid var(--line); }\n.filters { display: flex; gap: 5px; flex-wrap: wrap; }\n.filters button { font-size: 11.5px; padding: 3px 10px; border-radius: 999px; }\ninput[type=\"search\"] {\n font: inherit; font-size: 12.5px; padding: 5px 10px; min-width: 180px; flex: 1 1 180px;\n border: 1px solid var(--line); border-radius: 8px; background: var(--surface-2); color: var(--ink);\n}\ninput[type=\"search\"]::placeholder { color: var(--muted); }\n\n/* separate with zero spacing rather than collapse, and the difference is the whole\n reason the column headers work in Safari.\n\n Collapsed borders and sticky table cells are a long-standing sore point in WebKit: the\n CSSWG has an open issue on collapsed borders not following a cell when it sticks\n (csswg-drafts#3136), and Safari is widely reported to drop the stickiness of a th\n altogether under a collapsed table. Separating the borders is the standard remedy.\n\n What was actually measured: the header sticks correctly in Chromium and in Firefox,\n both before and after this change, and it was reported adrift in Safari — which is what\n a sticky element that has stopped sticking looks like. WebKit could not be run on the\n machine this was written on, so the Safari half of it rests on that report and on the\n documented behaviour rather than on a measurement taken here.\n\n The rendering is all but unchanged. Every border in these tables is a bottom border on\n the cell itself, plus the per-cell left accent on td.edge; no border is shared between\n two cells, so there is nothing for collapsing to merge and nothing for separating to\n double, and zero spacing keeps the cells touching. The one measurable difference is the\n accent column, which moves two pixels: collapsing centres that 3px border on the cell\n edge and leaves half of it outside the box, while separating puts all of it inside.\n Measured rather than assumed, and the leftmost column starting two pixels earlier is\n both imperceptible and the more correct of the two. */\ntable { width: 100%; border-collapse: separate; border-spacing: 0; }\nthead th {\n /* Measured at runtime — see trackHeaderHeight(). The literal is the fallback for\n the instant before the first measurement, and for the tab strip wrapping. */\n position: sticky; top: var(--header-h, 91px); z-index: 2; background: var(--surface);\n text-align: left; font-size: 11px; font-weight: 600; color: var(--muted);\n text-transform: uppercase; letter-spacing: .05em; padding: 9px 15px; border-bottom: 1px solid var(--line);\n}\ntbody td { padding: 7px 14px; border-bottom: 1px solid var(--line-soft); vertical-align: top; }\n/* Narrow, quiet, and never the reason a row wraps: the time is for scanning down, not\n for reading across. */\ntbody td.when { color: var(--muted); font-size: 11.5px; white-space: nowrap; width: 1%; padding-right: 4px; }\ntbody tr.row { cursor: pointer; }\ntbody tr.row:hover { background: color-mix(in srgb, var(--ink) 3.5%, transparent); }\ntbody tr.row.open { background: color-mix(in srgb, var(--s1) 7%, transparent); }\ntd.edge { border-left: 3px solid transparent; padding-left: 12px; }\ntr.a-allow td.edge { border-left-color: var(--good-text); }\ntr.a-mitigate td.edge { border-left-color: var(--s2); }\ntr.a-deny td.edge { border-left-color: var(--crit); }\ntr.a-guard td.edge { border-left-color: var(--warn-text); }\n.req { font-size: 12.5px; overflow-wrap: anywhere; }\n/* The disclosure for a row.\n The whole row used to be role=\"button\", with the actor link inside it — a control\n nested in a control, which leaves a screen reader with two things to announce and no\n way to say which one Enter belongs to. The row still opens on click, because that is\n a convenience rather than a role; the keyboard gets this, which is one control with\n one name and one job. */\n.row-toggle {\n border: 0; background: none; padding: 0; margin: 0; border-radius: 4px;\n font: inherit; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 12.5px; color: inherit; text-align: left; cursor: pointer; display: block; width: 100%;\n}\n.row-toggle:hover { background: none; color: inherit; border-color: transparent; }\n.req .ua { color: var(--muted); font-size: 11.5px; display: block; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 64ch; }\n.req .ua a { color: inherit; text-decoration: none; border-bottom: 1px dotted color-mix(in srgb, var(--muted) 60%, transparent); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n.req .ua a:hover { color: var(--ink); border-bottom-color: var(--ink); }\n.num { text-align: right; }\n\n/* The column ladder. Read the note beside .feed-panel above for what it is for and why\n the thresholds are in ch; this is where it has to live, below every declaration it\n overrides. */\n@container feed (max-width: 128ch) {\n thead th:nth-child(6), tbody td:nth-child(6) { display: none; }\n}\n@container feed (max-width: 119ch) {\n thead th:nth-child(1), tbody td.when { display: none; }\n}\n@container feed (max-width: 107ch) {\n .req .ua { white-space: normal; max-width: none; }\n}\n@container feed (max-width: 62ch) {\n .feed-scroll { overflow-x: auto; }\n}\n\n.badge { display: inline-block; padding: 1px 8px; border-radius: 999px; font-size: 11px; font-weight: 600; white-space: nowrap; border: 1px solid transparent; }\n.b-proven { background: color-mix(in srgb, var(--proven-text) 14%, transparent); color: var(--proven-text); }\n.b-suspected { background: color-mix(in srgb, var(--warn-text) 16%, transparent); color: var(--warn-text); }\n.b-human { background: color-mix(in srgb, var(--good-text) 14%, transparent); color: var(--good-text); }\n/* The secondary ink rather than the muted one, for the same reason as the weak tier:\n muted ink on its own muted chip is 4.31:1 on a plain row and 3.70:1 on an open one. */\n.b-unknown { background: color-mix(in srgb, var(--muted) 14%, transparent); color: var(--ink-2); }\n.act { font-weight: 600; font-size: 12.5px; }\n.act-allow { color: var(--good-text); }\n.act-mitigate { color: var(--warn-text); }\n.act-deny { color: var(--crit-text); }\n.act-tag { color: var(--ink-2); }\n.sub { color: var(--muted); font-size: 11.5px; display: block; margin-top: 2px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; max-width: 22ch; }\n.guard { color: var(--warn-text); font-size: 11.5px; display: block; margin-top: 3px; font-weight: 500; }\n\ntr.detail > td { background: var(--surface-2); padding: 14px 15px 16px; border-bottom: 1px solid var(--line); }\n.ev { display: grid; gap: 9px; }\n.ev-item { display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 12px; align-items: start; font-size: 12.5px; }\n.tier { font-size: 10px; font-weight: 700; text-transform: uppercase; letter-spacing: .05em; padding: 3px 6px; border-radius: 6px; text-align: center; }\n.t-certain { background: color-mix(in srgb, var(--proven-text) 15%, transparent); color: var(--proven-text); }\n.t-strong { background: color-mix(in srgb, var(--crit-text) 14%, transparent); color: var(--crit-text); }\n.t-moderate { background: color-mix(in srgb, var(--warn-text) 16%, transparent); color: var(--warn-text); }\n/* The secondary ink rather than the muted one: muted ink on its own muted chip clears\n 4.5:1 on the light surface and does not on the dark one, and this is 10px bold text\n saying how much a piece of evidence is worth. It still reads as the quiet tier — the\n only one with no hue — without being the one nobody can read at night. */\n.t-weak { background: color-mix(in srgb, var(--muted) 14%, transparent); color: var(--ink-2); }\n.ev-item.human .tier { background: color-mix(in srgb, var(--good-text) 14%, transparent); color: var(--good-text); }\n.ev-meta { color: var(--muted); font-size: 11.5px; margin-top: 2px; }\n.basis { margin-top: 5px; color: var(--ink-2); font-size: 12px; border-left: 2px solid color-mix(in srgb, var(--proven-text) 40%, transparent); padding: 2px 0 2px 9px; }\n.detail-foot { margin-top: 12px; padding-top: 10px; border-top: 1px solid var(--line); color: var(--muted); font-size: 11.5px; display: flex; gap: 14px; flex-wrap: wrap; }\n\n/* --- bars --------------------------------------------------------------- */\n.bars { padding: 12px 15px 14px; display: grid; gap: 8px; }\n.bar { display: grid; grid-template-columns: minmax(0, 1fr) 46px; gap: 10px; align-items: center; font-size: 12.5px; }\n.bar .track { position: relative; height: 20px; background: color-mix(in srgb, var(--ink) 5%, transparent); border-radius: 6px; overflow: hidden; }\n.bar .fill { position: absolute; inset: 0 auto 0 0; background: color-mix(in srgb, var(--s1) 26%, transparent); border-radius: 0 5px 5px 0; }\n.bar .lbl { position: relative; padding: 0 8px; line-height: 20px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }\n.bar .v { text-align: right; color: var(--ink-2); font-size: 12px; }\n\n/* --- charts ------------------------------------------------------------- */\n.chart { padding: 12px 15px 14px; position: relative; }\n.chart svg { display: block; width: 100%; overflow: visible; }\n.legend { display: flex; gap: 14px; flex-wrap: wrap; padding: 0 15px 12px; font-size: 12px; color: var(--ink-2); }\n.legend span { display: inline-flex; align-items: center; gap: 6px; }\n/* The marker for a runtime change. A line rather than a swatch, because it is an\n instant rather than a quantity. */\n.legend .mark { width: 2px; height: 11px; background: var(--proven-text); border-radius: 1px; flex: none; }\n.swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }\n.tip {\n position: absolute; pointer-events: none; opacity: 0; transition: opacity .1s;\n background: var(--raised); border: 1px solid var(--line); border-radius: 8px;\n padding: 8px 10px; font-size: 12px; box-shadow: 0 4px 16px rgba(0,0,0,.16); z-index: 10; min-width: 128px;\n}\n.tip .t { color: var(--muted); font-size: 11px; margin-bottom: 4px; }\n.tip .r { display: flex; align-items: center; gap: 7px; justify-content: space-between; }\n.tip .r em { font-style: normal; color: var(--ink-2); display: inline-flex; align-items: center; gap: 6px; }\n.tip .r b { font-weight: 600; font-variant-numeric: tabular-nums; }\n\n.stat-row { display: flex; justify-content: space-between; gap: 12px; padding: 7px 15px; font-size: 12.5px; border-bottom: 1px solid var(--line-soft); }\n.stat-row:last-child { border-bottom: 0; }\n.stat-row .k { color: var(--muted); }\n.stat-row .v { font-variant-numeric: tabular-nums; color: var(--ink); font-weight: 560; }\n/* The baseline a window is being read against. Quieter than the current value, and\n beside it rather than under it, because the comparison is the whole point. */\n.stat-row .v .was { color: var(--muted); font-weight: 400; font-size: 11.5px; }\n.note { padding: 13px 15px; font-size: 12.5px; color: var(--ink-2); }\n.note p { margin: 0 0 9px; }\n.note p:last-child { margin: 0; }\n.note b { color: var(--ink); }\n.empty { padding: 40px 15px; text-align: center; color: var(--muted); font-size: 13px; }\n.empty code { background: color-mix(in srgb, var(--ink) 7%, transparent); padding: .15em .4em; border-radius: 5px; }\n.detectors { padding: 4px 0; }\n.det { display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 10px; padding: 8px 15px; border-bottom: 1px solid var(--line-soft); font-size: 12.5px; }\n.det:last-child { border-bottom: 0; }\n.det .d { color: var(--muted); font-size: 11.5px; margin-top: 2px; }\n.det .n { color: var(--ink-2); font-variant-numeric: tabular-nums; white-space: nowrap; }\n.rules { padding: 10px 15px; display: flex; gap: 6px; flex-wrap: wrap; }\n.rules .chip { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n[hidden] { display: none !important; }\n/* Present to a screen reader, absent to everyone else. Clipped rather than moved\n off-screen or hidden with display:none, both of which take an element out of the\n accessibility tree along with the viewport. */\n.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; overflow: hidden; white-space: nowrap; clip-path: inset(50%); border: 0; }\n\n/* --- navigation --------------------------------------------------------- */\n/* Present for a keyboard, out of the way of everything else. */\n.skip {\n position: absolute; left: 10px; top: 10px; z-index: 40;\n background: var(--raised); color: var(--ink); border: 1px solid var(--line);\n border-radius: 8px; padding: 8px 13px; font-size: 12.5px; text-decoration: none;\n /* Hidden by clipping rather than by being moved off-screen. An element parked\n above the viewport has its whole border box outside it, and Chrome's sequential\n focus navigation skips those — so a skip link done that way is invisible to the\n one input device it exists for. Clipped to a pixel it stays in the tab order. */\n width: 1px; height: 1px; padding: 0; overflow: hidden; white-space: nowrap; clip-path: inset(50%);\n}\n.skip:focus { width: auto; height: auto; padding: 8px 13px; overflow: visible; clip-path: none; }\n\n/* A row is a control now, so it has to look like one when focused. outline-offset\n is negative because an outline drawn outside a table row is clipped by the cell. */\ntbody tr.row:focus-visible { outline: 2px solid var(--focus); outline-offset: -2px; }\ntbody tr.row:focus-visible td.edge { border-left-color: var(--focus); }\n\n.search { position: relative; display: flex; flex: 1 1 180px; min-width: 180px; }\n.search input[type=\"search\"] { flex: 1 1 auto; padding-right: 26px; }\n.search kbd {\n position: absolute; right: 7px; top: 50%; transform: translateY(-50%);\n font: 500 10.5px/1 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n color: var(--muted); background: color-mix(in srgb, var(--ink) 6%, transparent);\n border: 1px solid var(--line); border-radius: 4px; padding: 3px 5px; pointer-events: none;\n}\n/* The hint is an affordance for a mouse user who has not found the key yet. Once the\n box is in use it is noise, and it sits where the clear button wants to be. */\n.search input[type=\"search\"]:focus + kbd, .search input[type=\"search\"]:not(:placeholder-shown) + kbd { display: none; }\n/* --- added panels ------------------------------------------------------- */\n.range { display: flex; gap: 4px; }\n.range button { font-size: 11px; padding: 2px 8px; border-radius: 999px; }\n\n.kv { display: grid; grid-template-columns: auto 1fr; gap: 4px 14px; padding: 12px 15px; font-size: 12.5px; }\n.kv dt { color: var(--muted); }\n.kv dd { margin: 0; font-variant-numeric: tabular-nums; }\n\n.hdr { width: 100%; border-collapse: collapse; margin-top: 10px; }\n.hdr td { padding: 3px 8px 3px 0; vertical-align: top; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 11.5px; border: 0; }\n.hdr td.n { color: var(--muted); white-space: nowrap; width: 1%; }\n.hdr td.v { overflow-wrap: anywhere; }\n.hdr td.v.red { color: var(--muted); font-style: italic; }\n\n.tools { display: flex; gap: 6px; flex-wrap: wrap; margin-top: 12px; }\n.tools button { font-size: 11.5px; padding: 4px 9px; }\n\n.editor { padding: 12px 15px 14px; display: grid; gap: 10px; }\ntextarea {\n font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 12px; line-height: 1.5;\n width: 100%; min-height: 320px; resize: vertical; padding: 11px 12px; tab-size: 2;\n border: 1px solid var(--line); border-radius: 9px; background: var(--surface-2); color: var(--ink);\n}\ntextarea[readonly] { opacity: .8; }\n.editor-bar { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n.result { font-size: 12.5px; border-radius: 9px; padding: 10px 12px; border: 1px solid var(--line); background: var(--surface-2); }\n.result.ok { border-color: color-mix(in srgb, var(--good-text) 45%, var(--line)); }\n.result.bad { border-color: color-mix(in srgb, var(--crit-text) 45%, var(--line)); color: var(--crit-text); }\n.result.warn { border-color: color-mix(in srgb, var(--warn-text) 45%, var(--line)); }\n.result b { font-weight: 620; }\n.diff { width: 100%; border-collapse: collapse; margin-top: 8px; font-size: 12px; }\n.diff th { text-align: left; font-size: 10.5px; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); padding: 4px 8px 4px 0; border-bottom: 1px solid var(--line); }\n.diff td { padding: 5px 8px 5px 0; border-bottom: 1px solid var(--line-soft); vertical-align: top; }\n.diff .from { color: var(--muted); }\n.diff .to { font-weight: 600; }\n.diff .to.deny { color: var(--crit-text); }\n.diff .to.allow { color: var(--good-text); }\npre.code {\n margin: 0; padding: 11px 12px; border-radius: 9px; border: 1px solid var(--line);\n background: var(--surface-2); font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11.5px; line-height: 1.5; overflow-x: auto; white-space: pre; color: var(--ink-2);\n}\n.notice { display: grid; grid-template-columns: 74px 1fr; gap: 10px; padding: 8px 15px; border-bottom: 1px solid var(--line-soft); font-size: 12.5px; }\n.notice:last-child { border-bottom: 0; }\n.notice .when { color: var(--muted); font-size: 11.5px; font-variant-numeric: tabular-nums; }\n.notice.warning .tag { color: var(--warn-text); font-weight: 600; }\n.notice.error .tag { color: var(--crit-text); font-weight: 600; }\n.dead { color: var(--muted); }\n.dead .lbl { text-decoration: line-through; }\n.pill { font-size: 10.5px; padding: 1px 7px; border-radius: 999px; border: 1px solid var(--line); color: var(--muted); margin-left: 6px; }\n.actor-head { display: flex; align-items: center; gap: 10px; padding: 12px 15px; border-bottom: 1px solid var(--line); }\n.actor-head .who { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 13px; font-weight: 600; }\n/* --- the rule editor ----------------------------------------------------- */\n.bar-actions { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; padding: 10px 15px; border-bottom: 1px solid var(--line); background: var(--surface); }\n.bar-actions + .bar-actions, .rulelist + .bar-actions { border-bottom: 0; border-top: 1px solid var(--line); }\n.bar-actions .grow { flex: 1 1 auto; }\nbutton.primary { background: var(--accent-solid); border-color: var(--accent-solid); color: #fff; font-weight: 560; }\nbutton.primary:hover { background: color-mix(in srgb, var(--accent-solid) 86%, #000); border-color: transparent; color: #fff; }\nbutton.danger { color: var(--crit-text); }\nbutton.danger:hover { border-color: color-mix(in srgb, var(--crit-text) 50%, var(--line)); }\nbutton.icon { padding: 3px 8px; font-size: 12px; line-height: 1.2; }\n.dirty { font-size: 11.5px; color: var(--warn-text); font-weight: 560; display: inline-flex; align-items: center; gap: 6px; }\n.dirty::before { content: \"\"; width: 7px; height: 7px; border-radius: 50%; background: var(--warn-text); }\n\n.rulelist { display: grid; gap: 12px; padding: 14px 15px 16px; }\n.rule {\n border: 1px solid var(--line); border-radius: 11px; background: var(--surface-2);\n transition: border-color .12s, box-shadow .12s;\n}\n.rule:focus-within { border-color: color-mix(in srgb, var(--s1) 55%, var(--line)); box-shadow: 0 0 0 3px color-mix(in srgb, var(--s1) 12%, transparent); }\n.rule.locked { background: color-mix(in srgb, var(--ink) 3%, var(--surface)); }\n.rule-head { display: flex; align-items: center; gap: 8px; padding: 8px 10px; flex-wrap: nowrap; }\n.rule:not(.collapsed) .rule-head { border-bottom: 1px solid var(--line); }\n.rule-head .ord { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; min-width: 20px; text-align: right; }\n.rule-head input[type=\"text\"] { flex: 0 1 240px; width: auto; min-width: 130px; }\n.rule-head select { flex: 0 0 auto; }\n.chev { border: 0; background: none; padding: 2px 4px; color: var(--muted); font-size: 11px; line-height: 1; }\n.chev:hover { background: none; color: var(--ink); }\n.rule-summary { flex: 1 1 auto; display: flex; gap: 5px; flex-wrap: wrap; align-items: center; min-width: 0; overflow: hidden; }\n.rule-summary .k { font-size: 10.5px; color: var(--muted); }\n.rule-summary .t { font-size: 10.5px; padding: 1px 6px; border-radius: 999px; background: color-mix(in srgb, var(--ink) 6%, transparent); color: var(--ink-2); white-space: nowrap; }\n.rule-summary .none { font-size: 11px; color: var(--muted); font-style: italic; }\n.act-pill { font-size: 11px; font-weight: 620; padding: 2px 9px; border-radius: 999px; white-space: nowrap; }\n.act-pill.deny { background: color-mix(in srgb, var(--crit-text) 14%, transparent); color: var(--crit-text); }\n.act-pill.mitigate { background: color-mix(in srgb, var(--warn-text) 16%, transparent); color: var(--warn-text); }\n.act-pill.allow { background: color-mix(in srgb, var(--good-text) 14%, transparent); color: var(--good-text); }\n.act-pill.tag { background: color-mix(in srgb, var(--ink) 8%, transparent); color: var(--ink-2); }\n.rule-body { padding: 11px 12px 13px; display: grid; gap: 11px; }\n.rule.collapsed .rule-body { display: none; }\n.field { display: grid; grid-template-columns: 92px minmax(0, 1fr); gap: 10px; align-items: start; font-size: 12.5px; }\n.field > label { color: var(--muted); padding-top: 5px; }\n.field-row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }\n.hint { color: var(--muted); font-size: 11px; }\n\ninput[type=\"text\"], input[type=\"number\"], select {\n font: inherit; font-size: 12.5px; padding: 5px 9px; border-radius: 7px;\n border: 1px solid var(--line); background: var(--surface); color: var(--ink); min-width: 0;\n}\ninput[type=\"text\"] { width: 100%; }\ninput[type=\"number\"] { width: 92px; }\nselect { cursor: pointer; }\ninput::placeholder { color: color-mix(in srgb, var(--muted) 80%, transparent); }\n.mono-input { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }\n\n.chips-select { display: flex; gap: 4px; flex-wrap: wrap; }\n.chips-select button {\n font-size: 11px; padding: 2px 8px; border-radius: 999px; color: var(--muted);\n background: var(--surface); border: 1px solid var(--line);\n}\n.chips-select button[aria-pressed=\"true\"] { background: color-mix(in srgb, var(--s1) 14%, var(--surface)); border-color: color-mix(in srgb, var(--s1) 50%, var(--line)); color: var(--ink); font-weight: 560; }\n\n.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 7px; overflow: hidden; }\n.seg button { border: 0; border-radius: 0; padding: 4px 10px; font-size: 11.5px; background: var(--surface); }\n.seg button + button { border-left: 1px solid var(--line); }\n.seg button[aria-pressed=\"true\"] { background: color-mix(in srgb, var(--s1) 14%, var(--surface)); color: var(--ink); font-weight: 560; }\n\n.drop { outline: 2px dashed color-mix(in srgb, var(--s1) 60%, transparent); outline-offset: -6px; }\n\n/* --- the actors table, the tester and the range sets --------------------- */\n.warn-text { color: var(--warn-text); }\n#actor-rows td { font-size: 12.5px; }\n#actor-rows td.who { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; overflow-wrap: anywhere; }\n#actor-rows td.acts { text-align: right; white-space: nowrap; }\n/* The tracked/shown toggle above the actors table. A segmented pair rather than a\n dropdown: there are two answers and both are worth reading at a glance. */\n.scope { display: flex; gap: 6px; padding: 0 14px 10px; }\n.scope button { font-size: 11.5px; padding: 4px 10px; }\n.scope button.on { background: var(--accent); color: var(--on-accent, #fff); border-color: var(--accent); }\n\n#actor-rows td.acts button { font-size: 11px; padding: 3px 8px; margin-left: 4px; }\n/* The Label control, which becomes a text box with a Save and a Cancel in place.\n\n The cell does not wrap, so an editor that sat beside the row's other four buttons put\n Save off the right edge of the panel, where it could be seen and not clicked. While\n the editor is open it stands in for those buttons instead — which is also the right\n thing on its own, since Allowlist and Forget are not what somebody naming a client is\n reaching for. */\n.acts.editing > :not(.label-edit), .bar-actions.editing > :not(.label-edit) { display: none; }\n/* inline-flex rather than inline-block: the row is three fixed-size controls and a flex\n line is the layout that cannot spill them past its own edge. */\n.label-edit { display: inline-flex; align-items: center; gap: 4px; }\n.label-edit button { flex: 0 0 auto; }\n#actor-rows td.acts .label-save, #actor-actions .label-save { border-color: var(--accent); color: var(--accent); }\n/* Qualified with the element name on purpose: input[type=\"text\"] { width: 100% } above\n outranks a bare class, so the width here was quietly ignored and the box grew to fill\n whatever it was in — which is what put Save and Cancel outside the panel. */\ninput.label-input {\n font: inherit; font-size: 11px; padding: 3px 8px; width: 15ch; flex: 0 0 auto; box-sizing: border-box;\n color: var(--ink); background: var(--surface); border: 1px solid var(--accent); border-radius: 6px;\n}\ninput.label-input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }\n\n/* A tag, not a warning: a cleared actor is a decision somebody made, and a metronomic\n one is a measurement. Neither is a verdict, so neither gets a verdict's colour. */\n/* A shadowed finding: shown at full detail, and visibly not part of the decision. Dimmed\n and set behind a rule rather than coloured, because every colour on this page already\n means something about a verdict and this one took no part in a verdict. */\n.det.shadow { opacity: 0.72; }\n.ev-item.shadow { opacity: 0.72; border-left: 2px dashed var(--line); padding-left: 8px; }\n.shadow-verdict { margin-top: 8px; font-style: italic; }\n.shadow-verdict.changed { color: var(--ink-2); font-style: normal; }\n.tagline { font-size: 11px; color: var(--muted); }\n.tagline b { color: var(--ink-2); font-weight: 600; }\n\n#test-input { min-height: 82px; }\n#tester-panel .editor { gap: 8px; }\n#tester-panel .field-row { gap: 6px; }\n#tester-panel input[type=\"text\"] { flex: 1 1 120px; min-width: 90px; }\n.assumed { color: var(--muted); font-size: 11px; margin-top: 6px; }\n\n.rangeset { border-bottom: 1px solid var(--line-soft); padding: 10px 15px; }\n.rangeset:last-child { border-bottom: 0; }\n.rangeset h3 { margin: 0 0 6px; font-size: 12.5px; font-weight: 620; display: flex; align-items: baseline; gap: 8px; }\n.rangeset h3 span { font-weight: 400; color: var(--muted); font-size: 11.5px; }\n.cidrs { display: flex; gap: 5px; flex-wrap: wrap; }\n.cidr {\n display: inline-flex; align-items: center; gap: 5px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;\n font-size: 11px; background: color-mix(in srgb, var(--ink) 6%, transparent); border-radius: 999px; padding: 2px 4px 2px 9px;\n}\n.cidr button { border: 0; background: none; padding: 0 4px; font-size: 12px; line-height: 1; color: var(--muted); border-radius: 999px; }\n.cidr button:hover { background: none; color: var(--crit-text); }\n.cidr.readonly { padding-right: 9px; }\n.toasts { position: fixed; right: 18px; bottom: 18px; z-index: 60; display: grid; gap: 8px; max-width: 380px; }\n.toast {\n background: var(--raised); border: 1px solid var(--line); border-left: 3px solid var(--s1);\n border-radius: 9px; padding: 10px 12px; font-size: 12.5px; box-shadow: 0 6px 22px rgba(0,0,0,.18);\n animation: toast-in .16s ease-out;\n}\n.toast.ok { border-left-color: var(--good-text); }\n.toast.bad { border-left-color: var(--crit-text); }\n.toast.warn { border-left-color: var(--warn-text); }\n.toast b { display: block; margin-bottom: 2px; }\n.toast span { color: var(--muted); }\n@keyframes toast-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }\n@media (prefers-reduced-motion: reduce) { .toast { animation: none; } * { transition: none !important; } }\n\n/* Forced colours — Windows High Contrast and the like.\n The browser repaints text, backgrounds and borders from the user's palette but leaves\n SVG fill and stroke alone, which is the right outcome for the two series: they stay\n tellable apart instead of collapsing into one system colour. The gridlines are the\n casualty. They are drawn in --grid, a colour picked to recede against *this* dashboard's\n background, and against a forced black one they recede to 1.4:1 — measured, on the\n statistics charts — which is not recessive, it is gone. GrayText is the palette's own\n answer to \"present but secondary\", so the grid follows the user's colours while the data\n keeps the ones that carry meaning. */\n@media (forced-colors: active) {\n .gridline { stroke: GrayText; }\n}`;\n\n/**\n * Everything between `<body>` and the boot script.\n *\n * The standalone page and the embedded element render identical markup; only what they\n * render it *into* differs.\n */\nexport const DASHBOARD_MARKUP = String.raw`\n<a class=\"skip\" href=\"#view-live\">Skip to the feed</a>\n\n<header>\n <div class=\"head-row\">\n <div class=\"brand\"><h1 id=\"title\">__TITLE__</h1> <span>bot dashboard</span></div>\n <output class=\"live\" id=\"live\" aria-live=\"polite\"><span class=\"dot\" id=\"dot\"></span><span id=\"conn\">connecting…</span></output>\n <div class=\"grow\"></div>\n <nav class=\"head-links\" id=\"links\" aria-label=\"Related\"></nav>\n <div class=\"head-actions\">\n <button id=\"theme\" aria-label=\"Switch between light and dark\">Theme</button>\n <button id=\"pause\" aria-pressed=\"false\">Pause</button>\n <button id=\"reset\" class=\"danger\" hidden>Reset</button>\n </div>\n </div>\n <div class=\"nav-row\">\n <div class=\"tabs\" role=\"tablist\" aria-label=\"Dashboard views\">\n <button class=\"tab\" id=\"tab-live\" role=\"tab\" aria-selected=\"true\" aria-controls=\"view-live\">Live feed</button>\n <button class=\"tab\" id=\"tab-actors\" role=\"tab\" aria-selected=\"false\" aria-controls=\"view-actors\">Actors</button>\n <button class=\"tab\" id=\"tab-stats\" role=\"tab\" aria-selected=\"false\" aria-controls=\"view-stats\">Statistics</button>\n <button class=\"tab\" id=\"tab-policy\" role=\"tab\" aria-selected=\"false\" aria-controls=\"view-policy\">Policy<span id=\"notice-badge\" class=\"pill\" hidden></span></button>\n </div>\n <div class=\"facts\" id=\"chips\"></div>\n <div class=\"peers\" id=\"peers\"></div>\n </div>\n</header>\n\n<main class=\"stack\">\n <section class=\"tiles\" id=\"tiles\"></section>\n\n <div id=\"view-live\" role=\"tabpanel\" aria-labelledby=\"tab-live\" class=\"stack\">\n <section class=\"panel\" id=\"actor-panel\" hidden>\n <div class=\"actor-head\">\n <span class=\"who\" id=\"actor-key\"></span>\n <span class=\"grow\"></span>\n <button id=\"actor-close\">Close</button>\n </div>\n <div class=\"two\">\n <dl class=\"kv\" id=\"actor-stats\"></dl>\n <div class=\"bars\" id=\"actor-mix\"></div>\n </div>\n <div class=\"bar-actions\" id=\"actor-actions\"></div>\n </section>\n\n <div class=\"two\">\n <section class=\"panel feed-panel\">\n <h2>Requests <span class=\"sub\" id=\"feed-count\"></span><span class=\"sub win\" id=\"feed-window\"></span><span class=\"sub warn-text\" id=\"feed-skipped\" hidden></span><button class=\"sub load-skipped\" id=\"feed-load-skipped\" type=\"button\" hidden>Load them</button></h2>\n <div class=\"toolbar\">\n <div class=\"filters\" id=\"filters\"></div>\n <div class=\"search\">\n <input type=\"search\" id=\"search\" placeholder=\"path:/api actor:203.0.113.4 -rule:allow-crawlers\" spellcheck=\"false\" autocomplete=\"off\"\n role=\"combobox\" aria-expanded=\"false\" aria-controls=\"search-suggest\" aria-autocomplete=\"list\">\n <kbd aria-hidden=\"true\">/</kbd>\n <ul class=\"suggest\" id=\"search-suggest\" role=\"listbox\" aria-label=\"Filter suggestions\" hidden></ul>\n </div>\n <div class=\"saved\" id=\"saved-filters\"></div>\n <div class=\"timeframe\" id=\"timeframe\">\n <label>From <input type=\"datetime-local\" id=\"from-at\" step=\"1\"></label>\n <label>To <input type=\"datetime-local\" id=\"to-at\" step=\"1\"></label>\n <button type=\"button\" id=\"timeframe-clear\" hidden>Clear</button>\n </div>\n <button id=\"feed-export\" title=\"Download every request matching this filter as replay JSONL\">Export</button>\n <div class=\"pager pager-inline\" id=\"feed-pager-top\" hidden></div>\n </div>\n <div class=\"feed-scroll\">\n <table>\n <thead>\n <tr>\n <th class=\"num\">Time</th><th>Request</th><th>Verdict</th><th class=\"num\">Score</th><th>Action</th><th class=\"num\">ms</th>\n </tr>\n </thead>\n <tbody id=\"rows\"></tbody>\n </table>\n </div>\n <div class=\"pager\" id=\"feed-pager\" hidden></div>\n <div class=\"empty\" id=\"empty\">Nothing assessed yet. Send some traffic through the handler and it appears here within a moment.</div>\n </section>\n\n <div class=\"stack\">\n <section class=\"panel\">\n <h2>Detectors firing <span class=\"sub win\"></span></h2>\n <div class=\"bars\" id=\"live-detectors\"></div>\n </section>\n <section class=\"panel\" id=\"live-actors-panel\">\n <h2>Busiest actors <span class=\"sub win\"></span></h2>\n <div class=\"bars\" id=\"live-actors\"></div>\n </section>\n <section class=\"panel\" id=\"tester-panel\">\n <h2>Test a request <span class=\"sub\">nothing is recorded</span></h2>\n <div class=\"editor\">\n <textarea id=\"test-input\" rows=\"4\" spellcheck=\"false\" autocomplete=\"off\" aria-label=\"A User-Agent, a curl command, or a block of request headers\"\n placeholder=\"Paste a User-Agent, a curl command, or a block of request headers\"></textarea>\n <div class=\"field-row\">\n <input type=\"text\" id=\"test-ip\" class=\"mono-input\" placeholder=\"client address (optional)\" aria-label=\"Client address\">\n <input type=\"text\" id=\"test-url\" class=\"mono-input\" placeholder=\"/path (optional)\" aria-label=\"Path\">\n <button id=\"test-run\" class=\"primary\">Assess</button>\n </div>\n <div class=\"result\" id=\"test-result\" hidden></div>\n </div>\n </section>\n\n <section class=\"panel\" id=\"evidence-legend\">\n <h2>Reading this</h2>\n <div class=\"note\">\n <p><b>Purple</b> evidence is <em>proven</em>: a self-declaration, a refuted\n identity, a trap, a protocol violation. Only proven evidence may deny anybody.</p>\n <p><b>Red, amber and grey</b> are probabilistic. They accumulate into a score\n and may tag, delay, rate-limit or challenge — never block.</p>\n <p>An <b>amber left edge</b> is the safety guard stopping a rule: the policy\n asked for a terminal action the evidence could not support, and got the\n fallback instead. Those rows are the library working, not failing.</p>\n </div>\n </section>\n </div>\n </div>\n </div>\n\n <div id=\"view-actors\" role=\"tabpanel\" aria-labelledby=\"tab-actors\" class=\"stack\" hidden>\n <section class=\"panel\">\n <h2>Actors in the registry <span class=\"sub\" id=\"actors-count\"></span></h2>\n <div class=\"note\" id=\"actors-note\">Everyone the engine is currently remembering, busiest first — a far larger\n population than the feed's ring, which holds requests rather than clients. This is\n what <code>cadence</code>, <code>crawl-breadth</code> and <code>rate-anomaly</code> are reading.</div>\n <div class=\"scope\" role=\"group\" aria-label=\"Which actors to list\">\n <button id=\"actors-scope-tracked\" class=\"on\" aria-pressed=\"true\"\n title=\"Every client the engine is remembering, busiest first\">Tracked</button>\n <button id=\"actors-scope-feed\" aria-pressed=\"false\"\n title=\"Only the clients that appear in the feed you are looking at, after its filter\">Shown in the feed</button>\n </div>\n <div class=\"pager pager-top\" id=\"actors-pager-top\" hidden></div>\n <div class=\"feed-scroll\">\n <table>\n <thead>\n <tr>\n <th>Actor</th><th class=\"num\">Requests</th><th class=\"num\">Per min</th><th class=\"num\">Paths</th>\n <th class=\"num\">Cadence</th><th class=\"num\">Proven</th><th class=\"num\">Unsolved</th><th>State</th><th><span class=\"sr-only\">Actions</span></th>\n </tr>\n </thead>\n <tbody id=\"actor-rows\"></tbody>\n </table>\n </div>\n <div class=\"pager\" id=\"actors-pager\" hidden></div>\n <div class=\"empty\" id=\"actors-empty\" hidden>Nothing in the registry yet.</div>\n </section>\n </div>\n\n <div id=\"view-stats\" role=\"tabpanel\" aria-labelledby=\"tab-stats\" class=\"stack\" hidden>\n <div class=\"two\">\n <section class=\"panel\">\n <h2>Traffic <span class=\"range\" id=\"traffic-range\"></span><span class=\"sub\" id=\"traffic-window\"></span></h2>\n <div class=\"chart\" id=\"traffic-chart\"><svg id=\"traffic\" role=\"img\" aria-labelledby=\"traffic-alt\"></svg><div class=\"tip\" id=\"traffic-tip\"></div></div>\n <p class=\"sr-only\" id=\"traffic-alt\"></p>\n <div class=\"legend\" id=\"traffic-legend\"></div>\n </section>\n <section class=\"panel\">\n <h2>Assessment latency <span class=\"sub\">since start</span></h2>\n <div class=\"chart\" id=\"latency-chart\"><svg id=\"latency\" role=\"img\" aria-labelledby=\"latency-alt\"></svg><div class=\"tip\" id=\"latency-tip\"></div></div>\n <p class=\"sr-only\" id=\"latency-alt\"></p>\n <div class=\"legend\"><span id=\"latency-summary\"></span></div>\n </section>\n </div>\n\n <div class=\"two\">\n <section class=\"panel\">\n <h2>Score distribution <span class=\"range\" id=\"score-scope\"></span><span class=\"sub\" id=\"score-window\"></span></h2>\n <div class=\"chart\" id=\"score-chart\"><svg id=\"scores\" role=\"img\" aria-labelledby=\"score-alt\"></svg><div class=\"tip\" id=\"score-tip\"></div></div>\n <p class=\"sr-only\" id=\"score-alt\"></p>\n <div class=\"legend\" id=\"score-legend\"></div>\n </section>\n <section class=\"panel\">\n <h2>Guard stops by rule <span class=\"sub win\"></span></h2>\n <div class=\"bars\" id=\"stat-guard\"></div>\n </section>\n </div>\n\n <div class=\"grid3\">\n <section class=\"panel\"><h2>Verdicts <span class=\"sub\">since start</span></h2><div class=\"bars\" id=\"stat-verdicts\"></div></section>\n <section class=\"panel\"><h2>Actions taken <span class=\"sub\">since start</span></h2><div class=\"bars\" id=\"stat-actions\"></div></section>\n <section class=\"panel\"><h2>Bot classes <span class=\"sub\">since start</span></h2><div class=\"bars\" id=\"stat-classes\"></div></section>\n </div>\n\n <div class=\"grid3\">\n <section class=\"panel\"><h2>Detector firings <span class=\"sub\">since start</span></h2><div class=\"bars\" id=\"stat-detectors\"></div></section>\n <section class=\"panel\">\n <h2>Challenges</h2>\n <div id=\"stat-challenges\"></div>\n </section>\n <section class=\"panel\">\n <h2>Health</h2>\n <div id=\"stat-health\"></div>\n </section>\n </div>\n\n <div class=\"two\">\n <section class=\"panel\" id=\"audit-panel\">\n <h2>Audit <span class=\"sub\" id=\"audit-spans\"></span></h2>\n <div id=\"audit-body\"></div>\n </section>\n <section class=\"panel\" id=\"audit-checks-panel\">\n <h2>Checks installed</h2>\n <div class=\"detectors\" id=\"audit-checks\"></div>\n </section>\n </div>\n\n <div class=\"grid3\">\n <section class=\"panel\" id=\"identities-panel\"><h2>Identities seen <span class=\"sub win\"></span></h2><div class=\"bars\" id=\"stat-identities\"></div></section>\n <section class=\"panel\"><h2>Busiest paths <span class=\"sub win\"></span></h2><div class=\"bars\" id=\"stat-paths\"></div></section>\n <section class=\"panel\"><h2>Denied paths <span class=\"sub win\"></span></h2><div class=\"bars\" id=\"stat-denied-paths\"></div></section>\n </div>\n\n <div class=\"two\">\n <section class=\"panel\">\n <h2>Installed detectors <span class=\"sub\" id=\"detector-count\"></span></h2>\n <div class=\"detectors\" id=\"stat-detector-list\"></div>\n </section>\n <div class=\"stack\">\n <section class=\"panel\">\n <h2>Rules that fired <span class=\"sub win\"></span></h2>\n <div class=\"bars\" id=\"stat-rule-hits\"></div>\n </section>\n <section class=\"panel\">\n <h2>Bypassed <span class=\"sub\">detection never ran</span></h2>\n <div class=\"bars\" id=\"stat-bypassed\"></div>\n </section>\n </div>\n </div>\n </div>\n\n <div id=\"view-policy\" role=\"tabpanel\" aria-labelledby=\"tab-policy\" class=\"stack\" hidden>\n <div class=\"two\">\n <section class=\"panel\" id=\"editor-panel\">\n <h2>Rules <span class=\"sub\" id=\"policy-mode\"></span></h2>\n <div class=\"bar-actions\">\n <div class=\"seg\" id=\"editor-mode\">\n <button id=\"mode-gui\" aria-pressed=\"true\">Editor</button>\n <button id=\"mode-json\" aria-pressed=\"false\">JSON</button>\n </div>\n <button id=\"policy-preview\">Preview</button>\n <button id=\"policy-apply\" class=\"primary\" hidden>Apply</button>\n <button id=\"policy-revert\">Revert</button>\n <span class=\"grow\"></span>\n <button id=\"policy-import\">Import…</button>\n <button id=\"policy-export\">Export</button>\n <span class=\"dirty\" id=\"policy-dirty\" hidden>unsaved</span>\n </div>\n <div id=\"editor-gui\">\n <div class=\"rulelist\" id=\"rulelist\"></div>\n <div class=\"bar-actions\">\n <button id=\"rule-add\">+ Add rule</button>\n <button id=\"rule-expand\">Expand all</button>\n <span class=\"hint\" id=\"policy-note\"></span>\n </div>\n </div>\n <div class=\"editor\" id=\"editor-json\" hidden>\n <textarea id=\"policy-json\" spellcheck=\"false\" autocomplete=\"off\" aria-label=\"Policy rules as JSON\"></textarea>\n </div>\n <div class=\"editor\"><div class=\"result\" id=\"policy-result\" hidden></div></div>\n <input type=\"file\" id=\"policy-file\" accept=\"application/json,.json\" hidden>\n </section>\n\n <div class=\"stack\">\n <section class=\"panel\" id=\"guard-panel\">\n <h2>Guard <span class=\"sub\" id=\"guard-mode\"></span></h2>\n <div id=\"stat-policy\"></div>\n <div class=\"note\" id=\"guard-note\"></div>\n </section>\n <section class=\"panel\" id=\"ranges-panel\">\n <h2>Range sets <span class=\"sub\" id=\"ranges-mode\"></span></h2>\n <div id=\"ranges-body\"></div>\n <div class=\"note\" id=\"ranges-note\"></div>\n </section>\n <section class=\"panel\">\n <h2>Preview a shipped preset</h2>\n <div class=\"rules\" id=\"preset-buttons\"></div>\n <div class=\"note\">Runs the preset against the requests still in the window and shows what would change. Nothing is applied.</div>\n </section>\n <section class=\"panel\" id=\"changes-panel\">\n <h2>Changes <span class=\"sub\" id=\"change-count\"></span></h2>\n <div id=\"stat-changes\"></div>\n <div class=\"note\">What was applied to this process at runtime, and by whom when the dashboard's\n <code>auth</code> could say. Bounded and in memory: the durable copy is the\n <code>policy-change</code>, <code>guard-change</code>, <code>range-change</code> and\n <code>actor-change</code> events.</div>\n </section>\n <section class=\"panel\" id=\"notices-panel\">\n <h2>Notices <span class=\"sub\" id=\"notice-count\"></span></h2>\n <div id=\"stat-notices\"></div>\n </section>\n </div>\n </div>\n\n <div class=\"two\">\n <section class=\"panel\">\n <h2>Rules, in evaluation order</h2>\n <div class=\"rules\" id=\"stat-rules\"></div>\n </section>\n <section class=\"panel\" id=\"robots-panel\">\n <h2>robots.txt this policy implies</h2>\n <div class=\"editor\"><pre class=\"code\" id=\"robots-preview\"></pre><div id=\"robots-notes\"></div></div>\n </section>\n </div>\n </div>\n</main>\n\n<div class=\"toasts\" id=\"toasts\" aria-live=\"polite\"></div>\n`;\n\nconst PAGE = String.raw`<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<meta name=\"robots\" content=\"noindex, nofollow\">\n<title>__TITLE__ · bot dashboard</title>\n<style nonce=\"__NONCE__\">\n${DASHBOARD_CSS}\n</style>\n</head>\n<body>\n${DASHBOARD_MARKUP}\n<script nonce=\"__NONCE__\">\nwindow.__BOOTSTRAP__ = JSON.parse(__BOOT_JSON__);\n__SCRIPT__\n</script>\n</body>\n</html>\n`;\n"],"mappings":"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AA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA8xB7B,IAAM,mBAAmB,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAmTvC,IAAM,OAAO,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlB,aAAa;AAAA;AAAA;AAAA;AAAA,EAIb,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","names":["start","partial","options","field","matches","oldestAt","label","select","clear","label","container","run","pending","timer","label","label","apply","label","segmented","label","rendered","label","select","read","apply","start","draft","label","notice","label","start","label","apply","label","root","timer","dom","label"]}
|