Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Shares view (master/detail sidebar + in-app webview viewer for shared links)

Shares is a top-level entry in the Omniscio sidebar (under the Omniscio group, alphabetically between Settings and Skills) that opens a Cron-Jobs-shaped master/detail layout: a 268 px sidebar on the left lists every shared link you've created, and the right pane shows the currently-selected share with a three-mode in-app webview viewer (edit / view / fullscreen) and inline controls for label / expiration / view-cap / notify / password / send / revoke / delete / republish.

What it is

Shares is a top-level entry in the Omniscio sidebar (under the Omniscio group, alphabetically between Settings and Skills) that opens a Cron-Jobs-shaped master/detail layout: a 268 px sidebar on the left lists every shared link you've created, and the right pane shows the currently-selected share with a three-mode in-app webview viewer (edit / view / fullscreen) and inline controls for label / expiration / view-cap / notify / password / send / revoke / delete / republish.

It supersedes a previous single-pane view (SharesView) and two modal dialogs (SharesEditModal, SharesSendModal) — every editing surface is now inline in the right pane; the only modal left is the Publish pasted content dialog, which is a launcher for new pasted-text shares.

Threads, artifacts, and daily digests all show up in this one place. From here you can: filter, search, copy a link, rename it, change its expiration, set a view cap, enable view-notifications, set/change/remove a password, send via Gmail/SMS/Slack, revoke an active link, delete an inactive one, republish a revoked/expired link at a new URL, or publish brand-new pasted content. It's the same share_links table as before — see artifact-sharing.md and share-artifacts.md for the publish-side details — but with a dedicated home that doesn't live inside Settings.

Where to find it

Shares, a top-level entry in the Omniscio group of the sidebar, between Settings and Skills.

How it behaves

How to use it

The master pane (Shares sidebar)

  1. Open the view. Click Shares in the left Omniscio sidebar. The integration mounts the Shares sidebar in the normal sub-sidebar slot and an empty-state card in the right pane.
  2. Header. "Shares (N)" count on the left + a + Publish button on the right. Clicking the button opens the Publish pasted content modal — the same modal that's always handled paste-publishes.
  3. Search. Below the header, a single search input matches by label, URL token, ISO createdAt, ISO expiresAt, and the human-readable expiry string (so 5 days left / Expired are searchable too). Placeholder: Search by label, URL, or date.
  4. Filter by Kind. Segmented tab strip with four buttons: All / Threads / Artifacts / Digests. "Threads" covers conversation / message / selection scopes (artifactKind is null or thread); "Artifacts" covers HTML, MD, SVG, Image, PDF, Text, Code, React, pasted-text, mc-board (Mission Control board/dashboard snapshots); "Digests" covers the daily-digest scope.
  5. Two sections (no status pills). The list always shows everything, split into two collapsible sections: Live (active shares — neither revoked nor expired) and Archived & expired (revoked OR expired). Archived & expired is collapsed by default so dead links stay out of the way; each section header shows a (count) and a chevron, and the archived rows render dimmed (opacity-50) when expanded. When every share is live, the section headers are omitted and the list renders flat. The split composes with Kind + search — those filter first, then each surviving row falls into Live or Archived. (The old Active / Revoked / All pill row is gone; the collapsible sections replace it.)
  6. Each row is a full-width button (selectable target) laid out like a Cron-jobs row so the label never gets squeezed: line 1 is the label on its own line (truncates with an ellipsis, never to a single letter) plus a small amber lock icon when password-gated, plus a small muted-bell icon (BellOff) when notifications are muted; line 2 is one muted metadata line — Kind · scope · expiry (e.g. HTML · conversation · 5 days left) — followed by a small eye-icon N/CAP view counter when a view cap is set. A single Revoked (red) or Expired (grey) badge is pinned to the right edge on archived rows; live rows carry no status badge. The loud per-row scope/kind/Protected pills of the old layout are gone — kind and scope are now plain text, and the truncated URL was dropped from the row (it's still searchable and shown in full in the detail pane). Selected row gets the standard inbox-row "active" styling.
  7. Click to select. Clicking a row updates selectedShareId in the store; the right pane swaps to the new share. On mobile, the same click also drives a drill-down via useMobileNavStore.getState().navigateTo('shares-detail') so the detail pane fills the screen.
  8. Middle-click opens in the browser. Middle-clicking a row opens the share's published page in your system default browser (via openExternalUrlForce) — the same non-destructive action as the detail pane's "Open in browser" button, with no confirm dialog. Uses the inbox-row gesture (onMouseDown + e.button === 1 + e.preventDefault(), which also keeps the middle-click autoscroll circle from appearing). Revoke stays available in the detail pane.
  9. Drag and drop to publish. Drop one or more files anywhere onto the sidebar (the whole sidebar lights up with a dashed accent border and a "Drop files to publish" overlay). Release and Omniscio publishes each file as its own share, then refreshes the list. Files must live inside a session workdir, ~/Claude, or ~/.amc — drop something from your Desktop or Downloads and you'll see a single error toast naming the file and the reason. Mixed batches surface a single warning toast like "Published 3, 1 failed: foo.docx — …" instead of one toast per file. Web / mobile shows a "Drag-and-drop publishing requires the desktop app" failure (the renderer can't resolve OS paths there).
  10. Auto-refresh. Two subscribers keep the store's share list current. SharesLiveSync (mounted app-wide in GlobalModals) hydrates on mount and re-pulls on every SHARE_UPDATED push, so the list reflects your shares on every view — even when this panel is closed. The sidebar itself ALSO re-pulls on SHARE_UPDATED while open (a benign extra refresh). The detail pane reads its current share out of the store and never subscribes. The app-wide SharesLiveSync is what makes an agent-published share of your own resolve as one of your shares, so it opens in the in-app viewer with its full metadata (label, staleness) rather than the minimal build-from-token card a non-owned share gets — when you're not on the Shares panel (the "shares still open in Chrome" fix). A share you don't own also opens in-app now (built from its token — owner decision 2026-08-11); SharesLiveSync just adds the rich metadata for your own. See Share URL interception below.
  11. Empty states. First-time visit (no shares yet): centered card with "No shares yet" + a "Publish pasted content" CTA button. Filtered to nothing: "No shares match this filter" with a hint about adjusting the search / filter chips plus a one-click "Clear search & filters" link that empties the search box and resets the type filter to All.

