Skip to content

fix(connection): serialize + validate the switch session mutation (idea-7)#1958

Open
barakb wants to merge 9 commits into
stagingfrom
fix/serialize-connection-switch-session
Open

fix(connection): serialize + validate the switch session mutation (idea-7)#1958
barakb wants to merge 9 commits into
stagingfrom
fix/serialize-connection-switch-session

Conversation

@barakb

@barakb barakb commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

What & why

Closes the 🔴 Critical CodeRabbit thread on #1951 — "the ticket still does not serialize the session mutation" (app/components/ConnectionManager.tsx#L51-69 + app/providers.tsx#L1615-1620). Overlapping connection switches (A→B→C in one tab) must converge the persisted JWT, the React UI, the module-global id, and the switch gate on the last-selected connection — never a superseded one.

Design + rationale: idea-7-serialize-connection-switch-plan.md (passed 5 rounds of rubber-duck review; §1a has worked examples).

Scope (agreed with @barakb): minimal, single-tab

Code verification narrowed the real severity: header-routed graph ops already hit the correct connection (X-Connection-Id + Token DB role) and reload auto-select uses localStorage — so the residual bug is session/UI metadata divergence + header-less/fallback routing, not live query misrouting. Cross-tab / focus-refetch reordering and server-authoritative last-wins are out of scope (documented in §4).

Implementation (landed)

  • app/components/serializedRunner.ts (new) — createSerializedRunner (FIFO) + commitWithValidation (waits for status === "authenticated", validates result.activeConnectionId === target, bounded retries, throws on failure; no non-aborting timeout). 100% unit-tested and added to the test:coverage allowlist.
  • app/providers.tsxcommitActiveConnection wraps the raw next-auth update as the context updateSession; lastCommittedConnIdRef seeded from the session. Ticket-owned gate: Set<number> of live tickets + target-keyed committedSwitchRef {ticket,targetId} + handoffConnectionSwitch (retires the prior handed-off ticket) + isConnectionSwitchInProgress. The reset effect releases only the matching handed-off ticket after graph state is re-pinned (incl. the early-return / rollback-to-same-id / first-establishment branches) — never a global zero. Internal establishment/migration writes routed through the serialized commit; auth-reset clears the gate.
  • app/components/ConnectionManager.tsxtry/finally ticket lifecycle with a handedOff flag across all three handlers; on failure, converge React + global + localStorage to the last-committed connection (via handoff when it changes React). confirmRemove does commit-before-delete (never points the session at a deleted row), blocks removals while a switch is in progress, updates the list only after DELETE, and refetches on an ambiguous DELETE failure.
  • app/components/provider.ts — context type + defaults for the new API.

Validation — all green

  • npm test (358) · npm run test:coverage (100% incl. serializedRunner.ts) · npm run lint (0 errors) · npx tsc --noEmit
  • Independent code-review pass on the implementation → "runtime implementation is sound" (no runtime bugs; ticket algebra verified against the plan's worked examples)
  • Deterministic e2e regression (connectionManager.spec.ts, @admin, ≥3 connections): holds B's /api/auth/session update in-flight, triggers C, releases B, and asserts /api/auth/session converges on C (asserted directly, not via a graph op which routes by header). Verified locally against FalkorDB v4.18.10; the pre-fix code leaves the cookie on B.

Review

Monitoring + resolving Copilot & CodeRabbit threads. Not merging without @barakb's approval.

Summary by CodeRabbit

  • New Features
    • Improved connection switching with overlap convergence so the last selected connection is retained.
    • Automatically switches to a newly added connection.
  • Bug Fixes
    • Prevented conflicting connection switching and removal during in-progress operations.
    • Improved active-connection removal by committing the switch before deleting, with rollback on failures.
    • Added safer session-commit validation and retry behavior to avoid stale updates.
  • Tests
    • Added unit tests for serialized, validated session-commit helpers.
    • Added an end-to-end regression test for overlapping A→B→C switching.
  • Documentation
    • Added an implementation plan describing the approach, risks, and limitations.

…ngle-tab)

Follow-up to PR #1951: a detailed implementation plan closing the 🔴 Critical
CodeRabbit thread (serialize the connection-switch session mutation). Scoped to
the minimal single-tab fix agreed with @barakb; passed 5 rounds of rubber-duck
review (severity corrected, next-auth update() no-op/null semantics handled,
ticket-owned gate with target-keyed handoff, failure-rollback convergence,
commit-before-delete removal). Cross-tab / server-authoritative options
documented as out of scope.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@overcut-ai

