Skip to content

chore(example): add polling/streaming mode toggle and status panel - #349

Draft
duyhungtnn wants to merge 5 commits into
mainfrom
chore/example-see-update
Draft

chore(example): add polling/streaming mode toggle and status panel#349
duyhungtnn wants to merge 5 commits into
mainfrom
chore/example-see-update

Conversation

@duyhungtnn

@duyhungtnn duyhungtnn commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

This pull request significantly enhances the example application for the Bucketeer JS Client SDK by improving developer experience, documentation, and the demonstration of streaming capabilities. The updates include a more user-friendly and informative UI, better environment variable management, and the introduction of a custom streaming adapter to illustrate advanced SDK usage.

Example app improvements:

  • The example app UI (example/index.html) has been redesigned with a new header, clearer controls, and a status panel that displays update mode, stream state, last update, evaluation/heartbeat counts, and inferred connection status. Controls for clearing logs and toggling update modes (polling, built-in streaming, custom event source) have been added, and button states are more accurately managed.
  • The app now reads feature flag and goal identifiers from environment variables, allowing configuration via .env files. The default values are provided in example/env.template, and the README has been updated to clarify environment setup for both development and the example app. [1] [2]

Streaming and advanced usage:

  • A custom EventSourceAdapter (example/eventSourceAdapter.ts) has been introduced, demonstrating how to satisfy the SDK's EventSourceLike contract using the eventsource-client library. This enables advanced users to swap out the default streaming transport for a custom one if needed.
  • The example app's logic (example/index.ts) has been updated to support switching between polling, built-in streaming, and custom event source modes. It uses the new adapter and tracks streaming state, terminal failures, and network events to provide real-time feedback in the UI. [1] [2]

Documentation and configuration:

  • The README now clearly distinguishes between the main .env (for development/tests) and the example app's .env, with step-by-step setup instructions for both.
  • The new example/env.template documents all demo-related environment variables, including streaming options and demo flag/goal IDs.
Screenshot 2026-09-09 at 10 58 12

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

🟡 Changes recommended

Moderate issues remain in SSE lifecycle handling, status accuracy, mode switching, and accessibility.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Adds runtime-selectable polling/SSE modes and network-derived connection diagnostics to the example app.

Changes:

  • Adds mode controls, connection status metrics, and flag-value UI.
  • Adds fetch instrumentation and a custom SSE adapter.
  • Separates example environment configuration from SDK development settings.
File summaries
File Description
README.md Documents standalone example environment setup.
example/vite.config.ts Loads environment variables from the example directory.
example/types.ts Defines instrumentation and transport types.
example/package.json Adds the custom SSE client dependency.
example/loggingFetch.ts Instruments polling and streaming activity.
example/index.ts Implements mode selection and status management.
example/index.html Adds mode controls and status panels.
example/eventSourceAdapter.ts Adapts eventsource-client to the SDK interface.
example/env.template Provides example-specific configuration variables.
Review details
  • Files reviewed: 9/10 changed files
  • Comments generated: 9
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread example/eventSourceAdapter.ts
Comment thread example/eventSourceAdapter.ts
Comment thread example/index.html Outdated
Comment thread example/index.ts
Comment thread example/index.ts
Comment thread example/loggingFetch.ts Outdated
Comment thread example/loggingFetch.ts Outdated
Comment thread example/loggingFetch.ts Outdated
Comment thread example/eventSourceAdapter.ts Outdated

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

🟡 Changes recommended

Two moderate error-handling and terminal-stream-state issues remain unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 9/10 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread example/index.ts Outdated
Comment thread example/loggingFetch.ts
duyhungtnn added a commit that referenced this pull request Sep 8, 2026
A Copilot review on PR #349 found that the custom EventSourceAdapter
and loggingFetch instrumentation could report the stream as open or
connecting when it had actually failed or closed: eventsource-client
fires onConnect for any resolved response (even 401/403) and skips
onDisconnect whenever the fetch itself rejects. It also flagged a
mode-switch race during init, a stale timestamp left over after
re-init, a dead README link, and a missing status role for screen
readers.
duyhungtnn added a commit that referenced this pull request Sep 8, 2026
A second Copilot review on PR #349 found two more gaps: building
BKTConfig/BKTUser outside the init try-block meant a synchronous
validation error (e.g. a malformed endpoint) left Initialize and the
mode radios stuck disabled forever, and a successful stream response
with no body was reported the same as an ordinary disconnect even
though the SDK treats it as a permanent, non-retryable failure.
@duyhungtnn
duyhungtnn requested a balanced review from Copilot September 8, 2026 16:46

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

