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

Share Artifacts (publish local files and pasted content as links) (part 2)

The opposite of "Republish as new": keep the same public link but swap in new content. Use it when you regenerated a report at the same path, or want an already-shared link to reflect an edit without re-sending it — viewers of the old URL automatically see the new version. It reuses the share's existing token via the service's republishToken → updatedInPlace path (the same in-place mechanism KMS/Vault page-publishing uses, generalized to any artifact share).

What it is

This is part 2 of the Share Artifacts (publish local files and pasted content as 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 Share Artifacts (publish local files and pasted content as 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 Share Artifacts (publish local files and pasted content as links) page.

Update in place (keep the URL)

The opposite of "Republish as new": keep the same public link but swap in new content. Use it when you regenerated a report at the same path, or want an already-shared link to reflect an edit without re-sending it — viewers of the old URL automatically see the new version. It reuses the share's existing token via the service's republishToken → updatedInPlace path (the same in-place mechanism KMS/Vault page-publishing uses, generalized to any artifact share).

  • In the app: an Update action on a LIVE, file-backed artifact share in the Shares view detail pane re-reads the share's source file and refreshes the same URL (behind a confirm, since it overwrites what the link serves).

  • From a script / agent: pass updateToken: <the share's token> on POST /share/publish alongside your new path / content — see share-cli.md. The response returns the SAME url with updatedInPlace: true.

  • Guardrails: the target must be a live (non-revoked, non-expired) artifact share — a thread / digest / unknown token is refused (No such active share to update). The link's existing expiry is preserved (a content update never changes the lifetime; use the Edit form / SHARE_UPDATE to change expiry). If the cloud relay is ever too old to reuse the token, it falls back to minting a fresh link and retiring the old one — so you never end up with two live copies — and the response's updatedInPlace: false tells you that happened.

  • Verify it landed. The response carries contentHash + sizeBytes for the blob actually stored (matching what GET /share/list reports). ok: true means the call succeeded, not that your bytes are the ones now served — so on an update, compare sizeBytes with your source rather than trusting the status. If a content object cannot be written the publish now FAILS (firebase-upload-failed) instead of reporting success over stale bytes: for an html / react share the artifact is the separate run.html object, and refreshing only the shell would leave the previous page serving.

  • An update also re-points the share at the file it published. Update a share from a DIFFERENT file and the share now remembers THAT file, so a later Update (or a Republish) re-publishes the newest file — not the one the link was first created from.

Full invariant set (validate-the-target, preserve-expiry, retire-on-fallback, replace-the-bytes-or-fail, refresh-hash-and-size-together, repoint-the-source, never-tear-down-the-existing-share, additive/opt-in): share-artifacts-invariants-part2-contract.md § update-token-refreshes-in-place.

Unfurl previews (OpenGraph + Twitter cards)

Every published share emits <meta property="og:title">, og:description, og:type, og:url, og:image, plus twitter:card="summary_large_image" inside the HTML shell. The title is the share's title (falling back to label, then the filename); the description is the label truncated to ~200 chars; the image is a per-share 1200×630 PNG uploaded as a sibling blob and served at /s/<token>.png (a static, fast path — the Cloud Function never has to render at request time).

The card. One branded design for every kind, because an unfurl is Omniscio presenting itself outside the product: an obsidian plate lit by the aurora glow, the deep-fill gradient hairline across the top, the orb + Omniscio wordmark top-left, a plain-English kind badge top-right (WEB PAGE, DOCUMENT, NOTE — never the internal id), the share's title as the hero (wrapped to at most three lines and auto-fitted 66px → 38px), and shares.omniscio.com on a short accent rule at the foot. Each kind gets its own accent colour, so a wall of shares still reads as distinct. An untitled share falls back to a readable hero ("Shared note") rather than an empty plate.

  • Images become the centerpiece of that same frame — letterboxed (never cropped, so an odd aspect ratio loses nothing) with the title demoted to a caption beneath. Source bytes over 4 MB skip the embed and get the title-hero card instead.
  • Every other kind (html / react / markdown / svg / pdf / code / text / thread / pasted-text / board) gets the title-hero card.

Rendering is one path for all of them: share-thumbnail-card.ts builds the SVG (pure — no Electron, no fs), and share-thumbnail.ts rasterizes it through the isolated render-worker (@resvg/resvg-js in a utilityProcess child, bundled Noto Sans, no Chromium and no system-font load).

The result: paste a share URL into iMessage, Slack, Twitter, Discord, etc. and the unfurl shows a real title + description + a branded per-share card instead of a generic Firebase Hosting fallback. Thumbnail rendering failure does NOT block the publish — og:image is simply omitted and twitter:card degrades to summary, logged as non-fatal.

Limits

  • Image upload cap: 22 MB raw bytes. Larger images throw size-cap because the base64-expanded HTML would exceed the 25 MB final-blob budget.
  • PDF upload cap: 18 MB raw bytes. Same reason — base64 expansion is roughly 4/3 × raw.
  • Pasted content cap: 2 MB UTF-8 (enforced by both the IPC schema and the CLI body schema).
  • Final HTML blob cap: 25 MB. The builder throws size-cap if the rendered HTML exceeds this even when the source bytes were under their individual caps.
  • No per-user share count cap. The share_links table is unbounded; the Settings → Sharing view lists every active row with a Revoke button so you can prune.
  • Expiration: Optional per share. Choose Never, 1 hour, 1 day, 7 days, or 30 days from the modal; pass expiresIn seconds for CLI calls (60 to 60 * 60 * 24 * 365). Expired shares return 410 Gone from the Cloud Function and the blob is auto-deleted on the first 410 response.
  • Never-expiring cleanup (Smart Share Reaper): A Never share (or screen recording) that goes over a year old AND unviewed for 90+ days gets a Keep / Delete inbox card and a 90-day grace expiry — Keep it (snoozes a year) or let the grace lapse and it's reclaimed. A share still being viewed is never touched. Shipped 2026-07-23; a server-side GCS lifecycle rule backstops shares from users who never reopen the app. See smart-share-reaper-contract.md.
  • Firebase free-tier quota: 5 GB storage, 1 GB/day egress (default Spark plan; Blaze plan is unbounded). When the publish path hits a quota error from the Firebase SDK, Omniscio throws firebase-quota; the renderer IPC envelope surfaces this with an actionUrl of /settings/storage so the toast deep-links to the configuration UI.

Security model

  • Token-as-credential: The 16-hex token in the URL is the only thing protecting access. Anyone with the URL can view the content. Treat shared URLs the same way you'd treat a "view-only Google Drive link" — fine for non-sensitive content, never for secrets / credentials / private PII.
  • HTML and React run from a separate same-origin document, isolated by a sandbox CSP (opaque origin): The author's runnable code is NOT inlined into the shell. It is served as its own document at /s/<token>/run (stored as shares/<token>.run.html) and embedded by the shell via <iframe src="/s/<token>/run" sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads"> — src, not srcdoc. The run doc is served with the response header Content-Security-Policy: sandbox allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-modals allow-downloads (Claude.ai's flag set minus allow-same-origin, and with no allow-top-navigation). Why src and not srcdoc: a srcdoc document (about:srcdoc) inherits the embedder's CSP, so the shell's script-src 'none' would block every script — which is exactly why the old srcdoc design ran nothing in a real browser (see "Security model" history below). A separately-served document carries its OWN script-permitting policy. The result: scripts run, but in an opaque origin — they cannot read cookies / localStorage / sessionStorage of the share origin or any sibling share on shares.omniscio.com, cannot persist any web storage at all (localStorage access throws), cannot reach Omniscio, and cannot navigate the top tab. The same sandbox CSP is set on the /run response itself, so even a direct top-level hit on /s/<token>/run is sandboxed — it can never execute as the un-sandboxed share origin. Scripts CAN make fetch() to third-party origins, write to their own DOM, and use alert/confirm/prompt. Threat model: the run doc is treated the same as opening a third-party site in a new tab — mutually distrustful with everything else on the host.
  • The shell still serves script-src 'none'; it gained frame-src 'self': The outer page that frames the iframe runs no code of its own (its chrome — title, label, expiry footer — is static). Its CSP keeps script-src 'none' so nothing inside the iframe can tamper with the shell, and adds frame-src 'self' so it is allowed to frame the same-origin run doc. The shell, not the run doc, is the only script-free trusted surface.
  • The /run doc runs the SAME access gate as the shell: A request to /s/<token>/run is checked for revoked / expired / password exactly like /s/<token> (the gate logic is shared, so the two routes can never drift). Password protection "just works" with no separate handshake because the unlock cookie is scoped Path=/s/<token>, which is also sent to /s/<token>/run. The view cap is counted only by the shell — the run doc is a sub-resource of an already-counted view, so it never double-counts (and the cap check stays correct on the last allowed view). Known minor limitation: the soft view-cap is not separately enforced on a directly-saved /run URL; password / expiry / revoke ARE enforced there.
  • React loads from unpkg.com: React 18 + ReactDOM + Babel-standalone are loaded from the public unpkg CDN — same source Claude.ai's artifact runtime uses (unchanged by the run-doc refactor). The run doc's own <meta> CSP pins script-src to 'self' https://unpkg.com 'unsafe-inline' 'unsafe-eval' — Babel needs unsafe-eval to transpile — bounded by the opaque-origin sandbox. There's no integrity check on the unpkg responses; an unpkg outage / compromise would affect every published React artifact globally. We accept that risk because it matches Claude.ai's posture and the alternative (bundling React into every share blob) costs ~150 KB per share.
  • Markdown and text are still static: Even though sibling HTML artifacts run scripts, the text / code renderers HTML-escape the source before injecting it into the share-page shell — a .txt file containing <script>alert(1)</script> will display the literal characters, not execute. Markdown goes through the audited sanitize-html allow-list in the shared renderer (RT-F072): <script>, event handlers, javascript: / unsafe data: URLs, iframes, forms, styles and HTML comments are removed (not shown), safe markup such as <b> or an inline <img> survives, an <input> survives only as a disabled checkbox, and id / class attributes are restricted to the footnote anchors so a document can never clobber the shell's own elements. One sanitizer serves every markdown surface; a second marked pipeline under src/main is refused by tests/unit/lint/main-process-markdown-renderer-single-source.test.ts, and the per-feature rendering is pinned by tests/unit/services/share-markdown-fixture.test.ts.
  • SVG sanitization: SVG is the one type where the file content IS HTML-rendered. The builder strips <script>, <foreignObject>, <object>, and <embed> elements plus event-handler attributes (onload=, onclick=, etc.) and javascript:/unsafe-data: URLs before embedding. (<object>/<embed> are stripped so a shared SVG can't abuse the share page's object-src data: PDF grant.) Defence-in-depth, not zero-trust — if you don't trust an SVG, don't share it.
  • Path allow-list: Source paths must live inside an active session's working directory OR Omniscio's user-data dir. Symlinks resolve through fs.realpath BEFORE the check, defeating symlink-escape attacks.
  • Magic-byte check: Every type that has a well-known magic-byte signature (PNG, JPEG, GIF, WebP, PDF, SVG) is verified at submit time. Renaming malware.exe to cute.png and trying to publish it returns magic-byte-mismatch.
  • Token entropy: 16 hex chars = 64 bits. Brute-forcing the URL space is computationally infeasible.
  • No CSRF surface: Publish is a renderer-process IPC call (no cross-origin path) and a localhost-only CLI POST (bearer-token gated). There is no public HTTP endpoint for publishing.
  • Renderer-side spoofing is impossible: sourceType (the telemetry origin label — 'file-peek' / 'paste-modal' / 'chat-link') is derived server-side from the request shape (the presence of sourcePath vs pastedContent vs attachmentRef, plus a Zod-whitelisted origin: 'chat-link' discriminator for the right-click entry point). It is NOT accepted as an arbitrary IPC field. The renderer cannot lie about where the publish came from.
  • attachmentRef resolution is main-process only: When the chat-link menu publishes an attachment://<sessionId>/<filename> URL, the renderer sends { sessionId, filename } only — never an absolute path. The main process resolves it to <userData>/attachments/<sessionId>/<filename> via getAttachmentPath(), which is already inside the user-data allow-list. The renderer has no way to read or guess the user-data dir.

Why same-domain, and the real-browser lock

The runnable doc is deliberately served from the same domain as the shell (not a separate sandbox domain). The opaque-origin sandbox CSP already isolates the code, password "just works" via the Path=/s/<token> cookie reaching /run, and "don't save anything" is satisfied by the opaque origin (web storage throws). A separate domain would add deployment + cookie-scoping complexity for no extra isolation.

Security-model history (do not regress): the earlier v2 design wrapped the runnable code in an <iframe srcdoc=…> inside the shell, under the shell's script-src 'none' CSP. That was verified only in jsdom — which does not enforce CSP — so the "109 tests green" run was misleading: in a real browser an about:srcdoc document inherits the embedder's CSP, so script-src 'none' blocked every script and HTML/React shares shipped completely inert (no code ever ran). The fix is the separate-origin /s/<token>/run doc described above. The regression guard that v2 lacked is tests/integration/share-run-doc-browser.test.ts — a real Chromium asserts (1) the shell's own inline script is blocked, (2) the /run doc's script DOES run, and (3) the run doc is opaque-origin (reading localStorage throws). Re-collapsing /run back into srcdoc, weakening the shell CSP so it runs scripts, or adding allow-same-origin turns that test red. Full invariant set: .claude/memory/contracts/share-artifacts-contract.md.

Troubleshooting

"outside-allowlist" — the renderer auto-stages out-of-scope sourcePath values by copying them into <userData>/published-pastes/ before validating, so for renderer publishes this error surfaces in two cases: the stage itself failed (file moved or deleted between right-click and stage, missing read permission, disk full), or the file was refused outright because its name is credential-shaped — a secrets file is never staged, so it has no stage step to fail and no confirm to click through. In the first case the renderer maps it to a clearer "Could not access the file — it may have been moved, deleted, or is locked by another program." toast; the literal outside-allowlist string is what the service throws. For CLI publishes (sourceType: 'cli') the allow-list still rejects external paths with this error — scripted callers must stage their own files into an allow-listed dir first.

"unsupported-type" — the file extension isn't in the supported-types list above. The publish pipeline deliberately rejects Office / archives / binaries because the share page has no useful renderer for them. Workaround: open the doc, copy its text, paste into the modal as Markdown.

"magic-byte-mismatch" — the file's extension and its actual content disagree. Check the file's true type; re-extension it or open it in its real app and Save As the correct type.

"invalid-encoding" — a text file (HTML, Markdown, text, code, React, SVG) whose bytes are not valid UTF-8 — typically saved as Windows-1252 by an older editor, Windows PowerShell Set-Content, or Python's open(path, 'w') on Windows. Omniscio never guesses another encoding, because a wrong guess publishes garbled text under a public link. Fix: re-save the file as UTF-8 and publish again. The check runs before anything is uploaded, so a refused republish leaves the existing link serving its old content. The CLI answers 400 with a hint field saying the same; the app shows "This file isn't saved as UTF-8 text." A file saved as UTF-16 with a byte-order mark (Windows PowerShell > / Out-File, Notepad's "Unicode") is NOT refused: it declares its own encoding, so it is converted to UTF-8 with every character intact and publishes normally.

"empty-file" — zero-byte file (or a file that is nothing but a UTF-16 byte-order mark). Nothing to publish.

"size-cap" — the file (or the rendered HTML for it) exceeds the 25 MB final-blob budget. For images, downscale; for PDFs, compress or split.

"sign-in-required" — the user is not signed in, and publishing a share now requires sign-in (the relay authenticates each publish with the user's global-auth ID token). The renderer surfaces "sign in to share"; no share row is created and nothing uploads. The Share UI now flags this UPFRONT (so you rarely hit the error): when signed out of the cloud, the Share dialog and the Publish-pasted-content modal show a "Sign in to your account to publish shares" prompt with a Sign-in button and disable their publish button, and the quick-share triggers (the file-peek Share icon, the chat "Share as public artifact" menu item) show a "Sign in to publish shares" tooltip. Sign-in reuses the desktop Google/GitHub OAuth; on the mobile web app it shows a "sign in on the desktop" hint (the OAuth loopback is desktop-only). This is a convenience layer — the relay still enforces sign-in on the actual publish. It can also surface LATE (2026-08-26): if your session lapses between the upload and the publish-time mirror read-back, that read-back comes back "signed out", which never self-heals — so the publish fails and rolls back with this same actionable code rather than the opaque share-mirror-unavailable, and it raises no "cloud degraded" alert (being signed out is a local state, not a cloud outage).

"share-network-unreachable" — the desktop couldn't REACH the relay at all: no internet, a DNS failure, a refused/reset connection, or a request timeout. Detected structurally (the errno on err.code / err.cause.code, or an HttpTimeoutError), never from message text, so it's split out from the generic upload failure and points the user at THEIR connection (wifi / VPN / firewall): "Can't reach the share service — check your internet connection (or VPN / firewall) and try again." A user-environment condition, so it does NOT raise a fleet Sentry crash; the CLI route maps it to 502. This now also covers a publish-time mirror-confirm TIMEOUT — a share whose mirror write timed out (vs. the relay actively refusing it) surfaces here, quiet, instead of the louder share-mirror-unavailable (Sentry 7640659225).

"share-server-error" — the relay answered with a 5xx (HttpError.status >= 500) — the problem is on the service side, not the user's connection, so the copy says "The share service is temporarily unavailable — please try again in a few minutes." A real backend fault, so it DOES stay Sentry-visible; the CLI route maps it to 502.

"firebase-upload-failed" — the RESIDUAL relay mint / PUT failure, once offline (share-network-unreachable) and server-5xx (share-server-error) are split out above — an upload error we couldn't classify further ("Couldn't upload the share. Please try again in a moment."). Check <userData>/logs/main.log (the raw cause is logged before the mapped code is thrown). No Firebase key lives on the desktop anymore. The copy no longer blames the connection (that's the network code's job); the CLI route maps it to 502.

"firebase-upload-failed" / a 500 publishing HTML/React — historically a full-bleed HTML/React publish (inline content OR an allowlisted path) could return a generic 500 while plain-text / markdown / a fullBleed:false HTML publish still succeeded, because the deployed shareRelay Cloud Function was stale and rejected a share object kind the desktop app mints (main.log: share relay mint-upload returned 400: objects[N] must be a known object kind). The functions deploy as a separate artifact (no predeploy build hook), so a source kind-change — e.g. the run.inner.html kind a full-bleed publish mints — isn't live until redeployed. Two hardenings now prevent the total outage that caused: the relay skips an unrecognized object kind and mints the rest instead of 400-ing the whole publish, and it keeps accepting + serving backward-compat kinds (run.inner.html for shipped older full-bleed clients) — the server must support the UNION of every shipped client's kinds. The desktop also classifies a relay mint/upload failure as firebase-upload-failed (a 502) rather than a bare 500, so it's diagnosable. If publishes still fail because the deployed functions are genuinely stale or missing a needed kind — Fix: npm run cloud:share:deploy-functions (builds then redeploys shareRelay + serveShare). The client↔server object-kind rule (client ⊆ server), the runtime skip, and the deploy + backward-compat coupling are locked by share-artifacts-contract.md § object-kinds-client-subset-of-server.

There are TWO deployments of this relay, and the one the desktop calls is not the Firebase one. shareRelay was ported into the amc-back repo (apps/share-api, on the shared amc-backend Cloud Run service at https://amcback.jls.dev/share), and current builds POST there directly, bypassing Hosting and the Cloud Function entirely — see share-relay-transport-config.ts. npm run cloud:share:deploy-functions deploys only the Firebase copy, which now serves already-installed older builds; a fix for what the app in front of you is doing usually has to land in amc-back and deploy with its own main push. The port is a hand-copy with no mechanical sync, so it drifts silently — and it did: it shipped missing the mirror-changes op, and every install 400'd once a minute for 11 days with nothing raising an alarm. apps/share-api/tests/relay-op-parity-guard.test.ts pins the port's op surface against upstream so that cannot recur silently. A stale-deploy diagnosis that only checks the Firebase function reads green while the relay users talk to is stale.

"firebase-quota" — Firebase Spark-plan free-tier exceeded. The renderer-side toast deep-links to Settings → Storage; you can either prune old shares or upgrade to the Blaze pay-as-you-go plan.

"share-mirror-unavailable" — the bytes uploaded, but the public share record (the Firestore "mirror" doc the link-checker gates on) couldn't be confirmed after retries, so the link would 404. Rather than hand back a dead link, publishing rolls back (deletes the upload + the row) and surfaces "Couldn't finish publishing the share — the share service is temporarily unavailable. Please try again in a moment." This code is now reserved for a genuine confirm failure (the relay refused it — permission / auth, or a 5xx, or the mirror write acked but a read-back found no serve-visible doc, or the read-back couldn't tell for a reason that will never clear on its own — a forbidden read or an open circuit breaker); a persistent one also raises a single "cloud features degraded" inbox alert and stays Sentry-visible. A pure network/timeout blip no longer lands here — it surfaces as the quieter share-network-unreachable (no Sentry, no degraded alert), since a timeout is a self-healing transient, not a broken mirror (Sentry 7640659225). Either way the publish rolls back, so you never get a dead link — retry. See the packaged-firebase-admin-auth postmortem for the 2026-06-29 incident this guards against.

"Share Not Found" / the link 404s — a share's bytes live in Cloud Storage, but the public serveShare Cloud Function gates on a Firestore mirror doc at shares/{token}; no doc → 404. This can no longer happen silently: publishing now CONFIRMS the mirror landed before returning success — it does not merely trust the relay's write-ack, it reads the mirror doc back and only reports success once the doc is actually present (a write that acks but leaves no serve-visible doc is retried, then fails loud with share-mirror-unavailable). When the read-back genuinely can't tell either way, the REASON decides (2026-08-26). A wall-clock timeout self-heals, so it still accepts the write and leans on the backfill. Signed-out, forbidden, and circuit-breaker-open do NOT self-heal — and the backfill can't repair a signed-out or a different-owner share either — so those now FAIL the publish and roll it back. Accepting them was exactly how a share could hand back a working-looking URL, sit in your list as published, and 404 forever. A self-heal backfill also re-writes any mirror doc that went missing for an already-published share — it runs on startup AND periodically (~every 30 min), so a dead link recovers automatically within ~30 min of the cloud recovering, without needing to reopen Omniscio. The mirror is written over the same google-auth-library REST/JWT path the uploader uses, NOT firebase-admin (which presented the wrong identity in packaged builds — the postmortem above).

"Published, but failed to copy link to clipboard" — rare clipboard-permission failure. The share IS published; the URL is in Settings → Sharing (or the CLI GET /share/list response). Copy from there.

Reused link is to old content — content-hash dedup matches on the exact source bytes plus the presentation (title / full-screen / backdrop colour). If you're seeing a stale URL, the bytes AND the presentation are both identical to a previous publish. To force a new share: change the content (e.g. add a trailing newline), change the title / full-screen / backdrop, or Revoke the old share first.

Published page shows garbled characters (mojibake) — symbols like an em-dash rendering as â€", a bullet as •, or curly quotes as “ mean the content that was sent was already double-encoded before upload: a UTF-8 file read in the wrong code page (classically PowerShell Get-Content -Raw, which defaults to Windows-1252 — not UTF-8). The file on disk is fine; the upload step corrupted it, and Omniscio stored exactly what it received. Fix: publish the file by path instead of content — Omniscio reads it server-side as UTF-8 and can't be fooled by the caller's read encoding. If you must send content, read AND send it as UTF-8. A CLI publish of suspect content also comes back with a non-fatal warning that flags this. Always verify the LIVE url, not the local file render.

Full-bleed HTML/React looks small on a phone — this is by design, not a bug. A fixed-width desktop layout (no responsive CSS) is scaled down to fit the screen whole rather than cut off — "small but complete" beats "full-size but stranded". Pinch-to-zoom restores detail. If you want it to fill the phone screen instead of shrinking, give the artifact a responsive layout — a @media query or a <meta name="viewport" content="width=device-width"> — and Omniscio renders it full-width in its phone layout (since 2026-08-10 it hosts a responsive page at the device's real width so your mobile breakpoints actually fire; before that a phone laid the framed sub-page out at ~980px and shrank it). A fluid width:100% page with no breakpoints simply fills the width directly. A CLI publish of a fixed-width html/react content returns a non-fatal warning flagging this. (Note: this only affects newly published shares; an old share keeps its stored HTML — re-publish it to pick up the fit treatment.)

View stats stopped syncing / a "N older shares can't sync view stats" inbox notice — shares published before the 2026-06-30 move to per-account sharing were written with no owner tag, so Omniscio's owner-scoped read of their view stats was refused (403). Their public links still work and keep counting views — only the in-app stat sync was affected. Omniscio now auto-reclaims these on startup: the share relay adopts an un-owned legacy record on write, so a signed-in Omniscio re-owns each one and its stats resume syncing — for a signed-in user this heals itself on the next launch and no notice shows. The deduped inbox notice appears only for whatever is STILL un-synced afterward — you're signed out, or a share is genuinely owned by a different account — and signing in with the owning account then restarting re-links them. (Omniscio also stopped flooding main.log with [ShareMirror] 403 while a share is still un-synced.) Mechanism: the relay's legacy-adopt + the startup reclaim + the persisted per-token mirror-read circuit breaker — share-artifacts-contract.md § mirror-written-via-the-relay + mirror-reads-trip-a-breaker.

For agents

CLI

The CLI control server hosts four endpoints that mirror the renderer-side flow — POST /share/publish, GET /share/list, POST /share/:token/revoke, DELETE /share/:token — all bearer-token gated and apply-immediately (no inbox approval, because publish doesn't spawn Claude). Each call lands the same share_artifact_publish telemetry event with sourceType: 'cli' so the Stats view can split CLI traffic from in-app traffic. Full curl recipes, request/response shapes, and error code mapping: share-cli.md.

Related

The overview, the other parts, and everything else worth reading next all sit on Share Artifacts (publish local files and pasted content as links).

Last verified 2026-09-30