overcut-ai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Completed Working on "Code Review"

✅ Review submitted: COMMENT. Total comments: 5 across 1 files.

✅ Workflow completed successfully.


👉 View complete log

@vercel

vercel Bot commented Jul 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
falkordb-browser Ready Ready Preview, Comment Jul 16, 2026 6:35pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds serialized and validated connection-session commits, ticket-based switch gating, rollback convergence across connection state layers, commit-before-delete removal handling, and unit/E2E coverage for overlapping switches.

Changes

Connection switch convergence

Layer / File(s) Summary
Switch contract and serialized commit runner
app/components/provider.ts, app/components/serializedRunner.ts
Adds ticket-based context methods and serialized, authenticated, validated session commits with bounded retries.
Provider commit and switch gate wiring
app/providers.tsx
Tracks switch tickets and the last committed connection, routes internal writes through validated commits, and releases handoffs after graph reset.
Selection, removal, and addition flows
app/components/ConnectionManager.tsx
Applies ticketed handoffs and rollback to selection and addition, blocks removal during switches, and commits replacement sessions before deletion.
Unit and end-to-end regression coverage
app/components/serializedRunner.test.ts, e2e/logic/POM/connectionManagerComponent.ts, e2e/tests/connectionManager.spec.ts, package.json
Tests serialization and validation, adds connection-selection support, verifies overlapping-switch convergence, and preserves coverage settings.
Implementation plan and scope documentation
idea-7-serialize-connection-switch-plan.md
Documents the race, design, limitations, testing strategy, implementation tasks, and open decisions.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ConnectionManager
  participant Provider
  participant Session
  participant GraphReset
  User->>ConnectionManager: select connection B
  ConnectionManager->>Provider: begin switch ticket
  Provider->>Session: serialized validated commit B
  User->>ConnectionManager: select connection C
  ConnectionManager->>Provider: begin newer switch ticket
  Provider->>Session: serialized validated commit C
  Session-->>Provider: validate committed C
  Provider->>GraphReset: reset state for C
  GraphReset-->>Provider: release matching handoff
Loading

Possibly related PRs

Suggested reviewers: copilot, anchel123

Poem

Tickets queue, commits align,
B yields gently, C gets time.
Graphs reset and states converge,
Tests patrol each race and surge.
The final switch holds true.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: serializing and validating the connection switch session mutation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/serialize-connection-switch-session

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.

@overcut-ai overcut-ai Bot 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.

Review Summary

Findings posted in this review:

  • 0 BLOCKER
  • 0 CRITICAL
  • 5 MAJOR
  • 0 MINOR
  • 0 SUGGESTION
  • 0 PRAISE

Affected files: 1 (idea-7-serialize-connection-switch-plan.md)

Key themes

  1. Gate lifecycle resilience: multiple findings point to potential stuck/deadlock states if ticket release or recovery paths are bypassed.
  2. Failure-path convergence: fallback behavior is under-specified for delete/commit races and stale-session outcomes.
  3. Validation of plan claims/tests: scope/closure wording and e2e overlap criteria need tightening to match actual guarantees.

Actionable next steps

  1. Add explicit bounded recovery behavior for stalled switch/commit flows, with unit/e2e coverage.
  2. Ensure ticket handoff release cannot be skipped by any reset-effect early return path.
  3. Strengthen confirmRemove fallback when replacement commit retries fail, and tighten top-level scope wording plus overlap precondition assertions in e2e criteria.

Comment thread idea-7-serialize-connection-switch-plan.md
Comment thread idea-7-serialize-connection-switch-plan.md
Comment thread idea-7-serialize-connection-switch-plan.md
Comment thread idea-7-serialize-connection-switch-plan.md
Comment thread idea-7-serialize-connection-switch-plan.md
Adds §1a with step-by-step timelines: the core A->B->C cookie/UI divergence
under network reordering, the update() no-op-while-loading trap, failure-rollback
divergence, gate-zeroing clobber, and confirmRemove deleted-connection.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@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: 3

