Skip to content

feat: Live Updates via stream Worker (AP-25)#105

Merged
isuttell merged 6 commits into
mainfrom
cursor/live-updates-stream-worker-d095
May 27, 2026
Merged

feat: Live Updates via stream Worker (AP-25)#105
isuttell merged 6 commits into
mainfrom
cursor/live-updates-stream-worker-d095

Conversation

@isuttell

@isuttell isuttell commented May 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Phase 4 Live Updates (ADR 0069): apps/stream Worker with per-artifact Durable Objects, SSE connections, API authorize/notify hooks, and web client iframe refresh on publish.

Follow-up (CodeRabbit + coverage)

  • Coverage: focused unit tests raise branch coverage to ~81% (global threshold 80%).
  • Resilience: best-effort workspace disconnect fan-out; DO notify logs non-2xx status/body; stream authorize/JSON paths return null/400 instead of 500; hub fan-out isolates per-connection send failures; SSE close() runs controllerRef.close() even when onClose throws.
  • SSE lifecycle: connection close callbacks terminate the SSE stream before hub removal (production DO + memory harness).
  • Web: subscribe to live updates before initial iframe_src; CRLF/LF frame parsing; upstream request.signal on live proxies.
  • Local harness: stream text/event-stream responses incrementally (no arrayBuffer() buffering).
  • Docs: apps/stream moved to Components table; status ledger dates updated.

Verification

  • pnpm test:coverage — 785 tests, 80.93% branches
  • pnpm verify — 80 Turbo tasks

Linear Issue: AP-25

Open in Web Open in Cursor 

Summary by CodeRabbit

  • New Features

    • Live Updates: real-time SSE revision notifications for access-link and dashboard viewers via a new stream worker and Durable Object-backed fan-out.
    • Artifact detail responses include a viewer (iframe_src + render_mode); web UI embeds and live-updates the published viewer.
    • Viewer capacity limit with 503-at-cap and automatic disconnects on deletion/lockdown; web proxies to the stream worker; local dev runs a stream server.
  • Documentation

    • Added stream worker docs and updated project status, README, and implementation notes.
  • Contracts

    • Live-updates protocol schemas and types added.
  • Tests

    • Broad test coverage for stream, API, client, and contract behaviors.

Review Change Stack

Add apps/stream with per-artifact Durable Objects that fan out published-revision
pointers over SSE. The api worker authorizes connections for stream via an internal
route, notifies DOs after publish, and disconnects viewers on lockdown or deletion.

