* fix(ui): drop the annotation count pill from the header decision primary The primary reads Send Feedback / Post Comments with no inline count; the count stays in the caret menu's discard and note copy where it is needed. DecisionPrimary loses its count field, tests pin the count-free label, and the docs no longer spell the control as 'Send Feedback · n'. * chore(guides-show): sync the viewer manifest
16 KiB
UI Testing Guide
This guide helps you test UI changes in Plannotator. Whether you're adding new features or fixing bugs, follow these steps to ensure your changes work correctly.
Table of Contents
- Development Setup
- Development Workflow
- Quick Testing Guide
- Debugging Common Issues
- Decision Control Manual Checklist
- WebMCP Manual Checklist
Development Setup
Prerequisites
- Bun - JavaScript runtime and package manager (install)
- Git - Version control
- Modern browser - Chrome, Firefox, Safari, or Edge (latest version)
Installation
git clone https://github.com/backnotprop/plannotator.git
cd plannotator
bun install
Monorepo Structure
The project uses a monorepo structure:
-
packages/- Shared codeui/- Reusable React components, hooks, utilitiesserver/- Server implementation (plan/review servers)editor/- Plan review application logicreview-editor/- Code review application logic
-
apps/- Deployable applicationshook/- Claude Code plugin (plan review)opencode-plugin/- OpenCode pluginreview/- Standalone review appportal/- Share portal (share.plannotator.ai)marketing/- Marketing site (plannotator.ai)
First Build Test
Verify your setup works:
bun run build:hook
If successful, you'll see apps/hook/dist/index.html created.
Development Workflow
Making UI Changes
Shared components (used by both plan and review UIs):
- Location:
packages/ui/components/ - Examples:
TableOfContents.tsx,AnnotationToolbar.tsx,Viewer.tsx
Plan editor (plan review UI):
- Location:
packages/editor/App.tsx - Main application logic for plan review
Code review editor (code review UI):
- Location:
packages/review-editor/App.tsx - Main application logic for code review
Utilities and hooks:
- Location:
packages/ui/utils/,packages/ui/hooks/ - Examples:
parser.ts,useActiveSection.ts,annotationHelpers.ts
Development Servers (Hot Reload)
For rapid iteration, use development servers with hot reload:
# Plan review UI (most common)
bun run dev:hook
# Opens http://localhost:5173
# Code review UI
bun run dev:review
# Opens http://localhost:5174
# Portal (share.plannotator.ai)
bun run dev:portal
# Marketing site (plannotator.ai)
bun run dev:marketing
Note: Development servers run standalone without plugin integration. Changes appear instantly without rebuild.
Building for Testing
When you're ready to test with actual plugin integration:
# Build plan review UI
bun run build:hook
# Output: apps/hook/dist/index.html
# Build code review UI
bun run build:review
# Output: apps/review/dist/index.html
# Build OpenCode plugin
bun run build:opencode
# Copies HTML from hook/review dist folders
# Build everything
bun run build
# Runs build:hook && build:opencode
Important Build Note
The OpenCode plugin copies pre-built HTML files from hook and review dist folders.
When making UI changes:
✅ Correct:
bun run build:hook && bun run build:opencode
❌ Incorrect:
bun run build:opencode # Uses stale HTML from previous build!
Always rebuild hook/review apps BEFORE building OpenCode if you changed UI code.
Quick Testing Guide
Test Scripts
UI test scripts simulate plugin behavior locally:
# Plan review UI tests
./tests/manual/local/test-hook.sh # Claude Code simulation
./tests/manual/local/test-hook-2.sh # OpenCode origin badge test
./tests/manual/local/test-codex-plan-review-e2e.sh # Real Codex Stop-hook E2E
# Code review UI test
./tests/manual/local/test-opencode-review.sh # Code review UI test
What Each Script Does
test-hook.sh
- Builds the hook plugin (
bun run build:hook) - Pipes sample plan JSON (includes title, SQL/TypeScript code, checklist)
- Starts local server
- Opens browser with plan review UI
- Prints approve/deny decision to terminal
test-hook-2.sh
- Builds the hook plugin
- Starts server with
opencodeorigin flag - Verifies blue "OpenCode" badge appears in UI
- Tests origin detection logic
test-opencode-review.sh
- Builds review app (
bun run build:review) - Starts review server with sample git diff
- Opens browser with code review UI
- Verifies "OpenCode" badge + the header decision control (
Approveat zero annotations,Send Feedbackonce you annotate — not "Copy Feedback") - Tests feedback submission flow
test-codex-plan-review-e2e.sh
- Builds the hook + review apps (unless
--skip-build) - Creates a disposable
HOMEand sample git repo - Copies your Codex auth into the disposable config
- Enables
hooksand registers aStophook pointing at the local Plannotator entrypoint - Runs a real
codex execprompt that returns only a<proposed_plan>block - Leaves behind rollout logs, Plannotator history, plan files, and session URLs in an artifact directory
This is the best harness when you want to verify the full Codex deny/revise/approve loop instead of simulating hook
payloads. For browser automation, set PLANNOTATOR_BROWSER=/usr/bin/true, keep the script running in one terminal,
and drive the printed session URL with Playwright from another terminal.
See tests/README.md for additional integration and utility test scripts.
Manual Testing Workflow
-
Make your changes in
packages/ui/orpackages/editor/ -
Choose testing method:
- Option A: Dev server (fast iteration)
bun run dev:hook - Option B: Build and test with script (integration test)
bun run build:hook && ./tests/manual/local/test-hook.sh
- Option A: Dev server (fast iteration)
-
Verify your changes work correctly
-
Test responsive design:
- Desktop (>1024px): Full layout with TOC
- Tablet (768-1024px): TOC hidden
- Mobile (<768px): Touch-optimized
- Use browser DevTools (F12) → Device Toolbar (Cmd+Shift+M / Ctrl+Shift+M)
-
Check browser console for errors:
- Open DevTools (F12)
- Console tab
- Look for red errors
-
Test on multiple browsers (Chrome, Firefox, Safari, Edge)
Debugging Common Issues
Browser DevTools
Open DevTools to inspect and debug:
- Mac: Cmd+Option+I
- Windows/Linux: F12 or Ctrl+Shift+I
Useful tabs:
- Console: JavaScript errors and logs
- Network: Failed requests, slow resources
- Elements: Inspect DOM and CSS
- Performance: Profile rendering performance
- Memory: Check for memory leaks
Recommended extensions:
- React DevTools - Inspect component tree and props
- Redux DevTools - If using Redux (not currently)
Common Issues & Solutions
Port Already in Use
Error:
Error: listen EADDRINUSE: address already in use :::5173
Solution: Kill the process using that port
macOS/Linux:
lsof -ti:5173 | xargs kill -9
Windows:
netstat -ano | findstr :5173
taskkill /PID <pid> /F
Module Not Found
Error:
Error: Cannot find module '@plannotator/ui'
Solution: Clean install dependencies
rm -rf node_modules
bun install
Hot Reload Not Working
Symptom: Changes don't appear in browser after saving file
Solutions:
- Hard refresh browser: Cmd+Shift+R (Mac) or Ctrl+Shift+R (Windows/Linux)
- Restart dev server: Ctrl+C then
bun run dev:hook - Clear browser cache
- Check terminal for errors
CSS Not Applying
Symptom: Tailwind classes not working or styles look wrong
Solutions:
- Check for typos in class names (Tailwind is strict)
- Verify Tailwind config includes your file paths
- Try rebuilding:
bun run build:hook - Check if another CSS rule is overriding (use DevTools Elements tab)
- Ensure you're using correct responsive prefixes (
sm:,md:,lg:)
TypeScript/LSP Errors
Symptom: Editor shows red squiggles, but code works
Important: Many LSP errors in this codebase are warnings, not blockers.
Solutions:
- Focus on fixing errors in files YOU changed
- Run
bun run buildto see actual compilation errors - Existing files may have warnings - that's okay
- If new errors appear in your files, fix them
Common LSP warnings you can ignore:
- "Alternative text title element cannot be empty" (SVG icons)
- "This hook does not specify its dependency" (known)
- "Provide an explicit type prop for button" (existing code)
Build Fails
Error:
Build failed with X errors
Solutions:
- Read the error message carefully (shows file and line)
- Check for syntax errors in your changes
- Verify imports are correct
- Run
bun installto ensure dependencies are up to date - Check that file paths are correct (case-sensitive on Linux/macOS)
Viewing Logs
Server logs:
- Check terminal where
bunis running - Server prints requests and errors
- Hook output shows approve/deny decisions
Browser logs:
- DevTools → Console tab
- Network tab shows request/response details
- Preserve log checkbox keeps logs across page loads
Test script output:
- Test scripts print to terminal
- Shows build output, server startup, and hook decisions
- Use
echostatements to add debug output to scripts
Decision Control Manual Checklist
Not CI. Every annotate surface and the review header share one adaptive split control
(DecisionControl): a positive primary (Done / Approve / Send Feedback) plus a caret
menu with the alternate decisions and the in-place note composer. Run each flow in both states —
zero annotations and n annotations — on desktop AND on a real phone (touch has no Mod+Enter,
which is the regression class this control exists to fix).
- Annotate, single file (
plannotator annotate notes.md). At zero the primary readsDone; clicking it submits the "no feedback" record and the terminal prints it. Caret →Done with a note…opens the composer in place:Enterinserts a newline,Mod+Entersubmits,Escapesteps back to the menu keeping the draft. Add an annotation: the primary flips toSend Feedback, andDone, discard 1 annotation…raises the one confirm. - Annotate, gate mode (
plannotator annotate notes.md --gate --json). The zero-state primary isApproveand posts/api/approve(stdout records"approved"; with--require-approvalonly approval exits0);Request changes…records an annotated decision.Approve with a note…/Approve with notesappear only when the session advertises approval-notes support. - Annotate, folder and last (
plannotator annotate docs/,plannotator last). Same control, same states; in a folder session switch documents mid-draft and confirm the header count tracks the session's annotations. - HTML / live-app annotate (
plannotator annotate page.html,plannotator annotate http://localhost:<port>). Open the caret menu, then click the framed page: the popover dismisses (iframe focus is the dismissal signal — there is no parent pointerdown). - Review, agent mode (
plannotator review).Approveat zero,Send Feedbackafter annotating; approving despite annotations is two clicks (caret →Approve, discard n annotations…→Discard & approve). With the composer open,Escapereturns to the menu and does NOT collapse the file tree or close the sidebar; a secondEscapecloses the menu; a third runs the app's own ladder.Mod+Enterover the open discard confirm must fire only the dialog, never a second submission. - Review, platform (PR) mode (
plannotator review <pr-url>). Same control shape, no composer items: every menu action opensReviewSubmissionDialog. On your own PR the approve rows are muted with the "You can't approve your own PR/MR" reason whileRequest changes…/Post comments, then…stay live. - Compact/touch (real phone or DevTools device mode, both apps). The header menu carries a
visible positive decision row in every state; composer rows open the note dialog
(
DecisionNoteDialog), not an inline textarea. - Sidebar general comment (review). "+ General comment" is reachable at zero annotations
(empty state) and from the General section header; creating one flips the header control to
Send Feedback.
WebMCP Manual Checklist
Not CI. Run this in Chrome or Edge with the API on: chrome://flags/#enable-webmcp-testing, or launch with --enable-features=WebMCPTesting. Use a fresh profile so the first-run dialogs and a recovered draft do not get in the way. The Model Context Tool Inspector extension can call tools too, but the page console is enough: const tools = await document.modelContext.getTools() lists them, and JSON.parse(await document.modelContext.executeTool(tools.find((t) => t.name === 'plannotator.read_document'), {})) calls one.
Before the flows, confirm the footprint rules:
- Load
plannotator annotate <file.md>and do nothing. Sixplannotator.*tools are listed, but the header shows no "Agent" marker, no banner, anddocument.cookiehas noplannotator-webmcp-toolsentry. - Load the same session in a browser without the API. Nothing in the page changes, and the Settings General tab has no "Agent tools" row.
The five flows from the design (section 3.7):
- What is going on in this page right now? Call
read_documentwith no arguments. Expectsession.mode, the full text, the outline with per-section counts, the annotations,otherDocuments, andcursor. Calling it again returns the same comments withisNew: false. - The user just annotated something, what do they want? Open the comment composer in the page; a
read_documentwhile it is open carriescomposer_open. Submit the comment; the nextread_documentcarriesannotations_newnaming its id and the entry hasisNew: true. - Leave a comment on section X. Call
add_commentswith{ section: "<outline id>", quote: "<exact text>", text: "..." }. ExpectanchoredBy: "quote", a highlight in the document, and abrowser-agentcard in the panel. Repeat the same call with the samerequestId:created: 0,deduplicated: true. Delete the card from the panel and repeat once more: the item answersconflictand nothing is re-created. - Reply to the user's comment. Call
add_commentswith{ inReplyTo: "<the human's id>", text: "..." }. The reply renders indented under the human's card andread_documentlists it in the parent'sreplies.update_commentandremove_commentson the human's id answerforbidden; on the reply they succeed. - Several files in a folder session. Run
plannotator annotate <folder>, open one document, comment in it, then open another. Callread_document:otherDocumentsnames the first document with its count, and another_document_activenudge carries the exactread_document { path }call. Calllist_documents: every file in the tree is listed. Callreveal { annotationId, path }for a comment in the first document: the view navigates there and the card is selected.
Then the remaining surfaces:
reveal { section }scrolls to the heading;nudge_usershows one banner that the dismiss button removes; a 281-character message answersinvalid_input.- The "Agent" marker appears in the header only after the first successful call.
- Settings, General, "Agent tools" off:
getTools()is empty anddocument.cookienow hasplannotator-webmcp-tools=false. Back on: six tools again and the cookie is gone. plannotator annotate <file.html>andplannotator annotate http://localhost:<port>: from inside the iframe,document.modelContext.getTools()andregisterTool()reject withNotAllowedError; the parent page still lists Plannotator's tools.- Approve or send feedback from the page: the write tools disappear from
getTools()andread_documentcarriessession_decided.
Need Help?
If you're stuck:
- Check this guide again
- Review existing code for patterns
- Look at
CLAUDE.mdfor architecture details - Check
tests/README.mdfor test script details - Open an issue on GitHub with:
- What you're trying to do
- What you've tried
- Error messages (full text)
- Browser and OS version