Skip to content

refactor(web): extract WebSocket subscription into reusable composable#475

Merged
Aureliolo merged 3 commits intomainfrom
refactor/websocket-composable
Mar 16, 2026
Merged

refactor(web): extract WebSocket subscription into reusable composable#475
Aureliolo merged 3 commits intomainfrom
refactor/websocket-composable

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Extract duplicated WebSocket connect/subscribe/handler/cleanup boilerplate from 6 page views into a new useWebSocketSubscription composable
  • Composable accepts { bindings: ChannelBinding[], filters?: Record<string, string> } and manages the full lifecycle via onMounted/onUnmounted
  • Returns { connected, reconnectExhausted } as reactive ComputedRef<boolean> refs
  • All 6 pages refactored: DashboardPage, TaskBoardPage, ApprovalQueuePage, AgentProfilesPage, BudgetPanelPage, MessageFeedPage
  • Net reduction: 124 lines removed, ~83 lines of boilerplate eliminated across pages
  • Update CLAUDE.md Package Structure to list the new composable

Test plan

  • 15 unit tests for the composable covering: connect guard logic, channel deduplication, filter forwarding, handler wiring, unmount cleanup, error swallowing, empty bindings, reactive refs
  • All 62 existing test files pass (535 tests total)
  • vue-tsc --noEmit passes (no type errors)
  • ESLint passes (no new warnings)

Review coverage

Pre-reviewed by 4 agents (docs-consistency, frontend-reviewer, issue-resolution-verifier, test-quality-reviewer). 5 findings addressed: CLAUDE.md docs update + 4 additional test cases.

Closes #351

Replace duplicated connect/subscribe/handler/cleanup boilerplate across
6 page views with a single useWebSocketSubscription composable that
manages the full WebSocket lifecycle on mount/unmount.

Closes #351
Update CLAUDE.md Package Structure to include useWebSocketSubscription
in composables listing. Add 4 additional test cases: connect-throw
skips subscribe/handler wiring, subscribe-throw is caught, empty
bindings array edge case, reconnectExhausted reactivity.

Pre-reviewed by 4 agents, 5 findings addressed
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 15, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 15, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8e507092-604c-450c-bf24-18c12cac5754

📥 Commits

Reviewing files that changed from the base of the PR and between af22b73 and 786df9a.

