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.
116 lines
7.8 KiB
Markdown
116 lines
7.8 KiB
Markdown
# 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
|
|
|
|
```text
|
|
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:
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
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):
|
|
|
|
```bash
|
|
cd frontend
|
|
npm run test:e2e
|
|
```
|