feat: auto display labels, billing, login UX, and popover scroll fixes#394
feat: auto display labels, billing, login UX, and popover scroll fixes#394bensonwong merged 15 commits intomainfrom
Conversation
- Add PaymentRequiredError (402) with billingCode field; previously 402s
fell through to the generic ValidationError catch-all in createApiError
- Add checkUsageWarning() in DeepCitation client that reads X-DeepCitation-
Remaining / X-DeepCitation-Limit response headers and fires onUsageWarning
callback — called alongside checkLatestVersion on every successful response
- Add onUsageWarning callback to DeepCitationConfig (types.ts)
- Add USAGE_WARN_PCT=80 and USAGE_CRITICAL_PCT=90 to new billing.ts as the
canonical threshold source; export from index.ts
- CLI: formatNetworkError now detects PaymentRequiredError and emits an
actionable message with billing URL and pay-as-you-go benefits
- CLI: warnUsage() fires when budget hits USAGE_WARN_PCT / USAGE_CRITICAL_PCT
- CLI: new `billing` command opens ${BASE_URL}/api#billing in the browser
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- checkUsageWarning: add `!response.ok` early-return so the callback never fires on 402/5xx responses (contract states successful responses only) - formatNetworkError: wrap err.message through sanitizeForLog() before printing — the message comes from untrusted server JSON and could carry ANSI sequences or log-injection payloads - deepCitationPricing.ts: remove re-export of USAGE_WARN_PCT / USAGE_CRITICAL_PCT (violates no-variable-re-export policy); ApiBillingPage now imports USAGE_WARN_PCT directly from deepcitation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…op hardcode
- Extract ${BASE_URL}/api#billing to BILLING_URL const (was duplicated in
warnUsage, formatNetworkError, openBillingDashboard)
- billing case now uses formatNetworkError() like every other command handler
(was inlining err instanceof Error ternary)
- Remove hardcoded "$20/month" from openBillingDashboard copy — free tier
amount is not this package's responsibility to hardcode
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extends the first-connection messaging to the browser OAuth path — both the API-key and browser login flows now call printFreeTierWelcome() so every new user sees the $20/month free tier notice and billing URL. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of warning about display-label mismatches, the inject command now automatically adds data-dc-display-label to elements where the visible text doesn't match the anchorText. This lets the skill skip manual display-label audits — the CLI handles it post-hoc. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- `startCallbackServer` now returns a `cancel()` function that closes the HTTP server and rejects the pending result promise. - `readKeyFromStdin()` races against the browser callback: if the user pastes a valid `sk-dc-...` key into the terminal while the browser flow is pending, the server is cancelled and the key is saved directly via `saveApiKey`. - Putting stdin into readline flowing mode also prevents typed characters from leaking into the shell after process exit. - Fix self-referential `const BILLING_URL = BILLING_URL` typo. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace the gap-fill approach (patch gaps < MAX_GAP_PCT between adjacent word rects) with a hull-merge: all rects on the same line are collapsed into a single spanning rect whose left/right are the outermost edges. The gap-fill had a hard 3% threshold that silently missed larger gaps produced by character-level OCR fragmentation (e.g. "ss", "sso", "ee" returned as separate bounding boxes for a single word). Hull-merge absorbs any gap size, so the highlight always covers the full text run. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
`getBoundingClientRect()` returns viewport-relative coordinates. The previous code added `window.scrollX/Y` to convert to document space, which was correct for `position:absolute` but double-counts scroll for `position:fixed`. Switching to `position:fixed` (both React and CDN paths) lets the popover stay anchored to the viewport without any scroll arithmetic. `computePosition` in positioning.ts now returns raw viewport coords. Also: forward `data-dc-display-label` attribute into `CdnPopoverWrapper` so the CDN runtime honours auto-display-labels set by the attributor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…size Chrome fires ResizeObserver when a scrolled element's visible rect changes, causing the popover to reposition and follow the trigger during scroll. Remove trigger observation from both React and vanilla runtimes; window "resize" listener already covers legitimate trigger-resize cases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…ute positioning position:fixed breaks scroll passthrough in apps using overflow:hidden on body with a Radix ScrollArea as the actual scroll container. Portal the popover wrapper into the trigger's scroll-root ancestor and use position:absolute with container-relative coords so it scrolls naturally with page content. Applied to both React (Popover.tsx) and vanilla CDN runtime (cdn.ts). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix focus-trap inert: walk from popover up to body inerting siblings at each level, instead of inerting <main> (which now contains the popover portal) - Add SSR guard in portal detection useLayoutEffect - Cleanup radixVP.style.position mutation on effect teardown - Re-detect scroll root on each open (add `open` to deps) - Add scrollHeight > clientHeight guard to fallback scroll detection - Guard against detached scroll containers in CDN runtime - Fix misleading comment about initial wrapper attachment Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Matches the React path which cleans up via useLayoutEffect teardown. Without this, the CDN runtime permanently sets position:relative on the Radix ScrollArea viewport. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 4 Skipped Deployments
|
✅ Playwright Test ReportStatus: Tests passed 📊 Download Report & Snapshots (see Artifacts section) What's in the Visual SnapshotsThe gallery includes visual snapshots for:
Run ID: 23795512476 |
- Fix TS7022 in Citation.tsx: rename `parent` to `parentEl` with explicit Element type to break circular inference - Cast parent.children to Element[] for proper typing - Remove extra `triggerRef` from useEffect dep arrays (stable ref) - Auto-format fuzzyAnchor.test.ts and cli.ts for biome Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eWarning - Fix CodeQL high: loop HTML tag stripping until stable to handle nested fragments like <scr<script>ipt> - Update popoverScrollStability Playwright test: verify popover scrolls with page content (position:absolute) instead of staying viewport-fixed - Rename onUsageWarning → onUsageUpdate: the callback fires on every response with usage headers, not just near limits Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
PR Review: feat: auto display labels, billing, login UX, and popover scroll fixesThis is a substantial PR bundling five feature areas. The OCR hull-merge, fuzzy anchor tests, and focus-trap generalization are solid. Below is a prioritized list of issues to address before merge. Bugs[HIGH] Double
Fix: Call [MEDIUM] Stale
[MEDIUM] Fragile string comparison for cancel detection
if ((err as Error).message === "Login cancelled") return;This couples [LOW] Nested-element regex can match inner closing tag
Security[MEDIUM] Incomplete HTML attribute escaping in auto display label injection
const escaped = visibleText.replace(/"/g, """);Only |
Code Quality[LOW]
[LOW] Unnecessary local alias in const url = BILLING_URL; // redundant — use BILLING_URL directly[LOW] Inline The inject logic casts |
Test Coverage[MEDIUM]
[MEDIUM] The existing [MEDIUM] No test for: callback fires with correct [LOW] Auto display label injection not tested No tests in |
Stale Test Comment
Informational
Positives
Summary: The double-welcome-message bug (HIGH), incomplete HTML attribute escaping (MEDIUM/Security), and three missing billing/usage tests (MEDIUM) should be resolved before merge. The other items are lower-risk but worth addressing while the code is fresh. |
PR Review: feat: auto display labels, billing, login UX, and popover scroll fixesThis is a substantial PR bundling five feature areas. The OCR hull-merge, fuzzy anchor tests, and focus-trap generalization are solid. Below is a prioritized list of issues to address before merge. Bugs[HIGH] Double
Fix: Call [MEDIUM] Stale
[MEDIUM] Fragile string comparison for cancel detection
if ((err as Error).message === "Login cancelled") return;This couples [LOW] Nested-element regex can match inner closing tag
Security[MEDIUM] Incomplete HTML attribute escaping in auto display label injection
const escaped = visibleText.replace(/"/g, """);Only Code Quality[LOW]
[LOW] Unnecessary local alias in const url = BILLING_URL; // redundant — use BILLING_URL directly[LOW] Inline The inject logic casts Test Coverage[MEDIUM]
[MEDIUM] The existing [MEDIUM] No test for: callback fires with correct [LOW] Auto display label injection not tested No tests in Stale Test Comment
Informational
Positives
Summary: The double-welcome-message bug (HIGH), incomplete HTML attribute escaping (MEDIUM/Security), and three missing billing/usage tests (MEDIUM) should be resolved before merge. The other items are lower-risk but worth addressing while the code is fresh. |
Summary
anchorText, automatically setdisplayLabelso the popover shows what the user actually sees (582250f)PaymentRequiredError, usage-warning checks,billingCLI command, extractBILLING_URLconstant (0cc5d2a,04dadda,02def0b,ad060ae)logincommand; print free-tier welcome on browser OAuth (65498c4,3c798ee)c2531b0)overflow:scroll/autoparent) usingposition:absoluteinstead ofposition:fixed. The popover now scrolls with page content, fixing scroll-eating in apps withbody { overflow: hidden }. Applied to both React and vanilla CDN runtimes (5adacdd,4953a1e,a91a344,72267ef,8a76476)inertfocus-trap to walk from popover up to<body>, inerting siblings at each level — handles the popover now being inside<main>via the scroll container portalTest plan
billingCLI command opens billing URLlogincommand with terminal key-paste fallbackbun run test— 1713 tests passbun run lint— no errors (warnings pre-existing)