chore(example): add polling/streaming mode toggle and status panel - #349
chore(example): add polling/streaming mode toggle and status panel#349duyhungtnn wants to merge 5 commits into
Conversation
There was a problem hiding this comment.
🟡 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.
There was a problem hiding this comment.
🟡 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
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.
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.
There was a problem hiding this comment.
🟡 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.0itself requiresReadableStream, and its default entry point rejects bodies that are not WebReadableStreaminstances. 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
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>
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.
adead74 to
35d5710
Compare
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.
35d5710 to
d1a0fc2
Compare
There was a problem hiding this comment.
🟡 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
putsnapshots 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
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.
There was a problem hiding this comment.
🟡 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
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.
There was a problem hiding this comment.
🔵 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),onerrorsynchronously starts the SDK's polling fallback, whose request sets the panel topolling fallback; the subsequentclosedreport here overwrites it withdisconnected, 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_evaluationsrequest (BKTClient.scheduleAndFetch()starts tasks and then callsfetchEvaluations). This branch therefore labels a normally connecting stream aspolling fallbackbefore 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 whileinitializingis 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.
There was a problem hiding this comment.
🟡 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
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.
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:
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..envfiles. The default values are provided inexample/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:
EventSourceAdapter(example/eventSourceAdapter.ts) has been introduced, demonstrating how to satisfy the SDK'sEventSourceLikecontract using theeventsource-clientlibrary. This enables advanced users to swap out the default streaming transport for a custom one if needed.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:
.env(for development/tests) and the example app's.env, with step-by-step setup instructions for both.example/env.templatedocuments all demo-related environment variables, including streaming options and demo flag/goal IDs.