Client-side migration: unified renderer window with self-contained sign-in#651
Open
Client-side migration: unified renderer window with self-contained sign-in#651
Conversation
Author
|
@microsoft-github-policy-service agree company="Microsoft" |
Replace two server-side OneNote APIs with client-side implementations: 1. Article Extraction: Replace augmentation API with @mozilla/readability - Local Readability.js parsing instead of server POST - FullPage as default clip mode (no domain whitelist) 2. Full Page Screenshot: Replace DomEnhancer API with renderer window - Scroll-capture via captureVisibleTab in focused popup window - Canvas stitching with DPR-aware overlap detection - Binary MIME part upload (no base64 overhead) - URL rewriting for images, stylesheets, srcset, CSS url() - Fixed/sticky position neutralization - Mode-switch cancel/retry mechanism - Session storage cleanup Known limitations: - External CSS not inlined (next priority) - Renderer window must stay visible (captureVisibleTab) - Canvas height capped at 16384px, storage quota 10MB - Test infrastructure uses deprecated PhantomJS Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CSS caching pipeline: content script extracts CSS from document.styleSheets
(CSSOM), passes via PageInfo.stylesheetCache through the communicator to the
clipper UI, which stores it in chrome.storage.session. The renderer reads
cached CSS and replaces <link> tags with <style> blocks. For cross-origin
sheets (SecurityError), the renderer fetches CSS directly via fetch().
Renderer iframe isolation: page content renders inside an iframe to prevent
CSS conflicts between renderer styles and captured page styles.
Shadow DOM handling: flattenShadowDomSlots() detects shadow hosts via
element.shadowRoot on the live document (browser consumes <template shadowroot>
during parsing), hides non-button [slot] content since shadow roots are lost
during cloneNode(true).
Additional fixes:
- removeUnsupportedHrefs: use getAttribute("href") instead of linkElement.href
(DOM property doesn't resolve on cloned documents, was stripping all
relative-URL <link> tags)
- inlineHiddenElements: preserve display:none state from live page
- Sticky sidebar height capping to prevent grid layout stretching
- Viewport-height min-height reset (use 0, not auto)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Block user interaction (keydown, mousedown, wheel, touchstart) on the iframe document to prevent accidental scrolling during capture. Removed experimental features that caused regressions: - Pixel-level blank space cropping (caused delay, row-by-row getImageData) - Pre-adjustment height reporting (caused repeated elements on all pages) - Sticky sidebar maxHeight cap (unnecessary with CSS grid-template-rows:auto) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…pping Detect when scrollY stops changing during capture (caused by fixed→absolute conversion inflating scrollHeight) and stop capturing instead of looping to the 16384px safety cap. Measure content height before position conversions and use it to crop blank space from the stitched image. Also fix a race condition where cleanup() async-removed screenshot keys that were immediately re-set. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Block click, pointerdown, contextmenu, and selectstart events in addition to existing keydown/mousedown/wheel/touchstart — prevents user interaction with the renderer window during capture. Also add stopPropagation for defense in depth. Refactor cleanup() to accept removeOutputKeys flag: failure/cancel paths remove screenshot output keys from session storage, while the success path preserves them for fullPageScreenshotHelper to read. Reorder success path to write output before cleanup to prevent async remove/set race. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Use a fixed full-screen div (#interaction-shield) at max z-index to block all mouse, touch, pointer, hover, drag, and context menu interactions. Keyboard and wheel events still blocked via JS listeners since they don't respect z-index. Removes per-event blocking from both host page and iframe. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Instead of accumulating all viewport captures in session storage (4-8MB) and stitching in fullPageScreenshotHelper, each capture is now sent back to the renderer via port for incremental drawing onto a hidden canvas. The renderer produces a single final JPEG and stores it in session storage. - Captures switched to PNG (lossless) — single JPEG encode at finalize - stitchImages() and ScrollData removed from fullPageScreenshotHelper - Session storage holds only HTML + one final JPEG (~1-2MB total) - Overlap/DPR calculation moved to renderer drawCapture handler - drawComplete ack prevents memory buildup from queued captures Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reflect current architecture: incremental canvas stitching in renderer, PNG captures via port, scroll stall detection, content height cropping, interaction shield overlay, and simplified fullPageScreenshotHelper. Update data flow diagram, key decisions, and known issues sections. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…dling Replace interaction shield div with pointer-events:none on iframe CSS — simpler, no compositing layer. Add imageSmoothingEnabled=false on stitch canvas for pixel-perfect drawing. Lock renderer window size via resize event listener. Handle port disconnect (user closes renderer) to prevent clipper from spinning indefinitely. Switch final output to JPEG 95%. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…us renderer - offscreenCommunicator: use chrome API directly instead of WebExtension.browser which is only initialized in service worker context (fixes Article mode crash) - renderer: strip <link rel="preload" as="script"> and modulepreload tags that trigger CSP violations on extension pages - worker: re-focus renderer window before each capture via windows.update to handle user clicking away mid-capture Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…LESS styles
Renderer window now includes a branded sidebar (322px) alongside the content
iframe. During capture, sidebar shows localized progress ("Capturing 3 of 5").
After capture, shows preview with Save/Close buttons.
- renderer.html: flexbox layout with content iframe + sidebar panel
- renderer.less: new LESS file using OneNote brand colors, compiled via gulp
- renderer.ts: sidebar progress updates, sidebar pixel cropping from captures,
preview phase with scrollable image, localized strings from session storage
- fullPageScreenshotHelper.ts: passes fullPageStrings map with i18n labels
- webExtensionWorker.ts: window width = content + sidebar, passes totalViewports
- strings.json: new keys for IncrementalProgress and Saving
- gulpfile.js: compiles renderer.less alongside clipper.less
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…save flow Complete Phase 2 implementation of the unified clipper window: - Mode buttons (Full Page, Article, Bookmark, Region) with sidebar controls - Article mode: Readability extraction directly from content-frame DOM - Bookmark mode: og:image/description metadata extraction from DOM - Section picker from cached localStorage notebooks - Title, note, source URL fields with i18n from localStorage - Save flow: worker builds multipart form, POSTs to OneNote API - Post-save: "View in OneNote" button with page URL from API response - Error display with expandable diagnostics (correlation ID, date, status) - Sidebar auto-hides for signed-in users (hideUi via inject communicator) - Window stays open after capture for mode switching - Duplicate window prevention (focus existing on re-click) - Tab navigation detection closes renderer window - UI lock during save (all inputs disabled during API round-trip) - Cancel + Clip side-by-side button row - Modal-like focus retention Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gn-in flow Sign-out: renderer → worker → uiCommunicator.showSignInPanel → clipper.tsx resets state + clipperInject.showUi → sign-in panel appears. User info footer pinned to sidebar bottom with email + sign-out link. Section refresh: auto-fetches fresh notebooks from OneNote API on renderer open (direct fetch with Bearer auth). Token expiration check uses relative accessTokenExpiration with lastUpdated offset. Region capture: standalone regionOverlay.ts injected into original tab via scripting.executeScript. Crosshair overlay with canvas hole-punch selection. Coords sent as JSON string (required by offscreen.ts message handler). Worker captures tab as JPEG 95%, sends via port. Renderer crops with DPR handling. Multi-region: thumbnails with × remove buttons, + Add another region button. Regions cached across mode switches. Each region stored as separate session storage key to avoid size limits. Post-sign-in: clipper.tsx detects SignInAttempt updateReason, hides sidebar, starts capture → opens renderer. Non-signed-in guard prevents renderer opening without auth. Full-page preview restored from session storage when switching back from region mode. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… polish - Remove fullPageStrings legacy i18n passthrough from session storage - Resolve fullPageScreenshotHelper promise on finalizeComplete instead of leaving it pending indefinitely - Move save URL from session storage to port message (message.url) so all save parameters flow through the port; only fullPageFinalImage and regionImage_N remain in session storage (too large for port messages) - Cache fullPageDataUrl in page variable for instant mode switching (no async session storage read needed) - Session storage cleanup: worker cleanup() removes all fullPage*/regionImage* keys on window close; helper cleanup removes source data after finalize - Readability: dynamic import pattern added (browserify still bundles inline; ready for bundler upgrade) - Region overlay: hide scrollbar via CSS style injection instead of overflow:hidden (preserves scroll during selection); remove-all-regions stays in region mode with add button instead of snapping to fullpage - Update plan doc with resolved tech debt items Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Worker opens renderer window directly on button click (bypasses clipperInject.ts injection entirely). Renderer shows MSA/OrgId sign-in overlay when not signed in; transitions to capture mode after OAuth. Key changes: - New contentCaptureInject.ts: standalone content script reads page DOM + stylesheets, sends to worker via chrome.runtime.sendMessage - Worker: openRendererWindow() checks isUserLoggedIn, handles signIn/ signOut port messages, content capture listener - Renderer: sign-in overlay, signInResult/signOutComplete handlers, custom section picker (ul/li with scrollable dropdown), UI lock during capture, Close button, anti-maximize via chrome.windows API - Region overlay: selection border drawn on canvas (no separate div), overflow:hidden on root, no scrollbar manipulation - Sign-out keeps renderer open (shows sign-in overlay), full state reset including notebook cache and stale capture data Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Renderer telemetry: imports Funnel/LogMethods/Session enums, sends LogDataPackage via port to worker's logger (Invoke, AuthAttempted, AuthSignInCompleted/Failed, ClipAttempted, SignOut, session lifecycle) - Worker: telemetry port handler routes to parseAndLogDataPackage; save handler refreshes token via auth.updateUserInfoData before API call - docs/unified-window-plan.md: V3 flow diagram, V2/V3 checklist, telemetry docs (#14-16), consistent versioning - docs/client-side-migration.md: V3 data flow, updated CSS fidelity refs, JPEG 95%, V3 evolution section Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extracted DOM cleaning functions from DomUtils into contentCaptureInject as self-contained inline functions (zero external imports). Replaces the previous raw outerHTML approach that truncated at 2MB and missed content. Pipeline matches old clipperInject.ts → DomUtils.getCleanDomOfCurrentPage: clone → inline hidden elements → flatten shadow DOM → canvas→image → base tag → image sizes → remove unwanted items (scripts, noscript, clipper elements, non-web links, binary styles) → remove srcset → serialize. Plus lazy image resolution (data-src → src) for sites using loading="lazy". Output: ~10KB standalone script, no dependency on DomUtils/Constants/ ObjectUtils modules. Ready for dead code cleanup phase. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
contentCaptureInject.ts: Align with master DomUtils.getCleanDomOfCurrentPage
pipeline (isLocalReferenceUrl, split removeUnwantedItems into 5 sub-functions,
full DOCTYPE with publicId/systemId, iframe local-ref filtering). Layer
enhancements on top: neutralizePositioning converts sticky→relative and
fixed→absolute with !important to prevent repetition in stitched captures.
renderer.ts: Remove fullPageStylesheets session storage consumption (renderer
fetches CSS directly via <link> tags). Add safeSend() wrapper for all
port.postMessage calls to handle disconnected port errors. Inject
[hidden]{display:none!important} CSS override. Position neutralization
upgraded to use !important.
webExtensionWorker.ts: Remove fullPageStylesheets storage.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
unified-window-plan.md: Mark sticky element duplication as resolved (!important fix). Update sign-out flow to V3 (stays in renderer). Remove stale stylesheet reference from flow diagram. Add video/streaming embed as known limitation. Add safeSend, [hidden] CSS, position neutralization to verification checklist. client-side-migration.md: Remove resolved "bottom void" from remaining issues. Add video/streaming embed limitation. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
renderer.ts: Add cleanArticleHtml() to strip ONML-unsupported elements and style/class attributes from Readability output before caching — ensures preview matches what gets saved to OneNote (old toOnml pipeline parity). Fix article/bookmark preview styling to match OneNote page layout: Segoe UI 11pt, 624px max-width (@OneNotePageWidth), left-aligned margin instead of auto-centered. docs: Add CSR/shadow DOM sites as known limitation (pre-existing, shared with server-side Puppeteer). Document article mode ONML cleanup in CSS fidelity section. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
contentCaptureInject.ts: Inline the original page's computed body
font-size onto the cloned body element. CSS reset stylesheets (e.g.,
body{font-size:75%}) may resolve differently in the renderer iframe
due to stylesheet loading order, causing text to render 25% smaller.
Captures the actual computed value and applies it with !important,
matching the pattern used for inlineHiddenElements and
neutralizePositioning.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Feedback link (OrgId users only, matching old sidebar): - renderer.html: Smiley icon + i18n label in footer, left-aligned. Footer restructured: feedback left, email + sign-out stacked right. - renderer.ts: Click handler sends openFeedback port message with page URL. Hidden for MSA users. i18n via WebClipper.Action.Feedback. - renderer.less: Footer layout with flex-end alignment, stacked user-info-right, feedback-icon and separator styling. - webExtensionWorker.ts: openFeedback handler builds feedback URL with all 6 params (LogCategory, originalUrl, clipperId, usid, version, type). Opens 1000x700 popup via chrome.windows.create. Session USID and API correlation: - Per-session USID generated in launchRenderer (cccccccc- prefix + v4 UUID tail, matching old logger pattern). Sent as X-UserSessionId header in save API calls for server-side log correlation. Same USID used in feedback URL for support ticket correlation. - Refactored GUID generation into static newGuid() helper, used for both sessionUsid and per-request X-CorrelationId (was non-standard timestamp-based, now proper v4 UUID matching StringUtils.generateGuid). Error diagnostics: - Copy button (clipboard emoji) next to "More information" in error display. Uses navigator.clipboard.writeText (no extra permissions). Shows green checkmark on success. stopPropagation prevents toggling details open/close. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- i18n: wire sign-in panel, field labels, error messages to loc() using existing server-translated keys (zero new strings.json keys needed) - Contrast: error color #ff6b6b→#ff9999 (WCAG AA), region btn border #bbb→#999 (SC 1.4.11), focus outlines 2px solid #f8f8f8 - ARIA: radiogroup/radio for mode buttons, combobox for section picker, role="dialog" on sign-in overlay, role="alert" on error, role="main" on sidebar, aria-live announcements, progressbar role - Keyboard: arrow keys + Home/End on mode buttons and section picker, Escape to close dropdown, clean tab order (iframes tabindex=-1, Clip before Cancel), focus management on capture/sign-in transitions - Tab order: mode buttons→title→note→section→Clip→Cancel→feedback→signout - Focus outlines: 2px #f8f8f8 on purple, high-contrast Highlight mode - Dynamic html lang from localStorage.locale (BCP 47) - Removed blur re-focus handler (fought Alt+Tab, screen readers, popups) - Keydown handler allows arrows/modifier combos for screen reader compat - Screen reader testing: ARIA tree verified correct via edge://accessibility but Edge disables a11y API flags for extension popup windows (untested) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…fresh fix Region overlay: Shadow DOM instruction bar with i18n text + "Back (Esc)" button, CSS-isolated from page styles. Escape/Back stays in region mode instead of switching to full page. Worker injects i18n strings via window.__regionStrings before overlay script. Renderer: add mode button tooltips using existing i18n keys. Remove token expiry pre-check from notebook fetch so newly created sections appear. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…t and image width - Version bump to 3.11.0 across package.json and Chrome/Edge manifests - Upgrade gulp-uglify v2→v3 (UglifyJS v3) to support ES6+ minification (Readability) - Update preserveComments→output.comments for gulp-uglify v3 API, preserve license headers - Remove "Clipping Page" status heading during capture (reserve "clipping" for save phase) - Hide progress bar until first viewport capture arrives - Fix full-page screenshot width in OneNote ONML: pass actual CSS width from renderer (contentPixelWidth / DPR) instead of pre-calculated window width, fixing aspect ratio Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix minified × character showing as "×" by declaring UTF-8 charset - Update a11y docs: NVDA + Edge verified working after devbox reboot Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Mode panel: role="radiogroup" → role="toolbar", buttons use aria-pressed - More natural UX: NVDA announces "toggle button, pressed" instead of "radio button" - Arrow key navigation retained (standard toolbar pattern) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… timeout
- Add purple toolbar above article preview with three control groups:
- Highlighter toggle: TextHighlighter injected into iframe, yellow (#fefe56)
highlights with red circle × delete buttons (top-left corner)
- Font family: Sans-serif (Verdana) / Serif (Georgia) toggle buttons
- Font size: +/- buttons, 2px increments, 8px–72px range
- Font family and size applied to saved OneNote content via wrapping div style
- Highlight state preserved across mode switches (articleWorkingHtml snapshot)
- Save serializes highlights from cloned DOM (preserves live preview)
- Add 30-second save timeout via Promise.race (matches old OneNoteApi default)
- Consistent × button positioning: region thumbnails moved to top-left to match
- Header bar visible only in article mode, hidden in all other modes
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Set highlight_cursor.cur on iframe body when highlighter enabled - Restore default cursor when disabled - Cursor persists correctly after mode switch back to article Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Clip button always stays as "Clip" for re-clipping - Success banner below buttons shows "✓ Clip Successful!" + purple "View in OneNote" button (opens page, closes clipper window) - Banner clears on mode switch or re-clip - Fix display:none CSS default override for View button Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…, bug fixes - Section picker: notebook/section-group headings with icons, indented sections, keyboard nav skips headings, icons inverted white for dark background contrast - Success banner: separate "Clip Successful!" + "View in OneNote" button below Clip/Cancel. Clip button stays as Clip for re-clipping. Banner clears on mode switch, re-clip, or section change. - Save timeout moved to renderer (service worker setTimeout unreliable — SW suspends after ~30s). 30s client-side timeout with error display + retry. - Fix: section selection no longer resets button state (was enabling Clip during capture). Only clears success banner + saveDone flag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Lint fixes across new files: - renderer.ts: null→undefined, var→let, else placement, shadowed variable - regionOverlay.ts: single→double quotes - webExtensionWorker.ts: var→let, tslint-disable for Chrome API null param - domUtils.ts: single→double quotes Service worker keepalive: renderer pings every 25s via port to prevent MV3 service worker suspension while popup is open. Inactivity auto-close: renderer closes after 5 minutes without mouse, keyboard, scroll, or focus activity. Timer resets on any interaction. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Region overlay: unified canvas crosshair for mouse and keyboard, arrow keys with velocity acceleration, two-phase Enter selection, seamless keyboard→mouse handoff preserving start point, ARIA screen reader announcements, i18n strings for all a11y labels - Forced-colors: comprehensive @media (forced-colors: active) support in renderer.less — system colors for buttons, inputs, section picker, icons (drop-shadow halo), selected states (forced-color-adjust: none), sidebar/preview divider, success banner, sign-in panel, progress bar - Bookmark: table-layout:fixed with max-width:624px constrains long URLs, added <link rel="image_src"> and <link rel="icon"> fallbacks for thumbnail extraction (matches legacy BookmarkHelper) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… detection - Fix telemetry pipeline: set BrowserLanguage + FlightInfo context properties in worker constructor so ProductionRequirements are met and events flush to Aria (previously silently queued forever) - Add full event parity: CloseClipper (clipCount===0 only), ViewInWac, HandleSignInEvent, UserInfoUpdated, ClipToOneNoteAction, ClipCommonOptions, ClipRegionOptions, RegionSelectionCapturing/Processing, GetNotebooks, UnhandledExceptionThrown, OnLaunchOneNoteButton, mode button + section picker click events - Add context properties: AuthType + UserInfoId (set at sign-in and on already-signed-in path, cleared on sign-out), ContentType (set on mode switch) - PromiseEvent timers: ClipToOneNoteAction and HandleSignInEvent created at action start for accurate Duration - InvokeClipper event: deferred via consoleOutputEnabledFlagProcessed.then() to avoid logger initialization race - Nav-away detection: tabs.onUpdated listener closes renderer when original tab URL changes, logs HideClipperDueToSpaNavigate - Version consolidation: extensionBase.getExtensionVersion() reads from chrome.runtime.getManifest().version with static fallback - Worker sends cid in signInResult for UserInfo.Id context - ClipMode uses legacy enum names (Augmentation not Article) - Remove diagnostic console.log calls Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…nderer - contentCaptureInject.ts detects PDF pages (URL suffix, embed tag, PDFJS) and sends contentType to worker/renderer via session storage - Renderer loads pdf.combined.js, fetches PDF binary, renders pages at 2x scale with lazy-load on scroll (3 initial, ±1 near viewport) - PDF + Bookmark mode buttons shown for PDF pages (Full Page/Article/Region hidden). PDF options: all pages / page range with validation, attach PDF checkbox (disabled if >24.9MB), distribute pages checkbox - Page range parser (inline port of legacy StringUtils.parsePageRange) with live validation and error display - Unselected pages grayed out at 30% opacity matching legacy behavior - Page numbers: persistent top-left overlay matching legacy style - Save: non-distributed (single page, all images + optional attachment), distributed (sequential POSTs, actual page numbers in titles matching legacy getBatchedPageTitle). Attachment uses <object> tag before page images matching legacy ordering - Telemetry: ClipPdfOptions, PdfByteMetadata, ClipCommonOptions with all legacy properties. PDF mode button click logged as "pdfButton" - Title defaults to filename with .pdf extension (legacy behavior) - Bookmark fallback generates card from title+URL for PDF pages - Extended save timeout scales with page count (30s + 5s/page) - Full accessibility: ARIA roles, aria-live announcements, focus management, forced-colors support for all PDF elements - PDF session storage cleanup (pdfPageImage*, pdfAttachmentData) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Fix PDFJS.getDocument error handling: v1.7.290 PDFDocumentLoadingTask has .then(ok, err) but no .catch — use second arg of .then for errors - Local file:/// PDFs: try PDFJS.getDocument(url) directly (works when extension has file access permission), show permission panel on failure - Worker: detect file:/// tabs, catch scripting.executeScript rejection (sync throw + async promise), send localFileNotAllowed via port message (not session storage) to avoid cross-session leaks - showLocalPdfBlockedPanel(): unified message for Chrome/Edge with actionable instructions to enable "Allow access to file URLs" - Renderer reads localFileNotAllowed from port message, enters PDF mode and shows permission panel immediately (no "Loading PDF..." flash) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Replace session storage image transport with per-port chunked streaming: renderer sends save metadata (saveImageCount), then saveImage chunks (one per image), then optional saveAttachment (PDF binary). Worker accumulates in per-port buffer, executes save when all received. Enables multi-window isolation — each renderer's port is independent. - Remove updateRegionSessionStorage() and all session storage writes for fullPageFinalImage, regionImage_N, pdfPageImage_N, pdfAttachmentData - Worker cleanup filter simplified to fullPage* only (content HTML metadata) - Fix PDF mode buttons: enable PDF + Bookmark buttons in enterPdfMode() (were stuck disabled because full-page capture re-enable never runs) - Fix bookmark table styling to match legacy: table-layout:auto (not fixed), wrapping div + tables with font-size:16px;font-family:Verdana (matches createPostProcessessedHtml behavior), removed max-width:624px constraint - Fix generatePdfBookmarkHtml to match regular bookmark structure: same table layout, font styling, h2 title, styled URL link Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Show Region button alongside PDF + Bookmark on PDF pages in enterPdfMode() (region overlay is content-type agnostic) - switchToRegion() hides PDF options panel - switchToPdf() re-renders PDF pages from cached pdfPageDataUrls[] when switching back (clears region thumbnails from preview-container, regionImages[] array preserved separately for region mode) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Visual redesign per designer Figma spec: - Sidebar flipped from dark purple to white Fluent theme with OneNote brand ramp (/50 pressed, /60 hover, /80 rest). Introduced Fluent 2 neutral tokens. - Article toolbar, sign-in panel, PDF options, success/error banners all reskinned. - Mode buttons: Fluent outline with icon+text, selected state tints purple. - Collapsible section picker with arrow_down/arrow_right icons (depth-aware). - Preview frame: rounded 20px corners via .preview-ready overlay, 4px inset, applied only after capture to avoid baking margins into screenshots. - Region thumbnails: natural size, 24px gap, Discard button stacked above image (not overlay) with translucent black 5% bg per sticky note spec. - "Add another region": Fluent Add icon + centered via flex layout. - Separator between preview/sidebar: neutral 1px Fluent divider (not purple). - Success/error banners: Fluent inline alerts with Fluent Checkmark/Error Circle icons; View in OneNote as outline secondary button. - Footer layout flipped: avatar/email/signout left, feedback right. - Header icon swapped to full clipper logo (N + scissors) matching Figma. - All paddings aligned to multiples of 4 per Fluent spacing scale. Bug fixes: - Bookmark thumbnail: convert external URLs to base64 data URLs via canvas (OneNote API can't fetch external URLs). Description fallback chain expanded to match legacy BookmarkHelper (keywords, article:tag, page text). - Section picker $expand depth 2 -> 4 levels (matches legacy maxExpandedSections). - Window resize: unlocked after capture, min 1000x600 enforced via chrome.windows.update. macOS 2px tolerance prevents infinite resize loop. - Worker window sizing clamps to browser bounds (screen-aware creation). - PDF page range input: readonly (not disabled) when in "All pages" so it stays visible and clickable; click auto-switches to "Page range" mode. - SVGs fill="white" -> fill="currentColor" (feedback smiley, font up/down, highlight toggle) so they're visible on white background. - Close button label "Close the Clipper" -> "Cancel" (was wrapping to 2 lines). - Error details: restored copy button (keyboard + mouse accessible). Accessibility: - aria-modal=true on sign-in dialog; z-index 9999 + isolation: isolate to defend against macOS full-window edge cases. - role=status / role=alert on success/error banners (removed redundant manual announceToScreenReader calls). - Collapsible section headings: role=button + keyboard support (Enter/Space). - Removed redundant aria-checked on native PDF radios. - Forced-colors coverage expanded to error banner, status icons, copy button. Files ignored: .claude/, .mcp.json (local Claude/Figma MCP config — per-user). New i18n keys (need translation pipeline, English fallback until added): WebClipper.Label.WhatToCapture WebClipper.Action.Discard WebClipper.Label.ClipSuccessTitle WebClipper.Label.ClipSuccessDescription WebClipper.Label.ClipErrorTitle WebClipper.Label.ClipErrorDescription Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mode buttons: - Reorder: Full Page, Region, Bookmark, Article (was ..., Article, Bookmark, Region) - Icons use fill="currentColor" so they render dark on white bg (were invisible white-on-white from the old dark theme). Visible in default state now. - Padding/gap/sizing per Figma 242:4364: padding 6px 12px, icon gap 6px, icon size 20px, button stacking gap 8px, explicit 14px/20px weight 600 text. Clip/Close buttons: - Typography: Subtitle 2 (16px/22px, weight 600) matching Figma spec. - Padding: 6px 12px (Fluent snudge/m tokens). Save flow: - Clip button keeps "Clip" label during save (was changing to "Saving", which duplicated the "Saving..." status text below the progress bar). Focus indicators (Fluent 2 clean single-ring pattern, no doubled borders): - Bordered elements (button, textarea, [tabindex]): outline:none + box-shadow inset 1px purple + border-color purple = single 2px purple edge (1px border + 1px inset) that merges with the existing border. - Text links (<a>): outer 2px outline with 2px offset (no border to overlap). - Section list items: outline with negative offset (no layout shift in list). - Clip button (#save-btn) uses Fluent 2 "halo" pattern: 2px white inset ring (visible against purple fill) + 2px dark outer ring (visible against white sidebar bg). Specificity #sidebar #save-btn:focus (200) beats the global #sidebar button:focus (111). - View in OneNote button inherits the bordered-element treatment via its nested selector specificity (210). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…allback Docs updated to match current implementation: - client-side-migration.md: V3 data flow now shows chunked port save protocol (no longer session storage for images), JPEG 95%, fullPageDataUrl page var. Added "Beyond V3" section covering mode parity, Fluent 2 redesign, reliability/lifecycle, telemetry parity. - unified-window-plan.md: mode order corrected (Full Page, Region, Bookmark, Article, PDF), section picker $expand=4, region multi-region via port chunks (not separate session storage keys), keyboard region selection, Discard pill spec. Added PDF Mode and Fluent 2 Redesign sections covering tokens/palette, component patterns, typography, header/footer. - i18n-a11y-contrast-plan.md: contrast section flagged as legacy purple-theme values, current Fluent 2 white theme described. New i18n keys table for redesign additions. Sign-in dialog aria-modal documented. Collapsible section heading keyboard support documented. Copy diagnostics restored. Success/error banners use role=status / role=alert (no manual aria-live). - telemetry-parity-plan.md: ClipPdfOptions, PdfByteMetadata moved from "not migrated" to active events. ClipMode includes Pdf. pdfButton click ID added. LocalFilesNotAllowedPanelShown noted as implemented. Bug fix: - Location/section-label fallback was "Save to" but the i18n key returns "Location" (matching Figma). Updated fallback in both renderer.ts and renderer.html so first-time users (before locStrings cached) see the same label as localized users. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Keyboard / focus - Clip button regains focus indicator after success (programmatic refocus re-promotes :focus-visible after the button was disabled mid-save) - Region Esc with no captures snaps back to Full Page on web pages, PDF Document on PDF pages; focus stays on Region mode button for re-entry - Region overlay Back button is keyboard reachable: shadow root opened, :focus-visible style added, Tab trapped inside overlay, Enter/Space passes through when back button has focus (was being eaten by the region-selection start handler) - Section list headings reachable via arrow keys (shared focusAdjacentSectionRow walks all visible rows including headings; Enter/Space toggles) - Preview iframe wrapped in a focusable div: iframe itself is tabindex=-1 so Tab can't fall into article links/buttons. Wrapper drives arrow / Page / Home / End scrolling via keydown handler - Sign-in cancellation now silent (no error banner) — matches legacy Figma alignment - Sidebar width 322 -> 321 (320 content + 1px border, Figma 242:4365) - Header padding-top 20->24, sidebar-body padding-top 4->0, group padding 12->8, label margins 12/4 -> 16/8 - Mode buttons replaced PNG/SVG <img> tags with inline <svg fill='currentColor'> using Fluent UI System Icons (Page Fit, Tab Add, Bookmark, Form). Inline SVG inherits text color, works in normal + forced-colors without filter tricks - Source URL link icon and feedback smiley also inline SVG; URL text moved to dedicated span so the sibling icon survives textContent writes - PDF mode keeps pdf.png; filter: brightness(0) flips its white pixels to black on the white sidebar, purple hue-rotate for selected state - Forced-colors halo simplified to a single sharp drop-shadow (was stacked blurs); mode-button SVGs dropped from filter selector since currentColor handles theming natively Preview frame - Focus rings switched from outline + outline-offset:-2px to outer box-shadow on #preview-frame-wrap and #preview-container. Inset outlines were getting painted under PDF page images (compositor-layer promotion from box-shadow + position:relative); outer shadow lives outside the box where children can't reach it - preview-container gains overflow-x:hidden so absolutely-positioned PDF page-number badges clip to the rounded corner - Article-preview code blocks wrap (white-space:pre-wrap; overflow-wrap:anywhere) instead of horizontal-scroll — OneNote doesn't preserve scrollbars in saved pages Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Backports legacy clipper Voice Access fix (PR #652, commits 9fb6f3e and 271fb4c on master) to the V2 region overlay path. While the crosshair overlay is up, the user's webpage controls were still in the accessibility tree — Voice Access would number every focusable element behind the overlay so a voice command like "click 23" could trigger a hidden link. Mirror the legacy approach: - Append the overlay root to <html> (sibling of body) instead of body - Set aria-hidden="true" and inert on document.body when the overlay becomes active; restore on cleanup - Defensive: only strip attributes on cleanup if we were the ones who set them, so we don't clobber values the host page may rely on (one tiny refinement over legacy which strips unconditionally) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lowered content width cap from 1280 to 1024 and added 900px height cap (floored at 600) so the renderer popup doesn't cover most of the screen on large monitors. Sidebar width corrected from 322 to 321 to match renderer.less. Trade-off documented in known-limitations: sites with desktop breakpoints between 1024 and ~1280 (e.g. some MS Learn navs) may now render their tablet/mobile layout in captures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
strings.json:
- Add canonical ToggleHighlighterForArticleMode (matches resx); restore
ToggleHighlighterModeForArticle as V1 fallback (will be removed when
V1 is deleted).
- Add new keys for V2 UI: Discard, PageTitle, Source, WhatToCapture,
SigningIn, Pdf.Loading, ClipError{Title,Description},
ClipSuccess{Title,Description}.
- Fix smart-quote in Pdf.ProgressLabelDelay; correct apostrophes to
proper straight quotes in Pdf.InstructionsForClippingLocalFiles.
- ClipErrorDescription now ends with a period (matches
ClipSuccessDescription style).
- Update ThirdPartyCookiesDisabled to mention live.com + onenote.com.
renderer.ts (string-key/fallback alignment only):
- toggleHighlighter switched to canonical ToggleHighlighterForArticleMode.
- pdfPageRange switched to canonical
Preview.Header.PdfPageRangeRadioButtonLabel.
- clipperTitle hardcoded (was using a missing localization key).
- Error banner fallbacks: smart-apostrophe to straight, add trailing
period to match strings.json.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Section picker dropdown: - Switch #section-list-container from position:absolute to position:fixed so it doesn't contribute to #sidebar-body's scrollHeight when it extends past the sidebar viewport bottom. - positionSectionDropdown() computes top/left/width from #section-selected's bounding rect each time the dropdown opens, and shrinks max-height so it always fits between trigger and viewport bottom. Reposition on resize, close on sidebar scroll. Error banner: - Collapse <details>/<summary>/<pre>/copy-button into a single inline "Copy diagnostic info" button. Diagnostic text is held in JS rather than rendered, so the banner stays compact regardless of payload size. - Restyle button as a Fluent secondary action (border, padding, hover, focus ring, copied state). Article preview: - Add pointer-events:none + cursor:default on <a> in article CSS so link clicks don't navigate the preview iframe away from the captured content (matches bookmark mode). Selection still works. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Caret browsing mode (F7 in Edge/Chrome) and some accessibility tools force a text caret on every focused element — including buttons, the section combobox, and read-only value displays like the source URL. The caret looks wrong on these controls (they aren't text content meant to be read or edited). Solution: blanket caret-color:transparent on #sidebar, with caret-color restored on input/textarea elements where editing actually happens (Title, Note, PDF page range). Selection still works everywhere — only the visual caret indicator is suppressed. Also adds user-select:none on #section-selected so double-click on the combobox doesn't highlight the section path text (it's a click target, not selectable content). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
87f88ed to
e06eeda
Compare
clipper.html and pageNav.html are no longer used in V3 (they were the V1 sidebar entry HTML). Remove them from the web_accessible_resources list in both Chrome and Edge manifests since they're no longer needed externally. The V1 source files themselves are removed in the next commit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
V3 (popup-window architecture for Chrome+Edge) is the only shipping
path. The V1 architecture (Mithril sidebar + iframe-based inject
pattern) and the unused Safari/Firefox extension targets are removed
from the source tree to slim the build and reduce maintenance surface.
Source removed:
- src/scripts/clipperUI/{*.tsx, animations/, components/, panels/} —
V1 Mithril sidebar UI tree
- src/scripts/extensions/{clipperInject, pageNavInject, frameInjectBase,
styledFrameFactory, debugLoggingInject, injectBase}.ts — V1 inject
pattern
- src/scripts/communicator/iframeMessageHandler.ts — V1 iframe message
bridge
- src/scripts/extensions/{safari,firefox}/ — unused extension targets;
they predate V2/V3 and rely on browser APIs V2/V3 doesn't use
(chrome.scripting.executeScript, offscreen documents,
chrome.storage.session)
- src/scripts/extensions/{chrome,edge}/{chromeInject, chromeDebugLogging,
chromePageNavInject}.ts and Edge equivalents — V1-only inject targets
- src/scripts/extensions/webExtensionBase/{webExtensionInject,
webExtensionDebugLoggingInject, webExtensionPageNavInject}.ts —
V1 inject base classes
- src/scripts/extensions/bookmarklet/ — V1-only target
- src/scripts/saveToOneNote/ — V1 save pipeline; V3 worker has its own
multipart save logic
- src/scripts/highlighting/highlighter.tsx — V1 component
- src/scripts/contentCapture/{augmentation,bookmark,fullPage,pdfDocument,
pdfJsDocument,pdfScreenshot,viewportDimensions,captureFailureInfo}.ts
— V1 helpers
- Several V1-only logging decorators and bridges
- src/clipper.html, src/pageNav.html — V1 entry HTML
- src/styles/*.less except renderer.less — V1 stylesheets
- src/tests/ — entire V1-era test directory (its scaffolding references
deleted V1 code; rewriting for V3 is out of scope)
Source updates:
- src/scripts/clipperUI/clipperState.ts — stripped V1 result/Inject
imports; kept minimal interface for clipperUrls.ts feedback URL
- src/scripts/extensions/extensionBase.ts — kept TooltipHelper /
TooltipType imports (still referenced by dead code paths in
extensionBase.ts; cleanup deferred)
- src/scripts/extensions/webExtensionBase/webExtensionWorker.ts —
added /// <reference> for OneNoteApi types that previously came in
via tests.ts
gulpfile.js cleanup:
- Removed bundleClipperUI, bundleLogManager, bundleBookmarklet,
bundleFirefox, bundleSafari, bundleTests tasks
- Trimmed bundleChrome / bundleEdge to only the extension entry
- Removed exportFirefox*, exportSafari*, exportBookmarklet*,
exportTest* helper functions and their gulp tasks
- Removed packageFirefox, runTests tasks
- Removed clipper.html / pageNav.html / safari assets from copy and
watch source lists
- Trimmed exportCommonJS to drop V1 clipper.js/pageNav.js/locale-
specific tasks/unsupportedBrowser export and logManager export;
kept only oneNoteApi runtime which V3 worker uses
- Removed BOOKMARKLET, FIREFOX, SAFARI, TESTS entries from PATHS
and targetDirHasExportedCommonJs
Build (npm run build:prod -- --nointernal) succeeds and emits a
chrome target around 4.0M plus an equivalent edge target.
Known leftover dead code (deferred to follow-up cleanup):
- extensionBase.ts still has tooltip-related dead code paths
(showTooltipsInOrder, etc.) that reference TooltipHelper /
TooltipType but are never invoked in the V3 flow
- extensionWorkerBase.ts has invokeDebugLoggingBrowserSpecific /
invokePageNavBrowserSpecific dead methods
- Some lib files (mithril.min.js, rangy-core.js, sanitize-html.js,
velocity.min.js) still copy into the chrome target but are unused
by V3; trimming them is a follow-up
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Removes 96 unreferenced string keys (from 176 → 80) that supported the legacy V1 sidebar UI: ratings panel, rotating tooltips, image/ selection/product/recipe clip modes, V1 error labels, font-family helpers, etc. V3 (popup window) uses a much smaller string surface covering only what the unified sidebar shows: mode buttons, capture status, sign-in, save, error/success banners, section picker. Detection: scanned every .ts / .tsx / .html / .json under src/ for each string key as a substring; deleted any key that no source file references. Build verified after. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 Commit 2 deleted the logManager.ts standalone bundle and its
V1 logging-chain dependencies. At runtime the service worker hit:
Uncaught (in promise) ReferenceError: LogManager is not defined
Uncaught (in promise) TypeError: Cannot read properties of undefined
(reading 'logEvent')
because extensionWorkerBase.ts and authenticationHelper.ts still call
LogManager.createExtLogger / LogManager.reInitLoggerForDataBoundaryChange
at construction time. The bundled standalone exposed LogManager as a
global; with that gone, worker initialization threw on every clipper
invocation, so clip requests timed out without ever reaching the
OneNote API.
This also broke the Aria/MSIT internal telemetry pipeline, since the
sibling project's logManager_internal.ts and ariaLoggerDecorator.ts
import the same V1 logging classes (CommunicatorLoggerDecorator,
ConsoleLoggerDecorator, LoggerDecorator).
Restoring the chain is the minimal surgical fix:
- src/scripts/logging/{logManager, communicatorLoggerDecorator,
consoleLoggerDecorator, communicatorLoggerPure, loggerDecorator,
stubSessionLogger}.ts — restored from prior commit
- gulpfile.js — restored bundleLogManager task and re-added it to
the bundle sequence; restored the internal-vs-public-stub branch
in exportCommonJS that concats aria-web-telemetry library +
logManager_internal.js into logManager.js when the sibling project
is present; re-prepended logManager.js to the chromeExtension.js /
edgeExtension.js concat lists so the global is in place before the
service worker code runs
Build verified at 4.3M chrome target with logManager.js (203KB)
correctly prepended into chromeExtension.js (now 463KB total),
including AriaLogger / Aria_Token references for the internal
pipeline.
A proper V3 cleanup would refactor extensionWorkerBase.ts and
authenticationHelper.ts to use Log.* (from logging/log.ts) directly
and drop the LogManager singleton entirely. Deferred — this commit
just restores functional behavior so V3 ships cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a brief V1 cleanup section to docs/client-side-migration.md noting the removal of the unused V1 architecture and Safari/Firefox targets, plus a callout in docs/unified-window-plan.md pointing to the cleanup. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
e06eeda to
7e77d6e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A full client-side migration of the OneNote Web Clipper from the legacy injected Mithril sidebar to a standalone renderer window, with parity across all modes plus a Fluent 2 redesign.
Architecture
renderer.html(content iframe + sidebar) opened by the service workerclipperInject.tsinjection in the primary flowcontentCaptureInject.tsperforms full DOM cleaning (master-compatible DomUtils baseline + enhancements: hidden element preservation, position neutralization, shadow DOM flattening, lazy image resolution)saveImagechunks instead of session storage (multi-window safe, no 10MB quota bottleneck)Modes
captureVisibleTabwith sidebar cropping, JPEG 95% stitching in rendererDomUtils.getImageDataUrl) so OneNote can render it (it cannot fetch external URLs)regionOverlay.tswith Shadow DOM instruction bar, crosshair overlay, multi-region thumbnails. Keyboard support: arrow keys with velocity acceleration, two-phase Enter, ARIA live announcements, seamless keyboard→mouse handoffpdf.combined.jsintegration; lazy-loaded preview pages, page range selection (with validation), distributed-pages mode (one OneNote page per PDF page), attachment support up to 24.9MB, per-page progress reportingFluent 2 Design
Per designer Figma spec (
wK4ryPaULoiSDMt2xVc1fH):Brand/OneNote/50/60/80from Fluent 2 library) for primary button states (rest/hover/pressed)arrow_down/arrow_righticons, depth-aware collapse (4-level$expandmatches legacymaxExpandedSections).preview-readyclass — removed before subsequent captures so the rounded margin does not bake into screenshotsTelemetry parity
Full event parity with legacy clipper:
AuthType,UserInfoId,ContentType) set at sign-in and mode switch, cleared on sign-outPromiseEvents(ClipToOneNoteAction,HandleSignInEvent,GetNotebooks) with accurate Duration timingCloseClipperonly fires whenclipCount===0(abandonment metric)InvokeClipperdeferred to avoid logger init racetabs.onUpdatedin workerAugmentation, notArticle); click IDs match legacy constantsClipPdfOptions,PdfByteMetadata,ClipCommonOptionsAccessibility
role="toolbar"+aria-pressedfor mode buttons,role="combobox"+aria-expandedfor section picker,role="button"+ Enter/Space keyboard for collapsible headings,role="status"/role="alert"for success/error banners (auto-announce, no manualaria-live),role="dialog"+aria-modal="true"for sign-in<html lang>fromlocalStorage.locale(BCP 47)@media (forced-colors: active)block (system colors,forced-color-adjust: noneon selected/active states with system-color halo on icons)Reliability / lifecycle
resizingguard), unlocked after viashowPreviewFrame(). Post-capture min 1000x600 enforcedtabs.onUpdatedcloses renderer when original tab URL changessafeSend()wrapper prevents disconnected port errorsi18n
locStringsfrom localStorage (shared extension origin)loc()using existing server-translated keys (zero new keys for the migration itself)WhatToCapture,Discard,ClipSuccessTitle/Description,ClipErrorTitle/DescriptionKey new/modified files
src/renderer.htmlsrc/scripts/renderer.tssrc/styles/renderer.lesssrc/scripts/extensions/contentCaptureInject.tssrc/scripts/extensions/regionOverlay.tssrc/scripts/extensions/webExtensionBase/webExtensionWorker.tssrc/images/*.svgfill="currentColor"for the white themegulpfile.jsdocs/Test plan
--production) — minification, telemetry pipeline🤖 Generated with Claude Code