Clear ExchangeDocs Live demo

Web UI

npm start serves, from the same process and port as the API, a marketing page at / and a browser app at /app. In production the landing page is https://clearexchange.ai and the app is https://app.clearexchange.ai (at its root), with links rewritten to match; see Hostnames. The app is for demos and for understanding the flow. It uses the public API like any client.

  • Agent console (/app#agent). Type a question, set the max price, usage tier, limit and (under Advanced) agent ID and minimum relevance. Offers come back as a ruled list with publisher, title, snippet, all-in price, licence terms and a relevance meter; a note, a filter funnel, the routing panel and the band note explain that order comes from relevance, the max price only excludes, and the cheapest offer inside the relevance band is selected (it gets the only filled Accept button). Each offer shows its published and updated dates with their age ("Undated" if the source gives none); when the question asks for a period ("this month", "latest", "2009") a note says so and each offer carries a Recent, Stale, Updated recently or Undated badge whose tooltip gives the relevance adjustment. When nothing clears the 0.5 floor and the near-top band, a "No good match" callout says why instead of a list. The minimum relevance slider starts at the exchange's 0.5 floor and can only raise it. Accept an offer to see the signed entitlement (raw token, decoded claims, cx scope, expiry countdown) and verify it for its own content, for a wider tier (refused) and as a tampered copy (bad signature). Retrieve the content with and without the token, then report ground, cite, present and engage as a SPUR batch. A side panel lists every API call the page made, with request and response.
  • Operator (/app#operator). Totals (paid retrievals, gross, clearing fee, publisher net, telemetry events), totals per publisher, ledger entries, the telemetry stream (filterable by entitlement), and supply adapters with their live or stub status and health.
  • Coverage (/coverage). Verticals, publishers, item counts, date ranges and base prices per tier, with demo supply labelled. Rendered once at startup from the same data as GET /v1/coverage, as static HTML with no script. See Demo supply.
  • Publishers (/app#publishers). Per publisher: earnings (gross and net of the fee) and the rate card (tier prices, default terms, rules and raw JSON). Read-only.

The operator and publisher views read /v1/admin/*. If ADMIN_TOKEN is set, the UI asks for it, keeps it in sessionStorage (that browser tab only), sends it as a Bearer header on admin calls and never logs it.

The operator view is a demo convenience, not a hardened admin console. A strict CSP and textContent-only rendering limit what injected content could do, but any script that did run in the page could read a token held in sessionStorage. There are no per-user accounts, roles or audit trail. Use it on trusted machines, and rotate ADMIN_TOKEN if you think it has leaked.

How it is built

Plain ES modules and CSS in web/, with no framework, bundler, build step or dependencies:

FileRole
index.htmlMarketing page (no script)
app.html, app.jsThe app: DOM and API calls
lib.jsPure helpers, unit-tested under node:test
tokens.cssEvery colour, font, size, spacing step, radius and shadow, as CSS variables
fonts/The self-hosted headline serif (Newsreader, two WOFF2 files) and its licence (OFL.txt)
base.cssShared primitives: wordmark, buttons, badges, focus ring
site.css, styles.cssMarketing and app layout
favicon.svgThe mark, used as favicon (the wordmark is the name set in the headline serif)
docs.css, docs.jsThe documentation site's layout, search and copy buttons (pages are rendered from docs/; see Docs site)
  • src/http/static.ts serves a fixed allowlist of these files, read once at startup and held in memory: no per-request filesystem access and no traversal surface. A missing file stops startup. Restart the server to pick up edits (npm run dev watches only the TypeScript modules).
  • Responses carry a strict Content-Security-Policy (this origin only for scripts, styles, images and fonts, no inline script or style, no framing), nosniff and ETags.
  • app.js renders every server value with textContent, never innerHTML. test/ui.test.ts enforces this, checks the page has no inline scripts, checks every element ID the script uses exists, and checks there are no raw colours outside tokens.css.
  • app.js and lib.js carry JSDoc types checked by tsc -p web/tsconfig.json (part of npm run typecheck).
  • To rebrand, change tokens.css and favicon.svg. The identity is editorial: warm paper and ink neutrals with one oxblood accent, hairline and heavy rules instead of cards and shadows, near-square corners, headlines in Newsreader (SIL OFL 1.1, about 48 KB, self-hosted under /ui/fonts/ so the CSP only needs font-src 'self'), the system sans for UI and the system mono for data. AA contrast in light and dark mode; a test checks the main light-theme pairs, and another rejects gradients, blur, gradient text and hype words on the pages.
  • The landing page's worked example is a static snapshot of a run against the seeded catalogue. Update it by hand if prices, the fee or the catalogue change.
  • createHttpServer(exchange, { ui: false }) gives an API-only server.

Why no framework: it would add hundreds of packages and a build pipeline for a UI of a few screens, and in the planned confidential VM everything in the image becomes part of the attested measurement.