Skip to content

refactor(dashboard): use Svelte attachments and share the SSE client - #650

Merged
SantiagoDePolonia merged 3 commits into
mainfrom
feat/attachments
Aug 4, 2026
Merged

refactor(dashboard): use Svelte attachments and share the SSE client#650
SantiagoDePolonia merged 3 commits into
mainfrom
feat/attachments

Conversation

@SantiagoDePolonia

@SantiagoDePolonia SantiagoDePolonia commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Internal dashboard refactor — no user-visible behaviour change, no API surface touched.

Svelte attachments

Replaces element-bound bind:this + $effect pairs with {@attach ...}, which was unused in the codebase (there were no use: actions either). The element arrives as the attachment's argument and the returned teardown runs with it:

  • ChartCanvas (38 → 12 lines) — drops the nullable $state ref, the null guard, and the three duplicated chart.destroy(); chart = null branches that existed because the effect had to handle "no config", "rebuild" and "unmount" separately. One returned cleanup covers all three.
  • Modal — autofocus is an attachment, so the tick() hop is gone (attachments already run after the DOM exists). The backdrop-click test now compares event.target === event.currentTarget, which is what the shellEl ref was standing in for.
  • DatePicker — outside-click/Escape dismissal moves into the reusable dismissOnOutside attachment, live only while open via a falsy {@attach}. It stays on the picker root (trigger included) so a trigger click remains an inside click and toggle keeps owning that case.

New: $lib/utils/attachments.js (dismissOnOutside, autofocusWithin), plus a short note in CONVENTIONS.md.

Shared SSE client

The audit-log live stream and the overview's usage-signal stream each carried their own copy of the transport: byte-identical framing/data: parsing (live-logs-logic.js vs liveTokensState.svelte.js) and the same reconnect backoff. Both now use $lib/api/eventStream.js (consumeEventStream, nextReconnect) and keep only their own event handling and reconnect policy — roughly 75 duplicated lines down to one implementation.

live-logs-logic.js imports it by relative path, not $lib: that file is loaded directly by node --test, which cannot resolve the alias. Noted in both file headers.

Note, not fixed here

The "capped at 30s" backoff comment is unreachable — attempts cap at 6, so the largest delay is 16s. That was true of both copies before this change, so behaviour is preserved rather than silently retimed. Happy to raise the cap or correct the comment in a follow-up.