The detail pane (Shares detail)

The right pane is empty (with a friendly explainer) until a row is selected. Once selected, the pane is a stacked column inside an max-w-3xl container, vertically scrollable on overflow.

  1. Header band. Title (truncated <h2> of share.label), a status pill (Active green / Revoked red / Expired grey / View cap reached amber), scope chip, artifact-kind chip, Protected amber lock badge if password-gated, and a relative "Created N minutes/hours/days ago" timestamp. On the right: a View button (Eye icon) that switches the pane into in-app view mode (see mode system below), a secondary Open in browser icon button that launches the URL in the system browser via openExternalUrlForce (bypasses share URL interception), and a Copy URL button (uses CopyConfirmButton for the icon-swap visual confirmation).
  2. Three-mode viewer. The detail pane has three modes, tracked via a viewMode state ('edit' | 'view' | 'fullscreen'):
    • Edit mode (default). Shows the header band, the inline edit form, password sub-form, Send panel, comments, and destructive footer — all the editing controls. No webview is mounted.
    • View mode. Replaces the edit form with a full-height ShareViewerWebview — an Electron <webview> tag that renders the share in a separate renderer process using the SHARED_WEB_PARTITION ('persist:browser'), HARDENED_WEBVIEW_PREFERENCES, and a CHROME_USER_AGENT. The webview supports loading, error, and crash states: a fully-opaque loading surface (spinner) overlays during load and the <webview> itself is held invisible (opacity-0) until the page draws its first contentful paint — detected by a guest paint probe (executeJavaScript on dom-ready, a long-lived buffered PerformanceObserver), with did-finish-load kept as a backstop — and then fades in. Revealing at first paint rather than the full load event is what stops a font-heavy share (external Google/Fontshare fonts, display=swap) from sitting behind the spinner for the extra hundreds of ms while those fonts finish, over a page that has already drawn — so the guest's default white paint never flashes before the page draws (observing first-contentful-paint, never a blank first-paint, keeps that no-white-flash guarantee; mirrors the quick-launch modal's opaque-surface + reveal-when-ready approach; the opaque cover is the real guarantee since Electron doesn't reliably fade a <webview> guest via CSS opacity); a did-fail-load event (filtered to main-frame only, excluding ERR_ABORTED) shows an error overlay with an "Open in browser" fallback; a render-process-gone event shows a crashed overlay with a "Reload" button. A floating auto-hide ShareViewerToolbar overlays the top of the webview with mode-dependent actions: Edit (return to edit mode) + Maximize (enter fullscreen) in view mode, plus the always-present Refresh, Open in browser, Copy URL, and Screenshot buttons. Refresh reloads the share webview's TOP frame (the share shell), re-embedding the whole nested artifact tree fresh — it recovers a view an in-artifact link navigated off, or any share that rendered wrong (desktop-only, disabled until the page has a webContentsId; via the UI_RELOAD_SHARE_WEBVIEW route through the same vetted persist:browser-webview boundary as Screenshot). Screenshot captures the ENTIRE scrolling shared page (not just the visible part) and copies the PNG to the clipboard — desktop-only, and inert until the page has finished loading (see Full-page screenshot below). The toolbar fades out after 3 seconds of no interaction and reappears on hover.
    • Fullscreen mode. A ShareViewerFullscreen portal (createPortal to document.body) covers the entire window as a fixed inset-0 z-50 opaque layer — the same full-window treatment as TosConsentGate. Contains its own ShareViewerWebview + ShareViewerToolbar with mode="fullscreen" (shows an Exit full screen button instead of Edit/Maximize). Exits via Escape key or the toolbar's Exit button, both routing through onExit which sets viewMode back to 'view'. The inline view-mode webview stays mounted underneath so the persistent <webview> guest survives a fullscreen round-trip without a reload. The viewMode resets to 'edit' when the selected share changes (useEffect on share.id). The useCtrlEnterSubmit hook is gated with enabled: viewMode === 'edit' so Ctrl+Enter doesn't fire in view/fullscreen mode.
  3. Inline metadata + edit form (replaces the old SharesEditModal). One <form>-like region inside the pane, wired with useCtrlEnterSubmit scoped to the body — Ctrl+Enter / Cmd+Enter submits the patch. Fields:
    • Label. Text input, max 200 chars. Required (empty value disables Save).
    • Expires. A "Never expires" checkbox + a datetime-local input. Toggling the checkbox disables the input. Setting the picker to a past time immediately marks the share Expired on the next refresh.
    • View cap. A "Limit total views" checkbox + a number input (1 – 999,999). Below the input, when viewCount > 0, a hint reads Currently N views or Currently N views of CAP. The Cloud Function refuses to serve the share once view_count >= view_cap.
    • Notify on view. A "Notify me when someone views this share" checkbox. When enabled, the desktop fires a notification ("Share viewed: <label>") the first time the poller observes a view_count increase since the last poll.
    • Mute notifications. A "Mute notifications for this share" checkbox. When enabled, the per-share mute overrides BOTH the "notify on view" flag AND the global comment inbox alerts for this share. The data (view counts, comment counts, SHARE_COMMENTS_UPDATED push) still updates normally; only the user-facing alerts (OS notifications and inbox items) are suppressed. Stored locally in SQLite (mute_notifications column), not in Firestore.
    • Save / Cancel footer. Save is disabled until the form is dirty AND valid (hasChanges && !isLabelInvalid && !isDateInvalid && !isViewCapInvalid && !submitting). Cancel reverts every field to the authoritative server state. Neither button closes the pane — the pane is always open.
  4. Password protection (replaces the old SharesEditModal protection section). A dedicated sub-card with three states:
    • No password set → Set password button. Click to reveal a password input + a "Save password" button. The plaintext is hashed in the renderer via SubtleCrypto (PBKDF2-SHA256, 600,000 iterations, 16-byte random salt, 32-byte derived key) and only the hex-encoded { passwordHash, passwordSalt } ship across IPC. Plaintext never crosses the IPC boundary — the explainer below the input says so verbatim.
    • Password already set → Change password (same flow as above) + a Remove button (red text) that sends { passwordHash: null, passwordSalt: null } to clear the gate. An amber Protected lock badge appears beside the section header.
    • Save fires ipc.invoke(IPC.SHARE_SET_PROTECTION, { id, passwordHash, passwordSalt }) separately from the label/expiration patch — protection changes apply immediately, independent of the main Save button. There's an eye-icon toggle to show/hide the plaintext while typing.
  5. Send share (replaces the old SharesSendModal). Collapsed by default — a Send to… row at the top expands a 3-tile channel picker (Gmail / SMS / Slack), recipient input, optional message textarea, and a Send / Cancel footer. The channel picker is a 3-column grid of tile buttons; the recipient label, placeholder, and hint adjust per channel:
    • Gmail wants an email address. Sent as a new thread.
    • SMS wants an E.164 phone number.
    • Slack wants a raw channel ID (starts with C, D, or G). Recipient is required (1000-char cap); message is optional (5000-char cap, blank-line-separated above the URL). Submit calls ipc.invoke(IPC.SHARE_SEND, { shareId, channelKind, recipient, message? }). On success the panel collapses and a toast fires. On failure the panel stays open with the channel's error string surfaced verbatim so the user can fix the recipient and retry. While the Send panel is open, the form-level Ctrl+Enter submit is disabled so it doesn't fight the channel submit.
  6. Comments section. When the share has commentsEnabled, a collapsible "Comments" section appears showing mirrored comment activity from the linked public share page. The inline edit form also exposes an "Allow comments" toggle to enable or disable commenting per share.
  7. Destructive actions footer. A final card with four buttons:
    • Republish — only visible when the share is revoked OR expired. Opens a confirm dialog; on approve, mints a fresh URL via republishShare, revokes the old row server-side, and clears selectedShareId so the sidebar's push reconciliation re-surfaces the new row.
    • Update — only visible for an ACTIVE artifact share with an on-disk source (!isInactive && sourcePath && artifactKind !== 'thread'). Confirms (it overwrites what the live link serves), then refreshes the share's content IN PLACE from its source file via updateShareInPlace → SHARE_PUBLISH_ARTIFACT with updateToken — the same URL now serves the new content and the toast reads "Share updated (same link)". On the rare relay fallback it publishes a new link, retires the old, lands on the new row, and the toast reads "Published a new link…". Distinct from Republish, which deliberately mints a fresh URL. See share-artifacts.md § Update in place.
    • Revoke — only visible while the share is still active. Confirms via useConfirmDialog, calls revokeShare, marks the row Revoked.
    • Delete — always visible, red-bordered. Confirms (danger variant), calls deleteShare, navigates back to projects on mobile so the deleted row isn't stuck on screen.

