refactor(citation): drawer source-only view + popover expanded-view portal#273
refactor(citation): drawer source-only view + popover expanded-view portal#273bensonwong merged 13 commits intomainfrom
Conversation
… header - Remove "By status" toggle, view mode state, localStorage persistence, StatusProgressBar, StatusSectionHeader, and renderStatusView() — drawer always shows "By source" (sortedGroups) - Replace header summary text + progress bar with StackedStatusIcons (same component as the trigger, capped at maxIcons=5) - Export flattenCitations, FlatCitationItem, and StackedStatusIcons from CitationDrawerTrigger so the drawer header can reuse them - Lower default maxIcons from 10 → 5 and remove TriggerBadge from trigger - Remove dead UrlAnchorTextRow (copy button) and unused helpers from VerificationLog - Polish: rounded-lg → rounded-xs on EvidenceTray and FaviconImage; remove rounded from phrase quote boxes in DefaultPopoverContent - Add overflow cap showcase section (8 citations, +3 chip) and Playwright tests Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… bar - Remove lightbox portal from CitationDrawerItemComponent; proof images are now static full-width <img> elements in both the drawer and the not-found callout - Remove ZoomInIcon hover overlay from AnchorTextFocusedImage; use cursor:zoom-in when expandable instead - Remove TriggerStatusBar and TriggerBadge components; simplify CitationDrawerTrigger layout to a flat flex row - Update EvidenceTray CTA text to "Drag to pan · click to expand" when an image is present - Increase KEYHOLE_STRIP_HEIGHT_DEFAULT from 60 → 90px - Remove rounded-xs from FaviconImage Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rce heading - Add collisionPadding=8 default to PopoverContent so popovers never sit flush against viewport edges (Radix default was 0) - Shrink DOT_INDICATOR_SIZE_STYLE from 0.45em → 0.4em; fix DotIndicator and MissIndicator alignment with relative top offset - Add DrawerSourceHeading to citation drawer header: favicon/letter avatar + source name with +N overflow, replacing generic title text - Simplify StackedStatusIcons overflow badge to plain text (no ring/bg) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Neutralize Radix popper transform/position when popover enters expanded view via useLayoutEffect so the full-viewport overlay lands correctly (CSS fixed children are relative to transformed ancestors) - Switch expanded content styles from fixed+inset to width/height:100% filling the neutralized wrapper instead - Include webPageScreenshotBase64 in hasImage so URL citations show the expand affordance - Fix DotIndicator: inline-block + vertical-align:0.1em centers the dot above baseline instead of sitting on it like a period Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add webPageScreenshotBase64 fallback in keyhole strip and drawer - Extract normalizeScreenshotSrc() helper; add tests for all tier-3 cases - Add HandIcon/ExpandArrowsIcon corner affordances on keyhole strip - Show "← Drag" / "Drag →" pan hints on hover when image overflows - Fix PDF y-axis flip for highlight box centering and overlay position - Replace EvidenceTray hover CTA text with expand-arrow corner icon - Remove proof URL link from drawer expanded view - Simplify "Citation not found" → "Not found" in drawer and viewer Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the custom expanded-view header (with its own ColorScheme type, EXPANDED_STATUS_DISPLAY map, and expandedHeaderColorScheme function) with the shared SourceContextHeader component. Extends SourceContextHeader and PagePill with onClose/proofUrl props to support the expanded-view use case. Also fixes highlight auto-scroll: removes incorrect PDF y-axis flip since image coordinates already have y=0 at top, matching CSS scroll direction. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Expand expanded page viewer to full viewport (inset:0, 100vw×100dvh) instead of the previous 1rem inset, eliminating the gap that could accidentally dismiss the popover on edge clicks - Replace PagePill X button with a prominent '← Back' button in the header, clearly indicating the action returns to summary view; works for all citation types including URL citations with no page number - Strip redundant card chrome (border/shadow/rounded) from the expanded wrapper since it is now full-screen - Tighten header padding (py-4 mb-2 → py-2.5) for a more compact look Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…expand Replace pointer-events-none on ← Drag / Drag → hints with click handlers that smooth-scroll the container by 50% of its width. stopPropagation prevents the parent expand button from firing when clicking a pan hint. Renames hint labels from "Drag" to "Pan" for clarity. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…roof link - PagePill's expanded/close state is now a full `<button>` element rather than a `<span>` with a nested button, making the entire pill clickable to close and fixing the accessibility anti-pattern of interactive elements inside non-interactive elements - Remove proofUrl prop from SourceContextHeader and ExpandedPageViewer — the external link icon in the header is removed in favour of the cleaner drawer source-only layout - Shrink fullPhrase quote block font from text-sm to text-xs in popover for tighter visual density Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… click - Replace Radix wrapper DOM hack with createPortal to document.body for true full-screen coverage (Radix ResizeObserver was overwriting our imperative transform/position overrides after useLayoutEffect fired) - DefaultPopoverContent returns a 1×1 invisible placeholder in expanded state; CitationComponent owns the ExpandedPageViewer via the portal - Keyhole image click now passes verificationImageSrc as snippetSrc so the expanded overlay shows the evidence snippet, not the full proof page - Outer tray expand continues to use resolveExpandedImage (full proof page) - ESC key handled via document capture listener instead of Radix handler Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace binary "summary | expanded" state with three explicit states: - "expanded-evidence": centered modal for keyhole/snippet click - "expanded-page": full-screen ExpandedPageViewer for expand button Root bugs fixed: 1. Verified-state EvidenceTray passed `onImageClick` prop directly instead of `handleKeyholeClick`, so snippet src was never forwarded. 2. Evidence branch was gated on `customExpandedSrc` which required URL validation to pass — bypassed entirely by reading verificationImageSrc directly from verification in the portal render. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review: PR #273 - Citation UI RefactoringThanks for this substantial refactoring! The portal-based expanded views and simplified drawer are well-architected overall. Here's my detailed review: 🔴 CRITICAL ISSUES1. Unsafe Type Cast Violates CLAUDE.md GuidelinesFile: onKeyDown={e => {
if (e.key === "Enter" || e.key === " ") {
e.preventDefault();
handleProofClick(e as unknown as React.MouseEvent); // ❌ UNSAFE
}
}}Issue: Casting Fix: Create separate handlers or accept a union type: const handleProofClick = (e: React.MouseEvent | React.KeyboardEvent) => {
e.preventDefault();
// ... rest of logic
};🟠 HIGH PRIORITY BUGS2. Event Propagation Bug in "Read More" ButtonFile: <button
type="button"
onClick={handleReadMore} // ❌ Missing stopPropagation
className="ml-1 text-blue-500..."
>
Read more
</button>Issue: This button is inside an expandable item (which has Expected: Read more modal opens, item stays expanded Fix: onClick={(e) => {
e.stopPropagation();
handleReadMore();
}}Note: Other buttons properly use this pattern (lines 285, 690). 🟡 MEDIUM PRIORITY3. State Management Edge Case:
|
| Guideline | Status | Notes |
|---|---|---|
| Security Patterns | ✅ | Correct use of isValidProofImageSrc(), extractDomain() |
| No Variable Re-exports | ✅ | All imports from canonical locations |
| Type Safety | One unsafe cast violation (item #1) | |
| Discriminated Unions | Type narrowing works but type optionality unclear |
📊 SUMMARY
Security: Strong image validation, one unsafe type cast
Bugs: One event propagation bug, one state management edge case
Code Quality: Clean refactoring, good architectural separation
Tests: Excellent security tests, gaps in UI interaction tests
Recommended Actions Before Merge:
- ✅ Fix unsafe cast (CitationDrawerTrigger.tsx:307)
- ✅ Fix "Read more" propagation (CitationDrawer.tsx:670)
- 📝 Consider adding expanded view interaction tests
- 🤔 Clarify
citation.typeoptionality for discriminated union compliance
Overall this is solid work! The portal architecture is well-designed and the security patterns are excellent. Just need to address the two concrete bugs above.
❌ Playwright Test ReportStatus: Tests failed 📊 Download Report & Snapshots (see Artifacts section) What's in the Visual SnapshotsThe gallery includes visual snapshots for:
Run ID: 22180179836 |
- Move FlatCitationItem + flattenCitations() from CitationDrawerTrigger
to CitationDrawer.utils (canonical location per no-re-exports rule)
- Guard handleKeyholeClick to no-op when evidence image is missing/invalid,
preventing expanded-evidence state with a null portal on mobile
- Add biome-ignore comments for a11y lint on pan hints and backdrop div
(keyboard access handled by parent/document-level handlers)
- Remove unused MAX_MATCHED_TEXT_LENGTH constant and rename anchorText
to _anchorText to suppress unused-param warnings in VerificationLog
- Update CitationDrawer tests: use getByRole("dialog") for title checks
and fix "renders all citation items" assertion for source-only view
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Code Review for PR #273This is a substantial refactor that simplifies the citation drawer and enhances the popover with portal-based expanded views. Overall, the code quality is high with good attention to security and architecture. Here are my findings: ✅ Strengths1. Security
2. Architecture & Best Practices
3. Code Quality
|
- Fix test selectors: expanded-page portal has no role="dialog"; query img[alt='Full page verification'] and button[aria-label='Back to citation summary'] instead - Remove unused CitationDrawerItem import from CitationDrawerTrigger - Fix unsafe e as unknown as React.MouseEvent cast → React.SyntheticEvent - Remove unused MAX_MATCHED_TEXT_LENGTH constant from VerificationLog - Prefix unused _anchorText parameter in StatusHeader - Auto-fix formatting (biome check:fix) across all changed files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PR 273 Review: Drawer Source-Only View + Popover Expanded-View PortalSummaryThis PR refactors the citation drawer to a simplified source-only view and introduces portal-based expanded views for citation popovers. The changes are well-structured and demonstrate good engineering practices with comprehensive test coverage. Positive AspectsArchitecture & Design
Code Quality
Issues FoundSecurity Concerns (Medium Priority)1. Mixed URL Domain Validation Patterns
|
Summary
CitationDrawer — source-only view
StatusProgressBar,ViewModeToggle(by-status / by-source toggle), and the status-grouped view — drawer now renders source-grouped citations onlyZoomInIconimportStatusViewrender path andgroupCitationsByStatususageDrawerSourceHeadingfor favicon + source name label in the drawer headerCitationDrawerTriggerto reuse trigger icons in the header and remove status-specific UICitationComponent — portal-based expanded views
expanded-pageview (← Back navigation) rendered viacreatePortaltodocument.body, independent of Radix positioningexpanded-evidencemodal (keyhole click): centered overlay sized to the snippet/evidence image, with backdrop-click dismissPopoverViewStatefrom 2 states ("summary" | "expanded") to 3 ("summary" | "expanded-evidence" | "expanded-page") so each mode can have distinct layout behaviourhandleKeyholeClickpre-validates evidence image availability before transitioning, preventing a mobile stuck-state where the portal returns null but outside-tap is blockedonImageClickcallback threading throughDefaultPopoverContent; expanded state is now driven entirely throughonViewStateChangePagePillthe close button in the expanded view; removed external proof linkSourceContextHeaderinExpandedPageViewerfor consistent header UIresolveExpandedImageunit testsOther
VerificationLog.tsx: removed duplicated colour-scheme helper, simplified layouticons.tsx: added pan/arrow and stack icons used by new UIShowcaseComponents.tsx: added showcase for new expanded-view statesTest plan
expanded-pageoverlay renders; ← Back returns to summarybun test— all 803 tests pass