ui: stream host-detail fragments over SSE so the page updates live
CI / Lint + build + test (push) Successful in 1m29s
Release / release (push) Has been cancelled

The detail page was only partly live: Pipeline + LogTabs subscribed to
SSE, but the summary header, actions row, spec-diffs list and hold-key
block all froze at page-load and required a manual refresh to catch up
with state changes.

Extract each of those four regions into its own named templ component
with a stable id and sse-swap target, add Render*String helpers so the
orchestrator can publish pre-rendered fragments, and register a
HostDetailRenderer alongside the existing Tile/Pipeline renderers.
PublishHostDetail is folded into publishTileUpdate so every call site
that already refreshes a tile now also refreshes the detail page —
keeps the fan-out honest without scattering new publish calls.

The empty-state wrappers for spec-diffs and hold are load-bearing:
without the <section id=... sse-swap=...> present at initial GET, the
first live event after SpecValidate or Hold writes would have no DOM
node to swap into.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
2026-04-18 16:36:13 -04:00
parent 5e9ad7f569
commit 0db790ae3e
8 changed files with 1250 additions and 580 deletions
+21
View File
@@ -109,6 +109,27 @@ func main() {
PublicURL: cfg.Server.PublicURL,
}
// Inject the host-detail fragment renderer. The closure reuses
// LoadHostDetailData so the SSE-pushed HTML matches an identical
// reload-rendered page byte-for-byte, then hands each region to
// its Render*String helper.
orchestrator.HostDetailRenderer = func(ctx context.Context, hostID int64) (orchestrator.HostDetailFragments, bool) {
d, err := ui.LoadHostDetailData(ctx, hostID)
if err != nil {
return orchestrator.HostDetailFragments{}, false
}
f := orchestrator.HostDetailFragments{
Summary: templates.RenderDetailSummaryString(d),
Actions: templates.RenderDetailActionsString(d),
SpecDiffs: templates.RenderDetailSpecDiffsString(d),
Hold: templates.RenderDetailHoldString(d),
}
if d.Tile.Latest != nil {
f.LatestRunID = d.Tile.Latest.ID
}
return f, true
}
agentAPI := &api.Agent{
Hosts: hostStore,
Runs: runStore,