Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added samples/qa/config-chrome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/qa/config-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/qa/conversation-chrome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/qa/conversation-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/qa/home-chrome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/qa/home-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/qa/search-results-chrome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/qa/search-results-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/qa/workspace-chrome.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added samples/qa/workspace-firefox.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions static/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ h3 { font-size: 1.15rem; font-weight: 600; }
.workspace-grid { grid-template-columns: 1fr; }
}

/* Grid items default to min-width: auto, so a long code block or unbroken
* URL inside .main-content would push the column wider than 1fr — overflowing
* the viewport on the right. min-width: 0 lets the column shrink and lets the
* existing overflow-x: auto on .prose pre handle the scroll inside the bubble. */
.main-content { min-width: 0; }
Comment thread
timon0305 marked this conversation as resolved.

/* ---------- Sidebar ---------- */
.sidebar {
max-height: calc(100vh - 10rem);
Expand Down
300 changes: 300 additions & 0 deletions tests/web-ui-qa-checklist.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
# Web UI — manual QA checklist

Companion document for issue #28. Run this whenever the web UI (templates,
`static/js/*`, or any route in `api/`) is touched. Each section below has a
**backend smoke** result captured automatically by
[`tests/web-ui-smoke.sh`](web-ui-smoke.sh) and a **visual checklist** that a
human reviewer fills in with Chrome + Firefox screenshots.

Boot the app with `python app.py` (default port 3000) and follow the
sections in order. Attach the captured screenshots to the PR that closes
issue #28; visual bugs file as follow-up issues per the acceptance
criteria.

### Verification-method legend
Comment thread
timon0305 marked this conversation as resolved.
Outdated

A reviewer should be able to audit any `[x]` and tell *how* it was
verified. The default is **visual** — a human confirmed it in Chrome
**and** Firefox, with the screenshot in `samples/qa/` as evidence.
Ticks that deviate from that default carry an explicit tag:

- **(probe)** — confirmed by `tests/web-ui-smoke.sh` or a one-off curl;
the wire-level response was the evidence (no browser click).
- **(code)** — confirmed by reading the underlying handler / CSS rule;
no live browser click. Used only when the data path is shared with
another item that *was* visually verified.
- **⏳** — known not exercised; not blocking acceptance but the row is
honest about it. The reviewer can convert these to `[x]` with a click.

## Environment

| Item | Value |
|-------------------|----------------------------------------------------------|
| OS / version | |
| Python version | `python3 --version` |
| Browser A | Chrome |
| Browser A version | |
| Browser B | Firefox |
| Browser B version | |
| Repo SHA | `git rev-parse HEAD` |
| Cursor data | default `~/.config/Cursor/User/workspaceStorage` (or override via `WORKSPACE_PATH`) |
| Reviewer | |
| Date | |

## 0. Server launch

```bash
python app.py
```

- [x] **(probe)** Server stays running for at least 60s without crash
- [x] **(probe)** Stdout: `Cursor Chat Browser (Python) running at http://127.0.0.1:3000`
- [x] **(probe)** `tests/web-ui-smoke.sh` exits 0 — 11/11 probes pass
- [x] **(probe)** No `Traceback` / `Error` in stdout during smoke run

## 1. Home / Projects list — `GET /`

**Backend smoke:** expect `HTTP 200` and the page sniff must include
`<title>Projects — Cursor Chat Browser</title>` and the text `Cursor Chat
Browser`.

**Chrome:**

![home / Chrome](../samples/qa/home-chrome.png)

- [x] Page loads, no JS console errors
- [x] Workspace cards render with workspace name + conversation count
- [x] "Other chats" card present for global storage
- [x] Clicking a workspace card navigates to `/workspace/<id>`
- [x] Dark / light mode toggle works (if present)

**Firefox:**

![home / Firefox](../samples/qa/home-firefox.png)

- [x] Same checks pass as Chrome

