Skip to content

Conversation

@calebbourg
Copy link
Collaborator

Description

describe the intent of your changes here

GitHub Issue: [Closes|Fixes|Resolves] #your GitHub issue number here

Changes

  • ...
  • ...
  • ...

Screenshots / Videos Showing UI Changes (if applicable)

Testing Strategy

describe how you or someone else can test and verify the changes

Concerns

describe any concerns that might be worth mentioning or discussing

@calebbourg calebbourg marked this pull request as ready for review December 9, 2025 11:24
@jhodapp jhodapp changed the title initiall fe sse plan Initiall fe sse plan Dec 13, 2025
@jhodapp jhodapp changed the title Initiall fe sse plan Initiall fe SSE plan Dec 13, 2025
@jhodapp jhodapp added the documentation Improvements or additions to documentation label Dec 13, 2025
@jhodapp jhodapp moved this to 🏗 In progress in Refactor Coaching Platform Dec 13, 2025
@jhodapp jhodapp added this to the 1.0.0-beta2 milestone Dec 13, 2025
Copy link
Member

@jhodapp jhodapp left a comment

Choose a reason for hiding this comment

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

This is looking great. We came up with one suggestion yesterday to handle the following event scenarios:

  • To have an event that's just a signal for the client to do something (e.g. simple action like force logout)
  • To have an event that the client just consumes the payload passively (e.g. simple event with a text message like a notification)
  • To have an event that the client uses to update a component that display an Entity type via the payload (e.g. a specific action was added to the list of actions, or a specific action was updated)

I think I got these right, but my memory was a little fuzzy. Hope your written notes from our sync call yesterday captured it and can more accurately reflect what we discussed.

And here are some more general things to consider about the design and as you implement with Claude:

Connection State During Auth Transitions

useEffect(() => {
if (!isLoggedIn) {
eventSourceRef.current?.close();
// ...
return;
}
// Create new connection...
}, [isLoggedIn, ...]);

Circular Import Risk

Looking at the imports:

use-sse-connection.ts
→ imports useSseConnectionStore from sse-provider.tsx

sse-provider.tsx
→ imports useSseConnection from use-sse-connection.ts

This is a circular dependency. It may work due to how the exports are structured, but:

  • Could cause issues with hot module reloading
  • Could cause issues with tree-shaking
  • Makes the code harder to reason about

Possible fix: Extract useSseConnectionStore and context to a separate file (sse-connection-context.ts).

Scenarios to consider:

Scenario What happens?
Page load while logged in Connection created immediately? Or after hydration?
Login completes Does isLoggedIn update synchronously trigger connection?
Token refresh Does auth state flicker? Could cause disconnect/reconnect?
Logout while reconnecting Race between cleanup and reconnection?

The logoutCleanupRegistry integration helps, but the interplay with React's effect timing deserves attention.

Missing Reconnection State

Phase 2 mentions Reconnecting as a state:

States: Disconnected, Connecting, Connected, Reconnecting, Error

But the actual enum only has four states:

export enum SseConnectionState {
  Disconnected = "Disconnected",
  Connecting = "Connecting",
  Connected = "Connected",
  Error = "Error",  // No Reconnecting!
}

Native EventSource auto-reconnects silently. How do you know if you're in a reconnection vs. first connection? The onerror fires, but then... does onopen fire again on successful reconnect?

If you want accurate UI states, this needs clarification.

Comment on lines +698 to +716
<AuthStoreProvider>
<OrganizationStateStoreProvider>
<CoachingRelationshipStateStoreProvider>
<SessionCleanupProvider>
<SWRConfig
value={{
revalidateIfStale: true,
focusThrottleInterval: 10000,
provider: () => new Map(),
}}
>
<SseProvider>
{children}
</SseProvider>
</SWRConfig>
</SessionCleanupProvider>
</CoachingRelationshipStateStoreProvider>
</OrganizationStateStoreProvider>
</AuthStoreProvider>
Copy link
Member

Choose a reason for hiding this comment

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

Provider Dependency Chain

The nesting order in providers.tsx:

<AuthStoreProvider>           // 1. Auth state
  ...
    <SWRConfig>               // 2. SWR cache
      <SseProvider>           // 3. SSE (needs both auth AND SWR)
        {children}
      </SseProvider>
    </SWRConfig>
  ...
</AuthStoreProvider>

The concern: SseProvider calls useAuthStore() and useSWRConfig().

  • Does useAuthStore work correctly when called from inside SseProvider during initial render?
  • What's the initialization sequence when the app first loads?

It's worth tracing through the mount sequence mentally or with a quick prototype.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I added a note on this to the plan in b6c963a

I worked with Claude for a while on it and decided that I didn't want to touch auth logic as part of this effort as I was afraid that it would lead to regression and potentially just a headache of scope creep. Let me know what you think

jhodapp and others added 6 commits December 16, 2025 08:42
Upgrade next and eslint-config-next from 15.4.7 to 15.4.8 to address
CVE-2025-66478, a critical (CVSS 10.0) remote code execution vulnerability
in the React Flight protocol.

