Skip to content

feat(deploy): SSE deployments — live progress, health check, and history/detail#1399

Merged
dawsontoth merged 5 commits into
stagefrom
feat/sse-deployments
Jul 2, 2026
Merged

feat(deploy): SSE deployments — live progress, health check, and history/detail#1399
dawsontoth merged 5 commits into
stagefrom
feat/sse-deployments

Conversation

@dawsontoth

Copy link
Copy Markdown
Contributor

What & why

Adopts Harper 5.1.0's SSE deploy stream and the replicated hdb_deployment audit table (harper#641) in Studio. Everything is gated on Harper ≥ 5.1.0 (verified against release tags: the SSE_PROGRESS_OPERATIONS branch ships in v5.1.0 and is absent in v5.0.31); 5.0.x keeps today's buffered deploy path unchanged.

Closes #1233 · Closes #1231 · Closes #1230

Reusable browser SSE transport — src/integrations/api/sse/

The axios client is XHR-based and can't stream response bodies, and EventSource is GET-only (deploy is a POST), so SSE is consumed via fetch() + a stream reader.

  • resolveInstanceConnection — fetch-equivalent of getInstanceClient's base-URL/auth resolution (basic-auth header vs credentials: 'include' for the fabric-connect proxy). getInstanceClient itself is left untouched so its interceptors keep serving every non-streaming caller.
  • parseSSEStream — SSE record parser (chunk-split, CRLF, multi-line data:, : comments).
  • streamOperation — three explicit outcomes: SSEUnsupportedError (fall back to buffered), SSEOperationError (failed), SSEInconclusiveError (poll — never assume success); idle-timeout watchdog.

Deploy — #1233

  • deployComponentStream + the deployComponent mutation stream when supported, and fall back to the buffered POST only on SSEUnsupportedError (never re-POST after a deploy has started → no double deploys).
  • DeployProgress + useDeploymentStream: live phase checklist, install-log tail, peer results — wired into the redeploy modal and import flow.
  • Post-deploy health check (useComponentHealthCheck): polls get_components for the new per-component status, since deploy_component now returns before worker threads finish loading the component.

Deployments history + detail — #1230 / #1231

  • list_deployments / get_deployment (JSON read and SSE tail) + hdb_deployment types.
  • New Config → Deployments sub-nav: a table (project · status · started · completed · user · origin) with a row → detail modal showing metadata, peer_results, and the event_log activity timeline. Deep-linked from the cluster card's options menu.
  • Detail uses active-polling as the correctness backbone, with the SSE tail layered on for live granularity.

⚠️ Fabric-connect proxy buffers SSE

Verified live against a 5.1.14 cluster: a redeploy with Accept: text/event-stream through the central-manager proxy sat 170s+ with zero response bytes while get_components/list_deployments returned instantly — the proxy buffers text/event-stream. So live SSE is gated to direct connections; proxied (fabric-connect) entities use the buffered deploy + polling detail, which work fully. Drop the checkForFabricConnect check in useSupportsDeploymentSSE if/when the proxy gains streaming passthrough.

Verification

  • Unit: SSE parser, connection resolver, deploy fallback paths, deployment status helpers (full suite 1030 passed, lint clean, production build succeeds).
  • Live against a Harper 5.1.14 cluster: Config → Deployments renders; a real buffered redeploy completed in ~6s, populated hdb_deployment, appeared in the list, and the detail modal rendered full metadata + peer result + the event-log timeline; cluster-card deep-link navigates correctly; nav/version gating confirmed.

🤖 Generated with Claude Code

@dawsontoth dawsontoth requested a review from a team as a code owner June 30, 2026 20:25

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces Server-Sent Events (SSE) streaming support for live component deployment progress and adds a deployment history view for Harper >= 5.1.0. Feedback focuses on resolving a connection leak in the SSE stream parser, a memory leak in the manual anySignal fallback, a potential runtime crash on unexpected error payloads, and a performance bottleneck in log rendering caused by shifting array index keys.

Comment thread src/integrations/api/sse/parseSSEStream.ts
Comment thread src/integrations/api/sse/streamOperation.ts
Comment thread src/integrations/api/sse/streamOperation.ts Outdated
Comment thread src/features/instance/applications/components/DeployProgress/DeployProgress.tsx Outdated

@kriszyp kriszyp left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Approving — awesome work, @dawsontoth! 🎉 The SSE deployment flow is carefully built: clean connection lifecycle, no double-deploy, well-defined progress/health/history state, and the event log is bounded at 1000 so it can't grow unbounded. Two minor UI nits in the thread, neither blocking.

