Artur Mukhamadiev 1a8c220ca3 frontend: time-based playful status line while a query runs
The running view showed one fixed line for up to 15 minutes, which reads as a
hang. js/running-status.js maps elapsed run time to a line, starting neutral
("Searching Confluence…") and getting more playful at 20 s, 1, 2, 4, 7 and 11
minutes, in the same register as the Exit queue button. app.js re-evaluates
the line every 5 s from wall-clock time (correct after background-tab
throttling), restarts the clock when the running state is entered after a
queue wait, and stops the ticker when the view leaves the running state. The
backend streams no progress, so no line claims a specific activity.
2026-09-15 14:06:39 +03:00

7.8 KiB

Confluence Research - Web UI

Minimalist, secure Web UI for Confluence Research, designed to operate against the backend API contracts specified in docs/SPECIFICATION.md and docs/implementation/CONTRACTS.md.

Directory Layout

frontend/
├── index.html                  # Main HTML entrypoint (clean white minimalist theme)
├── css/
│   └── style.css               # Responsive styling, accessible components, thinking-orb and logo styles
├── js/
│   ├── app.js                  # State transitions, keyboard handling, memory credentials, staleness guards
│   ├── api.js                  # Relative /api/v1/... fetch boundary with UTF-8 byte validation
│   ├── render.js               # marked.js + DOMPurify, fail-safe render, bounded sectioning
│   ├── history.js              # Sources, lazy bounded history serialization, artifacts listing
│   ├── orb.js                  # Vanilla canvas driver for the vendored thinking-orbs engine (loading view)
│   ├── running-status.js       # Time-based status line for the loading view's running state
│   ├── queue.js                 # Pure admission-queue formatting helpers (ordinals, ETA, status line, Exit queue label)
│   └── logo.js                 # Book logo playback: CSS cover flip on page open and brand hover
├── assets/
│   └── book.svg                # Favicon (same book shape as the CSS logo)
├── vendor/                     # Pinned vendor libraries & licenses (locally served)
│   ├── marked.min.js
│   ├── marked.LICENSE
│   ├── purify.min.js
│   ├── dompurify.LICENSE
│   ├── thinking-orbs.engine.js # Framework-free engine build of thinking-orbs 0.3.1
│   └── thinking-orbs.LICENSE
├── dev/
│   ├── mock-server.js          # Zero-dependency same-origin mock server & scenario runner
│   ├── scenario-toolbar.js     # External dev toolbar script (CSP compliant, no inline scripts)
│   └── scenario-toolbar.css    # External dev toolbar styling (CSP compliant, no inline styles)
├── tests/
│   ├── contract.test.js        # Wire format, status code, header, & scenario tests (23 tests, incl. the 5 admission queue scenarios)
│   ├── api.test.js             # UTF-8 byte boundary and credential validation tests (6 tests)
│   ├── queue.test.js           # Admission queue formatting helpers: ordinals, ETA, status line, Exit queue label (12 tests)
│   ├── render.test.js          # Markdown section partitioning and fallback tests (10 tests)
│   └── e2e_runner.js           # End-to-end browser test runner connecting to Chrome (9444) via CDP (19 tests)
├── package.json
├── package-lock.json
├── .gitignore
└── README.md