🤖 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 `@idea-7-serialize-connection-switch-plan.md`:
- Around line 198-199: Update the sign-out/auth-reset cleanup around
pendingSwitchTicketsRef and committedSwitchRef to invalidate queued and
in-flight sessionChainRef work with an authentication epoch. Tag each queued
commit with the epoch it was created under, advance the epoch during reset, and
ensure stale queued or late-settling commits cannot call the current
updateSessionRef or overwrite the newly authenticated session; drain or abort
in-flight commits before accepting the new session.
- Around line 3-6: Clarify the goal and acceptance criteria to limit the
convergence guarantee to serialized connection-switch mutations, explicitly
excluding same-tab focus-refetch GETs; alternatively, add reconciliation for
session reads so those refetches cannot overwrite the last successfully
committed connection. Update the out-of-scope wording consistently and preserve
the guarantee for overlapping A→B→C switch mutations.
- Around line 202-215: Define rollback behavior for a null last-good connection
in the connection-switch flow: when rollback resolves to null, remove
lastActiveConnectionId from localStorage instead of persisting a "null" string.
Update the rollback helper or switch handlers around handleSelect and
handleAddConnection to preserve this representation, and add coverage for reload
with no active connection so auto-select does not interpret a sentinel as a
connection ID.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9408d1bb-e617-4001-9358-d04cd028d743

📥 Commits

Reviewing files that changed from the base of the PR and between 4141d32 and 6b48f03.

📒 Files selected for processing (1)
  • idea-7-serialize-connection-switch-plan.md

Comment thread idea-7-serialize-connection-switch-plan.md
Comment thread idea-7-serialize-connection-switch-plan.md
Comment thread idea-7-serialize-connection-switch-plan.md
barakb and others added 2 commits July 16, 2026 10:23
…ea-7)

Implements the minimal single-tab fix from idea-7:
- serializedRunner.ts: createSerializedRunner (FIFO) + commitWithValidation
  (waits for authenticated, validates result.activeConnectionId, bounded
  retries, throws on failure) + unit tests.
- providers.tsx: commitActiveConnection wraps the raw next-auth update as the
  context updateSession; lastCommittedConnIdRef seeded from the session.
  Ticket-owned gate (Set<number>) + target-keyed committedSwitchRef +
  handoffConnectionSwitch + isConnectionSwitchInProgress; reset effect releases
  only the matching handed-off ticket (no global zero). Internal establishment/
  migration writes routed through the serialized commit.
- ConnectionManager.tsx: try/finally ticket lifecycle with handoff on win/
  rollback; converge React+global+localStorage to the last committed connection
  on failure; commit-before-delete in confirmRemove; block removals while a
  switch is in progress; refetch the list on an ambiguous DELETE failure.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds an @admin test that holds the first switch's /api/auth/session update
in-flight, triggers a second switch, releases the first, and asserts the JWT
converges on the last-selected connection (via /api/auth/session directly, not a
graph op which routes by X-Connection-Id). Requires ≥3 connections (adds two via
the API). POM: getConnectionIds + selectConnectionById.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown

🔒 Trivy Security Scan Results


Report Summary

┌───────────────────────────────────────────────────────────────────────────┬──────────┬─────────────────┐
│                                  Target                                   │   Type   │ Vulnerabilities │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ falkordb/falkordb-browser:test (alpine 3.23.4)                            │  alpine  │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/checksums/package.json                          │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/client-s3/package.json                          │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/core/package.json                               │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/credential-provider-env/package.json            │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/credential-provider-http/package.json           │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/credential-provider-ini/package.json            │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/credential-provider-login/package.json          │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/credential-provider-node/package.json           │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/credential-provider-process/package.json        │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/credential-provider-sso/package.json            │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/credential-provider-web-identity/package.json   │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/middleware-sdk-s3/package.json                  │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/nested-clients/package.json                     │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/signature-v4-multi-region/package.json          │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/token-providers/package.json                    │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws-sdk/xml-builder/package.json                        │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@aws/lambda-invoke-store/package.json                    │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@falkordb/text-to-cypher/package.json                    │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@img/colour/package.json                                 │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@img/sharp-libvips-linux-x64/package.json                │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json            │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@img/sharp-linux-x64/package.json                        │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@img/sharp-linuxmusl-x64/package.json                    │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@js-temporal/polyfill/package.json                       │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@next/env/package.json                                   │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@redis/bloom/package.json                                │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@redis/client/dist/package.json                          │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@redis/client/package.json                               │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@redis/json/package.json                                 │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@redis/search/package.json                               │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@redis/time-series/package.json                          │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@smithy/core/package.json                                │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@smithy/credential-provider-imds/package.json            │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@smithy/node-http-handler/package.json                   │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@smithy/signature-v4/package.json                        │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@smithy/types/package.json                               │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/@swc/helpers/package.json                                │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/bowser/package.json                                      │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/client-only/package.json                                 │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/cluster-key-slot/package.json                            │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/detect-libc/package.json                                 │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/falkordb/node_modules/@redis/client/dist/package.json    │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/falkordb/node_modules/@redis/client/package.json         │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/falkordb/package.json                                    │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/jsbi/package.json                                        │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/lodash/package.json                                      │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/next/dist/compiled/@edge-runtime/cookies/package.json    │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/next/dist/compiled/@edge-runtime/ponyfill/package.json   │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/next/dist/compiled/@edge-runtime/primitives/package.json │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/next/dist/compiled/react-is/package.json                 │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/next/dist/compiled/regenerator-runtime/package.json      │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/next/package.json                                        │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/react-dom/package.json                                   │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/react/package.json                                       │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/redis/node_modules/@redis/client/dist/package.json       │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/redis/node_modules/@redis/client/package.json            │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/redis/package.json                                       │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/semver/package.json                                      │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/sharp/package.json                                       │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/node_modules/styled-jsx/package.json                                  │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ app/package.json                                                          │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ opt/yarn-v1.22.22/package.json                                            │ node-pkg │        0        │
├───────────────────────────────────────────────────────────────────────────┼──────────┼─────────────────┤
│ usr/local/lib/node_modules/corepack/package.json                          │ node-pkg │        0        │
└───────────────────────────────────────────────────────────────────────────┴──────────┴─────────────────┘
Legend:
- '-': Not scanned
- '0': Clean (no security findings detected)