📒 Files selected for processing (6)
  • web/src/__tests__/composables/useWebSocketSubscription.test.ts
  • web/src/api/types.ts
  • web/src/composables/useWebSocketSubscription.ts
  • web/src/stores/websocket.ts
  • web/src/views/DashboardPage.vue
  • web/src/views/MessageFeedPage.vue
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Web
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (5)
{src/synthorg/**/*.py,tests/**/*.py,web/src/**/*.{ts,tsx,vue},cli/**/*.go,**/*.md,**/*.yml,**/*.yaml,**/*.json,pyproject.toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, example-medium-001, example-small-001, or size aliases

Files:

  • web/src/api/types.ts
  • web/src/stores/websocket.ts
  • web/src/views/MessageFeedPage.vue
  • web/src/composables/useWebSocketSubscription.ts
  • web/src/__tests__/composables/useWebSocketSubscription.test.ts
  • web/src/views/DashboardPage.vue
web/src/api/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Axios client in web/src/api/ for REST API communication with TypeScript types mirroring backend Pydantic models

Files:

  • web/src/api/types.ts
web/src/stores/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Pinia stores in web/src/stores/ for state management (auth, agents, tasks, budget, messages, meetings, approvals, websocket, analytics, company, providers)

Files:

  • web/src/stores/websocket.ts
web/src/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

Use PrimeVue components + Tailwind CSS for styling in Vue components

Files:

  • web/src/views/MessageFeedPage.vue
  • web/src/views/DashboardPage.vue
web/src/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Vitest for Vue component unit tests with fast-check for property-based testing

Files:

  • web/src/__tests__/composables/useWebSocketSubscription.test.ts
🧠 Learnings (4)
📚 Learning: 2026-03-15T22:42:34.899Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:42:34.899Z
Learning: Applies to web/src/stores/**/*.{ts,tsx} : Use Pinia stores in `web/src/stores/` for state management (auth, agents, tasks, budget, messages, meetings, approvals, websocket, analytics, company, providers)

Applied to files:

  • web/src/stores/websocket.ts
  • web/src/views/DashboardPage.vue
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • web/src/views/MessageFeedPage.vue
  • web/src/views/DashboardPage.vue
📚 Learning: 2026-03-15T22:42:34.899Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:42:34.899Z
Learning: Applies to web/src/**/*.vue : Use PrimeVue components + Tailwind CSS for styling in Vue components

Applied to files:

  • web/src/views/MessageFeedPage.vue
📚 Learning: 2026-03-15T22:42:34.899Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:42:34.899Z
Learning: Applies to web/src/__tests__/**/*.{ts,tsx} : Use Vitest for Vue component unit tests with fast-check for property-based testing

Applied to files:

  • web/src/__tests__/composables/useWebSocketSubscription.test.ts
🧬 Code graph analysis (2)
web/src/stores/websocket.ts (1)
web/src/api/types.ts (3)
  • WsChannel (619-626)
  • WsSubscriptionFilters (658-658)
  • WsEventHandler (682-682)
web/src/__tests__/composables/useWebSocketSubscription.test.ts (3)
web/src/stores/websocket.ts (1)
  • useWebSocketStore (19-239)
web/src/api/types.ts (1)
  • WsEventHandler (682-682)
web/src/composables/useWebSocketSubscription.ts (1)
  • useWebSocketSubscription (47-105)
🪛 GitHub Check: Dashboard Lint
web/src/__tests__/composables/useWebSocketSubscription.test.ts

[warning] 304-304:
Generic Object Injection Sink


[warning] 302-302:
Generic Object Injection Sink

🔇 Additional comments (9)
web/src/api/types.ts (1)

657-663: LGTM! Good type safety improvement.

The WsSubscriptionFilters type with Readonly wrapper provides immutability guarantees and improves type safety over the previous inline Record<string, string>. Clean integration with WsSubscribeMessage.

web/src/stores/websocket.ts (1)

3-3: LGTM! Type narrowing improves API safety.

The updated signatures provide better type safety:

  • subscribe now uses WsSubscriptionFilters for consistent filter typing
  • onChannelEvent/offChannelEvent now use WsChannel | '*' instead of plain string, preventing typos in channel names while still allowing the wildcard

Also applies to: 155-155, 203-203, 210-210

web/src/composables/useWebSocketSubscription.ts (1)

1-105: LGTM! Well-structured composable with comprehensive error handling.

The implementation correctly:

  • Guards all setup operations with auth token check
  • Separates try/catch blocks per operation to allow partial success
  • Deduplicates channels while wiring all handlers
  • Provides defensive cleanup in onUnmounted with per-operation error handling
  • Exposes clear reactive state via connected, reconnectExhausted, and setupError

The documentation clearly communicates the behavior, including that setup is skipped silently without a token.

web/src/views/MessageFeedPage.vue (1)

12-18: LGTM! Clean migration to composable.

The WebSocket lifecycle management is now handled by useWebSocketSubscription, simplifying this component. The binding correctly routes messages channel events to messageStore.handleWsEvent.

Note: The composable returns connected, reconnectExhausted, and setupError refs which could be used for UI feedback if needed in the future, but the current implementation doesn't require displaying connection status.

web/src/views/DashboardPage.vue (2)

30-36: LGTM! Good integration with connection status binding.

The composable correctly handles multiple channel bindings, and the connected ref is properly destructured and bound to SystemStatus for displaying WebSocket connection state.

Also applies to: 120-120


57-61: Good addition of error logging for rejected fetches.

Logging Promise.allSettled rejections with sanitized output helps with debugging while maintaining security.

web/src/__tests__/composables/useWebSocketSubscription.test.ts (3)

1-42: LGTM! Well-structured test setup.

The Vue lifecycle mocking approach is clean:

  • onMounted synchronously invokes callbacks to run setup logic immediately
  • onUnmounted records callbacks for later invocation via getUnmountCallback()
  • Re-establishing mocks after clearAllMocks is correctly documented

294-306: LGTM! Parameterized test for ref state reflection.

The static analysis warnings about "Generic Object Injection Sink" are false positives here. The refName parameter is constrained to the literal union 'connected' | 'reconnectExhausted' via the as const assertion, making injection impossible. The parameterized approach reduces duplication effectively.


44-292: Comprehensive test coverage.

The 15 test cases thoroughly cover:

  • Happy path (connect, subscribe, handler wiring)
  • Auth guard (skips setup without token)
  • Channel deduplication with multiple handlers
  • Filter forwarding
  • Error handling for both connect and subscribe failures
  • Cleanup on unmount (including after failed setup)
  • Empty bindings edge case

This provides good confidence in the composable's behavior.


📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added a reusable WebSocket subscription hook to manage realtime bindings and connection state.
  • Refactor

    • Updated dashboard, pages (agents, approvals, budget, messages, tasks, etc.) to use the new subscription approach, simplifying lifecycle and event wiring and surfacing connection state.
  • Tests

    • Added comprehensive test coverage for the new WebSocket subscription behavior.

Walkthrough

Extracts duplicated WebSocket lifecycle logic into a new useWebSocketSubscription composable and updates six page views to use it; adds types for subscription filters and a comprehensive test suite for the composable.

Changes

Cohort / File(s) Summary
New WebSocket Subscription Composable
web/src/composables/useWebSocketSubscription.ts, web/src/__tests__/composables/useWebSocketSubscription.test.ts
Adds useWebSocketSubscription with typed channel bindings, lifecycle wiring (connect, deduplicated subscribe, handler registration) and cleanup; includes extensive unit tests covering connect/subscribe flows, deduplication, error handling, and unmount cleanup.
Page View Refactors
web/src/views/AgentProfilesPage.vue, web/src/views/ApprovalQueuePage.vue, web/src/views/BudgetPanelPage.vue, web/src/views/DashboardPage.vue, web/src/views/MessageFeedPage.vue, web/src/views/TaskBoardPage.vue
Replaces per-page manual WebSocket connect/subscribe/onChannelEvent/unsubscribe logic with useWebSocketSubscription bindings to respective store handlers; removes direct useWebSocketStore/useAuthStore usage and onUnmounted cleanup code.
WebSocket API types & store signatures
web/src/api/types.ts, web/src/stores/websocket.ts
Introduces WsSubscriptionFilters type and updates WsSubscribeMessage.filters; adjusts subscribe, onChannelEvent, and offChannelEvent signatures to use typed WsSubscriptionFilters and `WsChannel
Exports / Index Update
CLAUDE.md
Adds useWebSocketSubscription to the composables index/export list.

Sequence Diagram(s)

sequenceDiagram
  participant Component as "Page Component"
  participant Composable as "useWebSocketSubscription"
  participant WSStore as "WebSocket Store"
  participant Server as "WebSocket Server"

  Component->>Composable: mount + provide bindings (channel, handler, filters)
  Composable->>WSStore: connect() (if token & not connected)
  WSStore->>Server: open connection
  Composable->>WSStore: subscribe(dedupedChannels, filters)
  WSStore->>Server: subscribe channels with filters
  Server-->>WSStore: events (per channel)
  WSStore-->>Composable: dispatch channel event
  Composable-->>Component: invoke handler(s) for binding(s)
  Component->>Composable: unmount
  Composable->>WSStore: unsubscribe(channels) && offChannelEvent(handler)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% 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 change: extracting WebSocket subscription boilerplate into a reusable composable, which is the core objective of this PR.
Description check ✅ Passed The description clearly relates to the changeset by detailing the extraction of WebSocket boilerplate, listing affected pages, test coverage, and linking to issue #351.
Linked Issues check ✅ Passed All acceptance criteria from issue #351 are met: composable created with types, all 6 pages refactored, unit tests added, and existing tests pass.
Out of Scope Changes check ✅ Passed All changes align with issue #351: new composable, page refactoring, type updates (WsSubscriptionFilters), and test additions. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/websocket-composable
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch refactor/websocket-composable
📝 Coding Plan
  • Generate coding plan for human review comments

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

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new reusable Vue composable, useWebSocketSubscription, to centralize and simplify WebSocket management across the application. By abstracting the complex lifecycle of WebSocket connections, subscriptions, and event handling, it significantly reduces boilerplate code in page components and improves maintainability. The change impacts several key pages, making their WebSocket interactions more consistent and less error-prone.

Highlights

  • New Composable: Extracted duplicated WebSocket connection, subscription, and handler boilerplate into a new useWebSocketSubscription composable, which manages the full lifecycle via onMounted and onUnmounted hooks.
  • Refactoring: Refactored six page views (DashboardPage, TaskBoardPage, ApprovalQueuePage, AgentProfilesPage, BudgetPanelPage, MessageFeedPage) to utilize the new useWebSocketSubscription composable, significantly reducing redundant code.
  • Code Reduction: Achieved a net reduction of 124 lines, eliminating approximately 83 lines of boilerplate across the refactored pages.
  • Documentation Update: Updated CLAUDE.md to include the new useWebSocketSubscription composable in the package structure documentation.
  • Comprehensive Testing: Added 15 unit tests for the new composable, covering various scenarios including connect guard logic, channel deduplication, filter forwarding, handler wiring, unmount cleanup, error swallowing, empty bindings, and reactive refs.
Changelog
  • CLAUDE.md
    • Updated the list of composables in the package structure documentation to include useWebSocketSubscription.
  • web/src/tests/composables/useWebSocketSubscription.test.ts
    • Added a new test file containing comprehensive unit tests for the useWebSocketSubscription composable.
  • web/src/composables/useWebSocketSubscription.ts
    • Created a new Vue composable, useWebSocketSubscription, to encapsulate WebSocket connection, subscription, and event handling logic.
  • web/src/views/AgentProfilesPage.vue
    • Removed direct WebSocket store imports and logic.
    • Integrated useWebSocketSubscription to manage WebSocket events for agent profiles.
  • web/src/views/ApprovalQueuePage.vue
    • Removed direct WebSocket store imports and logic.
    • Integrated useWebSocketSubscription to manage WebSocket events for the approval queue.
  • web/src/views/BudgetPanelPage.vue
    • Removed direct WebSocket store imports and logic.
    • Integrated useWebSocketSubscription to manage WebSocket events for the budget panel.
  • web/src/views/DashboardPage.vue
    • Removed direct WebSocket store imports and logic.
    • Integrated useWebSocketSubscription to manage WebSocket events for tasks, budget, and approvals.
    • Updated the SystemStatus component to receive WebSocket connection status from the new composable.
  • web/src/views/MessageFeedPage.vue
    • Removed direct WebSocket store imports and logic.
    • Integrated useWebSocketSubscription to manage WebSocket events for the message feed.
  • web/src/views/TaskBoardPage.vue
    • Removed direct WebSocket store imports and logic.
    • Integrated useWebSocketSubscription to manage WebSocket events for the task board.
Activity
  • Aureliolo created this pull request to refactor WebSocket subscription logic.
  • The pull request was pre-reviewed by 4 agents (docs-consistency, frontend-reviewer, issue-resolution-verifier, test-quality-reviewer).
  • 5 findings were addressed during the pre-review, including a CLAUDE.md documentation update and 4 additional test cases.
  • The pull request closes issue refactor: extract WebSocket subscription logic into reusable composable #351.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This is an excellent refactoring that significantly reduces boilerplate code across multiple components by introducing the useWebSocketSubscription composable. The new composable is well-designed and its lifecycle management is correctly handled with onMounted and onUnmounted. The accompanying unit tests are comprehensive and cover a wide range of scenarios, including error handling and edge cases, which is great to see. I've found one potential logic issue in the new composable related to handling subscriptions when no auth token is present, for which I've left a specific comment and suggestion.

Comment on lines +45 to +51
if (authStore.token && !wsStore.connected) {
wsStore.connect(authStore.token)
}
wsStore.subscribe(uniqueChannels, options.filters)
for (const binding of options.bindings) {
wsStore.onChannelEvent(binding.channel, binding.handler)
}
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.

high

The current implementation attempts to subscribe to WebSocket channels and wire event handlers even if no authentication token is present. This leads to subscriptions being queued in the websocket store that will never be sent, as a connection is never established. The entire setup logic within onMounted should be conditional on the presence of an auth token to prevent this. This change will likely require updates to some unit tests that currently expect subscribe to be called without a token.

      if (authStore.token) {
        if (!wsStore.connected) {
          wsStore.connect(authStore.token)
        }
        wsStore.subscribe(uniqueChannels, options.filters)
        for (const binding of options.bindings) {
          wsStore.onChannelEvent(binding.channel, binding.handler)
        }
      }

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

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 the current code and only fix it if needed.

Inline comments:
In `@web/src/composables/useWebSocketSubscription.ts`:
- Around line 57-62: The onUnmounted cleanup should use defensive try/catch so
one failing call doesn't abort remaining cleanup: wrap the
wsStore.unsubscribe(uniqueChannels) call in a try/catch and log the error, then
iterate options.bindings and for each binding call
wsStore.offChannelEvent(binding.channel, binding.handler) inside its own
try/catch (or catch per-iteration) so a thrown error for one binding doesn't
prevent unregistering the rest; reference onUnmounted, wsStore.unsubscribe,
wsStore.offChannelEvent, options.bindings, and uniqueChannels when locating
where to add these guards.
🪄 Autofix (Beta)

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

Run ID: eaf32335-89dc-4e2d-9173-bca247dc71b2

📥 Commits

Reviewing files that changed from the base of the PR and between 30e96d5 and af22b73.

📒 Files selected for processing (9)
  • CLAUDE.md
  • web/src/__tests__/composables/useWebSocketSubscription.test.ts
  • web/src/composables/useWebSocketSubscription.ts
  • web/src/views/AgentProfilesPage.vue
  • web/src/views/ApprovalQueuePage.vue
  • web/src/views/BudgetPanelPage.vue
  • web/src/views/DashboardPage.vue
  • web/src/views/MessageFeedPage.vue
  • web/src/views/TaskBoardPage.vue
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Build Sandbox
  • GitHub Check: Build Web
  • GitHub Check: Build Backend
  • GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (3)
{src/synthorg/**/*.py,tests/**/*.py,web/src/**/*.{ts,tsx,vue},cli/**/*.go,**/*.md,**/*.yml,**/*.yaml,**/*.json,pyproject.toml}

📄 CodeRabbit inference engine (CLAUDE.md)

Never use real vendor names (Anthropic, OpenAI, Claude, GPT, etc.) in project-owned code, docstrings, comments, tests, or config examples; use generic names: example-provider, example-large-001, example-medium-001, example-small-001, or size aliases

Files:

  • CLAUDE.md
  • web/src/__tests__/composables/useWebSocketSubscription.test.ts
  • web/src/views/TaskBoardPage.vue
  • web/src/views/DashboardPage.vue
  • web/src/composables/useWebSocketSubscription.ts
  • web/src/views/BudgetPanelPage.vue
  • web/src/views/MessageFeedPage.vue
  • web/src/views/AgentProfilesPage.vue
  • web/src/views/ApprovalQueuePage.vue
web/src/__tests__/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Vitest for Vue component unit tests with fast-check for property-based testing

Files:

  • web/src/__tests__/composables/useWebSocketSubscription.test.ts
web/src/**/*.vue

📄 CodeRabbit inference engine (CLAUDE.md)

Use PrimeVue components + Tailwind CSS for styling in Vue components

Files:

  • web/src/views/TaskBoardPage.vue
  • web/src/views/DashboardPage.vue
  • web/src/views/BudgetPanelPage.vue
  • web/src/views/MessageFeedPage.vue
  • web/src/views/AgentProfilesPage.vue
  • web/src/views/ApprovalQueuePage.vue
🧠 Learnings (9)
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to web/src/components/** : Vue components organized by feature (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/).

Applied to files:

  • CLAUDE.md
  • web/src/views/TaskBoardPage.vue
  • web/src/views/BudgetPanelPage.vue
  • web/src/views/AgentProfilesPage.vue
  • web/src/views/ApprovalQueuePage.vue
📚 Learning: 2026-03-15T22:42:34.899Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:42:34.899Z
Learning: Applies to web/src/components/**/*.vue : Vue components should be organized by feature in `web/src/components/` (agents/, approvals/, budget/, common/, dashboard/, layout/, messages/, org-chart/, tasks/)

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T22:42:34.899Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:42:34.899Z
Learning: Applies to web/src/stores/**/*.{ts,tsx} : Use Pinia stores in `web/src/stores/` for state management (auth, agents, tasks, budget, messages, meetings, approvals, websocket, analytics, company, providers)

Applied to files:

  • CLAUDE.md
  • web/src/views/TaskBoardPage.vue
  • web/src/views/DashboardPage.vue
📚 Learning: 2026-03-15T22:42:34.899Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:42:34.899Z
Learning: Applies to web/src/router/**/*.{ts,tsx} : Use Vue Router with auth guards in `web/src/router/` for routing configuration

Applied to files:

  • CLAUDE.md
  • web/src/views/AgentProfilesPage.vue
📚 Learning: 2026-03-15T22:42:34.899Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:42:34.899Z
Learning: Applies to web/src/api/**/*.{ts,tsx} : Use Axios client in `web/src/api/` for REST API communication with TypeScript types mirroring backend Pydantic models

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to web/** : Web dashboard: Node.js 20+, dependencies in web/package.json (Vue 3, PrimeVue, Tailwind CSS, Pinia, VueFlow, ECharts, Axios, vue-draggable-plus, Vitest, fast-check, ESLint, vue-tsc).

Applied to files:

  • CLAUDE.md
  • web/src/views/DashboardPage.vue
  • web/src/views/MessageFeedPage.vue
📚 Learning: 2026-03-15T22:42:34.899Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:42:34.899Z
Learning: Applies to web/src/**/*.vue : Use PrimeVue components + Tailwind CSS for styling in Vue components

Applied to files:

  • CLAUDE.md
  • web/src/views/TaskBoardPage.vue
  • web/src/views/MessageFeedPage.vue
  • web/src/views/ApprovalQueuePage.vue
📚 Learning: 2026-03-15T11:48:14.867Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T11:48:14.867Z
Learning: Applies to docker/{Dockerfile*,compose.yml} : Docker: Backend uses 3-stage build (builder → setup → distroless runtime), Chainguard Python, non-root (UID 65532), CIS-hardened. Web uses nginxinc/nginx-unprivileged, Vue 3 SPA with PrimeVue + Tailwind CSS, SPA routing, API/WebSocket proxy to backend.

Applied to files:

  • CLAUDE.md
📚 Learning: 2026-03-15T22:42:34.899Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T22:42:34.899Z
Learning: Applies to web/src/__tests__/**/*.{ts,tsx} : Use Vitest for Vue component unit tests with fast-check for property-based testing

Applied to files:

  • web/src/__tests__/composables/useWebSocketSubscription.test.ts
🧬 Code graph analysis (1)
web/src/__tests__/composables/useWebSocketSubscription.test.ts (3)
web/src/stores/websocket.ts (1)
  • useWebSocketStore (19-239)
web/src/api/types.ts (1)
  • WsEventHandler (679-679)
web/src/composables/useWebSocketSubscription.ts (1)
  • useWebSocketSubscription (35-68)
🔇 Additional comments (10)
web/src/__tests__/composables/useWebSocketSubscription.test.ts (1)

1-266: Well-structured and comprehensive test suite.

The tests thoroughly cover the composable's functionality: connection guards, channel deduplication, filter forwarding, handler wiring, cleanup on unmount, error handling (both connect and subscribe failures), empty bindings edge case, and reactive ref synchronization. The mock setup for Vue lifecycle hooks is a clean approach for testing composables outside component context.

One minor observation: the coding guidelines mention fast-check for property-based testing, but deterministic unit tests are appropriate here since the composable behavior is straightforward to verify with specific inputs.

web/src/composables/useWebSocketSubscription.ts (1)

35-68: Clean composable implementation.

The composable properly encapsulates WebSocket lifecycle management: conditional connect, channel deduplication via Set, handler wiring, and cleanup. The use of readonly modifiers on interfaces and computed refs for reactive state exposure are good TypeScript practices.

web/src/views/TaskBoardPage.vue (1)

34-47: Clean refactor using the new composable.

The WebSocket subscription is now declaratively configured, and onMounted focuses solely on initial data fetching. This separation of concerns improves maintainability and aligns with the PR's goal of eliminating duplicated boilerplate.

web/src/views/ApprovalQueuePage.vue (1)

35-45: Consistent adoption of the composable pattern.

The refactor follows the same clean pattern as other views, binding the approvals channel to approvalStore.handleWsEvent while keeping data fetching in onMounted.

web/src/views/BudgetPanelPage.vue (1)

16-26: Consistent refactor pattern applied.

Budget channel subscription is cleanly delegated to the composable while data fetching remains in onMounted.

web/src/views/MessageFeedPage.vue (1)

16-26: Refactor maintains existing channel-switching behavior.

The composable handles WebSocket lifecycle while the watch on activeChannel correctly remains for fetching messages when the user switches channels.

web/src/views/AgentProfilesPage.vue (1)

18-28: Consistent composable adoption.

The agents channel subscription follows the established pattern, cleanly separating WebSocket lifecycle from data fetching.

web/src/views/DashboardPage.vue (2)

29-35: Good use of multi-channel bindings.

The Dashboard demonstrates the composable's ability to handle multiple channel subscriptions in a single call. Destructuring connected to pass to SystemStatus is a clean way to expose connection state to the UI.


37-66: Resilient data loading with partial failure handling.

Using Promise.allSettled with failure detection and toast notification provides good UX when some data sources are unavailable. This pattern gracefully degrades rather than failing the entire dashboard load.

CLAUDE.md (1)

137-137: Documentation update is accurate and consistent with the refactor.

The composables index correctly includes useWebSocketSubscription, matching the PR’s stated structural change.

…d CodeRabbit

Composable improvements:
- Guard subscribe/handler wiring behind auth token check (Gemini)
- Separate try/catch per operation so partial failures don't cascade
- Add defensive try/catch to onUnmounted cleanup (CodeRabbit)
- Add setupError ref to return type for user-facing error state
- Update JSDoc: document error-swallowing, channel dedup, conditions
- Use WsSubscriptionFilters (Readonly) for filters type

Store type tightening:
- Narrow onChannelEvent/offChannelEvent from string to WsChannel | '*'
- Import and use WsSubscriptionFilters for subscribe parameter

Type system:
- Extract WsSubscriptionFilters type alias in api/types.ts
- Update WsSubscribeMessage to use the shared type

Page fixes:
- Fix empty catch block in MessageFeedPage watch (was silently swallowing)
- Log Promise.allSettled rejection reasons in DashboardPage
- Re-add sanitizeForLog import to DashboardPage

Test improvements:
- Explicitly re-establish both onMounted and onUnmounted mocks after clearAllMocks
- Strengthen error assertions with actual messages (replace expect.any(String))
- Add test: unmount cleanup after failed connect
- Add test: handler wiring continues when subscribe throws
- Combine reactive-ref tests with it.each
- Add auth token to tests that exercise subscribe/handler wiring
- Add setupError assertions to error path tests
@Aureliolo Aureliolo merged commit 96e6c46 into main Mar 16, 2026
28 checks passed
@Aureliolo Aureliolo deleted the refactor/websocket-composable branch March 16, 2026 06:17
Aureliolo added a commit that referenced this pull request Mar 16, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.2.8](v0.2.7...v0.2.8)
(2026-03-16)


### Features

* add RRF rank fusion to memory ranking
([#478](#478))
([42242b5](42242b5))
* collaboration scoring enhancements — LLM sampling and human override
([#477](#477))
([b3f3330](b3f3330))


### Bug Fixes

* add .gitattributes to enforce LF line endings for Go files
([#483](#483))
([1b8c7b6](1b8c7b6))
* **cli:** Windows uninstall, update UX, health check, sigstore
([#476](#476))
([470ca72](470ca72))


### Refactoring

* **web:** extract WebSocket subscription into reusable composable
([#475](#475))
([96e6c46](96e6c46)),
closes [#351](#351)


### Maintenance

* bump hypothesis from 6.151.5 to 6.151.9 in the minor-and-patch group
([#482](#482))
([a7297d5](a7297d5))
* bump nginxinc/nginx-unprivileged from `aec540f` to `ccbac1a` in
/docker/web ([#479](#479))
([176e052](176e052))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.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.

refactor: extract WebSocket subscription logic into reusable composable

1 participant