---
title: Shares view (master/detail sidebar + in-app webview viewer for shared links) (part 2)
---
# Shares view (master/detail sidebar + in-app webview viewer for shared links) (part 2)

## What it is

This is part 2 of the [Shares view (master/detail sidebar + in-app webview viewer for shared links)](shares-view.md) 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)](shares-view.md) — 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 sentinel `SHARES_PROJECT_ID` (from `src/shared/virtual-project-ids.ts`).
- **UI registry shape.** The Shares entry in `src/renderer/src/integrations/ui-registry.ts` is 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 **no** `panelOwnsLayout` flag on Shares; the standard `RoutedSubSidebar` mounts `SharesSidebar` in 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)` from `src/renderer/src/lib/activate-virtual-project.ts` — that helper resolves the sentinel to the project-row UUID before calling `setActiveProject` (passing the sentinel directly to `setActiveProject` silently no-ops, per the virtual-project activation contract).
- **Share URL interception.** When any call to `openExternalUrl` encounters a URL matching a share host (`https://shares.omniscio.com/s/<token>` — the brand default — or the legacy `https://agentmc-shares.web.app/s/<token>`; regex `SHARE_URL_RE`, built from the shared `SHARE_HOSTING_DOMAINS` list, in `src/renderer/src/lib/platform.ts`), the share is opened in-app instead of launching an external browser. The function calls `resolveShareUrlToken(token, opts)` from the registration-seam module `src/renderer/src/lib/share-url-intercept-access.ts`, which looks up the token in the local share list. A `#comments` deep 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 the `WEBVIEW_GUEST_DISMISS` push; see `keyboard-shortcut-dispatch-contract.md` `webview-close-gesture-closes-the-overlay`), dropped below the 50px `CustomTitlebar` strip (`topOffset`) so the window's min/maximize/close controls stay un-dimmed and clickable; it mirrors the `ImageLightboxShell` media-modal pattern and matches `ShareViewerFullscreen`'s a11y (portal + `role="dialog"` + Escape, no `aria-modal`/focus-trap so it never fights the embedded webview's focus) — via `openFloatingShare(token)`. **Any valid share token opens in-app when `sharesOpenInApp` is 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, so `FloatingShareViewer` synthesizes a minimal card whose `<webview>` src is **built from the token** via `buildShareUrl` (`src/shared/share-hosting-domains.ts`) — the canonical, fixed-domain `https://shares.omniscio.com/s/<token>`, **never the link's raw href**. `openFloatingShare` still fetches the owned list on demand, so an owned-but-unsynced just-published token upgrades to its rich metadata once it lands. When `sharesOpenInApp` is OFF the resolver returns false and the caller opens the system browser (unchanged); a `#comments` deep 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 makes `buildShareUrl` return null and the viewer simply stays closed, and a raw/hostile URL can never be coerced into the privileged viewer.** Locked by `tests/unit/shared/share-hosting-domains.test.ts` (`buildShareUrl` charset/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), and `tests/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 from `SharesLiveSync` (below). A bypass function `openExternalUrlForce(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 by `DeckPublishedDialog` (which shows a just-published share URL that would otherwise be recursively intercepted). The registration-seam pattern (mirrors `session-nav-store-access.ts`) prevents an import cycle: `platform.ts` is a foundational leaf that `ipc.ts` imports at module scope, so it cannot statically import `share-store.ts` (which imports `ipc.ts`).
- **Share hosting domain (brand + legacy).** New share links mint on the brand domain `https://shares.omniscio.com` (the product-wide default); the original `https://agentmc-shares.web.app` stays fully served, so every already-sent link keeps resolving — both hostnames point at the SAME Firebase Hosting site, so content is byte-identical and the `serveShare` Cloud Function needs no per-host logic (the unlock cookie is `Path=/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` + the `SHARE_HOSTING_DOMAINS` allowlist), which the URL generator (`firebase-publisher`) and every matcher (interception regex, CSP `frame-src`, framing-header strip, team-chat link classifier, broadcast-embed origin allowlist) import — so a future domain change lands in one place. The per-user `firebaseHostingDomain` setting still overrides the default. The `/t/*` relay endpoints are NOT share links and deliberately stay on `agentmc-shares.web.app`.
- **Deep-link route for shares.** `omniscio://share/<token>` (or `agentmc://share/<token>`) opens a share in the built-in viewer. Parsed by `parseDeepLinkUrl()` as `{ action: 'open-share-in-viewer', token }` — a bare token (no sub-action) triggers the viewer route; the existing `share/fork/<token>` sub-action is unaffected. The renderer's `routeDeepLinkAction` dispatches to `openShareInViewer(token)` (wired in `useDeepLinkListener` to `resolveShareUrlToken(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)` in `src/main/services/deep-link.ts`.
- **CSP `frame-src`.** The webview viewer and any remaining iframe usage requires BOTH share hosts in the renderer CSP `frame-src` (`https://shares.omniscio.com` + the legacy `https://agentmc-shares.web.app`) in `src/renderer/index.html`. Without them, framed content from the share host is blocked. Pinned by `tests/unit/lib/shares-iframe-csp.test.ts` so 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: DENY` and CSP `frame-ancestors 'none'` (anti-clickjacking for external browsers). The Electron main process strips these two framing headers from share-host responses loaded as sub-frames via `session.webRequest.onHeadersReceived` (`src/main/services/share/share-preview-headers.ts`, registered in `src/main/app/window.ts`). Only `subFrame` resource types on `/s/<token>` and `/s/<token>/run` paths are affected — main-frame navigations and all other resource types pass through unmodified. Both the `frame-src` CSP 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's `X-Frame-Options`). The main process wires a `will-frame-navigate` handler in `src/main/webcontents-hardening.ts` that externalizes off-origin `http(s)` navigations **originating from a share-owned frame** (the artifact itself + the full-bleed `about:srcdoc` fit-host, decided by the exported `isShareOwnedFrame` helper) through the validated `shell.openExternal` path — 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 `/run` frame) pass through untouched, and same-document `#anchor` links (which never fire `will-frame-navigate`) are unaffected. That handler covers the common plain link (`will-navigate` fires for the main frame only, so it never saw the sub-frame click). A `target="_blank"` link — which is what the report template's source link and the "Made with Omniscio" badge emit — takes a **different** path: it is a `window.open`, externalized by `setWindowOpenHandler`, and Electron refuses a `<webview>` guest's `window.open` outright unless the tag carries `allowpopups="true"`. The refusal happens below the app, so until the share viewer opted in (`src/renderer/src/features/shares/ShareViewerWebview.tsx`) `setWindowOpenHandler` was never consulted and clicking a `_blank` link inside a share did nothing at all. Opting in grants no privilege — the handler still returns `deny` for every popup, so no window opens in-app; it only gets the chance to hand the URL to `shell.openExternal`. The same shared hardening also benefits the shared-partition Gmail link viewer. Locked by `tests/unit/main/webcontents-hardening.test.ts` (the externalize path) + `tests/unit/features/shares/ShareViewerWebview.test.tsx` (the `allowpopups` opt-in).
- **In-page `#anchor` nav works inside srcdoc-hosted shares.** A separate issue from the off-origin externalization above: a responsive/fixed-width full-bleed artifact renders in an `about:srcdoc` frame (`src/main/services/share/share-artifact-builder.ts` / `buildFitHost`), whose base URL is inherited from the parent `/run` URL — so the browser resolved a bare `#section` link to `…/run#section`, a **cross-document** navigation that yanked the artifact off screen (blank). The fix injects a tiny click interceptor (`SRCDOC_ANCHOR_SCROLL_SCRIPT` in `src/main/services/share/share-artifact-scripts.ts`) into ONLY the two srcdoc hosts that `preventDefault`s 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 `#id` links 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 by `tests/unit/services/srcdoc-anchor-scroll.test.ts` (click behavior) + the srcdoc-anchor wiring cases in `tests/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 legacy `shares` / `isCreating` / `error` slices, the store owns `selectedShareId`, `searchQuery`, `kindFilter` (`'all' | 'thread' | 'artifact' | 'digest'`), `collapsedGroups` (a `Set<'live' | 'archived'>` seeded with `'archived'` so that section starts collapsed; in-memory only, resets each launch — mirrors the Cron sidebar), and `pendingViewMode` (`'view' | null` — set by the share URL resolver when `openInViewer` is true, consumed by `SharesDetailPane` on `share.id` change to open in view mode instead of edit mode, then cleared), plus `setSearch` / `setKindFilter` / `toggleGroupCollapsed`. A derived selector `useGroupedShares()` applies the kind + search filters and partitions the survivors into `{ live, archived }` by active-vs-(revoked-or-expired). `fetchShares` clears `selectedShareId` only 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`, `republishShare` all do an optimistic local write, then `await get().fetchShares().catch(() => {})` in `finally` so a missed `SHARE_UPDATED` push doesn't leave the UI desynced. `updateShareInPlace(id)` re-reads the share's `sourcePath` and invokes `SHARE_PUBLISH_ARTIFACT` with `updateToken` (the share's own token), refetching in `finally`; 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 in `GlobalModals`) calls `fetchShares()` (which invokes `ipc.invoke(IPC.SHARE_LIST, { includeRevoked: true })`) on mount and on every `SHARE_UPDATED` push, so the store's `shares` list is hydrated on every view — not just when the Shares panel is open. `SharesSidebar` ALSO calls `fetchShares()` on mount and on `SHARE_UPDATED` while it's open (a benign extra refresh). `SharesDetailPane` reads the currently-selected share out of the store and does NOT subscribe. The handler is `src/main/ipc/share-handlers.ts`.
- **SHARE_UPDATE IPC.** Channel registered in `share-handlers.ts` next to `SHARE_REVOKE` / `SHARE_DELETE`. Schema is `shareUpdateSchema` in `src/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), calls `shareQueries.updateShare(id, patch)`, mirrors the patch to Firestore, emits `SHARE_UPDATED` with `action: 'updated'`, returns `{ success: true, data: { share } }`. The `SHARE_UPDATED` push 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 `shareSetProtectionSchema` in `src/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, emits `SHARE_UPDATED` with `action: 'updated'`, returns `{ success: true, data: { share } }`. `passwordHash` / `passwordSalt` are stripped from every IPC response — only the renderer-facing `hasPassword: boolean` is exposed.
- **Share view poller.** `src/main/services/share/share-view-poller.ts` runs as a periodic task (60 s interval, 30 s initial delay, jittered) via `createPeriodicTask`. Each tick pulls all `shares.*` docs from the Firestore mirror, compares `view_count` / `last_viewed_at` against the local row, writes the deltas into the local `shares` row + appends new `share_view_events` rows, then fires a desktop notification for each share with `notify_on_view = 1` AND `mute_notifications = 0` whose 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_view` to the `shares` table + a `share_view_events` audit log. Migration v180 added `share_send_log`. Migration v181 added per-event privacy hashes (`country`, `ip_hash`) on `share_view_events` (see `src/main/db/queries-share-view-events.ts`).
- **Mobile push coverage.** `SHARE_UPDATED` is **desktop-only** — it's listed in `DESKTOP_ONLY_CHANNELS` in `tests/unit/lint/mobile-push-listener-coverage.test.ts`. Both renderer listeners (the app-wide `SharesLiveSync` and the sidebar) subscribe via `useIpcListener`, but the WS bridge never forwards `SHARE_UPDATED` to 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 push `WEBVIEW_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 `PastePublishModal` remains as a modal in this integration. It's `React.lazy()`-imported inside `SharesSidebar.tsx` per the heavy-modal contract — the chunk only downloads when the user clicks **+ Publish**. (`SharesEditModal` and `SharesSendModal` no longer exist — both flows are now inline in `SharesDetailPane`.)
- **SHARE_SEND IPC.** Channel registered in `share-handlers.ts`. Schema is `shareSendSchema` in `src/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 a `share_send_log` pending row (so a thrown channel call still leaves an audit row), dispatches through `gmailApiService.sendEmail`, `pushbulletService.sendSms`, or `slackService.sendMessage`. Flips the log row to `sent` / `failed`, emits `SHARE_UPDATED` with 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 off `entry.status === 'failed'` so the inline panel stays open.
- **Data layer.** `share_send_log` table added in migration v180 (DDL + indexes in `src/main/db/database.ts`). Query layer is `src/main/db/queries-share-send-log.ts`: `insertSendLog`, `updateSendLogStatus`, `getSendLogById`, `listSendLogForShare`. CHECK constraints enforce `channel_kind IN ('gmail','sms','slack')` and `status IN ('pending','sent','failed')`. Indexes on `share_id` and `created_at` keep the list query fast.
- **Drag-and-drop fan-out.** The HTML5 drop events are owned by `useFileDropZone` from `src/renderer/src/hooks/useFileDropZone.ts` (handles the drag-counter, Files-type filter, and `preventDefault` on dragover). The per-file IPC fan-out lives in `publishDroppedFiles` from `src/renderer/src/features/shares/publish-dropped-files.ts`, which: (a) resolves each `File`'s OS path through preload's `webUtils.getPathForFile` (Electron 39 dropped the renderer-side `File.path`), (b) calls `IPC.SHARE_PUBLISH_ARTIFACT` once per file, (c) maps the seven structured `share-artifact-service` error 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 single `PublishOutcome = { 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 local `SharesSectionHeader` mirrors 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, `PastePublishModal` launcher. Owns the `SHARE_UPDATED` listener.
- `src/renderer/src/features/shares/SharesDetailPane.tsx` — detail pane. Header band with View/Open buttons, three-mode viewer (`viewMode` state), 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's `visible` prop, and the signal is live in BOTH directions so scrolling back un-mutes it. Locked by `tests/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, so `share.url` is byte-identical while `contentHash` changes — the component reloads the guest (`reloadIgnoringCache`, guarded with a `typeof` check so the mobile / non-Electron webview never throws) when `contentHash` changes 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 pure `nextViewerLoad(prevKey, url, contentHash)` helper, unit-tested in `tests/unit/features/shares/ShareViewerWebview.test.tsx`. **Reveal timing:** it lifts the loading cover at **first-contentful-paint** via a guest paint probe (`executeJavaScript` on `dom-ready`, resolving on a buffered `PerformanceObserver` paint 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 keeps `did-finish-load` as 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 in `tests/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>` delegates `setAudioMuted` / `executeJavaScript` to its guest and that delegation THROWS before `dom-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 a `dom-ready` re-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 in `tests/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 via `ShareScreenshotButton`). Takes the viewed webview's id (lifted from its parent) so Screenshot targets the right page. ARIA `role="toolbar"`, fades after 3 seconds.
- `src/renderer/src/features/shares/ShareViewerFullscreen.tsx` — fullscreen viewer portal (`createPortal` to `document.body`). Opaque `fixed inset-0 z-50` overlay 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 between `platform.ts` and `share-store.ts` for share URL interception.
- `src/renderer/src/features/shares/SharesLiveSync.tsx` — app-wide share-list live-sync: a null-rendering component (mounted once in `GlobalModals` beside `FloatingShareViewer`) whose `useSharesLiveSync` hook hydrates the store on mount and refetches on `SHARE_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 to `document.body`). Its header carries **Copy URL** (desktop + mobile, via `CopyConfirmButton`), a desktop-only **Screenshot** (`ShareScreenshotButton`), and a **Refresh** button (reloads the visible share via `useShareWebviewReload` → `UI_RELOAD_SHARE_WEBVIEW`, disabled until the page reports a `webContentsId`) alongside the open-in-browser + close controls; it tracks a `url → webContents id` map 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 a `SharePrewarmSlot` per URL in the store's `backgroundPrewarmQueue` (capped `SHARE_PREWARM_MAX_SLOTS`, read through `useDeferredValue` so 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 plain `ShareViewerWebview`; warm slots persist after they load and are evicted oldest-first on cap pressure. Each warm slot also passes its share's `contentHash` to 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 passes `visible={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 by `tests/unit/features/shares/SharePrewarmSlot.test.tsx`.
- `src/renderer/src/features/shares/use-background-share-prewarm.ts` — the hook `agent-markdown-helpers` calls 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 on `isElectron` (desktop-only; the mobile web bundle can't render `<webview>`). Locked by `tests/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 off `isElectron`; takes the viewed webview's id, disabled until it arrives; drives the busy state from the capture hook. Locked by `tests/unit/features/shares/ShareScreenshotButton.test.tsx`.
- `src/renderer/src/features/shares/useShareWebviewCapture.ts` — the shared hook behind that button: fires `IPC.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 by `tests/unit/features/shares/useShareWebviewCapture.test.tsx`.
- `src/main/services/share/capture-share-full-page.ts` — main-side `captureShareWebviewFullPage(webContentsId)`: resolves the target through the `resolveHumanTabWebContents()` boundary (share/browser webview only), CDP full-page capture (`captureBeyondViewport`, clamped to `MAX_CAPTURE_EDGE`), attach-only-if-needed + detach-only-what-it-attached, failure-as-a-value. Governed by `ui-screenshot-contract.md` full-page share-capture rules; locked by `tests/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)](shares-view.md).