🟡 Changes recommended

Multiple moderate SSE parsing, lifecycle, and accessibility issues remain unresolved.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (2)

example/eventSourceAdapter.ts:6

  • This React Native example is inaccurate: eventsource-client@1.2.0 itself requires ReadableStream, and its default entry point rejects bodies that are not Web ReadableStream instances. Remove this claimed use case or use an adapter/library that actually supports that runtime without the polyfill.
// the built-in transport can't work (e.g. React Native without a fetch +
// ReadableStream polyfill) or a specific SSE library is required.

example/index.html:24

  • This wording implies that the SDK's default polling interval is 60 seconds, but the SDK default is 10 minutes; only this example overrides it to 60 seconds. Distinguish the default mode from the demo-specific interval.
      <label style="display: block;"><input type="radio" name="mode" value="polling"> polling (comparison mode, matches the SDK default: up to 60s to see a change)</label>
  • Files reviewed: 9/10 changed files
  • Comments generated: 5
  • Review effort level: Balanced

Comment thread example/eventSourceAdapter.ts
Comment thread example/eventSourceAdapter.ts
Comment thread example/index.html Outdated
Comment thread example/loggingFetch.ts Outdated
Comment thread example/loggingFetch.ts
duyhungtnn added a commit that referenced this pull request Sep 8, 2026
A third Copilot review on PR #349 found the custom EventSourceAdapter
still reported an unreadable-body stream response as recoverable
instead of terminal like the built-in transport, dropped an explicit
"event: message" block because StreamConnection never registers a
listener under that name, made every heartbeat re-announce the whole
status panel to screen readers, mis-stated the SDK's default polling
interval, and could split or silently drop the logging instrumentation's
own CRLF and mid-stream failure handling.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
duyhungtnn added a commit that referenced this pull request Sep 8, 2026
A third Copilot review on PR #349 found the custom EventSourceAdapter
still reported an unreadable-body stream response as recoverable
instead of terminal like the built-in transport, dropped an explicit
"event: message" block because StreamConnection never registers a
listener under that name, made every heartbeat re-announce the whole
status panel to screen readers, mis-stated the SDK's default polling
interval, and could split or silently drop the logging instrumentation's
own CRLF and mid-stream failure handling.
@duyhungtnn
duyhungtnn force-pushed the chore/example-see-update branch from adead74 to 35d5710 Compare September 8, 2026 17:17
Adds a runtime-selectable polling/streaming/custom-eventSource mode
toggle to the example app, a status panel that infers connection
state from network activity (open/connecting/disconnected/stopped
permanently/polling fallback), and a demo adapter over the
eventsource-client library showing how to satisfy the SDK's
EventSourceLike contract. Separates the example's own env config
from SDK development settings and adds branding/docs links.

The instrumentation and custom adapter went through several rounds
of review (false "open"/"connecting" states on non-2xx or bodyless
responses, silently dropped reconnect notifications, an explicit
"event: message" block being dropped instead of reaching onmessage,
CRLF split across chunk boundaries, a config-validation error
leaving the UI stuck mid-init, and an overly broad ARIA live
region), fixed to bring the demo's behavior and status reporting in
line with how the SDK's built-in FetchEventSource/StreamConnection
actually classify recoverable vs. terminal failures.

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

🟡 Changes recommended

The new SSE adapter and framing parser lack required automated coverage for critical edge cases.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details

Suppressed comments (1)

example/loggingFetch.ts:52

  • This rescans and reallocates the entire unterminated SSE block on every network chunk. Large initial put snapshots split across many chunks therefore make the display-side tee branch quadratic and can stall the page even though the SDK parser avoids this with a retained search offset (src/internal/streaming/FetchEventSource.ts:140-174). Parse completed delimiters incrementally and retain only the unconsumed suffix.
      buffer += chunkText.replace(/\r\n/g, '\n').replace(/\r/g, '\n')
      const blocks = buffer.split('\n\n')
      buffer = blocks.pop() ?? ''
  • Files reviewed: 9/10 changed files
  • Comments generated: 2
  • Review effort level: Balanced