@barakb

barakb commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review — the implementation has landed since your last review (which was on the plan only). Please review the actual code changes in app/components/serializedRunner.ts, app/providers.tsx, app/components/ConnectionManager.tsx, app/components/provider.ts, and the e2e test.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

#!/bin/bash
set -euo pipefail

fd -a 'serializedRunner.ts' 
fd -a 'providers.tsx'
fd -a 'ConnectionManager.tsx'
fd -a 'provider.ts'
echo "---e2e---"
fd -a . -e spec.ts -p e2e | rg -i 'connection|admin' || true
fd -a . | rg -i 'e2e' | rg -i 'connection' || true
</shell>

Wait, I need proper shell block.

```shell
#!/bin/bash
set -euo pipefail

fd -a 'serializedRunner.ts'
fd -a 'providers.tsx'
fd -a 'ConnectionManager.tsx'
fd -a 'provider.ts'
echo "---e2e search---"
fd . -e spec.ts | rg -i 'connection|admin' || true

- serializedRunner: remove the redundant tail.catch (tail is provably never
  rejecting), and add a null-target retry-exhaustion test.
- package.json: add app/components/serializedRunner.ts to the test:coverage
  allowlist (now 100% line/branch/function), per idea-7 plan §5c/§9.2.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@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: 6

🤖 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 `@app/components/ConnectionManager.tsx`:
- Around line 69-71: Update each rollback branch in ConnectionManager around
rollbackId to keep localStorage synchronized when the resolved ID is null:
retain setItem for non-null IDs, and remove the "lastActiveConnectionId" key
when rollbackId is null. Apply this consistently to all three indicated rollback
paths.
- Around line 115-120: Make connection removal and selection mutually exclusive
across the entire DELETE operation. In the removal handler containing the
isConnectionSwitchInProgress() preflight, acquire and retain a shared mutation
lock until deletion completes, and update handleSelect to reject or wait while
removal is active; ensure the lock is released on both success and failure so
the JWT cannot target a deleted connection.
- Around line 47-63: Update the switch initiation logic around
activeConnectionId and the pending-switch ticket so a displayed target is
skipped only when no switch is currently pending, allowing A→B→A to supersede B.
In the post-updateSession path, when the committed connId already matches React
state, complete the ticket directly rather than calling handoffConnectionSwitch,
since the reset effect will not rerun. Preserve stale-ticket checks and normal
effect handoff for targets that differ from the displayed connection.

In `@app/components/serializedRunner.ts`:
- Around line 80-118: Bound the session update await in commitWithValidation so
a hung deps.update call cannot block the serialized runner indefinitely. Add an
abortable timeout or equivalent cancellation boundary around deps.update, while
preserving validation, retry, and rollback behavior when the update times out or
is cancelled.

In `@app/providers.tsx`:
- Around line 1268-1275: Update both establishment paths using
commitActiveConnection so React/global connection state is not published before
the commit succeeds. On commitActiveConnection failure, roll back every state
layer to the prior connection (including the JWT-resolved session connection),
or defer publication until success; do not merely log and continue with target.
Preserve successful establishment behavior.
- Around line 708-715: Reset or cancel commitRunnerRef during the auth cleanup
that clears the switch refs, ensuring any queued serialized commits are
discarded when authentication drops. Update the commitRunnerRef initialization
in the commit runner flow so a new runner is created for the next authenticated
session and pending updateSession operations cannot cross sessions.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ac6a5fa9-9c97-4c5a-bc9a-e2564d25181f

📥 Commits

Reviewing files that changed from the base of the PR and between 8da189f and f6f1d81.

📒 Files selected for processing (8)
  • app/components/ConnectionManager.tsx
  • app/components/provider.ts
  • app/components/serializedRunner.test.ts
  • app/components/serializedRunner.ts
  • app/providers.tsx
  • e2e/logic/POM/connectionManagerComponent.ts
  • e2e/tests/connectionManager.spec.ts
  • package.json

Comment thread app/components/ConnectionManager.tsx Outdated
Comment thread app/components/ConnectionManager.tsx
Comment thread app/components/ConnectionManager.tsx
Comment thread app/components/serializedRunner.ts
Comment thread app/providers.tsx Outdated
Comment thread app/providers.tsx Outdated
@barakb
barakb requested a review from Copilot July 16, 2026 08:30
@barakb barakb changed the title docs: plan #7 — converge overlapping connection switches (minimal, single-tab) fix(connection): serialize + validate the switch session mutation (idea-7) Jul 16, 2026

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.

Pull request overview

This PR implements the “idea-7” design to make overlapping single-tab connection switches converge deterministically on the last-selected connection by serializing and validating next-auth session updates, tightening the connection-switch gate, and adding regressions tests.

Changes:

  • Added a serialized + validated session commit helper (serializedRunner.ts) and wired providers.tsx to expose it via the existing updateSession({activeConnectionId}) context API.
  • Reworked the connection-switch gate from a counter to a ticket set with reset-effect handoff/release semantics; updated ConnectionManager to use try/finally ticket lifecycle and safer rollback/remove flows.
  • Added unit tests for the serializer/validator and a Playwright regression for A→B→C overlap convergence; updated coverage allowlist.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
package.json Adds app/components/serializedRunner.ts to the strict coverage allowlist.
idea-7-serialize-connection-switch-plan.md Documents the problem, rationale, design, risks, and test plan for “idea-7”.
e2e/tests/connectionManager.spec.ts Adds an overlapping-switch regression that asserts /api/auth/session converges on the last selection.
e2e/logic/POM/connectionManagerComponent.ts Adds POM helpers to list connection IDs and select by ID for the new e2e test.
app/providers.tsx Implements serialized+validated updateSession, ticket-set switch gate, and reset-effect ticket handoff/release.
app/components/serializedRunner.ts New pure utilities: FIFO serialization + validated commit with bounded retries and authenticated readiness wait.
app/components/serializedRunner.test.ts Unit tests for serialization, waitUntil, delay, and commit-with-validation behavior.
app/components/provider.ts Extends ConnectionContext with new gate/rollback helpers and updates ticket API.
app/components/ConnectionManager.tsx Updates selection/add/remove flows to use the serialized commit + ticket handoff, and safer rollback behavior.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/components/ConnectionManager.tsx
Comment thread app/components/ConnectionManager.tsx
Comment thread app/components/ConnectionManager.tsx
Comment thread app/components/ConnectionManager.tsx
Comment thread e2e/tests/connectionManager.spec.ts
- handleSelect: allow re-selecting the displayed connection while a switch is
  pending so A→B→A ends on A (skip the no-op only when no switch is in progress);
  when the committed target already equals React state, release the ticket
  directly instead of handing it to a reset effect that won't rerun.
- Mutual exclusion: a removingRef blocks handleSelect while a removal's
  commit+DELETE is in flight, so a select can't target a connection being deleted.
- Rollback now clears lastActiveConnectionId when the last-good target is null
  (all three handlers) so localStorage converges with React/global.
- commitWithValidation: add an isCancelled hook; providers bumps an auth
  generation on sign-out so a queued commit can't fire into a later session.
- Establishment/migration: publish React state only after the commit validates
  (roll back the global on failure) so a failed commit can't leave the UI on a
  connection the session doesn't reflect.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

@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

🧹 Nitpick comments (1)
app/providers.tsx (1)

711-728: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use useState lazy initialization for pure renders.

Mutating a ref directly during render violates React's pure render constraints and triggers lint warnings. As per path instructions regarding proper hooks usage, use useState with an initializer function to safely create the serialized runner exactly once without relying on mutable refs.

♻️ Proposed refactor
-  const commitRunnerRef = useRef<((data: { activeConnectionId?: string | null }, isCancelled: () => boolean) => Promise<unknown>) | undefined>(undefined);
-  if (!commitRunnerRef.current) {
-    commitRunnerRef.current = createSerializedRunner((data: { activeConnectionId?: string | null }, isCancelled: () => boolean) =>
-      commitWithValidation(data.activeConnectionId ?? null, {
-        update: (d) => updateSessionRef.current(d),
-        getStatus: () => statusRef.current,
-        onCommitted: (id) => { lastCommittedConnIdRef.current = id; },
-        isCancelled,
-      }));
-  }
-  const commitActiveConnection = useCallback((data: { activeConnectionId?: string | null }) => {
-    const runner = commitRunnerRef.current;
-    if (!runner) return Promise.resolve(undefined);
+  const [commitRunner] = useState(() =>
+    createSerializedRunner((data: { activeConnectionId?: string | null }, isCancelled: () => boolean) =>
+      commitWithValidation(data.activeConnectionId ?? null, {
+        update: (d) => updateSessionRef.current(d),
+        getStatus: () => statusRef.current,
+        onCommitted: (id) => { lastCommittedConnIdRef.current = id; },
+        isCancelled,
+      })
+    )
+  );
+
+  const commitActiveConnection = useCallback((data: { activeConnectionId?: string | null }) => {
     // Pin this commit to the current auth session; if a sign-out bumps the auth
     // generation while it is queued, it aborts instead of committing cross-session.
     const gen = authGenRef.current;
-    return runner(data, () => authGenRef.current !== gen);
-  }, []);
+    return commitRunner(data, () => authGenRef.current !== gen);
+  }, [commitRunner]);
🤖 Prompt for 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.

In `@app/providers.tsx` around lines 711 - 728, Replace the render-time mutation
of commitRunnerRef in the commit runner setup with lazy useState initialization,
creating the serialized runner exactly once through the initializer function.
Update commitActiveConnection to read the initialized runner from state while
preserving its current auth-generation cancellation behavior and callback
dependencies.

Sources: Path instructions, Linters/SAST tools

🤖 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 `@app/providers.tsx`:
- Around line 1283-1285: Only roll back the global connection ID after initial
or migrated session commit failures when it still matches the connection being
committed, preventing newer user-initiated switches from being overwritten. In
app/providers.tsx lines 1283-1285, update the rollback to require
getActiveConnectionIdGlobal() === target alongside !cancelled; apply the
equivalent check at lines 1324-1326 using migratedConn.id.

---

Nitpick comments:
In `@app/providers.tsx`:
- Around line 711-728: Replace the render-time mutation of commitRunnerRef in
the commit runner setup with lazy useState initialization, creating the
serialized runner exactly once through the initializer function. Update
commitActiveConnection to read the initialized runner from state while
preserving its current auth-generation cancellation behavior and callback
dependencies.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5b18deea-30db-45ec-9a83-3ac5de285841

📥 Commits

Reviewing files that changed from the base of the PR and between f6f1d81 and 56382bd.

📒 Files selected for processing (4)
  • app/components/ConnectionManager.tsx
  • app/components/serializedRunner.test.ts
  • app/components/serializedRunner.ts
  • app/providers.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/components/serializedRunner.test.ts
  • app/components/ConnectionManager.tsx

Comment thread app/providers.tsx
- confirmRemove: surface a failure toast when the DELETE request throws (network
  error) — securedFetch only toasts for HTTP responses, so it was silent before.
- e2e reorder test: capture the two temporary connections from the POST
  responses and delete them in a finally (switching the session back to the
  original first), so they don't accumulate in the shared admin storageState.
  Removed the now-unused getConnectionIds POM helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only revert the global connection id to null on a failed establishment/migration
commit if it still equals the target — otherwise a concurrent user switch that
already moved the global would be clobbered. (CodeRabbit)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@barakb

barakb commented Jul 16, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

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.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment on lines +63 to +73
export interface CommitDeps {
/** Performs the session mutation (next-auth `useSession().update`). */
update: (data: { activeConnectionId?: string | null }) => Promise<unknown>;
/** Current next-auth session status (`"loading" | "authenticated" | ...`). */
getStatus: () => string;
/** Called with the target once a commit is validated as successful. */
onCommitted?: (targetId: string | null) => void;
/** Abort the commit (without a write) when it becomes stale — e.g. the auth
* session changed (sign-out / re-login) while this commit was queued. */
isCancelled?: () => boolean;
}

@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: 5

🤖 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 `@app/components/serializedRunner.ts`:
- Around line 109-114: Recheck the cancellation state immediately after the
`waitUntil` call and before `deps.update` in the serialized runner flow,
returning the existing cancellation result when the generation is stale. Add a
regression test covering authentication changing while readiness polling is
suspended, ensuring no session write occurs afterward.

In `@app/providers.tsx`:
- Around line 1272-1281: Update both provider-driven commit paths around
commitActiveConnection to register with the switch gate using the same
begin/latest/handoff/end lifecycle as ConnectionManager. Allocate a ticket
before changing global routing, verify it remains latest before publishing React
state, and always complete the ticket lifecycle on success, cancellation, or
failure. Ensure stale commits cannot expose or retain a removed pending target.

In `@e2e/tests/connectionManager.spec.ts`:
- Around line 68-90: Ensure the connection overlap test waits until B’s
session-update request is intercepted before selecting C: add a route-handler
signal and await it after selecting B, then select C and release the held
request. Also call releaseFirst() at the start of the existing finally block so
cleanup cannot remain blocked.

In `@idea-7-serialize-connection-switch-plan.md`:
- Around line 93-100: Add the text language identifier to every unlabeled fenced
code block in idea-7-serialize-connection-switch.md, including the timeline and
ASCII diagram examples near the referenced sections. Change each opening fence
to use ```text while preserving all diagram content unchanged.
- Around line 304-309: Update the removal sequence’s handoff call to pass the
replacement connection ID, using handoffConnectionSwitch(ticket, newActive)
after commitActiveConnection(newActive). Keep the ordering and surrounding
DELETE behavior unchanged, and align the documentation with the contract in §3b
and ConnectionManager.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d8d9e5a6-ef4b-463a-ad54-44d0dc82238d

