Shares view (master/detail sidebar + in-app webview viewer for shared links) (part 2)
Sidebar entry. Registered in INTEGRATIONREGISTRY (left of the existing Settings / Skills rows in the Omniscio group) as a virtual project with sentinel SHARESPROJECTID (from src/shared/virtual-project-ids.ts). UI registry shape.
What it is
This is part 2 of the Shares view (master/detail sidebar + in-app webview viewer for shared links) page. It carries the next stretch of the material on that page, moved here because a single page is capped at 40,000 characters.
Where to find it
Reach this part through Shares view (master/detail sidebar + in-app webview viewer for shared links) — it lists every part and explains where the feature lives in the product. Everything below is reached from the same place.
How it behaves
Everything below is the behaviour, detail and edge cases that belong to this stretch of the Shares view (master/detail sidebar + in-app webview viewer for shared links) page.
How it works
- Sidebar entry. Registered in
INTEGRATION_REGISTRY(left of the existing Settings / Skills rows in the Omniscio group) as a virtual project with sentinelSHARES_PROJECT_ID(fromsrc/shared/virtual-project-ids.ts). - UI registry shape. The Shares entry in
src/renderer/src/integrations/ui-registry.tsis a two-component integration:panelComponent: lazy(() => import('../features/shares/SharesDetailPane'))+sidebarComponent: lazy(() => import('../features/shares/SharesSidebar')). Both components are lazy-loaded — the integration-ui-registry-lazy lint test enforces no static imports from the entry chunk. There is nopanelOwnsLayoutflag on Shares; the standardRoutedSubSidebarmountsSharesSidebarin the normal sidebar slot. Mirrors the Cron Jobs two-pane shape. - Activation. All navigation that wants to land on this view MUST route through
activateVirtualProject(SHARES_PROJECT_ID, source)fromsrc/renderer/src/lib/activate-virtual-project.ts— that helper resolves the sentinel to the project-row UUID before callingsetActiveProject(passing the sentinel directly tosetActiveProjectsilently no-ops, per the virtual-project activation contract). - Share URL interception. When any call to
openExternalUrlencounters a URL matching a share host (https://shares.omniscio.com/s/<token>— the brand default — or the legacyhttps://agentmc-shares.web.app/s/<token>; regexSHARE_URL_RE, built from the sharedSHARE_HOSTING_DOMAINSlist, insrc/renderer/src/lib/platform.ts), the share is opened in-app instead of launching an external browser. The function callsresolveShareUrlToken(token, opts)from the registration-seam modulesrc/renderer/src/lib/share-url-intercept-access.ts, which looks up the token in the local share list. A#commentsdeep link takes the detail-pane path (select the row, arm the comment scroll, activate the Shares virtual project) and needs the loaded row, so an unsynced token there falls through to the system browser. Otherwise it opens the full-size in-app share popup — a large centered card over a dim, click-to-close backdrop (Escape, Ctrl+W, or the header X close it too — and Escape/Ctrl+W close it even when focus is inside the share's<webview>, forwarded from the main process via theWEBVIEW_GUEST_DISMISSpush; seekeyboard-shortcut-dispatch-contract.mdwebview-close-gesture-closes-the-overlay), dropped below the 50pxCustomTitlebarstrip (topOffset) so the window's min/maximize/close controls stay un-dimmed and clickable; it mirrors theImageLightboxShellmedia-modal pattern and matchesShareViewerFullscreen's a11y (portal +role="dialog"+ Escape, noaria-modal/focus-trap so it never fights the embedded webview's focus) — viaopenFloatingShare(token). Any valid share token opens in-app whensharesOpenInAppis on — owned OR a teammate's (owner decision 2026-08-11: shares open in-app everywhere they appear — team chat, AI chat, inbox — not just your own). An OWNED token renders with its full metadata (label, staleness, reload-on-republish); a NON-owned token has no local row, soFloatingShareViewersynthesizes a minimal card whose<webview>src is built from the token viabuildShareUrl(src/shared/share-hosting-domains.ts) — the canonical, fixed-domainhttps://shares.omniscio.com/s/<token>, never the link's raw href.openFloatingSharestill fetches the owned list on demand, so an owned-but-unsynced just-published token upgrades to its rich metadata once it lands. WhensharesOpenInAppis OFF the resolver returns false and the caller opens the system browser (unchanged); a#commentsdeep link still needs the loaded row, so an unsynced token there falls through to the browser. Security invariant (build-from-token — this REPLACES the former own-only rule): the viewer's<webview>src is ALWAYS built from the VALIDATED token, so it can only ever load a well-formed Omniscio share page — a malformed token makesbuildShareUrlreturn null and the viewer simply stays closed, and a raw/hostile URL can never be coerced into the privileged viewer. Locked bytests/unit/shared/share-hosting-domains.test.ts(buildShareUrlcharset/length + null-on-malformed),tests/unit/features/shares/FloatingShareViewer.test.tsx(a non-owned token opens by building the canonical URL; a malformed token stays closed), andtests/unit/stores/share-store.test.ts(the ambient path opens in-app when the setting is on, browser when off). The app-wide freshness that makes agent-published shares resolve reliably comes fromSharesLiveSync(below). A bypass functionopenExternalUrlForce(url)skips interception entirely — used by the viewer's own "Open in browser" buttons, the right-click "Open in browser" item on an in-app share link in chat (CopyableLink→CopyableLinkMenu; the escape hatch that opens the real share URL in the system browser instead of the in-app popup, shown for a share link that renders in-app — owned or a teammate's — since a plain external link already opens externally on click), and byDeckPublishedDialog(which shows a just-published share URL that would otherwise be recursively intercepted). The registration-seam pattern (mirrorssession-nav-store-access.ts) prevents an import cycle:platform.tsis a foundational leaf thatipc.tsimports at module scope, so it cannot statically importshare-store.ts(which importsipc.ts). - Share hosting domain (brand + legacy). New share links mint on the brand domain
https://shares.omniscio.com(the product-wide default); the originalhttps://agentmc-shares.web.appstays fully served, so every already-sent link keeps resolving — both hostnames point at the SAME Firebase Hosting site, so content is byte-identical and theserveShareCloud Function needs no per-host logic (the unlock cookie isPath=/s/<token>, host-only — no redeploy required). The two hostnames live in ONE module,src/shared/share-hosting-domains.ts(PRIMARY_SHARE_HOSTING_DOMAIN+LEGACY_SHARE_HOSTING_DOMAIN+ theSHARE_HOSTING_DOMAINSallowlist), which the URL generator (firebase-publisher) and every matcher (interception regex, CSPframe-src, framing-header strip, team-chat link classifier, broadcast-embed origin allowlist) import — so a future domain change lands in one place. The per-userfirebaseHostingDomainsetting still overrides the default. The/t/*relay endpoints are NOT share links and deliberately stay onagentmc-shares.web.app. - Deep-link route for shares.
omniscio://share/<token>(oragentmc://share/<token>) opens a share in the built-in viewer. Parsed byparseDeepLinkUrl()as{ action: 'open-share-in-viewer', token }— a bare token (no sub-action) triggers the viewer route; the existingshare/fork/<token>sub-action is unaffected. The renderer'srouteDeepLinkActiondispatches toopenShareInViewer(token)(wired inuseDeepLinkListenertoresolveShareUrlToken(token, { openInViewer: true })), which navigates to the Shares panel and opens the share in view mode. If the token is unknown locally, the renderer shows an error toast. Builder:shareViewerDeepLink(token)insrc/main/services/deep-link.ts. - CSP
frame-src. The webview viewer and any remaining iframe usage requires BOTH share hosts in the renderer CSPframe-src(https://shares.omniscio.com+ the legacyhttps://agentmc-shares.web.app) insrc/renderer/index.html. Without them, framed content from the share host is blocked. Pinned bytests/unit/lib/shares-iframe-csp.test.tsso a future CSP hardening pass can't drop the directive silently. - Response-header override. The Cloud Function correctly serves share shell pages with
X-Frame-Options: DENYand CSPframe-ancestors 'none'(anti-clickjacking for external browsers). The Electron main process strips these two framing headers from share-host responses loaded as sub-frames viasession.webRequest.onHeadersReceived(src/main/services/share/share-preview-headers.ts, registered insrc/main/app/window.ts). OnlysubFrameresource types on/s/<token>and/s/<token>/runpaths are affected — main-frame navigations and all other resource types pass through unmodified. Both theframe-srcCSP allowlist AND this header override are required for the webview to load share content. - Links inside a share open in your browser. A link a user clicks inside a rendered share's artifact — a sub-frame navigation to an off-origin site — opens in the system browser instead of dying inside the sandboxed
<iframe>(a bare in-artifact link previously loaded into the tiny frame and silently failed against the target'sX-Frame-Options). The main process wires awill-frame-navigatehandler insrc/main/webcontents-hardening.tsthat externalizes off-originhttp(s)navigations originating from a share-owned frame (the artifact itself + the full-bleedabout:srcdocfit-host, decided by the exportedisShareOwnedFramehelper) through the validatedshell.openExternalpath — while a nested off-origin embed the artifact legitimately included (e.g. a YouTube iframe) keeps loading/navigating in place, same-origin sub-resource loads (the shell's own/runframe) pass through untouched, and same-document#anchorlinks (which never firewill-frame-navigate) are unaffected. That handler covers the common plain link (will-navigatefires for the main frame only, so it never saw the sub-frame click). Atarget="_blank"link — which is what the report template's source link and the "Made with Omniscio" badge emit — takes a different path: it is awindow.open, externalized bysetWindowOpenHandler, and Electron refuses a<webview>guest'swindow.openoutright unless the tag carriesallowpopups="true". The refusal happens below the app, so until the share viewer opted in (src/renderer/src/features/shares/ShareViewerWebview.tsx)setWindowOpenHandlerwas never consulted and clicking a_blanklink inside a share did nothing at all. Opting in grants no privilege — the handler still returnsdenyfor every popup, so no window opens in-app; it only gets the chance to hand the URL toshell.openExternal. The same shared hardening also benefits the shared-partition Gmail link viewer. Locked bytests/unit/main/webcontents-hardening.test.ts(the externalize path) +tests/unit/features/shares/ShareViewerWebview.test.tsx(theallowpopupsopt-in). - In-page
#anchornav works inside srcdoc-hosted shares. A separate issue from the off-origin externalization above: a responsive/fixed-width full-bleed artifact renders in anabout:srcdocframe (src/main/services/share/share-artifact-builder.ts/buildFitHost), whose base URL is inherited from the parent/runURL — so the browser resolved a bare#sectionlink to…/run#section, a cross-document navigation that yanked the artifact off screen (blank). The fix injects a tiny click interceptor (SRCDOC_ANCHOR_SCROLL_SCRIPTinsrc/main/services/share/share-artifact-scripts.ts) into ONLY the two srcdoc hosts thatpreventDefaults a same-page#click and scrolls in-document instead. A directly-served (/run, non-srcdoc) artifact keeps its own real URL as the base, so its#idlinks already work and it is deliberately left untouched. Existing shares recover via the viewer's Refresh button; they get the permanent fix on re-publish. Locked bytests/unit/services/srcdoc-anchor-scroll.test.ts(click behavior) + the srcdoc-anchor wiring cases intests/unit/services/share-artifact-builder-presentation.test.ts. - Store. All master + detail state lives in
src/renderer/src/stores/share-store.ts. On top of the legacyshares/isCreating/errorslices, the store ownsselectedShareId,searchQuery,kindFilter('all' | 'thread' | 'artifact' | 'digest'),collapsedGroups(aSet<'live' | 'archived'>seeded with'archived'so that section starts collapsed; in-memory only, resets each launch — mirrors the Cron sidebar), andpendingViewMode('view' | null— set by the share URL resolver whenopenInVieweris true, consumed bySharesDetailPaneonshare.idchange to open in view mode instead of edit mode, then cleared), plussetSearch/setKindFilter/toggleGroupCollapsed. A derived selectoruseGroupedShares()applies the kind + search filters and partitions the survivors into{ live, archived }by active-vs-(revoked-or-expired).fetchSharesclearsselectedShareIdonly when the selected row has vanished server-side (so revoke/delete from another window doesn't strand the detail pane); a status change no longer clears it, because the row simply moves into the Archived section.revokeShare,deleteShare,republishShareall do an optimistic local write, thenawait get().fetchShares().catch(() => {})infinallyso a missedSHARE_UPDATEDpush doesn't leave the UI desynced.updateShareInPlace(id)re-reads the share'ssourcePathand invokesSHARE_PUBLISH_ARTIFACTwithupdateToken(the share's own token), refetching infinally; it resolves{ url, id, updatedInPlace }so the pane stays on the same row on a same-link update and lands on the new row on the relay fallback. - Data source.
SharesLiveSync(mounted app-wide inGlobalModals) callsfetchShares()(which invokesipc.invoke(IPC.SHARE_LIST, { includeRevoked: true })) on mount and on everySHARE_UPDATEDpush, so the store'sshareslist is hydrated on every view — not just when the Shares panel is open.SharesSidebarALSO callsfetchShares()on mount and onSHARE_UPDATEDwhile it's open (a benign extra refresh).SharesDetailPanereads the currently-selected share out of the store and does NOT subscribe. The handler issrc/main/ipc/share-handlers.ts. - SHARE_UPDATE IPC. Channel registered in
share-handlers.tsnext toSHARE_REVOKE/SHARE_DELETE. Schema isshareUpdateSchemainsrc/shared/ipc-schemas.ts—{ id: string, patch: { label?: string, expiresAt?: string | null, liveUpdating?: boolean, viewCap?: number | null, notifyOnView?: boolean, commentsEnabled?: boolean, muteNotifications?: boolean } }. Handler reads the existing row (returns{ success: false, error: 'Share not found: ...' }if missing), callsshareQueries.updateShare(id, patch), mirrors the patch to Firestore, emitsSHARE_UPDATEDwithaction: 'updated', returns{ success: true, data: { share } }. TheSHARE_UPDATEDpush channel includes'updated'alongside the existing'created' | 'revoked' | 'deleted' | 'expired'actions. Password protection is a separate channel. - SHARE_SET_PROTECTION IPC. Dedicated channel for the password hash/salt handshake so plaintext never round-trips through the patch object. Schema is
shareSetProtectionSchemainsrc/shared/ipc-schemas.ts—{ id: string, passwordHash: string(64-hex) | null, passwordSalt: string(32-hex) | null }. Both fields must move together (both set, or both null — partial sets are rejected). Handler updates the SQLite row + Firestore mirror, emitsSHARE_UPDATEDwithaction: 'updated', returns{ success: true, data: { share } }.passwordHash/passwordSaltare stripped from every IPC response — only the renderer-facinghasPassword: booleanis exposed. - Share view poller.
src/main/services/share/share-view-poller.tsruns as a periodic task (60 s interval, 30 s initial delay, jittered) viacreatePeriodicTask. Each tick pulls allshares.*docs from the Firestore mirror, comparesview_count/last_viewed_atagainst the local row, writes the deltas into the localsharesrow + appends newshare_view_eventsrows, then fires a desktop notification for each share withnotify_on_view = 1ANDmute_notifications = 0whose count advanced. The row write commits before the notification fires for crash-safe ordering. - Schema. Migration v173 added
password_hash,password_salt,view_cap,view_count,last_viewed_at,notify_on_viewto thesharestable + ashare_view_eventsaudit log. Migration v180 addedshare_send_log. Migration v181 added per-event privacy hashes (country,ip_hash) onshare_view_events(seesrc/main/db/queries-share-view-events.ts). - Mobile push coverage.
SHARE_UPDATEDis desktop-only — it's listed inDESKTOP_ONLY_CHANNELSintests/unit/lint/mobile-push-listener-coverage.test.ts. Both renderer listeners (the app-wideSharesLiveSyncand the sidebar) subscribe viauseIpcListener, but the WS bridge never forwardsSHARE_UPDATEDto the mobile/web renderer, so the listeners simply no-op there. The mobile inbox reconciles via the standard inbox push path. The viewer's keyboard-dismiss pushWEBVIEW_GUEST_DISMISS(Escape/Ctrl+W forwarded out of the popup's<webview>) is likewise desktop-only in the same list — mobile web has no<webview>guest, and the mobile viewer closes via its X button. - Modal lazy-loading. Only
PastePublishModalremains as a modal in this integration. It'sReact.lazy()-imported insideSharesSidebar.tsxper the heavy-modal contract — the chunk only downloads when the user clicks + Publish. (SharesEditModalandSharesSendModalno longer exist — both flows are now inline inSharesDetailPane.) - SHARE_SEND IPC. Channel registered in
share-handlers.ts. Schema isshareSendSchemainsrc/shared/ipc-schemas.ts—{ shareId, channelKind: 'gmail'|'sms'|'slack', recipient: string(1..1000), message?: string(0..5000) }. Handler validates the share is published / not revoked / not expired, inserts ashare_send_logpending row (so a thrown channel call still leaves an audit row), dispatches throughgmailApiService.sendEmail,pushbulletService.sendSms, orslackService.sendMessage. Flips the log row tosent/failed, emitsSHARE_UPDATEDwith action'sent'or'send-failed', and tracks a'share', 'send'event with the channel kind. Returns{ success: true, data: { entry } }even on channel-level failure — the renderer keys offentry.status === 'failed'so the inline panel stays open. - Data layer.
share_send_logtable added in migration v180 (DDL + indexes insrc/main/db/database.ts). Query layer issrc/main/db/queries-share-send-log.ts:insertSendLog,updateSendLogStatus,getSendLogById,listSendLogForShare. CHECK constraints enforcechannel_kind IN ('gmail','sms','slack')andstatus IN ('pending','sent','failed'). Indexes onshare_idandcreated_atkeep the list query fast. - Drag-and-drop fan-out. The HTML5 drop events are owned by
useFileDropZonefromsrc/renderer/src/hooks/useFileDropZone.ts(handles the drag-counter, Files-type filter, andpreventDefaulton dragover). The per-file IPC fan-out lives inpublishDroppedFilesfromsrc/renderer/src/features/shares/publish-dropped-files.ts, which: (a) resolves eachFile's OS path through preload'swebUtils.getPathForFile(Electron 39 dropped the renderer-sideFile.path), (b) callsIPC.SHARE_PUBLISH_ARTIFACTonce per file, (c) maps the seven structuredshare-artifact-serviceerror contract strings (outside-allowlist,unsupported-type,magic-byte-mismatch,empty-file,size-cap,firebase-upload-failed,firebase-quota) to user-friendly sentences, (d) returns a singlePublishOutcome = { ok, failed[] }the sidebar collapses into one toast. Synthetic Files (no backing path) and browser mode both short-circuit without ever invoking IPC.
For agents
Component layout
src/renderer/src/features/shares/SharesSidebar.tsx— master pane. Header, search, segmented Kind tabs, the two collapsible Live / Archived & expired sections (a localSharesSectionHeadermirrors the Cron group-header look without importing it — a static import would drag cron-group-label's store graph into this lazy chunk), scrollable list, drop zone,PastePublishModallauncher. Owns theSHARE_UPDATEDlistener.src/renderer/src/features/shares/SharesDetailPane.tsx— detail pane. Header band with View/Open buttons, three-mode viewer (viewModestate), inline edit form, inline password sub-form, collapsible Send panel, destructive footer. In edit mode its live preview guest is silenced while it is scrolled out of view — the preview sits at the top of the pane's scrolling body and the edit form lives below it, so reaching the form scrolls a playing guest away;useInViewport(the pane's scroll container as the observer root) drives the webview'svisibleprop, and the signal is live in BOTH directions so scrolling back un-mutes it. Locked bytests/unit/features/shares/SharesDetailPane-viewer.test.tsx+tests/unit/hooks/useInViewport.test.tsx.src/renderer/src/features/shares/ShareViewerWebview.tsx— the Electron<webview>component that renders a share in a separate renderer process. Handles loading, error (did-fail-load), and crash (render-process-gone) states with appropriate overlays. Its content identity is(url, contentHash), not the url alone: an in-place republish (the Update button /updateToken) REUSES the token, soshare.urlis byte-identical whilecontentHashchanges — the component reloads the guest (reloadIgnoringCache, guarded with atypeofcheck so the mobile / non-Electron webview never throws) whencontentHashchanges under a stable url, so the viewer shows the NEW content instead of the stale kept page. A bare re-render or a new share object with the SAME identity is a no-op (deps are the primitive url/hash, not the share object), preserving instant-open and the no-stuck-spinner invariant. The decision is the purenextViewerLoad(prevKey, url, contentHash)helper, unit-tested intests/unit/features/shares/ShareViewerWebview.test.tsx. Reveal timing: it lifts the loading cover at first-contentful-paint via a guest paint probe (executeJavaScriptondom-ready, resolving on a bufferedPerformanceObserverpaint entry; a cleanup timeout resolves not-painted, which never reveals), guarded to the current(url, contentHash)so a superseded navigation can't reveal the wrong page, and keepsdid-finish-loadas the backstop. The probe's observer is long-lived, so a background-prewarmed slot that only paints once it is promoted (a hidden→visible CSS flip, not a reload) is still caught — the instant-open path now holds even for font-heavy pages, not just light ones. Locked by the "reveals at first contentful paint" / backstop / stale-nav tests intests/unit/features/shares/ShareViewerWebview.test.tsx. Off-screen guests are SILENCED.visible(default true) is the host's "this guest is on screen to the user right now" signal: when it goes false the guest's media is paused AND the guest audio is muted, and the mute is lifted when it comes back. It exists because the floating viewer closes by moving its stage off-screen (position: fixed; left: -99999; visibility: hidden) rather than unmounting — CSS visibility is a paint instruction and does not stop media, so a share playing a video kept playing after the viewer was closed (reported 2026-09-27). The pause stops the video itself; the mute is the guarantee, because a sweep of the guest's top document cannot reach a cross-origin iframe player or Web Audio, and cannot stop a page that resumes on a timer. Playback is NEVER auto-resumed — a page may start playing only from a user gesture, and reopening a viewer is not a request to resume. Every call is guarded: a<webview>delegatessetAudioMuted/executeJavaScriptto its guest and that delegation THROWS beforedom-ready(measured on real Electron: "The WebView must be attached to the DOM and the dom-ready event emitted…"), which is the state of every hidden prewarm on its first render — and a throw inside a passive effect reaches React's error boundary, so the applier can never be allowed to throw. A guest that attaches late is covered by adom-readyre-assert. Locked by the "silences the guest the moment it goes off screen" / "lifts the mute … never resumes" / "only attaches AFTER it was already hidden" / shim-guard tests intests/unit/features/shares/ShareViewerWebview.test.tsx+tests/unit/features/shares/SharePrewarmSlot.test.tsx.src/renderer/src/features/shares/ShareViewerToolbar.tsx— floating auto-hide toolbar overlaying the webview. Mode-dependent actions (Edit + Maximize in view mode; Exit in fullscreen mode) plus the always-present actions (Open in browser, Copy URL, and the desktop-only Screenshot full-page capture viaShareScreenshotButton). Takes the viewed webview's id (lifted from its parent) so Screenshot targets the right page. ARIArole="toolbar", fades after 3 seconds.src/renderer/src/features/shares/ShareViewerFullscreen.tsx— fullscreen viewer portal (createPortaltodocument.body). Opaquefixed inset-0 z-50overlay with its own webview + toolbar. Escape key exits.src/renderer/src/features/shares/SharesEmptyDetail.tsx— empty state for when no row is selected.src/renderer/src/features/shares/shares-display-helpers.tsx— extracted helpers:kindBucket,expiryDisplay,scopeBadge,artifactBadge(shared between the two components).src/renderer/src/features/shares/share-viewer.ui-anchors.ts— co-located UI-anchor metadata for the viewer webview, toolbar, and all viewer buttons.src/renderer/src/lib/share-url-intercept-access.ts— registration-seam that breaks the import cycle betweenplatform.tsandshare-store.tsfor share URL interception.src/renderer/src/features/shares/SharesLiveSync.tsx— app-wide share-list live-sync: a null-rendering component (mounted once inGlobalModalsbesideFloatingShareViewer) whoseuseSharesLiveSynchook hydrates the store on mount and refetches onSHARE_UPDATED, so an agent-published share is recognized as your own and routes to the in-app viewer on any view.src/renderer/src/features/shares/FloatingShareViewer.tsx— the full-size in-app share popup (portal todocument.body). Its header carries Copy URL (desktop + mobile, viaCopyConfirmButton), a desktop-only Screenshot (ShareScreenshotButton), and a Refresh button (reloads the visible share viauseShareWebviewReload→UI_RELOAD_SHARE_WEBVIEW, disabled until the page reports awebContentsId) alongside the open-in-browser + close controls; it tracks aurl → webContents idmap for the promoted/visible slot so Screenshot targets the page you're actually looking at. Since the 2026-08-05 instant-open rework it is ALSO the single owner of the warm slots: it renders an always-mounted off-screen STAGE (the card shell + webview host) so the slots' React positions never change, creates aSharePrewarmSlotper URL in the store'sbackgroundPrewarmQueue(cappedSHARE_PREWARM_MAX_SLOTS, read throughuseDeferredValueso warming yields to the user's paint —never-slow-the-user), and on open PROMOTES the slot matching the open share by a pure style flip (same DOM element → the already-loaded page renders instantly, no reload). A share with no warm slot yet (a cold click, or a stale-risk share — revoked / expired / live-updating, where a kept copy could be out of date) loads fresh via a plainShareViewerWebview; warm slots persist after they load and are evicted oldest-first on cap pressure. Each warm slot also passes its share'scontentHashto the webview, so an in-place republish (which bumps the hash) reloads the warmed copy in the background — a promoted republished share shows the new content, never the stale warm.src/renderer/src/features/shares/SharePrewarmSlot.tsx— one share<webview>slot: either a hidden off-screen warm (loaded while the share link is on screen) or the promoted one filling the card. Settles on a terminal load status or an 8s timeout, which advances the warm queue (markBackgroundPrewarmed); an evicted slot is un-warmed (dropBackgroundPrewarm) so a later re-render can warm it again. It passesvisible={promoted}to its webview, so an off-screen slot is silenced — which covers BOTH a hidden preload (a warming share never makes noise) and the page the viewer just closed (the element stays mounted for the instant reopen rather than unmounting, so without that prop the sound simply carried on). Locked bytests/unit/features/shares/SharePrewarmSlot.test.tsx.src/renderer/src/features/shares/use-background-share-prewarm.ts— the hookagent-markdown-helperscalls at each rendered in-app share link (rendersAsInAppShare— your own OR a teammate's): it enqueues that link's URL for the background preload above (idempotent — deduped against the queue + a warm-once set), gated onisElectron(desktop-only; the mobile web bundle can't render<webview>). Locked bytests/unit/features/shares/use-background-share-prewarm.test.ts.src/renderer/src/features/shares/ShareScreenshotButton.tsx— the shared desktop-only Screenshot button dropped into both the quick opener and the manager toolbar. Renders nothing offisElectron; takes the viewed webview's id, disabled until it arrives; drives the busy state from the capture hook. Locked bytests/unit/features/shares/ShareScreenshotButton.test.tsx.src/renderer/src/features/shares/useShareWebviewCapture.ts— the shared hook behind that button: firesIPC.UI_CAPTURE_SHARE_FULL_PAGE, guards re-entrancy (a rapid second click can't fire a second capture), and surfaces the copied / trimmed / failed toast — the capture+clipboard+toast contract in ONE place. Locked bytests/unit/features/shares/useShareWebviewCapture.test.tsx.src/main/services/share/capture-share-full-page.ts— main-sidecaptureShareWebviewFullPage(webContentsId): resolves the target through theresolveHumanTabWebContents()boundary (share/browser webview only), CDP full-page capture (captureBeyondViewport, clamped toMAX_CAPTURE_EDGE), attach-only-if-needed + detach-only-what-it-attached, failure-as-a-value. Governed byui-screenshot-contract.mdfull-page share-capture rules; locked bytests/unit/services/capture-share-full-page.test.ts.
Related
The overview, the other parts, and everything else worth reading next all sit on Shares view (master/detail sidebar + in-app webview viewer for shared links).
Last verified 2026-09-28