## 2. Workspace detail — `GET /workspace/<workspace_id>`

**Backend smoke:** expect `HTTP 200`, content length > 5KB. JSON endpoints
`/api/workspaces/<id>` and `/api/workspaces/<id>/tabs` must both return 200.

**Chrome:**

![workspace detail / Chrome](../samples/qa/workspace-chrome.png)

- [x] Conversation list (left panel) renders with at least one row
- [x] Each row shows title, timestamp, model name
- [x] Clicking a conversation loads its bubbles in the right panel
- [x] Last-updated sort order is descending (newest first)
- [x] Search box at the top filters conversations as you type
- [x] No `404 / 500` in the network tab when switching conversations
- [x] Right panel stays inside viewport (regression check for the
`.main-content { min-width: 0 }` fix landed in this PR — see §8)

**Firefox:**

![workspace detail / Firefox](../samples/qa/workspace-firefox.png)

- [x] Same checks pass as Chrome

## 3. Conversation view — markdown / code / tools / thinking

Open any conversation that contains code blocks, tool calls, and a
thinking block (the seeded `cppa-cursor-browser` PR review conversations
are good candidates).

**Markdown:**

- [x] Headers `#`, `##`, `###` render at distinct sizes
- [x] Inline `` `code` `` renders in monospace with background tint
- [x] Fenced code blocks render with syntax highlighting (Prism)
(backend-verified: bubble text payload contains fenced-code-block
delimiters; conversation screenshot shows highlighted output)
- [x] Numbered + bulleted lists render with correct indentation
- [x] Inline links are clickable, open in a new tab
- [x] Tables render with cell borders

**Tool calls:**

- [x] Tool name shown in the bubble header
(backend-verified: sampled bubble had `toolCalls[0].name = "glob_file_search"`)
- [x] Tool input (`params`) shown in a collapsible / styled block
(backend-verified: `toolCalls[0]` carried a non-empty `parameters` field)
- [x] Tool output (`result`) shown below the input
(rendered in the conversation screenshot)
- [x] Tool status (success / error) visually distinguishable
(backend-verified: `toolCalls[0].status = "completed"`; CSS rules
`.tool-call-status.completed / .error / .running` in style.css)
- [x] Long tool outputs wrap or scroll, not overflow horizontally
(`.tool-call-content { overflow: auto; word-break: break-all }`
plus the `.main-content { min-width: 0 }` fix landed in this PR)

**Thinking blocks:**

- [x] Collapsible "Thinking" section is collapsed by default
(verified in the workspace screenshot — `Thinking 21s` chip visible)
- [x] Backend data shape correct: a sampled bubble carries
`metadata.thinking = "The user is asking whether it's a good idea..."`
and `metadata.thinkingDurationMs = 3569`
- ⏳ Expanding it shows the reasoning text — not exercised this pass (a one-click visual check; data presence already verified via `metadata.thinking` on a sampled bubble above)
- [x] Duration (`thinkingDurationMs`) shown in human format (e.g. `4.2s`)
— visible as `Thinking 21s` in the workspace screenshot

**XSS sanitization (regression check from `test_xss_sanitization.py`):**

- [x] No raw `<script>` tags visible in any bubble
- [x] No `onerror` / `onclick` handlers fire on hover / click of bubble content

**Chrome:**

![conversation view / Chrome](../samples/qa/conversation-chrome.png)

**Firefox:**

![conversation view / Firefox](../samples/qa/conversation-firefox.png)

## 4. Search — `GET /search` + `GET /api/search?q=...`

**Backend smoke:**
- `/search` (page) → `HTTP 200`
- `/api/search?q=<known-term>` → `HTTP 200`
- `/api/search` (no `q`) → `HTTP 400` with body `{"error":"No search query provided"}`

**Chrome:**

![search results / Chrome](../samples/qa/search-results-chrome.png)

**Firefox:**

![search results / Firefox](../samples/qa/search-results-firefox.png)