— 🤖 KrAIs (Kris's review assistant)

@dawsontoth

Copy link
Copy Markdown
Contributor Author

@kriszyp or KrAIs, can you expand upon your " Two minor UI nits in the thread, neither blocking." comment? I don't see a thread! 😅

@dawsontoth

Copy link
Copy Markdown
Contributor Author

We want to land #1400 before this PR since it will unlock the SSE when someone is using fabric connect, improving the experience a lot.

@dawsontoth dawsontoth force-pushed the feat/sse-deployments branch from d35ae54 to 62ba45d Compare July 2, 2026 15:34
dawsontoth and others added 4 commits July 2, 2026 12:15
…ory/detail

Adopt Harper 5.1.0's SSE deploy stream and `hdb_deployment` audit table
(harper#641). All gated on Harper >= 5.1.0; 5.0.x keeps today's buffered path.

Closes #1233, #1231, #1230.

## Reusable browser SSE transport (src/integrations/api/sse/)
- resolveInstanceConnection: fetch-equivalent of getInstanceClient's auth/URL
  resolution (axios is XHR and can't stream response bodies); getInstanceClient
  left untouched.
- parseSSEStream: robust SSE record parser (chunk-split, CRLF, multi-line data,
  comments).
- streamOperation: fetch+stream with three explicit outcomes —
  SSEUnsupportedError (fall back to buffered), SSEOperationError (failed), and
  SSEInconclusiveError (poll, never assume success); idle-timeout watchdog.

## Deploy (#1233)
- deployComponentStream + deployComponent mutation: stream when supported, fall
  back to the buffered POST on SSEUnsupportedError only (never re-POST after a
  deploy has started — no double deploys).
- DeployProgress + useDeploymentStream: live phase checklist, install-log tail,
  peer results, wired into the redeploy modal and import flow.
- Post-deploy health check (useComponentHealthCheck): polls get_components for the
  new per-component status, since deploy_component now returns before worker
  threads finish loading.

## Deployments history + detail (#1230, #1231)
- list_deployments / get_deployment (JSON read + SSE tail) + hdb_deployment types.
- New Config sub-nav "Deployments": a table (project/status/started/completed/
  user/origin) with a row -> detail modal showing metadata, peer_results, and the
  event_log activity timeline. Deep-linked from the cluster card.
- Detail uses active-polling as the correctness backbone, with the SSE tail
  layered on for live granularity.

## Proxy note
The central-manager fabric-connect proxy buffers text/event-stream (verified
live), so live SSE is gated to direct connections; proxied entities use the
buffered deploy + polling detail, which work fully. Drop the checkForFabricConnect
check in useSupportsDeploymentSSE if the proxy gains streaming passthrough.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The modal passed `overflow-y-auto`/`max-h` to the resizable DialogContent, but the
resizable shell deliberately leaves its outer content unclipped so the resize
handles (which extend past the edges at negative insets) stay reachable. Clipping
there swallowed the handles — draggable but not resizable. Move scrolling onto an
inner `flex-1 min-h-0 overflow-y-auto` body, matching ViewLogModal/EditTableRowModal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The mount effect set `unmountedRef.current = true` in its cleanup but never reset
it on setup. Under React StrictMode's dev mount→cleanup→remount the cleanup fires
once and the ref persists, leaving the flag stuck `true` — so every gesture
teardown skipped its `setIsDragging(false)` / `setIsResizing(false)`, and the
overlay stayed transparent after releasing a drag. Reset the flag on (re)mount.

Affects all resizable dialogs; production (no StrictMode double-invoke) was already
fine, but this makes the teardown guard robust either way.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…able log keys

- parseSSEStream: cancel the reader when a consumer breaks early (or aborts) so the
  underlying connection is released instead of left open (browser per-host cap).
- streamOperation: anySignal now returns a cleanup that detaches listeners from the
  caller's signal (manual fallback path), invoked in a single outer finally; guard
  the terminal `error` payload against null/primitive before reading its fields.
- useDeploymentStream/DeployProgress: give each install-log line a stable id and key
  on it, so front-slicing the capped log doesn't churn every row's React key.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dawsontoth dawsontoth force-pushed the feat/sse-deployments branch from 62ba45d to a1b6b7a Compare July 2, 2026 16:16
…able

Leverage the direct-connect (Bearer operation token) path so SSE actually streams
instead of buffering through the central-manager proxy, and keep the deployments
table fresh without manual refresh.

- resolveInstanceConnection: mirror getInstanceClient's direct-connect precedence —
  prefer the direct instance URL + Bearer operation token (and Basic), only using the
  proxy URL when genuinely proxy-only. Expose mode: 'direct' | 'proxy'.
- authStore.isDirectConnection(id): true when operations go straight to the instance
  (Bearer token, or no Fabric Connect) — i.e. when SSE can stream.
- useSupportsDeploymentSSE: gate on isDirectConnection, not merely the absence of the
  Fabric Connect flag, so Fabric-Connect-resolved-to-direct clusters get live SSE.
- list_deployments: pollWhileOpen option — refetch while the Deployments view is open
  (2s while a deploy is active, 10s otherwise). hdb_deployment is a system table and
  isn't exposed as a subscribable resource, so polling is the live-update mechanism.
- New Application import toast streams live phase + install-output lines when SSE is
  available (richer progress), falling back to the plain toast otherwise.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@dawsontoth dawsontoth added this pull request to the merge queue Jul 2, 2026
Merged via the queue into stage with commit eada740 Jul 2, 2026
4 checks passed
@dawsontoth dawsontoth deleted the feat/sse-deployments branch July 2, 2026 20:39
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.

Use SSE for deployments Studio: Deployment detail + live status (get_deployment / SSE) Studio: Deployments list view (list_deployments)

2 participants