Verification

  • 444/444 dashboard tests (6 new framing/backoff cases in tests/event-stream.test.js; the existing consumeLiveLogsBody cases still run through the shared code, which is the equivalence check), svelte-check 0 errors, clean build.
  • Exercised against a running gateway: date picker open/outside-click/trigger-toggle/Escape unchanged; budget editor autofocuses, closes on backdrop click, stays open on inside click, closes on Escape; overview charts survive repeated theme rebuilds with no console output (a leaked chart would throw Chart.js's "Canvas is already in use"); audit-log page went 1 → 3 entries live while requests were curled through the gateway; overview usage stream returns 200 with parseable frames.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Improved live audit logs and token updates with more reliable streamed event handling.
    • Added automatic reconnection with capped backoff when live updates are interrupted.
    • Improved handling of interrupted, fragmented, empty, or invalid live events.
  • Bug Fixes

    • Modal dialogs now focus controls more consistently and close only when clicking the backdrop.
    • Date pickers close reliably when clicking outside or pressing Escape.
    • Charts now initialize and clean up more reliably across theme changes.

SantiagoDePolonia and others added 2 commits August 4, 2026 18:02
Replace element-bound `bind:this` + `$effect` pairs with `{@Attach ...}`:
the element arrives as an argument and the returned teardown runs with it,
so ChartCanvas drops its nullable ref and its three duplicated
`chart.destroy()` branches, Modal drops the `tick()` hop before autofocus
(the backdrop test now compares `event.currentTarget`), and DatePicker's
outside-click dismissal becomes a reusable attachment.

Extract the duplicated live-stream transport into $lib/api/eventStream.js.
The audit-log stream and the overview's usage-signal stream carried
byte-identical SSE framing plus the same reconnect backoff; both now share
one implementation and keep only their own event handling and reconnect
policy. live-logs-logic.js imports it by relative path because node --test
loads that file directly and cannot resolve the $lib alias.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
# Conflicts:
#	internal/admin/dashboard/static/dist/index.html
Copilot AI lite review requested due to automatic review settings August 4, 2026 16:37

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d0e28c93-27b9-4c44-9ace-81e71cae9793

📥 Commits

Reviewing files that changed from the base of the PR and between 35a3034 and c3601e7.

📒 Files selected for processing (2)
  • web/dashboard/src/lib/api/eventStream.js
  • web/dashboard/src/pages/audit-logs/liveLogs.svelte.js

📝 Walkthrough

Walkthrough

The PR adds shared SSE parsing and reconnect helpers. It adds reusable Svelte attachments. Live streams and dashboard components now use these shared utilities.

Changes

Shared SSE event streaming

Layer / File(s) Summary
SSE transport and backoff helpers
web/dashboard/src/lib/api/eventStream.js, web/dashboard/tests/event-stream.test.js
The shared client parses chunked SSE frames, handles multiline and trailing payloads, ignores invalid events, and caps reconnect attempts and delays.
Live-stream integrations
web/dashboard/src/pages/audit-logs/..., web/dashboard/src/pages/overview/liveTokensState.svelte.js
Audit-log and live-token streams use the shared event consumer and reconnect helper. Their event handlers continue to process application events.

Svelte attachment utilities

Layer / File(s) Summary
Attachment utilities and conventions
web/dashboard/src/lib/utils/attachments.js, web/dashboard/CONVENTIONS.md
Added dismissOnOutside and autofocusWithin, with listener teardown and conditional activation guidance.
Component attachment adoption
web/dashboard/src/lib/components/atoms/Modal.svelte, web/dashboard/src/lib/components/molecules/{ChartCanvas,DatePicker}.svelte
Modal autofocus, chart lifecycle management, and date-picker dismissal now use Svelte attachments.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant StreamReader
  participant consumeEventStream
  participant LiveStreamHandler
  StreamReader->>consumeEventStream: Read streamed chunks
  consumeEventStream->>consumeEventStream: Split SSE frames and parse JSON
  consumeEventStream->>LiveStreamHandler: Forward valid events
  LiveStreamHandler->>consumeEventStream: Request reconnect delay after failure
Loading

Possibly related PRs

Suggested reviewers: copilot

Poem

I hop through frames of data bright,
And close each stream with timing right.
Attachments focus, clean, and bind,
While charts and pickers fall in line.
Squeak, reconnect, and parse anew! 🐇

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main refactoring work: replacing element-bound patterns with Svelte attachments and consolidating duplicated SSE client code.
Description check ✅ Passed The description fully addresses the template sections with clear explanations of changes, includes AI-generated context, covers Svelte attachments and SSE client consolidation, and documents verification and implementation notes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/attachments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai 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.

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 `@web/dashboard/src/lib/api/eventStream.js`:
- Around line 14-15: Correct the reconnect documentation to match the existing
capped behavior: in web/dashboard/src/lib/api/eventStream.js lines 14-15,
describe MAX_RECONNECT_ATTEMPTS as capping the attempt counter rather than
stopping retries; in lines 64-73, document the effective 16000ms delay cap
without changing nextReconnect(); and in
web/dashboard/src/pages/audit-logs/liveLogs.svelte.js lines 20-22, align the
stream documentation with this capped-attempt and 16000ms-delay behavior.
🪄 Autofix

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: e068c950-52e2-46b6-a592-4b1834201739

📥 Commits

Reviewing files that changed from the base of the PR and between a37ec24 and 35a3034.

⛔ Files ignored due to path filters (3)
  • internal/admin/dashboard/static/dist/assets/index-G3uz-AWc.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/assets/index-uqP7q8EU.js is excluded by !**/dist/**
  • internal/admin/dashboard/static/dist/index.html is excluded by !**/dist/**
📒 Files selected for processing (10)
  • web/dashboard/CONVENTIONS.md
  • web/dashboard/src/lib/api/eventStream.js
  • web/dashboard/src/lib/components/atoms/Modal.svelte
  • web/dashboard/src/lib/components/molecules/ChartCanvas.svelte
  • web/dashboard/src/lib/components/molecules/DatePicker.svelte
  • web/dashboard/src/lib/utils/attachments.js
  • web/dashboard/src/pages/audit-logs/live-logs-logic.js
  • web/dashboard/src/pages/audit-logs/liveLogs.svelte.js
  • web/dashboard/src/pages/overview/liveTokensState.svelte.js
  • web/dashboard/tests/event-stream.test.js

Comment thread web/dashboard/src/lib/api/eventStream.js Outdated
@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown

Confidence Score: 4/5

Not safe to merge until stale live-usage SSE readers are prevented from reconnecting after a replacement stream is active.

A focused runtime harness reproduced an extra live-usage stream request after completing only a superseded reader. The interactive attachment behaviors were also exercised successfully in Chromium.

Files Needing Attention: web/dashboard/src/pages/overview/liveTokensState.svelte.js needs an active-controller ownership check before scheduling reconnects.

T-Rex T-Rex Logs

What T-Rex did

  • T-Rex produced proof for a posted P1 finding related to the stale live-usage stream reader race and attached a Node harness and logs.
  • The attempted overview race execution was blocked before the failure path could run due to a SyntaxError in the generated test module, so no runtime proof could be produced.
  • The svelte-check run reported zero errors or warnings, but the Playwright harness could not reach the application assertions because dependency optimization failed, so no browser-based failure evidence was observed.
  • T-Rex documented exact harness reproduction showing baseline two streams after restart and a third stream when the stale reader completes.
  • T-Rex captured a detailed browser harness run with the attachment lifecycle, including video, images, and logs that illustrate the lifecycle flow.

View all artifacts

T-Rex Ran code and verified through T-Rex

Comments Outside Diff (1)

  1. General comment

    P1 A stale usage SSE reader can reconnect after a replacement stream is active

    • Bug
      • #readStream(controller) schedules reconnect unconditionally after consumeEventStream completes. When start() aborts/replaces controller 0 with controller 1, a delayed normal completion of reader 0 still reaches line 168 and schedules a reconnect. The harness observed the fetch count change from 2 active/restarted stream requests to 3 after only reader 0 completed.
    • Cause
      • #readStream does not verify that its controller is still this.#sseController before calling #scheduleReconnect() in its completion, invalid-response, and non-abort-error paths.
    • Fix
      • Before every reconnect scheduling path in #readStream, return unless controller === this.#sseController (and retain the existing active check). Optionally clear the stored controller only when that same controller finishes.

    T-Rex Ran code and verified through T-Rex

Reviews (1): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

await consumeEventStream(res.body.getReader(), (event) =>
this.#handleEvent(event),
);
this.#scheduleReconnect();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Stale SSE reader reopens the usage stream

start() replaces and aborts the previous controller, but an older #readStream(controller) can continue to this completion path after its reader finishes. It then unconditionally schedules a reconnect against the current state, even though a replacement stream is already active. The executed harness restarted the state to create two streams, completed only the stale reader, and observed a third stream request. Guard reconnect scheduling in this method with controller === this.#sseController and ignore aborted controllers so only the active stream can reconnect.

Artifacts

Node harness source for the stale live-usage stream reader race

  • The captured harness starts two replacement streams, then selectively completes the old reader; it exercises the actual source after only dependency substitution.

Baseline stream count before stale reader completion

  • The first executed harness capture confirms two stream fetches after restart and before the stale reader is allowed to complete; this is the expected baseline.

Stale reader completion opens a third stream

  • The second executed harness capture completes the stale reader and observes stream fetches increase from two to three; this reproduces the defect.

View artifacts

T-Rex Ran code and verified through T-Rex

MAX_RECONNECT_ATTEMPTS caps the attempt counter, it does not stop retries:
both consumers reconnect indefinitely once it pins. With the counter capped
at 6 the delay tops out at 16s, so the "capped at 30s" wording described a
ceiling the attempt cap keeps unreachable. Comments only — nextReconnect()
and the retry behaviour are unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings August 4, 2026 17:13

Copilot AI 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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@SantiagoDePolonia
SantiagoDePolonia merged commit 7c16a72 into main Aug 4, 2026
20 checks passed
SantiagoDePolonia added a commit that referenced this pull request Aug 7, 2026
* fix(dashboard): stop a stale usage stream from reopening itself

#readStream scheduled a reconnect whenever its reader finished, without
checking whether its controller was still the active one. Leaving the
Overview page and returning replaces the controller, but the old reader can
still complete normally afterwards and schedule a reconnect against the
current state, opening a second usage stream alongside the live one.

Guard the completion path with the controller identity/abort check the
audit-log stream already uses (liveLogs.svelte.js), so only the active
stream can reconnect.

Reported by Greptile on #650; the flaw predates that PR.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* fix(dashboard): guard every usage-stream reconnect path

The first pass only guarded the normal-completion path. The invalid-response
branch and the catch block also schedule reconnects, and both are reachable
by a reader whose stream was already replaced — the fetch can resolve before
the abort lands. Route all three through one controller-identity check.

Firefox also rejects a deliberately-aborted read with a plain TypeError
instead of an AbortError, so the catch now trusts the controller state as
well as the error name, matching liveLogs.svelte.js.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.

3 participants