Wire web viewers on /al/* and dashboard artifact detail to same-origin SSE proxies,
extend WebArtifactDetailResponse with a signed viewer iframe, and update deploy/local
harness plus status docs for ADR 0069.
@linear-code

linear-code Bot commented May 27, 2026

Copy link
Copy Markdown
AP-25 Phase 4: Implement Live Updates via stream Worker

Context

ADR 0069 Live Updates are parked until multi-revision Artifacts, latest-moving Access Links, and viewer surfaces exist.

Source docs

  • docs/ops/status/phase-backlog.md
  • docs/ops/live-updates-todo.md
  • docs/adr/0069-live-updates-via-stream-worker-and-per-artifact-durable-object.md

Scope

Add apps/stream, per-Artifact Durable Objects, SSE over fetch(), publish notification, dashboard/public connection authorization, viewer caps, client swap behavior, and proactive disconnect on takedown.

Out of scope

Do not implement Access Links or Access Link viewer prerequisites here.

Dependencies

Blocked by Access Link resolve API/viewer and related Phase 4 lifecycle dependencies.

Implementation notes

stream forwards credential verification to api; it should not verify credentials itself. Reject Revision Link connections for public Live Updates.

Acceptance

Public Share Link and dashboard viewers can advance to the latest Published Revision without manual reload, with caps and revocation behavior enforced.

Verification

Run stream/API/web tests, browser verification for viewer behavior, and pnpm verify.

Remote Cursor handoff

Start by reading AGENTS.md, then docs/agents/remote-cursor-agent.md, then this issue. Fetch current repo status from docs/ops/project-status.md and relevant ADR/spec docs named above. Keep the change scoped to this issue. Run the ticket-specific verification plus pnpm verify unless the issue explicitly says a narrower check is acceptable. Do not run hosted production deploys or smokes unless the issue explicitly grants credentials and approval.

Review in Linear

@isuttell isuttell temporarily deployed to pr-preview-105 May 27, 2026 07:29 — with GitHub Actions Inactive
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 2dd1b23f-d14a-4ca6-8134-2ee826ebc932

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements Live Updates (AP-25): adds Zod/TS contracts and OpenAPI update; introduces a new stream worker (apps/stream) with per-artifact Durable Objects, SSE connect/notify endpoints, in-memory namespace and hub enforcing viewer caps; adds API-side authorize handler, pointer construction, and best-effort notify helpers wired into publish, artifact detail, lockdown, and deletion flows; provides a web SSE client library, web proxy routes, and UI iframe-swap integration; updates local harness, Wrangler configs, deploy scripts, and adds extensive tests across packages.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as Web Client
  participant WebProxy as Web Server (/api/live proxy)
  participant Stream as Stream Worker
  participant API as API Worker (/v1/internal/live-updates/authorize)
  participant DO as ArtifactLiveUpdates DO
  Browser->>WebProxy: GET/POST /api/live/... (Accept: text/event-stream)
  WebProxy->>Stream: Proxy request (forward body / Authorization)
  Stream->>API: POST /v1/internal/live-updates/authorize (x-agent-paste-caller: stream)
  API->>Repository: resolveAccessLink / getAgentView
  API-->>Stream: LiveUpdateAuthorizeResponse (pointer, audience)
  Stream->>DO: POST /sse/connect (connection_id, artifact_id, audience, pointer)
  API->>DO: POST /internal/notify on publish/disconnect
  DO-->>Browser: SSE published_revision / revoked events
Loading

Possibly related issues

  • AP-25 — This PR implements AP-25: Live Updates via stream Worker (per-artifact Durable Objects, SSE, API authorize, viewer caps).

Possibly related PRs

  • zaks-io/agent-paste#79 — Related to publish/publishRevision handling that this PR notifies into live updates.
  • zaks-io/agent-paste#96 — Related access-link resolve and viewer signing work that live-updates authorization builds upon.
  • zaks-io/agent-paste#41 — Overlapping routing/contract registrar changes; this PR extends nonContractRoutePaths to include the live-updates authorize endpoint.

"I’m a rabbit on a codestream hop,
I bounce on pointers that never stop.
Iframes swap and viewers cheer,
Live updates bring the content near. 🐇✨"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cursor/live-updates-stream-worker-d095

coderabbitai[bot]
coderabbitai Bot previously requested changes May 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/ops/status/implementation.md (1)

3-3: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Update the "Last updated" date.

The PR was opened on 2026-05-27, but this shows 2026-05-26. Consider updating to match the PR date.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ops/status/implementation.md` at line 3, Update the "Last updated" line
in docs/ops/status/implementation.md by replacing the existing literal string
"Last updated: 2026-05-26." with the correct PR date "Last updated: 2026-05-27."
to reflect the actual update date shown in this pull request.
docs/ops/status/coverage.md (1)

3-3: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Update the "Last updated" date.

The PR was opened on 2026-05-27 according to the PR objectives, but this file shows "Last updated: 2026-05-26". Consider updating to 2026-05-27 to reflect the current status.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/ops/status/coverage.md` at line 3, Update the "Last updated" date string
"Last updated: 2026-05-26." to "Last updated: 2026-05-27" in the
docs/ops/status/coverage.md content so the file reflects the PR opening date;
ensure the exact line containing "Last updated:" is replaced accordingly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/live-updates.ts`:
- Around line 225-243: In notifyLiveUpdateDisconnectWorkspace, make the
workspace disconnect fan-out best-effort by wrapping the call to
db.listArtifacts (and the subsequent notifyLiveUpdateDisconnect Promise.all) in
a try/catch so any rejection from db.listArtifacts or the notify calls is caught
and does not propagate to the caller; on error, log or warn the failure via the
available environment logger (referencing env) and return void so
webAdminSetLockdown/setLockdown cannot fail due to post-commit disconnect
errors.
- Around line 245-269: notifyArtifactLive treats any fetch completion as success
even for non-2xx responses; update the logic after calling stub.fetch (from
env.ARTIFACT_LIVE.get(...) and stub.fetch(...)) to capture the Response, check
response.ok, and throw or log and rethrow when response.ok is false (include
status and body/text in the error details) so that 4xx/5xx Durable Object
responses are treated as notify failures and fall into the existing catch block;
ensure you still handle transport/runtime exceptions and preserve artifactId and
parsed.data.op in the error logging.

In `@apps/stream/src/artifact-live.ts`:
- Around line 82-84: The current close callback only calls
this.#hub.remove(payload.connection_id) and doesn’t terminate the SSE transport;
update createSseStream to return a stream-level close function (e.g., a
closeStream handle) to the onConnect callback, store that handle alongside the
hub entry for the connection, and change the hub close implementation so it
first calls the returned closeStream handle and then calls
this.#hub.remove(payload.connection_id as string). Apply the same change to the
other place with the identical close callback to ensure both connection entries
call the stream close before removing state.
- Around line 42-43: Wrap the direct calls to request.json() in a try/catch so
malformed JSON doesn't throw a 500; for the blocks that do const body = (await
request.json()) as unknown (the occurrences before
LiveUpdateNotifyMessage.safeParse and the similar call at the later spot), catch
the parsing error (SyntaxError) and route to the existing invalid_request
response path (same handling used when safeParse fails) instead of allowing the
exception to bubble. Ensure both places use the same try/catch pattern so
request.json() errors return the standardized invalid_request response.

In `@apps/stream/src/authorize.ts`:
- Around line 24-35: authorizeLiveUpdate currently can throw on network or JSON
parse errors (api.fetch and response.json), causing 500s instead of a controlled
deny; wrap the fetch/response.json/parse sequence in a try/catch and return null
on any thrown error, ensure you still check response.ok and that
LiveUpdateAuthorizeResponse.safeParse succeeded (return null on parse failure),
and optionally log the caught error; update the block around api.fetch,
response.json, and LiveUpdateAuthorizeResponse.safeParse to implement this
error-to-null behavior.

In `@apps/stream/src/live-hub.ts`:
- Around line 64-66: The broadcast loop over this.#connections currently calls
connection.send(event) directly so a thrown error from one connection aborts the
loop; wrap each per-connection send in its own try/catch (and await it if send
returns a promise) so failures for one client are caught and logged but do not
stop broadcasting to other connections; apply the same per-connection try/catch
pattern to the other loop around lines 76-78 (the revoked/publish fan-out) and
use an existing logger (or add one) to record which connection failed while
continuing the loop.

In `@apps/stream/src/memory-artifact-live.ts`:
- Line 56: Replace the hardcoded literal 10 in the connection-cap check on
hub.connectionCount with the shared viewer-cap constant used across the codebase
(e.g., VIEWER_CAP or MAX_VIEWERS); update the condition from "if
(hub.connectionCount >= 10)" to "if (hub.connectionCount >= VIEWER_CAP)" and
import the shared constant from the central constants/config module so the local
namespace mirrors production cap semantics and avoids drift.

In `@apps/stream/src/sse.ts`:
- Around line 21-24: Wrap the call to handlers.onClose in a try/finally so
controllerRef?.close() always executes even if onClose throws: move
handlers.onClose?.() into a try block and call controllerRef?.close() in the
finally (preserving the existing catch around close errors if present), and
ensure the closed flag semantics remain the same (reference symbols:
handlers.onClose, controllerRef.close, closed).

In `@apps/web/src/lib/live-updates.ts`:
- Around line 74-75: The parser currently splits SSE frames using
buffer.split("\n\n") which fails on CRLF-delimited frames; update the split
logic in live-updates.ts (where buffer is split into chunks and buffer =
chunks.pop() ?? "") to use a regex that accepts both LF and CRLF boundaries
(e.g. /\r?\n\r?\n/), apply the same change to the other instance around lines
89-90, and trim any trailing CR/LF characters from each chunk before dispatch so
events are not dropped when frames arrive with CRLF separators.

In `@apps/web/src/routes/_authed.artifacts`.$artifactId.tsx:
- Around line 56-67: The effect currently skips calling connectLiveUpdates when
artifact?.viewer?.iframe_src is missing, so SSE won't subscribe for artifacts
that gain a preview later; modify the useEffect containing connectLiveUpdates to
always call connectLiveUpdates (i.e., remove the early return that checks
artifact?.viewer?.iframe_src), keep the same connectLiveUpdates call and
onPointer handler (setIframeSrc(pointer.iframe_src)), ensure the cleanup return
() => connection.close() remains, and update the dependency array to artifactId
(and any stable setters) so the subscription is established even when the
initial viewer URL is absent.

In `@apps/web/src/routes/al`.$publicId.tsx:
- Around line 97-123: The effect currently uses liveUpdatesEnabled = state.kind
=== "resolved" && Boolean(state.iframe_src), which prevents subscribing when a
resolved access link has no iframe yet; remove the iframe_src gate so
subscription is allowed whenever state.kind === "resolved". Update the effect to
base its guard on state.kind === "resolved" (still early-return if
liveBlobRef.current is missing) and adjust the dependency array to include
publicId and state.kind (instead of liveUpdatesEnabled) so connectLiveUpdates
(called with connectLiveUpdates, publicId, liveBlobRef, setState) runs for
first-time publishes and still cleans up via connection.close().

In `@apps/web/src/routes/api/live/access-links/`$publicId.ts:
- Around line 10-17: The SSE proxy is not forwarding client aborts to the
upstream fetch, so add the request's abort signal to upstream fetch calls: in
access-links/$publicId.ts include signal: request.signal in the fetch options
where upstream is created (the fetch that posts to
`${streamBase}/v1/live/access-links/${params.publicId}`), and in
artifacts/$artifactId.ts change the route handler signature to accept { request,
params } and pass signal: request.signal into its upstream fetch call so
upstream streams are aborted when the client disconnects.

In `@docs/ops/project-status.md`:
- Line 19: The "Current Implementation Reality" section contradicts the earlier
statement that `apps/stream` implements ADR 0069; open the
docs/ops/project-status.md file and remove `apps/stream` from the list of absent
components in that "Current Implementation Reality" section so the document
consistently reflects that `apps/stream` exists (ensure any trailing commas or
bullets remain valid and adjust surrounding punctuation as needed).

In `@scripts/local-mvp-server.mjs`:
- Around line 295-304: The local harness is buffering SSE responses (breaking
incremental delivery) by calling response.arrayBuffer() in writeFetchResponse
when proxying the stream worker; change writeFetchResponse (and the similar
logic used around streamEnv/API.fetch wiring) to detect text/event-stream or
transfer-encoding: chunked responses and stream the response body to the client
incrementally instead of awaiting arrayBuffer(): use the response.body
ReadableStream/getReader or pipeThrough to forward chunks as they arrive,
preserve headers/status, and only fall back to arrayBuffer() for non-streaming
responses so SSE events are delivered in real time.

---

Outside diff comments:
In `@docs/ops/status/coverage.md`:
- Line 3: Update the "Last updated" date string "Last updated: 2026-05-26." to
"Last updated: 2026-05-27" in the docs/ops/status/coverage.md content so the
file reflects the PR opening date; ensure the exact line containing "Last
updated:" is replaced accordingly.

In `@docs/ops/status/implementation.md`:
- Line 3: Update the "Last updated" line in docs/ops/status/implementation.md by
replacing the existing literal string "Last updated: 2026-05-26." with the
correct PR date "Last updated: 2026-05-27." to reflect the actual update date
shown in this pull request.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f46681f9-d384-471e-bffc-4c03c909c87f

📥 Commits

Reviewing files that changed from the base of the PR and between 664cb74 and e11bae5.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (42)
  • README.md
  • apps/api/src/index.test.ts
  • apps/api/src/index.ts
  • apps/api/src/live-updates.ts
  • apps/api/wrangler.jsonc
  • apps/stream/README.md
  • apps/stream/package.json
  • apps/stream/src/artifact-live.ts
  • apps/stream/src/authorize.ts
  • apps/stream/src/index.test.ts
  • apps/stream/src/index.ts
  • apps/stream/src/live-hub.test.ts
  • apps/stream/src/live-hub.ts
  • apps/stream/src/memory-artifact-live.ts
  • apps/stream/src/sse.ts
  • apps/stream/tsconfig.json
  • apps/stream/wrangler.jsonc
  • apps/web/src/lib/live-updates.ts
  • apps/web/src/routeTree.gen.ts
  • apps/web/src/routes/_authed.artifacts.$artifactId.tsx
  • apps/web/src/routes/al.$publicId.tsx
  • apps/web/src/routes/api/live/access-links/$publicId.ts
  • apps/web/src/routes/api/live/artifacts/$artifactId.ts
  • apps/web/src/server/env.ts
  • apps/web/wrangler.jsonc
  • docs/ops/live-updates-todo.md
  • docs/ops/project-status.md
  • docs/ops/status/coverage.md
  • docs/ops/status/implementation.md
  • docs/ops/status/phase-backlog.md
  • packages/contracts/openapi/api.json
  • packages/contracts/src/index.ts
  • packages/contracts/src/liveUpdates.test.ts
  • packages/contracts/src/liveUpdates.ts
  • packages/contracts/src/web.ts
  • packages/db/src/index.ts
  • packages/db/src/repository/core.ts
  • packages/db/src/repository/interface.ts
  • packages/db/src/resolve-access-link.test.ts
  • packages/db/src/resolve-access-link.ts
  • scripts/deploy-preview.mjs
  • scripts/local-mvp-server.mjs

Comment thread apps/api/src/live-updates.ts
Comment thread apps/api/src/live-updates.ts
Comment thread apps/stream/src/artifact-live.ts Outdated
Comment thread apps/stream/src/artifact-live.ts Outdated
Comment thread apps/stream/src/authorize.ts Outdated
Comment thread apps/web/src/routes/_authed.artifacts.$artifactId.tsx Outdated
Comment thread apps/web/src/routes/al.$publicId.tsx Outdated
Comment thread apps/web/src/routes/api/live/access-links/$publicId.ts
Comment thread docs/ops/project-status.md
Comment thread scripts/local-mvp-server.mjs
Add focused unit tests for stream DO/SSE/authorize routes, API
live-update authorize/notify helpers, and the web SSE client so
pnpm test:coverage meets the global 80% branch threshold.
@cursor cursor Bot temporarily deployed to pr-preview-105 May 27, 2026 07:42 Inactive
coderabbitai[bot]
coderabbitai Bot previously requested changes May 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/live-updates.test.ts`:
- Around line 210-227: The test currently returns null from the
Repository.getAgentView mock, exercising the "missing view" path instead of the
malformed-URL branch; update the mock used in handleLiveUpdateAuthorize to
return an AgentView-like object that includes a deliberately malformed view_url
(e.g., "not-a-url") so the code takes the invalid-URL branch, and update the
assertion for badViewUrl.status to the expected status for an invalid view_url
(change from 404 to the correct status your handler returns for malformed URLs).
- Around line 144-153: The test currently unsets ACCESS_LINK_SIGNING_KEY_V1
which causes early 404 before the revision-link code in
handleLiveUpdateAuthorize is exercised; restore or provide a valid
ACCESS_LINK_SIGNING_KEY_V1 in the env passed to handleLiveUpdateAuthorize so the
function reaches the revision-link branch, and then make the request payload
provoke revision-link rejection (e.g., use an invalid or malformed public_id or
signature in the body) so the expect(revisionDenied.status).toBe(404) assertion
verifies the intended revision-link rejection path.

In `@apps/stream/src/artifact-live.test.ts`:
- Around line 123-124: The test is asserting helper return values instead of the
actual route response; change the two expectations to check the real response
object created around Line 110: assert response.headers.get("content-type")
contains "text/event-stream" and assert response.status is 503 (or use
response.ok/failure as appropriate) instead of using sseResponseHeaders() and
liveUpdateAtCapResponse(); update assertions referencing sseResponseHeaders()
and liveUpdateAtCapResponse() to use the actual response variable from the test
(the one created at Line 110).

In `@apps/stream/src/memory-artifact-live.test.ts`:
- Around line 37-60: The two await stub.fetch calls to the /internal/notify
endpoint (the publish and disconnect requests) currently ignore their responses;
capture each response (e.g., respPublish = await stub.fetch(...), respDisconnect
= await stub.fetch(...)) and assert the HTTP status is 200 (for example using
expect(respPublish.status).toBe(200) and
expect(respDisconnect.status).toBe(200)) so the test fails on non-200
regressions; you can also optionally assert response body JSON if the test
framework expects specific contents.

In `@apps/web/test/live-updates.test.ts`:
- Around line 91-92: Replace the two fixed 20ms sleeps with deterministic
synchronization by awaiting an observable condition instead of setTimeout;
specifically, stop using the inline setTimeout Promise around the test
assertions and instead wait (using a helper like waitFor or a small polling
loop) for the condition under test to stabilize before asserting onPointer (the
mock/handler used in this test) has not been called; update the assertions
around onPointer in live-updates.test.ts to await that synchronization helper
(or use testing-library's waitFor) so the test no longer relies on hardcoded
delays.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 02cd60ec-3e0a-43ec-ae13-ea6d3a1c7c21

📥 Commits

Reviewing files that changed from the base of the PR and between e11bae5 and e72469b.

📒 Files selected for processing (7)
  • apps/api/src/live-updates.test.ts
  • apps/stream/src/artifact-live.test.ts
  • apps/stream/src/authorize.test.ts
  • apps/stream/src/index.test.ts
  • apps/stream/src/memory-artifact-live.test.ts
  • apps/stream/src/sse.test.ts
  • apps/web/test/live-updates.test.ts

Comment thread apps/api/src/live-updates.test.ts
Comment thread apps/api/src/live-updates.test.ts
Comment thread apps/stream/src/artifact-live.test.ts
Comment thread apps/stream/src/memory-artifact-live.test.ts
Comment thread apps/web/test/live-updates.test.ts
Make workspace disconnect and DO notify paths best-effort, terminate SSE
streams before hub removal, guard JSON/authorize failures, isolate hub
fan-out errors, stream local SSE proxies, and subscribe before initial
iframe URLs. Update implementation/coverage status docs.
@cursor cursor Bot temporarily deployed to pr-preview-105 May 27, 2026 07:49 Inactive
coderabbitai[bot]
coderabbitai Bot previously requested changes May 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/api/src/live-updates.test.ts`:
- Around line 340-371: The test currently claims to cover per-artifact notify
failures but the second phase still uses a successful fetch; update the second
phase in the "swallows workspace list and per-artifact disconnect failures" test
so that the ARTIFACT_LIVE mock (idFromName/get/fetch used by
notifyLiveUpdateDisconnectWorkspace) simulates a failing per-artifact notify
(e.g., make fetch throw or return an error Response) when listArtifacts returns
artifacts, then assert console.warn (the warn spy) is called for that failure;
target the notifyLiveUpdateDisconnectWorkspace invocation and the ARTIFACT_LIVE
mock (idFromName, get, fetch) to implement this change.

In `@apps/stream/src/live-hub.ts`:
- Around line 85-86: Wrap each per-connection shutdown in a try/catch so a
thrown error from connection.close() doesn't abort the loop; for each id call
connection.close() inside a try, always call this.#connections.delete(id) in
finally (or after catch) and log any error (including the id) using the existing
logger so the fan-out continues for remaining connections. Reference:
connection.close() and this.#connections.delete(id).

In `@scripts/local-mvp-server.mjs`:
- Around line 89-100: In pipeWebStreamToNode(), avoid unbounded buffering by
observing outgoing.write's boolean return: after calling
outgoing.write(Buffer.from(value)) check if it returns false and if so await the
outgoing stream's "drain" event before continuing; also abort the loop early if
outgoing is closed/destroyed (check outgoing.writableEnded or
outgoing.destroyed) to avoid waiting forever. Implement this around the existing
write in pipeWebStreamToNode so each chunk awaits drain when backpressure
occurs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 95ac3051-2a81-45c5-aaab-3bf41f464d06

📥 Commits

Reviewing files that changed from the base of the PR and between e72469b and 1b64b40.

📒 Files selected for processing (20)
  • apps/api/src/live-updates.test.ts
  • apps/api/src/live-updates.ts
  • apps/stream/src/artifact-live.test.ts
  • apps/stream/src/artifact-live.ts
  • apps/stream/src/authorize.test.ts
  • apps/stream/src/authorize.ts
  • apps/stream/src/live-hub.test.ts
  • apps/stream/src/live-hub.ts
  • apps/stream/src/memory-artifact-live.ts
  • apps/stream/src/sse.test.ts
  • apps/stream/src/sse.ts
  • apps/web/src/lib/live-updates.ts
  • apps/web/src/routes/_authed.artifacts.$artifactId.tsx
  • apps/web/src/routes/al.$publicId.tsx
  • apps/web/src/routes/api/live/access-links/$publicId.ts
  • apps/web/src/routes/api/live/artifacts/$artifactId.ts
  • apps/web/test/live-updates.test.ts
  • docs/ops/status/coverage.md
  • docs/ops/status/implementation.md
  • scripts/local-mvp-server.mjs

Comment thread apps/api/src/live-updates.test.ts
Comment thread apps/stream/src/live-hub.ts Outdated
Comment thread scripts/local-mvp-server.mjs
Assert per-artifact notify failure in workspace disconnect test, isolate
hub close errors during revocation fan-out, and respect Node backpressure
when piping local SSE streams.
@cursor cursor Bot temporarily deployed to pr-preview-105 May 27, 2026 08:05 Inactive
coderabbitai[bot]
coderabbitai Bot previously requested changes May 27, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@scripts/local-mvp-server.mjs`:
- Around line 95-102: waitForDrain currently only waits for the "drain" event
and can hang if the client disconnects; update waitForDrain to also listen for
"close" and "error" on outgoing (and still check isOutgoingClosed(outgoing)
first) and ensure all three listeners are cleaned up when any fires—use
outgoing.once("drain", ...), outgoing.once("close", ...), outgoing.once("error",
...) and have the close/error handlers remove the other listeners and resolve
the promise so the proxy doesn't hang; reference waitForDrain, outgoing, and
isOutgoingClosed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 565e44a3-551c-4b69-a403-8e60f2144d96

📥 Commits

Reviewing files that changed from the base of the PR and between 1b64b40 and feadd14.

📒 Files selected for processing (4)
  • apps/api/src/live-updates.test.ts
  • apps/stream/src/live-hub.test.ts
  • apps/stream/src/live-hub.ts
  • scripts/local-mvp-server.mjs

Comment thread scripts/local-mvp-server.mjs
Listen for close and error as well as drain in waitForDrain(), and
remove all listeners when any event fires so backpressure waits cannot
hang after the client drops the connection.
@cursor cursor Bot temporarily deployed to pr-preview-105 May 27, 2026 08:12 Inactive
@isuttell isuttell dismissed stale reviews from coderabbitai[bot], coderabbitai[bot], coderabbitai[bot], and coderabbitai[bot] May 27, 2026 08:18

Superseded by head 6f239eb; hosted checks are green and CodeRabbit status is SUCCESS with no latest-head actionables.

@isuttell isuttell enabled auto-merge (squash) May 27, 2026 08:18
@isuttell isuttell temporarily deployed to pr-preview-105 May 27, 2026 15:52 — with GitHub Actions Inactive
@isuttell isuttell merged commit 89d2d87 into main May 27, 2026
4 checks passed
@isuttell isuttell deleted the cursor/live-updates-stream-worker-d095 branch May 27, 2026 15:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants