RFAI-10: PWA share-target quick capture with offline queue#1078
Conversation
Extends CaptureSource with ShareTarget (9) and BrowserExtension (10) to support ambient capture from PWA share API and browser extensions.
Mirrors backend CaptureSource enum extension for share-target and browser extension provenance tracking.
Configures Web Share Target API so Android/iOS can share text and URLs directly into Taskdeck. Uses GET method to leverage existing SPA navigation fallback without custom service worker handling.
Implements a queue in IndexedDB that stores captures when offline and replays them via the capture API when connectivity returns. Includes retry counting (max 5) and Background Sync API registration.
ShareTargetView handles incoming share data from the OS share API, sends it to the capture API when online or queues it in IndexedDB for later sync. Registered at /capture/share as a public route.
Tests cover IndexedDB queue operations (enqueue, dequeue, retry, count) and ShareTargetView behavior (online send, offline queue, API fallback, empty content error, URL-only share, title deduplication).
Adds a Share button to CardModalActions (visible only when Web Share API is available). Uses useShareCard composable to share card title and description via the native OS share sheet.
|
Warning You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again! |
Adversarial Review R1 — RFAI-10: PWA Share-Target Quick CaptureFindingsCRITICAL-1: Unauthenticated share-target can lose captures silently CRITICAL-2: Race condition in replayQueue HIGH-3: IndexedDB HIGH-4: Router comment says "POST" but manifest uses GET HIGH-5: HIGH-6: Silent capture discard at max retries Fix Plan
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 38f848108a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| await enqueueCapture(dto) | ||
| status.value = 'queued' | ||
| } | ||
| } else { | ||
| await enqueueCapture(dto) | ||
| status.value = 'queued' |
There was a problem hiding this comment.
Wire queued share captures into replay flow
When online create fails or the device is offline, this path enqueues the capture and reports "Queued for sync", but nothing in the app mounts the new replay composable (useCaptureQueueSync) or registers background sync, so queued records are never retried automatically. I verified with repo search (rg "useCaptureQueueSync") that the only occurrence is its own definition. In normal use, shared items can remain stuck in IndexedDB indefinitely.
Useful? React with 👍 / 👎.
| const db = await openDb() | ||
| return new Promise((resolve, reject) => { | ||
| const tx = db.transaction(STORE_NAME, 'readonly') | ||
| const request = tx.objectStore(STORE_NAME).getAll() |
There was a problem hiding this comment.
Preserve FIFO order when loading pending captures
The queue store key is a random UUID, and getAll() reads records in key order, not enqueue time order. That means replaying multiple offline shares can process them in arbitrary order, which can reorder what the user shared first/last. To keep deterministic behavior, the queue should sort by queuedAt (or use an ordered key/index) before replay.
Useful? React with 👍 / 👎.
- CRITICAL-1: Check auth token before API call; show login-required state with queued content when session is invalid - CRITICAL-2: Use module-level replayInProgress flag to prevent concurrent replay across component instances - HIGH-3: Add onblocked handler to IndexedDB openDb() to prevent infinite hangs when another tab holds the connection - HIGH-4: Fix router comment (POST → GET) - HIGH-5: Wrap enqueueCapture in safeEnqueue helper with error handling - HIGH-6: Log discarded captures via logError before removal Tests updated with auth mocks and 3 new cases for login-required flow.
Adversarial Review R2 — RFAI-10: Post-Fix VerificationR1 Fix Verification
R2 Deep-Dive Results
Verdict: PASS — R1+R2 complete, all findings fixed. PR ready pending CI.Note: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 8cc35d70f5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const { canShare, shareCard } = useShareCard() | ||
|
|
||
| function handleShare() { | ||
| void shareCard(props.card) |
There was a problem hiding this comment.
Handle share failures instead of dropping rejected promise
handleShare fires shareCard with void and no catch, but useShareCard rethrows every non-AbortError from navigator.share (for example NotAllowedError when no share target is available or sharing is blocked). In those cases the click produces an unhandled promise rejection and no recoverable UX path, so users see a broken share action while the app logs runtime errors.
Useful? React with 👍 / 👎.
The composable now activates at shell mount, replaying queued captures when the user comes back online. A subtle info banner shows the pending count while items are queued or syncing. Test mocks added for both AppShell spec files to stub the IndexedDB-dependent composable.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1fe8614a9b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| method: 'GET', | ||
| params: { | ||
| title: 'title', | ||
| text: 'text', | ||
| url: 'url', |
There was a problem hiding this comment.
Use POST for share_target payloads
Defining the share target with method: 'GET' puts the shared title/text/url into the page URL, which exposes captured content via browser history, address-bar sharing, and any URL logging layer; this is especially risky because share payloads can contain sensitive notes. Since this route creates persisted capture data, sending content in query params is a privacy regression that should be replaced with a POST-based share-target flow.
Useful? React with 👍 / 👎.
| onMounted(async () => { | ||
| const title = (route.query.title as string) || '' | ||
| const text = (route.query.text as string) || '' | ||
| const url = (route.query.url as string) || '' | ||
|
|
There was a problem hiding this comment.
Prevent duplicate capture creation on share-route remount
This view processes and persists the incoming share query every time it mounts, without any idempotency guard or query-consumption step. After a successful share, users can create duplicate captures by revisiting this history entry (for example browser Back from Inbox, page refresh, or reopening the same URL), because the same title/text/url is re-submitted on each mount.
Useful? React with 👍 / 👎.
Cleanup Fix Evidence — 2026-05-16Addressed the unresolved review/bot blockers in commit
Verification run locally from
Waiting for GitHub CI on the pushed head before considering merge readiness. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ae76fe56b6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const pending = await getAllPending() | ||
| pendingCount.value = pending.length |
There was a problem hiding this comment.
Scope replayed captures to the originating user
This replay path pulls every queued record and submits it under the currently authenticated session without any owner filter, so captures queued by one account can be created in another account after logout/login on the same browser profile. Because getAllPending() returns a global origin-wide queue and entries don’t carry a user/session owner, this is a cross-user data leak risk in shared-device scenarios; persist an owner key with each queued item and only replay entries for the active user (or clear foreign entries on auth boundary changes).
Useful? React with 👍 / 👎.
| try { | ||
| const reg = await navigator.serviceWorker.ready | ||
| if ('sync' in reg) { | ||
| await (reg as ServiceWorkerRegistration & { sync: { register(tag: string): Promise<void> } }).sync.register(SYNC_TAG) |
There was a problem hiding this comment.
Add a sync-event handler for registered background sync
The code registers the taskdeck-capture-sync tag, but the imported service-worker script only listens for fetch and never handles sync, so background sync cannot actually replay queued captures when the app is closed. In that state, offline shares stay queued until the user manually reopens the workspace, which contradicts the current queued-for-sync behavior and makes the registration a no-op; add a service-worker sync handler that triggers replay (or remove the registration/wording).
Useful? React with 👍 / 👎.
|
Follow-up fix pushed for the failing Windows frontend unit lane. Root cause:
Fix commit:
Local verification:
Fresh CI is pending after the push. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 94cec5c264
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| } catch { | ||
| const queued = await safeEnqueue(dto) | ||
| status.value = queued ? 'queued' : 'error' |
There was a problem hiding this comment.
Queue only transient failures from share capture
The online path enqueues the capture for any createItem error, not just network/offline failures. If the API rejects permanently (for example validation/authz/unsupported payload), the UI reports “Queued for sync” even though replay will never succeed and the item can later be dropped by retry limits. This creates a data-loss path where users believe content was saved but it is eventually discarded.
Useful? React with 👍 / 👎.
| if (entry.retryCount >= MAX_RETRIES) { | ||
| logError('Capture queue: discarding entry after max retries', { | ||
| id: entry.id, | ||
| queuedAt: entry.queuedAt, | ||
| }) | ||
| await dequeueCapture(entry.id) | ||
| pendingCount.value-- |
There was a problem hiding this comment.
Preserve failed queued captures instead of deleting at retry cap
Queue replay deletes entries once retryCount reaches MAX_RETRIES regardless of error type. Non-transient failures (such as repeated 4xx responses or session-state issues) therefore end in silent permanent loss of captured content with no user recovery path. Consider keeping terminal failures for manual retry/review instead of hard-deleting them.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d1b6da7474
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (!entry.ownerUserId) { | ||
| logWarn('Capture queue: parking entry without an owner', { | ||
| id: entry.id, | ||
| queuedAt: entry.queuedAt, | ||
| }) | ||
| await markCaptureFailed(entry.id, 'Missing queue owner') |
There was a problem hiding this comment.
Keep unauthenticated queued captures replayable
This branch permanently parks any queue entry that has no ownerUserId, but ShareTargetView intentionally enqueues while logged out/expired and can persist ownerUserId: null (e.g., after clearAll removed session data). In that common path, the UI tells users “Content saved locally. Log in to sync it,” but replay later marks the entry failed and never submits it, creating silent data loss for logged-out shares.
Useful? React with 👍 / 👎.
| const clients = await self.clients.matchAll({ | ||
| type: 'window', | ||
| includeUncontrolled: true, | ||
| }) | ||
|
|
There was a problem hiding this comment.
Sync queued captures without requiring open app windows
The service-worker sync handler only forwards a message to existing window clients and performs no replay itself, so when background sync fires with zero open clients the event becomes a no-op. In that state, captures remain queued until the user manually opens Taskdeck, which breaks expected background retry behavior despite successful sync registration.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 847e96f217
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const title = queryValue(route.query.title) | ||
| const text = queryValue(route.query.text) | ||
| const url = queryValue(route.query.url) | ||
|
|
||
| if (title || text || url) { |
There was a problem hiding this comment.
Reject unauthenticated URL-triggered capture injection
Do not trust route.query as proof of a real OS share event. This branch accepts any title/text/url query and later calls createItem, so a third-party site can force a logged-in user’s browser to navigate to /capture/share?... and silently create inbox captures (spam/poisoning) without user intent. The share route should require verifiable share-target provenance (for example, consume only service-worker staged payloads or enforce a one-time nonce) before persisting data.
Useful? React with 👍 / 👎.
| const pending = await getAllPending() | ||
| pendingCount.value = pending.length |
There was a problem hiding this comment.
Scope queued-capture count to the active session
pendingCount is set from all pending IndexedDB entries before owner/session filtering, but replay later skips foreign-owner records. On shared browsers, users can permanently see a nonzero “captures queued” banner for entries they can never sync, and this also leaks that another account has unsynced captures. Count only entries replayable by the current session (or maintain a separate global/admin count).
Useful? React with 👍 / 👎.
|
Fix evidence for the latest review findings:
Verification run locally:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7a2ca22b67
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| try { | ||
| await captureStore.createItem(entry.dto) | ||
| await dequeueCapture(entry.id) | ||
| replayed++ |
There was a problem hiding this comment.
Claim queued entries before replaying in each client
This loop submits each pending entry to createItem before any cross-tab claim/lock is recorded, so two open Taskdeck tabs can read the same pending row from IndexedDB and both post it to the API before either tab runs dequeueCapture. In that multi-window scenario, one shared item can be created twice in Inbox even though there is only one queue record. Marking an entry as in-flight/claimed (atomically in IndexedDB) before calling createItem would prevent duplicate replays.
Useful? React with 👍 / 👎.
|
|
||
| const cache = await caches.open(TASKDECK_SHARE_CACHE) | ||
| await cache.put( | ||
| TASKDECK_SHARE_REQUEST, |
There was a problem hiding this comment.
Store share-target payloads under unique cache keys
Every incoming POST share writes to the same cache key (/capture/share-data), so a second share arriving before the first /capture/share page consumes and deletes that key overwrites the first payload. That drops user data in back-to-back share flows (for example rapid shares from another app/browser), because only the last payload survives. Using a per-share key (or queueing in IndexedDB directly) avoids this overwrite loss.
Useful? React with 👍 / 👎.
|
Fix evidence for the latest adversarial review findings:
Verification run locally in
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ff0fb10778
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| function getCurrentQueueOwnerUserId(): string | null { | ||
| return tokenStorage.getSession()?.userId ?? null | ||
| } |
There was a problem hiding this comment.
Queue unauthenticated shares without stale user ownership
When hasValidSession() is false, safeEnqueue() still stamps the queued record with tokenStorage.getSession()?.userId, which can be a stale user from an expired/cleared auth state. Because replay filters strictly by ownerUserId, captures shared while logged out can become permanently tied to the wrong account (or replay under a different user on shared devices) instead of syncing for the user who next authenticates. This is a cross-user data integrity/privacy risk in the logged-out share flow.
Useful? React with 👍 / 👎.
| await captureStore.createItem(claimed.dto) | ||
| await dequeueCapture(claimed.id) | ||
| replayed++ | ||
| pendingCount.value-- | ||
| } catch (error) { | ||
| if (isTransientCaptureError(error)) { | ||
| await incrementRetry(claimed.id) |
There was a problem hiding this comment.
Split API-submit and dequeue error handling paths
The try block wraps both createItem and dequeueCapture, so if the API call succeeds but IndexedDB deletion fails (for example blocked/transaction errors), the catch path treats it as a capture-send failure and retries/parks the row. That can replay the same already-created capture again on the next sync cycle, producing duplicate inbox items. Handle dequeue failures separately so a successful server create is not retried.
Useful? React with 👍 / 👎.
Chris0Jeky
left a comment
There was a problem hiding this comment.
Fresh adversarial review at head ff0fb10778e831f93a84aa20dcff205a10ad4692 found two remaining issues.
HIGH
frontend/taskdeck-web/src/views/ShareTargetView.vue:37/:64- Logged-out share captures are still queued with whatevertokenStorage.getSession()?.userIdreturns, even whenhasValidSession()has already decided the token is missing or expired. That means a share made while unauthenticated can be pinned to stale session metadata, then either stay hidden from the next real user because replay is owner-filtered, or sync under the stale account on a shared browser. Queue login-required captures ownerless when the token is invalid/missing, and add a regression wheregetToken()is null butgetSession()still returns a user.
MEDIUM
frontend/taskdeck-web/src/composables/useCaptureQueueSync.ts:98- The sametrywraps bothcaptureStore.createItem(claimed.dto)anddequeueCapture(claimed.id). If the API create succeeds but IndexedDB deletion fails, the catch treats it like the send failed and callsincrementRetry/markCaptureFailed, leaving a row that can be replayed into a duplicate inbox item. Split the API-submit and local-dequeue error paths so a server-created capture is not retried after local cleanup fails.
Requested fix verification status:
- No route-query content persistence: verified. Legacy
title/text/urlquery payloads are cleared and treated as empty. - Unique share cache keys: verified. POST payloads are cached under
/capture/share-data/{shareId}. - Owner-scoped queue counts: verified. Counts use
getPendingCountForOwner()/getPendingForOwner(). - Replay claim before POST: verified.
claimCaptureForReplay()runs beforecreateItem().
Commands run:
git fetch origin pull/1078/head:refs/remotes/origin/pr/1078 pull/1079/head:refs/remotes/origin/pr/1079 pull/1080/head:refs/remotes/origin/pr/1080 main- PASSnpx vitest --run src/tests/utils/captureQueue.spec.ts src/tests/composables/useCaptureQueueSync.spec.ts src/tests/views/ShareTargetView.spec.tsin.worktrees/pr-1078/frontend/taskdeck-web- PASS, 33 tests
Note: GitHub would not allow a REQUEST_CHANGES review from the PR owner account, so this is posted as a comment review.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 49564201ac
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| event.request.method !== 'POST' || | ||
| url.origin !== self.location.origin || | ||
| url.pathname !== '/capture/share' |
There was a problem hiding this comment.
Require trusted provenance for share-target POST payloads
The fetch handler accepts any same-origin POST /capture/share request and stages its fields for automatic capture replay, so a third-party site can submit a hidden cross-site form to this endpoint and inject inbox items for a logged-in user (their local token is then used by ShareTargetView without extra user intent). Fresh evidence: even though legacy query payloads are now dropped, this new POST path still has no origin/provenance check before caching and redirecting, which keeps the capture-injection vector open.
Useful? React with 👍 / 👎.
|
Fix evidence for current-head review findings at
Verification run locally in
One command was run from the worktree root by mistake ( |
|
Fresh adversarial review for PR #1078 at head HIGH
Prior finding verificationVerified fixed at current head:
Existing comments / botsChecked issue comments, inline review comments, pull reviews, and PR body. The Gemini quota warning and Copilot review error are non-actionable bot status messages. Earlier Codex/owner review findings have fix evidence or code fixes, except the current-head Codex P1 trusted-provenance finding above, which remains valid. Commands run
Note: the first direct |
|
Fix evidence for current-head HIGH finding at
Verification run locally in
Fresh CI is pending on the pushed head. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a824be3d8d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const fetchSite = request.headers.get('Sec-Fetch-Site') | ||
| if (!fetchSite) { | ||
| return true |
There was a problem hiding this comment.
Reject share-target POSTs without provenance headers
The trust gate still allows unverified POST /capture/share requests when browsers omit both provenance headers, because it only rejects mismatched Origin and then returns true when Sec-Fetch-Site is missing. In browsers/privacy modes that suppress these headers, a cross-site form post can still stage capture data and inject inbox content under an active session. Fresh evidence: isTrustedShareTargetPost accepts the request on the !fetchSite path even when Origin is absent.
Useful? React with 👍 / 👎.
| if (!isTransientCaptureError(error)) { | ||
| status.value = 'error' | ||
| return |
There was a problem hiding this comment.
Preserve share payloads when API returns auth errors
If local token checks pass but the API responds with 401/403 (for example server-side revocation, key rotation, or clock skew), this branch treats the failure as non-transient, sets error, and drops the shared content instead of preserving it for re-auth. That causes data loss in a recoverable login scenario: users shared valid content, but it is neither created nor queued for retry after sign-in.
Useful? React with 👍 / 👎.
|
Fresh adversarial review for PR #1078 at head HIGH
MEDIUM
Prior finding verificationVerified fixed at current head:
Existing comments / botsInspected the PR body, issue comments, inline review comments, pull reviews, and bot comments through the latest current-head bot review. Gemini quota and Copilot review-error comments are non-actionable status messages. The latest Codex inline comments at Commands / checks
|
|
Follow-up fix evidence at
Verification run locally in
Fresh CI is pending on the pushed head. |
|
Follow-up fix evidence at
Verification run locally in
Fresh CI is pending on the pushed head. |
|
Fresh adversarial review for PR #1078 at head Verification of prior findings
Existing comments / botsInspected PR body, latest diff, issue comments, inline review comments, pull reviews, and bot surfaces. All actionable Codex/GitHub review findings I found are addressed by current code or current tests. The Gemini quota warning and Copilot review error are non-actionable bot status messages. Residual risks / test gaps
Commands run
Note: three initial |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9330b5e953
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (isOnline.value) { | ||
| void replayQueue() | ||
| } |
There was a problem hiding this comment.
Trigger queue replay after session hydration
This replay is only kicked off during onMounted, but replayQueue() immediately returns when currentUserId is null. In this app, session.restoreSession() runs in App.vue's mounted hook, which happens after child mounts, so useCaptureQueueSync can mount first with userId === null and skip replay; since there is no watcher for later userId changes, queued captures can remain unsent until an online event or remount occurs. This breaks the normal startup path for users with persisted sessions and pending offline captures.
Useful? React with 👍 / 👎.
Summary
navigator.sharefrom card modal for sharing cards via native OS share sheetCaptureSourceenum withShareTarget(9) andBrowserExtension(10) on both backend and frontendPartial delivery of #982 — covers PWA share-target inbound/outbound and offline queue. Browser extension prototype to follow.
Test plan
captureQueue.spec.ts— 6 tests for IndexedDB queue operations (enqueue, dequeue, retry, count)ShareTargetView.spec.ts— 7 tests (online send, offline queue, API fallback, empty error, URL-only, dedup)vue-tsc --noEmitpasses cleanly