- [x] Search box at `/search` accepts input
- [x] Submitting an empty query shows a useful error, not a 500 page
- [x] A known-good query (e.g. `project` — returned 9 results in the screenshot) returns results
- [x] Each result shows: workspace name, chat title, snippet with the
query highlighted
- [x] Clicking a result navigates to the correct conversation
(backend-verified: `/workspace/global?tab=<chatId>` returns HTTP 200
with the workspace template; JS handles the `?tab=` param client-side
to scroll to the matching conversation)
- [x] No layout breakage with very long query strings (paste a 500-char query)

## 5. Config page — `GET /config`

**Backend smoke:** `HTTP 200`, content length > 5KB. `/api/detect-environment`
must return `HTTP 200`.

**Chrome:**

![config / Chrome](../samples/qa/config-chrome.png)

**Firefox:**

![config / Firefox](../samples/qa/config-firefox.png)

- [x] Current workspace path shown
- [x] "Change workspace" form accepts a valid path and applies it
- [x] Invalid path (traversal, missing dir) is rejected with a clear error
- [x] Environment auto-detect indicator matches actual platform
(`Linux native`, `WSL`, `macOS`, `SSH remote`, `Windows`)

## 6. Export functionality

Open any conversation, then trigger each export button. Each must produce
a file that opens cleanly in its native viewer.

| Format | Button trigger | Expected result |
|--------|----------------|-----------------|
| Markdown | "Export → Markdown" | `.md` with YAML frontmatter from the web UI pipeline (`title`, `created`, `conversation_id`, plus optional `models_used` / token + cost fields) + transcript. NOTE: this is the **web UI** schema in `static/js/download.js`, not the CLI-export schema from issue #27 (`log_id`, `workspace`, etc.). |
| HTML | "Export → HTML" | `.html` with Prism-highlighted code; opens in browser |
| JSON | "Export → JSON" | `.json` is valid JSON parseable by `jq .` |
| CSV | "Export → CSV" | `.csv` opens in a spreadsheet; one row per bubble |
| PDF | "Export → PDF" | `.pdf` opens in Acrobat / Preview; pagination clean |

**Verified during this QA pass — all 5 buttons clicked, each file inspected:**

| Format | File size | Validation result |
|--------|-----------|-------------------|
| `.md` | 587 KB | UTF-8, starts with YAML frontmatter (`title`, `created`, `conversation_id`, `models_used`, token + cost stats) |
| `.html` | 663 KB | `<!DOCTYPE html>` + embedded CSS + dark-mode `@media (prefers-color-scheme: dark)` |
| `.pdf` | 214 KB | `file` reports "PDF document, version 1.3, **153 page(s)**" — opens cleanly |
| `.json` | 654 KB | Valid JSON, top-level keys `bubbles`, `codeBlockDiffs`, `id`, `metadata`, `timestamp`, `title` |
| `.csv` | 592 KB | 391 rows (1 header + 390 bubbles), 21 columns covering tokens, thinking, tool calls |

Backend endpoint check (run earlier in this QA pass):
```text
POST /api/generate-pdf
→ HTTP 200, Content-Type: application/pdf, %PDF-1.3 magic, %%EOF terminator
```

- [x] All five formats download from Chrome
- [x] All five formats download from Firefox
- [x] PDF endpoint returns valid `application/pdf` over the wire
- [x] No silent failures — every clicked button produced a file
- [x] **Cross-browser parity proven byte-identical for `.md` / `.html` /
`.json` / `.csv`** — `cmp -s` returns equal between Chrome and
Firefox outputs. The JS export pipeline in `static/js/download.js`
is browser-agnostic.
- [x] PDF bytes differ across browsers but both are valid 153-page PDFs
— expected because `/api/generate-pdf` embeds a fresh
`/CreationDate` on each call.

## 7. Cross-browser parity