📥 Commits

Reviewing files that changed from the base of the PR and between 4141d32 and 17599f3.

📒 Files selected for processing (9)
  • app/components/ConnectionManager.tsx
  • app/components/provider.ts
  • app/components/serializedRunner.test.ts
  • app/components/serializedRunner.ts
  • app/providers.tsx
  • e2e/logic/POM/connectionManagerComponent.ts
  • e2e/tests/connectionManager.spec.ts
  • idea-7-serialize-connection-switch-plan.md
  • package.json

Comment on lines +109 to +114
const ready = await waitUntil(() => deps.getStatus() === "authenticated", readyTimeoutMs, {
now: opts.now,
sleep,
});
if (ready) {
const result = (await deps.update({ activeConnectionId: targetId })) as

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Recheck auth cancellation immediately before the session write.

If authentication changes during waitUntil, a subsequent login can make ready true and allow this stale generation to call update. Check isCancelled again after polling and before the write.

Proposed fix
     if (ready) {
+      if (deps.isCancelled?.()) {
+        throw new Error(`Commit for ${targetId} cancelled (auth changed)`);
+      }
       const result = (await deps.update({ activeConnectionId: targetId })) as

Add a regression test where authentication changes while readiness polling is suspended.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const ready = await waitUntil(() => deps.getStatus() === "authenticated", readyTimeoutMs, {
now: opts.now,
sleep,
});
if (ready) {
const result = (await deps.update({ activeConnectionId: targetId })) as
const ready = await waitUntil(() => deps.getStatus() === "authenticated", readyTimeoutMs, {
now: opts.now,
sleep,
});
if (ready) {
if (deps.isCancelled?.()) {
throw new Error(`Commit for ${targetId} cancelled (auth changed)`);
}
const result = (await deps.update({ activeConnectionId: targetId })) as
🤖 Prompt for 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.

In `@app/components/serializedRunner.ts` around lines 109 - 114, Recheck the
cancellation state immediately after the `waitUntil` call and before
`deps.update` in the serialized runner flow, returning the existing cancellation
result when the generation is stale. Add a regression test covering
authentication changing while readiness polling is suspended, ensuring no
session write occurs afterward.

Comment thread app/providers.tsx
Comment on lines +1272 to +1281
// Pin the global so the commit's own request routes to `target`, but
// publish React state only AFTER the JWT commit validates — a failed
// commit must not leave the UI on a connection the session doesn't
// reflect. Route through the serialized commit so a concurrent user
// switch can't be reordered against this establishment write.
setActiveConnectionIdGlobal(target);
// Sync activeConnectionId into the JWT so session.user reflects
// the correct connection's role/host/port. The JWT callback looks
// up the full connection details from Token DB.
if (!cancelled) {
await updateSessionRef.current({
activeConnectionId: target,
});
try {
await commitActiveConnection({ activeConnectionId: target });
if (!cancelled) setActiveConnectionId(target);

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Register provider-driven commits with the switch gate.

These paths change global routing while React/JWT state still disagrees, but never allocate a ticket. The gate therefore remains open; notably, the initial path has already exposed the connection list, allowing removal of the pending target.

Wrap both commits in the same begin/latest/handoff/end lifecycle used by ConnectionManager, and only publish React state when their ticket remains latest.

Also applies to: 1318-1323

🤖 Prompt for 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.

In `@app/providers.tsx` around lines 1272 - 1281, Update both provider-driven
commit paths around commitActiveConnection to register with the switch gate
using the same begin/latest/handoff/end lifecycle as ConnectionManager. Allocate
a ticket before changing global routing, verify it remains latest before
publishing React state, and always complete the ticket lifecycle on success,
cancellation, or failure. Ensure stale commits cannot expose or retain a removed
pending target.

Comment thread e2e/tests/connectionManager.spec.ts
Comment on lines +93 to +100
```
t0 click B: beginConnectionSwitch()→ticket#1 global=B POST update(B) ──┐ (in flight)
t1 click C: beginConnectionSwitch()→ticket#2 global=C POST update(C) ─┐│ (in flight)
t2 server handles update(C) → Set-Cookie jwt(active=conn-C) ││
t3 server handles update(B) → Set-Cookie jwt(active=conn-B) ││
t4 update(C) response arrives → browser cookie=C handleSelect(C): isLatestSwitch(#2)=true → UI=C
t5 update(B) response arrives → browser cookie=B handleSelect(B): isLatestSwitch(#1)=false → no publish
```

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.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add language identifiers to the fenced examples.

Markdownlint reports MD040 for these six unlabeled fences. Use ```text for the timeline and ASCII diagrams so the documentation passes the configured Markdown checks.

Also applies to: 110-117, 123-129, 137-144, 148-153, 160-164

🧰 Tools
🪛 markdownlint-cli2 (0.23.0)

[warning] 93-93: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for 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.

In `@idea-7-serialize-connection-switch-plan.md` around lines 93 - 100, Add the
text language identifier to every unlabeled fenced code block in
idea-7-serialize-connection-switch.md, including the timeline and ASCII diagram
examples near the referenced sections. Change each opening fence to use ```text
while preserving all diagram content unchanged.

Source: Linters/SAST tools

Comment on lines +304 to +309
- `confirmRemove` — **commit the replacement before deleting** the old connection, and hand the ticket
to the reset effect (do **not** hold it across the DELETE — a hung DELETE would otherwise stick the
gate). Order: `beginConnectionSwitch` → `commitActiveConnection(newActive)` (validated) →
`handoffConnectionSwitch(ticket)` + publish React/global/localStorage → **then** DELETE the old row →
update `additionalConnections` **only after DELETE succeeds**. If the commit fails first, nothing was
deleted and we roll back to the last-good id (`finally` releases the ticket). If the DELETE fails

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pass the target ID to the removal handoff.

The plan defines handoffConnectionSwitch(ticket, targetId) in §3b, but the removal sequence shortens this to handoffConnectionSwitch(ticket). Document the concrete call as handoffConnectionSwitch(ticket, newActive) so it matches the context contract and app/components/ConnectionManager.tsx.

🤖 Prompt for 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.

In `@idea-7-serialize-connection-switch-plan.md` around lines 304 - 309, Update
the removal sequence’s handoff call to pass the replacement connection ID, using
handoffConnectionSwitch(ticket, newActive) after
commitActiveConnection(newActive). Keep the ordering and surrounding DELETE
behavior unchanged, and align the documentation with the contract in §3b and
ConnectionManager.

@barakb
barakb requested a review from Copilot July 16, 2026 18:33

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.

Pull request overview

Copilot reviewed 9 out of 9 changed files in this pull request and generated 1 comment.

Comment on lines +56 to +70
// Need ≥3 connections to do A→B→C (selecting the active one is a no-op). Add two
// extra connections to the same local FalkorDB via the API, capturing their ids.
const added: string[] = [];
for (let i = 0; i < 2; i += 1) {
const res = await page.request.post(`${origin}/api/connections`, {
data: { host: "localhost", port: "6379" },
});
expect(res.ok()).toBe(true);
added.push((await res.json()).connection.id as string);
}
const [connB, connC] = added;

try {
await page.reload();
await connManager.waitForPageIdle();
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