Sending a share via channel

The inline Send to… sub-panel hands the share URL off to one of three channels: Gmail, SMS, or Slack. Omniscio builds the body (optional note + share URL), dispatches via the matching service, and writes a row to share_send_log so you can audit every send — successful or failed.

  • Outcome. A success toast fires and the sub-panel collapses on a successful dispatch ("Share sent via Gmail"). A failure toast surfaces the channel's error verbatim if the channel returns a structured failure (e.g. Slack channel_not_found) — the sub-panel stays open so you can fix the recipient and retry. Revoked, expired, or unpublished shares fail the pre-flight check with Share is revoked / Share has expired / Share has not been published before any channel call.
  • Audit. Every send writes a share_send_log row — pending on insert, then flipped to sent (stamps sentAt, clears error) or failed (stamps the channel's error string, leaves sentAt null). The pending row is inserted before the channel dispatch so a thrown channel call still leaves an audit trail. List via ipc.invoke(IPC.SHARE_SEND_LOG_LIST, { shareId }) — returns rows newest-first.

How protection enforcement works

The Cloud Function that serves shares (the same one that mints the OG unfurl + renders the public page) reads the password_hash, password_salt, view_cap, and view_count columns out of the Firestore mirror on every request:

  • View cap. If view_cap is non-null and view_count >= view_cap, the CF returns a 410 "share view cap reached" page without writing a view event.
  • Password gate. If password_hash is set, the CF returns a password-prompt page that asks the visitor for the plaintext. On submit, the CF runs the same PBKDF2 derivation (600,000 iter, SHA-256, the same salt) and constant-time-compares the result to password_hash. Mismatch → re-render the prompt with an error; match → set a short-lived signed cookie + serve the share. The plaintext never leaves the verifier function.
  • View counter. Every successful render writes a share_view_events row to Firestore (shareId, viewedAt, IP-derived geo hint), atomically increments share.view_count, and stamps last_viewed_at. The Omniscio desktop poller pulls these every 60 seconds (jittered, 30-second initial delay) and mirrors the deltas back into the local SQLite shares row + appends to the local share_view_events audit table. After each poll, every share with notify_on_view = 1 whose view count advanced since the last poll fires a desktop notification — the row write is committed before the notification fires so a crash mid-fire costs at most one missed notification, never a double-fire.

Smart Share Reaper

An opt-in background sweep (setting smartShareReaperEnabled) that keeps the Shares list from accumulating forgotten "never expires" links. It finds shares and screen recordings that are over a year old AND have never been viewed, gives each a 90-day grace expiry instead of deleting anything outright, and drops a single inbox card asking you to Keep (clear the new expiry) or let it expire. Nothing is removed without that confirmation, so a still-wanted link is one click from permanent again. Runs quietly on a schedule when enabled; the shipped default is off.

Full-page screenshot

Both in-app share viewers — the quick opener (FloatingShareViewer, the full-size popup that opens share links in-app — your own OR a teammate's) and the manager viewer (ShareViewerToolbar, the detail-pane view/fullscreen overlay) — carry a Screenshot button that copies the ENTIRE scrollable shared page to the clipboard as a PNG.

  • Why it's a separate capture path. Electron's capturePage() (the AMC-window screenshot path) only captures the visible viewport, so it can't image a long scrolling artifact. The Screenshot button instead drives a Chrome DevTools-protocol capture (Page.captureScreenshot with captureBeyondViewport, clip sized from Page.getLayoutMetrics) against the share's <webview>, so the whole page is captured in one image — a distinct path from the window-capture helper. See ui-screenshot-contract.md (the full-page share-capture rules).
  • Desktop-only. The button renders only under isElectron — the mobile/web renderer has no embedded <webview> to capture — and stays inert (disabled) until the viewed page has reported its webContents id. The Copy URL button (below) works everywhere.
  • Copies to the clipboard. On success a toast confirms "Full-page screenshot copied to clipboard"; a very long page is clamped to a safe maximum (12,000 px/edge) and the toast reads "…(very long page — trimmed)". Any failure surfaces a friendly message, never a raw error. The capture + clipboard write happen in the main process — the image bytes never round-trip back to the renderer.
  • Security. The capture only ever targets the share's own <webview> (a live persist:browser guest); a forged or foreign target id, the main window, and agent tabs are all refused (ui-screenshot-contract only-a-share-webview-is-capturable).
  • Copy URL. The quick opener's header also carries a Copy URL button (the app's canonical CopyConfirmButton with the "Copied ✓" confirmation), on desktop and mobile — matching the Copy URL the manager viewer's header band + toolbar already had.

Implemented by the shared useShareWebviewCapture hook + ShareScreenshotButton component (one unit dropped into both surfaces); the main-process capture is captureShareWebviewFullPage() behind the IPC.UI_CAPTURE_SHARE_FULL_PAGE channel.

For agents

Settings → Sharing — what's still there

The Settings → Sharing page is no longer the list view. It now contains:

  • A migration banner explaining that the list moved, with a primary Go to Shares button that activates the Shares virtual project (activateVirtualProject(SHARES_PROJECT_ID, 'settings-sharing-banner')).

  • An Open shares in app toggle (sharesOpenInApp setting, default ON; desktop-only — the in-app viewer is Electron-gated, so web/mobile always open in the browser). When ON, share links open in the built-in viewer instead of the system browser. POST /share/publish swaps data.url to a deep-link URL (omniscio://share/<token>) and always includes data.publicUrl for the HTTP URL. Existing installs are moved to the new default once by a one-time migration (migrate-shares-open-in-app-default), then any later opt-out is respected. The ShareDialog "Open" button also respects the setting: when ON it navigates in-app via interceptShareUrl(url, { openInViewer: true }) instead of window.open. See share-cli.md for the CLI response shape.

    Show new shares automatically (sharesAutoOpenOnCreate)

    A sub-toggle nested under Open shares in app — only visible and active when sharesOpenInApp is ON. Defaults to OFF (opt-in). When both toggles are ON:

    • Shares you create yourself (from the Share dialog or the Digest Share dialog) open immediately in the built-in viewer the moment they're published, via interceptShareUrl(url, { openInViewer: true }) — no need to click "Open" manually. The viewer mounts and starts loading at the earliest signal; the normal brief loading spinner is still visible while the share page loads. (Every in-app share link that appears on screen — your own OR a teammate's — is background-preloaded into a kept-alive hidden <webview> slot — capped at SHARE_PREWARM_MAX_SLOTS, desktop-only — and clicking one PROMOTES that already-loaded page into the floating viewer, so it renders instantly instead of a 1-2s network load. The share server serves every page Cache-Control: no-store, so a cache-warm can't help; keeping the loaded page alive is what makes the click instant. See FloatingShareViewer / SharePrewarmSlot under Component layout.)
    • Shares an agent publishes via POST /share/publish raise a one-click "Go there" toast through the shared suggestAgentNavigation() path — never a forced foreground switch or view change. The toast carries only the share token (never a raw URL); the viewer resolves it from your local share list when it's your own, otherwise builds the canonical shares.omniscio.com/s/<token> URL from the token — so an arbitrary or hostile URL still cannot be coerced into the viewer this way. Clicking the toast opens the share in the built-in viewer via the { action: 'open-share-in-viewer', token } deep-link route. If you're in another app when the toast would fire, it becomes a durable inbox card instead — the same behaviour as any other agent nav-suggestion. Toasts coalesce per source-session (a looping agent bumps one toast, latest label wins).
  • The Firebase Configuration collapsible section, which lives there because it's backing config — the service-account key, bucket name, etc. that the share publisher reads at upload time. Configuration belongs in Settings; the list of shared links does not.

This split is intentional: a share is content (lives in its own view); the Firebase service-account key is configuration (lives in Settings).

Related

Last verified 2026-09-23