Upgrade react and react-dom from 19.1.0 to 19.1.2 to address CVE-2025-55182,
the upstream React vulnerability that CVE-2025-66478 is based on.
- Move Members link to be a collapsible sub-item under Organization settings
- Only show Organization settings menu for Admin and SuperAdmin users
- Regular users can no longer see the Members link in the sidebar
- Regular users visiting the members page now see a 404 error
- Add comprehensive tests for sidebar permission logic
- Add tests for members page access control
- Add explicit check for users not in the organization (no_access status)
- Extract access control logic into testable shouldDenyMembersPageAccess function
- Add comprehensive unit tests for the access control function
Docker image tags must be lowercase, but branch names with uppercase
characters (like Claude-generated branch IDs) were causing build failures.
Add note clarifying that AuthStoreProvider's deferred initialization
causes two render cycles at root level, but SseProvider only mounts
once when AuthStoreContext is available. This guarantees safe access
to hydrated auth state when establishing SSE connection.

Addresses PR feedback on provider dependency chain analysis.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
1. Extract SSE connection context to prevent circular imports
   - New file: src/lib/contexts/sse-connection-context.tsx
   - Separates context/hook from provider (matches auth pattern)
   - Fixes circular dependency between sse-provider.tsx and use-sse-connection.ts

2. Add Reconnecting state to connection state enum
   - Distinguishes initial connection from reconnection attempts
   - Enables accurate UI feedback for users

3. Update connection hook to check EventSource.readyState
   - Detects network errors (auto-reconnect) vs HTTP errors (permanent failure)
   - Sets Reconnecting state when browser attempts reconnection
   - Closes connection on permanent failures (401, 403, 500, etc.)

4. Update all import paths to use extracted context
   - use-sse-connection.ts, use-sse-event-handler.ts, sse-provider.tsx
   - Connection indicator component includes Reconnecting state

Addresses PR feedback on circular imports and reconnection state tracking.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@calebbourg
Copy link
Collaborator Author

@jhodapp I updated the plan to remove the circular dependency and add a reconnection state bc8cb85

calebbourg and others added 9 commits December 18, 2025 06:52
Update OverarchingGoal interface to use ts-luxon DateTime instead of
string for date fields (status_changed_at, completed_at, created_at,
updated_at). This aligns with the Action and Agreement types for
consistency across the codebase.

Also extend transformEntityDates helper to handle the additional date
fields (status_changed_at, completed_at) used by OverarchingGoal.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Create strongly-typed SSE event definitions using discriminated unions
that match the backend's Rust serialization format. Includes:

- Action events (action_created, action_updated, action_deleted)
- Agreement events (agreement_created, agreement_updated, agreement_deleted)
- Overarching goal events (overarching_goal_created, overarching_goal_updated, overarching_goal_deleted)
- System events (force_logout)

TypeScript automatically narrows event types based on the 'type' property,
providing compile-time safety and IntelliSense support.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Create Zustand store for SSE connection state tracking with:
- Connection states: Disconnected, Connecting, Connected, Reconnecting, Error
- Error tracking with timestamps and attempt numbers
- Event tracking (lastConnectedAt, lastEventAt)
- DevTools integration

Extract context to prevent circular imports between provider and hooks,
following the same pattern as AuthStoreProvider.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implement core SSE infrastructure:

- use-sse-connection: Manages native EventSource connection with
  automatic reconnection, connection state tracking, and integration
  with logout cleanup registry. Follows browser standards for SSE.

- use-sse-event-handler: Type-safe event handler hook using handler
  ref pattern to prevent listener re-registration. Includes automatic
  date transformation via transformEntityDates and event tracking.

Both hooks integrate with the SSE connection store for centralized
state management.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implement automatic SWR cache revalidation when SSE events arrive.
Handles all data change events (action, agreement, and overarching_goal
created/updated/deleted) by triggering cache invalidation.

This approach requires zero changes to existing components - SWR
automatically refetches data when caches are invalidated, maintaining
consistency with REST API patterns and providing automatic optimistic
updates.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Implement force_logout event handling by reusing the existing
useLogoutUser hook. When a force_logout event is received, triggers
the full logout sequence (cleanup registry execution, cache clearing,
session deletion, and redirect) automatically.

This ensures consistent logout behavior whether initiated by the user
or by the server via SSE.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Create SseProvider component that composes all SSE hooks and manages
the connection store lifecycle. Follows the same pattern as
AuthStoreProvider with store creation using useRef.

Integrate SseProvider into the root Providers component, nested within
SWRConfig to ensure proper access to SWR's mutate functionality for
cache invalidation.

The provider automatically:
- Establishes SSE connection when user is logged in
- Invalidates SWR caches on data change events
- Handles force logout system events
- Cleans up connection on logout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update mutate filter to check both string and array keys
- Add explicit revalidate: true option to force immediate refetch
- Handle SWR's array key format: [url, params]

Previously, the filter only checked for string keys, but SWR uses
array keys for parameterized requests like [url, sessionId]. This
caused SSE events to trigger cache invalidation without actually
revalidating any data, as no keys matched the filter.

This fix ensures real-time UI updates when SSE events arrive.
- Change context default from undefined to null for better type safety
- Extract SSE connection manager into separate component
- Use lazy initialization for store creation with useRef check
- Add explicit StoreApi type annotations

This improves React's hooks rules compliance and ensures the store
is only created once per provider instance.
@calebbourg calebbourg changed the title Initiall fe SSE plan SSE Frontend Implementation Dec 22, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation

Projects

Status: 🏗 In progress

Development

Successfully merging this pull request may close these issues.

4 participants