Comment thread example/eventSourceAdapter.ts
Comment thread example/loggingFetch.ts
The custom EventSource adapter and the display-side SSE framing both
carry correctness-sensitive logic with no automated coverage, so a
regression there would be silent: eventsource-client uses
onScheduleReconnect for rejected fetches and calls it alongside
onDisconnect at end of stream, and the framing has to survive CRLF
split across chunk boundaries.

Writing the tests surfaced a real bug: a '\r' held back at the end of
a chunk was never flushed at EOF, so a CR-only stream lost its final
block. The block scan also restarted from the beginning of the buffer
on every chunk, making a large put snapshot quadratic on the display
branch; it now resumes from a retained offset the way the SDK parser
does. The sse event's size field counted the raw block in one
transport and the payload in the other, so both now report the
payload and the panel actually shows it.

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

🟢 Approval recommended

All reviewed changes are covered with no unresolved approval-blocking issues.

Review details
  • Files reviewed: 13/14 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

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

🟡 Changes recommended

SSE data parsing incorrectly removes all leading whitespace from payload lines.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 13/14 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread example/loggingFetch.ts
trimStart() removed every leading space/tab after "data:", not just
the one optional separator space the SSE spec allows, so an indented
payload like "data:   x" undercounted its displayed length and
disagreed with what the custom adapter (via eventsource-parser)
actually parses for the same line.

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

🔵 Needs a closer look

Two moderate status-reporting issues must be corrected before approval.

Review details

Suppressed comments (2)

Previously missed (2) — in code that hasn't changed since the last review.

example/eventSourceAdapter.ts:172

  • Report the closure before invoking onerror. For non-terminal statuses outside the fast-retry set (for example 400/413/422), onerror synchronously starts the SDK's polling fallback, whose request sets the panel to polling fallback; the subsequent closed report here overwrites it with disconnected, leaving the custom-mode status incorrect while polling is active.
    example/index.ts:136
  • Every streaming initialization still performs the SDK's mandatory bootstrap get_evaluations request (BKTClient.scheduleAndFetch() starts tasks and then calls fetchEvaluations). This branch therefore labels a normally connecting stream as polling fallback before any streaming failure has occurred; with a slow stream handshake, the incorrect state can remain visible until the connection opens or times out. Ignore this bootstrap request while initializing is true, and reserve the fallback label for later polling requests.
  • Files reviewed: 13/14 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

handleDisconnect() called onerror before reporting closed. For a
non-terminal, non-fast-retry status (400/413/422), onerror
synchronously starts the SDK's polling fallback, whose request set
the panel to "polling fallback"; the closed report right after then
overwrote that back to "disconnected" even though polling was
actually running. Report closed first instead.

Separately, BKTClient.scheduleAndFetch() always fires one bootstrap
get_evaluations request alongside opening the stream, whether or not
streaming ever fails. The panel counted that request as evidence of
a polling fallback, mislabeling a normally connecting stream until
the connection opened or timed out. Ignore get_evaluations requests
observed while still initializing.

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

🟡 Changes recommended

The fallback polling status can be incorrectly suppressed during initialization.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review details
  • Files reviewed: 13/14 changed files
  • Comments generated: 1
  • Review effort level: Balanced

Comment thread example/index.ts Outdated
The prior !initializing guard suppressed every get_evaluations
request seen during init, not just the bootstrap one. If the stream
fails with a non-terminal status before the bootstrap fetch resolves,
StreamingTask.startFallback() issues a second, real get_evaluations
request while initializing is still true, and that one was being
suppressed too, leaving the panel stuck at disconnected while polling
was actually running.

Track it as a one-shot flag instead: set right before
initializeBKTClient() and consumed by exactly the next
get_evaluations request, so only that first bootstrap request is
ignored and a genuine fallback request right behind it still reports
correctly.

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

🟢 Approval recommended

The reviewed changes have no unresolved approval-blocking issues.

Review details
  • Files reviewed: 13/14 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

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