This section was initially drafted as a list of "weird browser things"
but two of the four rows ended up testing features that don't exist in
this codebase (per-bubble copy button and arrow-key sidebar navigation).
Replaced with the cross-browser checks that target features actually
shipped today. Issue #28's required acceptance criteria are covered by
§1–§6 + the byte-identical export evidence; §7 is supplementary.

| Check | Chrome | Firefox | Evidence |
|-------|--------|---------|----------|
| Long code blocks scroll horizontally inside the bubble (do not overflow the column) | ✅ | ✅ | Visible in the `conversation-*.png` screenshots after the `.main-content { min-width: 0 }` fix |
| Export buttons (md / html / json / csv / pdf) all download a valid file | ✅ | ✅ | `.md`/`.html`/`.json`/`.csv` byte-identical across browsers (cmp -s); both PDFs are valid 153-page documents — see §6 |
| `Copy All` button copies the whole chat as Markdown to the clipboard | ✅ | ✅ | `copyAllMarkdown()` at [download.js:286-298](../static/js/download.js#L286-L298) calls `convertChatToMarkdown(selectedTab, true)` — the same function the .md / .html / .pdf exports use (already byte-identical across browsers in §6) — and pipes the result to the standard `navigator.clipboard.writeText` API. Same input, same output, different destination. |
| Print preview renders without obvious overlap (no `@media print` rules — relies on browser default) | ⏳ | ⏳ | No project-specific print styles exist (zero `@media print` rules in `static/css/style.css`); whatever the browser does at print time is unverified. Cmd-P in either browser closes this in 5 seconds; flag the result if anything wraps or clips. |

Phantom rows removed (filed as enhancement ideas, not bugs):
- Per-bubble copy-to-clipboard button — only `copyAllMarkdown()` exists today
- Arrow-key keyboard navigation through the conversation list — no `keydown` arrow handler in `static/js/*.js`

## 8. Regression notes — fixes shipped in this PR

During the QA pass, one visual bug was found and fixed in the same PR:

- **`.main-content` grid column overflowing viewport on the right.**
The right-hand conversation panel (`1fr` grid column in
`.workspace-grid`) had no explicit `min-width: 0`, so any unbreakable
child (long code block, long URL, long `tool-call-content`) was
pushing the column wider than the viewport. Header + title rendered
fine because they live outside the grid. Fix: added
`.main-content { min-width: 0; }` at
[static/css/style.css:316–321](../static/css/style.css#L316-L321).
Existing `overflow-x: auto` on `.prose pre` and `word-break: break-all`
on `.tool-call-content` then take over inside the bubble.

- [x] **(visual)** Post-fix screenshots in `samples/qa/workspace-chrome.png`,
`workspace-firefox.png`, `conversation-chrome.png`,
`conversation-firefox.png` were captured *after* the
`.main-content { min-width: 0 }` fix landed — the conversation
column stays inside the viewport in all four.

## 9. Sign-off

State at the time of PR submission (2026-05-14). Reviewer fills in the
remaining row(s) before merge.

| Item | Status | Notes |
|------|--------|-------|
| All 7 sections complete | ✅ | §1–§6 covered with `(visual)` + `(probe)` evidence; §7 row 4 (print preview) deferred as `⏳`, non-blocking — see §7 |
| Screenshots attached to PR | ✅ | 10 PNGs in `samples/qa/`, embedded inline in §1–§5 |
| Visual bugs filed as follow-up issues | ✅ | None — the one bug found (`.main-content` overflow) was fixed in this PR rather than deferred; see §8 |
| `tests/web-ui-smoke.sh` passes | ✅ | 11/11 probes, captured in CI + locally |
| 1+ reviewer approval | ⏳ | Pending — open question for the reviewer |

For future passes: re-run this file from §0 onward whenever
`templates/*.html`, `static/{css,js}/*`, or any route in `api/` is
touched. The point of a sign-off block isn't to be a one-shot artifact —
it's to make the next QA pass start from a known baseline.
Loading
Loading