Security & Architecture Highlights

  1. In-Memory Credentials:
    • Confluence Base URL and Personal Access Token (PAT) reside strictly in browser JavaScript memory.
    • Never written to localStorage, sessionStorage, cookies, query parameters, console logs, or exported files.
    • A cw_session HttpOnly cookie is set by the origin for artifact download ownership.
  2. Content Security Policy (CSP):
    • default-src 'none'; script-src 'self'; style-src 'self'; connect-src 'self'; img-src 'self'; media-src 'none'; font-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'
    • Completely prevents automatic third-party network requests, tracking pixels, and unauthorized script injection. Images are same-origin only (the favicon); the sanitizer allowlist never emits <img> from agent output.
    • Verified via browser network tracing (zero automatic external requests).
  3. Markdown Sanitization & Link Safety:
    • Restricted element allowlist using locally vendored DOMPurify.
    • Fail-safe rendering: if parser or sanitizer are absent or fail, displays a safe notice without ever injecting raw untrusted HTML.
    • All links rewritten to require explicit user clicks with target="_blank" and rel="noopener noreferrer".
    • Disallowed protocols (javascript:, data:, file:) have href stripped.
  4. Large Result Handling & Memory Bounding:
    • Large answers partitioned into bounded sections (~48 KiB soft target, ~64 KiB hard cap) rendered on demand.
    • Giant code fences (e.g. 12 MB) are safely split and re-opened so every section is a valid Markdown code block.
    • Tables preserve row boundaries and repeat column headers across sections.
    • Pathological blocks fall back to a bounded plain-text preview with full export available.
    • "Export to MD" always exports the complete, untouched raw Markdown client-side via Blob.
    • Tool call results in history are rendered lazily with bounded serialization buffers (serializeBounded).
  5. Admission Queue (docs/QUEUE_SPECIFICATION.md §7):
    • The backend runs one query at a time; submitting calls POST /api/v1/queue/join first. A ready answer sends the query immediately. A queued answer shows the loading view's queued sub-state (shaping orb, js/orb.js's setState) with a status line such as "You're 3rd in line · about 4 min" and polls GET /api/v1/queue/status every 2 seconds until ready, then sends the query at once.
    • The queued sub-state's "Exit queue" button (aria-label fixed as "Leave the queue") relabels itself by time waited — "I will try next time" (under 1 min), "Ohhh, it's so long" (1-3 min), "I'm dying in this queue" (over 3 min) — and calls DELETE /api/v1/queue/ticket on click, returning to the prompt view with the prompt text preserved.
    • A 409 busy right after ready, or a 404 ticket_not_found while polling, triggers exactly one automatic rejoin (js/api.js's joinQueue/queueStatus/leaveQueue); a second failure shows a user-facing message instead of retrying forever.
    • A best-effort DELETE /api/v1/queue/ticket keepalive fetch fires on pagehide while the loading view is visible, so closing the tab releases the ticket promptly instead of waiting out the server's heartbeat timeout.

Development & Testing

Running the Dev Mock Server

The mock server runs entirely with Node.js built-ins (zero dependencies) on loopback:

cd frontend
npm run dev
# Or custom port:
node dev/mock-server.js --port 5173

Open http://127.0.0.1:5173/ in your browser. A floating dev toolbar in the bottom-right corner allows toggling between all 18 deterministic mock scenarios (e.g. normal shared example, 403 verify, 409 busy, 504 timeout, malicious content, large output, delayed cancellation, and the 5 admission queue scenarios below).

Admission queue scenarios (docs/QUEUE_SPECIFICATION.md §10), each with its own per-session poll counter that resets whenever the active scenario changes:

Scenario Behaviour
queued join answers position 3 with eta_seconds 150; each status poll decrements the position; the third poll answers ready; the query then succeeds
queued_no_estimate as queued with eta_seconds: null throughout
queue_full join answers 503 queue_full
reservation_lost join always answers ready; the first query after ready answers 409 busy; the automatic rejoin then succeeds
ticket_lost join answers queued; the second status poll answers 404 ticket_not_found; the automatic rejoin answers ready

Running Unit & Contract Tests

Tests verify API limits, wire contracts, headers, cookies, markdown partitioning, and the admission queue formatting/scenarios (51 tests):

cd frontend
npm test

Running E2E Browser Tests

Runs comprehensive browser tests against Chrome on port 9444 via CDP, including the queued → ready → result flow, Exit queue label timing (clock-stubbed), and a reservation-lost rejoin (19 tests):

cd frontend
npm run test:e2e