Artur Mukhamadiev 77768ba3ca credentials: offer the approved Confluence origin as a fixed choice
The origin check is an exact match including the context path, so users had
to type "https://collab.lge.com/main" precisely. GET /api/v1/config now
returns the approved origins in canonical form (non-secret: they are the only
destinations the backend will talk to), and the UI swaps the URL text field
for a select listing them, keeping the element id, focus handling and the
Test connection flow unchanged. The text field remains the fallback when the
fetch fails. Backend validation of the submitted URL is untouched. Mock server
serves the endpoint; contract, API and e2e tests cover it.
2026-09-15 15:24:17 +03:00
..

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.
    • The URL field is a select filled from GET /api/v1/config (the backend's approved origins, canonical form). If that fetch fails, the plain text input stays as the fallback.
    • 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