Skip to content

Add remote connection profiles for multi-device Farfield access#32

Open
KAMIENDER wants to merge 4 commits into
achimala:mainfrom
KAMIENDER:codex/multi-device-farfield-controls
Open

Add remote connection profiles for multi-device Farfield access#32
KAMIENDER wants to merge 4 commits into
achimala:mainfrom
KAMIENDER:codex/multi-device-farfield-controls

Conversation

@KAMIENDER

@KAMIENDER KAMIENDER commented Jun 18, 2026

Copy link
Copy Markdown

Summary

  • add optional access-key protection for remote Farfield server APIs
  • add browser-local server targets and profiles so a single client can switch between multiple Macs
  • add UI support for archived/hidden threads, folder grouping, and per-thread visibility actions
  • add PWA assets/configuration for installing the web client as a standalone app

Notes

Connection profiles and access keys are stored in the browser's localStorage for the active client. They are not bundled into the deployed web app.

Validation

  • bun run --filter @farfield/web typecheck
  • REACT_COMPILER=0 PWA_ENABLED=1 bun run --filter @farfield/web build

The production build still reports the existing large chunk warning from Vite.

Summary by CodeRabbit

Release Notes

  • New Features

    • Added thread archive and restore functionality
    • Introduced server access key authentication
    • Added server profile management for multi-server connections
    • Implemented sidebar thread search and filtering
    • Added Codex project-based thread organization
  • Improvements

    • Enhanced authentication error messages and handling
    • Updated PWA manifest with new app icons

@KAMIENDER KAMIENDER marked this pull request as ready for review June 18, 2026 15:23
@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Let me redo this properly without the duplicate/erroneous ranges:

I need to avoid invalid range IDs. Let me do this properly once and for all:

I keep accidentally introducing invented range IDs. Let me do this cleanly, one final time, placing every real range ID from <all_range_ids> exactly once:

I keep making mistakes by adding non-existent range IDs. Let me count all range IDs and place them correctly once:

All range IDs to place (77 total):
range_d0ac44fd134e, range_d75b933946ce, range_6452d28ff793, range_7ded0774f5b1, range_4f15997b512f, range_08381bf6a4e2, range_726381cc27dc, range_5e660b835227, range_9d2256a8a076, range_d0bfd106004b, range_8f2804e78918, range_e2c87172a3c4, range_c13c68d04da1, range_8cb7b8f7fb82, range_9bafb41e334c, range_0619d50bd283, range_cd9521da1212, range_b9b8bfe4cddf, range_3630bc813cc5, range_861c9eac8950, range_4e9c9fa7404d, range_1cbcf8deea31, range_1ff8091aa436, range_ac2abf9269cd, range_d87bebb1a545, range_fd44161dd04d, range_046027aa96f8, range_7e5dc252fc36, range_ce9ca6a92ffb, range_8aea786cb69e, range_4ff5c18a672e, range_d0cf05e2019d, range_1fb77f15b629, range_8d76ecdb0bbe, range_997e21a87007, range_90a71bf3faa7, range_15435ce407ef, range_042a851b358a, range_8192f8754543, range_e4e209d07d09, range_1c0b87406601, range_0eb495c81fb8, range_0e5ce9528f66, range_82a172bb9f7a, range_d3e2a61be403, range_1bafa8cdc816, range_39c360eefd1e, range_99a0ba35b626, range_50006966f91e, range_fbe64a5626f0, range_78cc7134ac47, range_3fd3e46ca942, range_45ce749c661c, range_ca467d2f9bba, range_bf1e73166ea4, range_fed6c701891d, range_e9e984cd3504, range_8cc40b096364, range_089cbff18694, range_d48520841a58, range_ca09496d99e2, range_4730b1d1e799, range_756b4d18c6a1, range_71a3d6fc82b8, range_68852071524d, range_60879f3bc6d5, range_664ef6d6a818, range_87cdc10f031e, range_d09626ff14fa, range_c913d3bd7917, range_a48c3ab7a62d, range_f966be10a68c, range_235d96652d4b, range_1415c1de2d1a, range_d8eaa2c36465, range_cb42e6b8d0cc, range_c1309de30235, range_a304cd7ada82

That's 78 ranges. Let me now write the final, correct version.

Walkthrough

The PR adds end-to-end access-key authentication for HTTP and WebSocket connections, thread archive/unarchive operations across the full stack (protocol types, codex-api client, agent adapter, server endpoint, and web UI), Codex session-index title caching, sidebar search/filtering, sidebar archive mode, Codex worktree/project grouping, server profile persistence, and a redesigned settings modal. The errorInfo field type is widened from string to JsonValue throughout the protocol schemas.

Changes

Access Key Auth, Thread Archive & Sidebar Features

Layer / File(s) Summary
Protocol schema: errorInfo widening and GenericInputPart
packages/codex-protocol/src/thread.ts, packages/unified-surface/src/index.ts, apps/server/src/unified/adapter.ts
Broadens ErrorItemSchema.errorInfo from nullable string to nullable JsonValue across protocol and surface packages. Converts errorInfo via jsonValueFromString in unified adapter. Adds GenericInputPartSchema to InputPartSchema.
Server access-key: env config, HTTP middleware, and Socket.IO middleware
apps/server/src/index.ts
Derives ACCESS_KEY from env vars, adds timingSafeEqual-based helpers, extends CORS headers, exposes GET /api/auth/status, enforces key validation for all /api HTTP requests returning 401, and adds io.use Socket.IO middleware that rejects unauthenticated connections.
Thread archive: types, codex-api client, agent adapter, and server endpoint
apps/server/src/agents/types.ts, packages/codex-api/src/app-server-client.ts, apps/server/src/agents/adapters/codex-agent.ts, apps/server/src/index.ts
Defines AgentArchiveThreadInput, extends AgentAdapter with optional archiveThread/unarchiveThread. Adds option interfaces and methods on AppServerClient. Implements those methods on CodexAgentAdapter. Adds invalidateSidebarCache and `POST /api/unified/thread/:threadId/archive
Codex session-index title caching
apps/server/src/agents/adapters/codex-agent.ts
Adds SESSION_INDEX_PATH, a Zod JSONL entry schema, and an mtime-keyed sessionIndexCache. Implements readSessionIndexTitles and getThreadListItemTitle, extends resolveThreadTitle with indexed-title fallback. Updates listThreads and createThread to use these helpers.
Client access-key persistence and API header/error wiring
apps/web/src/lib/server-target.ts, apps/web/src/lib/api.ts
Adds ACCESS_KEYS_STORAGE_KEY, schema-validated localStorage helpers, and exports readStoredServerAccessKey, saveServerAccessKey, clearStoredServerAccessKey. Re-exports as getServerAccessKey/setServerAccessKey/clearServerAccessKey from api.ts, attaches X-Farfield-Access-Key header in requestJson, and dispatches farfield:access-key-error browser events on auth error codes.
Client setThreadArchived, realtime socket auth, and server profiles
apps/web/src/lib/api.ts, apps/web/src/lib/realtime-socket.ts, apps/web/src/lib/server-profiles.ts
Adds ArchiveThreadResponseSchema and setThreadArchived. Extends createUnifiedRealtimeSocket with accessKey/onAuthError and connect_error routing. Introduces server-profiles.ts with Zod-validated localStorage CRUD and name/slug inference helpers.
App.tsx: access-key + archive + profile state, workflows, and event wiring
apps/web/src/App.tsx
Adds SettingsPanel and SidebarArchiveMode types, initializes serverAccessKey from storage, adds draft/error/profile/settings-panel state. Implements switchSidebarArchiveMode, runSetThreadArchived, clearServerScopedState, saveAccessKeyAndRetry, profile management actions, and server-target switching. Wires farfield:access-key-error listener and onAuthError socket callback.
App.tsx: sidebar search, worktree grouping, and archive-aware core loading
apps/web/src/App.tsx
Adds search helpers and visibleSidebarThreads. Implements worktree parsing, projectGroupKey, group model with projectPaths, and conversationThreads for projectless paths. Updates loadCoreData for archive pagination, changes thread retention to optimistic-only, adds sidebarArchiveMode hook dependency, and applies same retention change in realtime delta handling.
App.tsx: sidebar rendering, header controls, access-key prompt, and settings modal
apps/web/src/App.tsx, apps/web/vite.config.ts
Introduces renderSidebarThreadRow for centralized archive/generation/waiting indicators, adds archive toggle and search UI, group labels and projectPaths tooltips. Adds header access-key indicator, worktree dropdown, animated access-key prompt banner, and redesigned panel-based settings modal. Expands PWA manifest with scope and icon definitions.

Sequence Diagram(s)

sequenceDiagram
  rect rgba(173, 216, 230, 0.5)
    note over WebApp,SocketIO: Connection & Authentication
  end
  participant WebApp
  participant APIClient as api.ts
  participant HTTPServer as HTTP Server
  participant SocketIO as Socket.IO
  WebApp->>HTTPServer: GET /api/auth/status
  HTTPServer-->>WebApp: { enabled: true }
  WebApp->>WebApp: show access-key prompt
  WebApp->>APIClient: setServerAccessKey(baseUrl, key)
  APIClient->>APIClient: persist to localStorage
  WebApp->>HTTPServer: POST /api/... (X-Farfield-Access-Key header)
  HTTPServer->>HTTPServer: timingSafeEqual check
  HTTPServer-->>WebApp: 200 or 401 accessKeyInvalid
  WebApp->>SocketIO: connect(auth.accessKey)
  SocketIO->>SocketIO: io.use timingSafeEqual check
  SocketIO-->>WebApp: connected or connect_error

  rect rgba(144, 238, 144, 0.5)
    note over WebApp,CodexAgentAdapter: Thread Archive
  end
  participant ArchiveEndpoint as POST /api/unified/thread/:id/archive
  participant CodexAgentAdapter
  WebApp->>APIClient: setThreadArchived({ threadId, archived: true })
  APIClient->>ArchiveEndpoint: POST threadId + provider
  ArchiveEndpoint->>CodexAgentAdapter: archiveThread(threadId)
  CodexAgentAdapter-->>ArchiveEndpoint: void + notifyThreadStateChanged
  ArchiveEndpoint->>ArchiveEndpoint: invalidateSidebarCache, queue core delta
  ArchiveEndpoint-->>WebApp: { ok, threadId, archived }
  WebApp->>WebApp: switchSidebarArchiveMode, reload core data
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • achimala

Poem

🐇 Hoppity-hop, I locked the door,
With a secret key you can't ignore.
Archived threads sleep, search finds the rest,
Worktrees branch out — the sidebar's blessed.
A profile saved, the socket secured tight,
This bunny ships auth into the night! 🔑✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.69% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add remote connection profiles for multi-device Farfield access' accurately describes the main feature added: browser-local connection profiles enabling a single client to switch between multiple devices.
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.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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 and usage tips.

@coderabbitai coderabbitai 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.

Actionable comments posted: 3

🧹 Nitpick comments (2)
apps/web/src/lib/server-target.ts (1)

165-167: ⚡ Quick win

Remove runtime type introspection for optional-string narrowing.

baseUrlOverride is already string | undefined; use explicit undefined branching to keep this on strict typed flow.

Proposed refactor
 export function readStoredServerAccessKey(baseUrlOverride?: string): string {
   const baseUrl =
-    typeof baseUrlOverride === "string"
+    baseUrlOverride !== undefined
       ? parseServerBaseUrl(baseUrlOverride)
       : resolveServerBaseUrl();
   return readStoredAccessKeyMap()[baseUrl] ?? "";
 }

As per coding guidelines "No code outside of Zod can EVER do type introspection. Everything MUST operate on strict types ONLY."

🤖 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 `@apps/web/src/lib/server-target.ts` around lines 165 - 167, The code uses
runtime type introspection with typeof baseUrlOverride === "string" which
violates the strict typing guideline. Since baseUrlOverride is already typed as
string | undefined, replace the typeof check with an explicit undefined branch
instead. Use baseUrlOverride !== undefined or a similar explicit undefined check
to keep the code on strict typed flow without runtime type introspection.

Source: Coding guidelines

apps/web/src/lib/api.ts (1)

835-837: ⚡ Quick win

Avoid runtime type introspection for provider before query serialization.

Use strict optional branching for the typed field instead of typeof checks.

Proposed refactor
   const params = new URLSearchParams();
-  if (typeof input.provider === "string") {
+  if (input.provider !== undefined) {
     params.set("provider", input.provider);
   }

As per coding guidelines "No code outside of Zod can EVER do type introspection. Everything MUST operate on strict types ONLY."

🤖 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 `@apps/web/src/lib/api.ts` around lines 835 - 837, The code is performing
runtime type introspection using typeof on the provider field before query
serialization, which violates the coding guideline that prohibits type
introspection outside of Zod validation. Replace the typeof check in the
conditional that sets the provider parameter with strict optional branching that
relies on the type system instead. This means checking only for the existence or
undefined status of input.provider without using typeof, since the type
definition should already guarantee the type when it's not undefined.

Source: Coding guidelines

🤖 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 `@apps/server/src/agents/adapters/codex-agent.ts`:
- Around line 2847-2861: The JSON.parse call within the loop that processes
content.split("\n") can throw an exception when encountering malformed JSON on a
line, which propagates to the outer catch block and discards all subsequent
valid entries. Wrap the JSON.parse(trimmed) call in a per-line try-catch block
so that when parsing fails, only that specific line is skipped with a continue
statement, allowing the loop to proceed with processing remaining lines. This
should be done around the JSON.parse invocation that feeds into
SessionIndexEntrySchema.safeParse.
- Around line 2910-2927: The getThreadListItemTitle function uses unknown type
and manual runtime type introspection with typeof checks instead of Zod
validation. Define a Zod schema that describes the expected thread object shape
with optional title and name string properties, then replace the manual type
checking logic with Zod's safeParse method. When safeParse succeeds, extract the
title or name from the validated data object, and return undefined if validation
fails. This ensures type safety and consistency with the codebase's validation
patterns.

In `@apps/web/src/lib/server-profiles.ts`:
- Around line 75-77: The findIndex method in the profile matching logic is using
both baseUrl OR name as matching criteria, which risks overwriting different
profiles that happen to share the same name. Change the condition to only match
by baseUrl, removing the OR clause that checks profile.name === name, since
baseUrl is the unique identifier that should determine profile identity for
upsert operations.

---

Nitpick comments:
In `@apps/web/src/lib/api.ts`:
- Around line 835-837: The code is performing runtime type introspection using
typeof on the provider field before query serialization, which violates the
coding guideline that prohibits type introspection outside of Zod validation.
Replace the typeof check in the conditional that sets the provider parameter
with strict optional branching that relies on the type system instead. This
means checking only for the existence or undefined status of input.provider
without using typeof, since the type definition should already guarantee the
type when it's not undefined.

In `@apps/web/src/lib/server-target.ts`:
- Around line 165-167: The code uses runtime type introspection with typeof
baseUrlOverride === "string" which violates the strict typing guideline. Since
baseUrlOverride is already typed as string | undefined, replace the typeof check
with an explicit undefined branch instead. Use baseUrlOverride !== undefined or
a similar explicit undefined check to keep the code on strict typed flow without
runtime type introspection.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: bda5192a-2df2-4b5e-967f-238084b27157

📥 Commits

Reviewing files that changed from the base of the PR and between a479046 and ce4bbb4.

⛔ Files ignored due to path filters (4)
  • apps/web/public/maskable-512.png is excluded by !**/*.png
  • apps/web/public/pwa-192.png is excluded by !**/*.png
  • apps/web/public/pwa-512.png is excluded by !**/*.png
  • apps/web/public/pwa-icon.svg is excluded by !**/*.svg
📒 Files selected for processing (13)
  • apps/server/src/agents/adapters/codex-agent.ts
  • apps/server/src/agents/types.ts
  • apps/server/src/index.ts
  • apps/server/src/unified/adapter.ts
  • apps/web/src/App.tsx
  • apps/web/src/lib/api.ts
  • apps/web/src/lib/realtime-socket.ts
  • apps/web/src/lib/server-profiles.ts
  • apps/web/src/lib/server-target.ts
  • apps/web/vite.config.ts
  • packages/codex-api/src/app-server-client.ts
  • packages/codex-protocol/src/thread.ts
  • packages/unified-surface/src/index.ts

Comment on lines +2847 to +2861
for (const line of content.split("\n")) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
const parsed = SessionIndexEntrySchema.safeParse(JSON.parse(trimmed));
if (!parsed.success) {
continue;
}
const title = parsed.data.thread_name.trim();
if (!title) {
continue;
}
titlesByThreadId.set(parsed.data.id, title);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Invalid JSON in a single line aborts processing of remaining entries.

If JSON.parse(trimmed) throws on a malformed line, the exception propagates to the outer catch block, discarding all subsequent valid entries. Wrap the parse in a per-line try-catch to skip only the invalid line.

🐛 Proposed fix
       for (const line of content.split("\n")) {
         const trimmed = line.trim();
         if (!trimmed) {
           continue;
         }
-        const parsed = SessionIndexEntrySchema.safeParse(JSON.parse(trimmed));
-        if (!parsed.success) {
+        let parsed;
+        try {
+          parsed = SessionIndexEntrySchema.safeParse(JSON.parse(trimmed));
+        } catch {
+          continue;
+        }
+        if (!parsed.success) {
           continue;
         }
📝 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
for (const line of content.split("\n")) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
const parsed = SessionIndexEntrySchema.safeParse(JSON.parse(trimmed));
if (!parsed.success) {
continue;
}
const title = parsed.data.thread_name.trim();
if (!title) {
continue;
}
titlesByThreadId.set(parsed.data.id, title);
}
for (const line of content.split("\n")) {
const trimmed = line.trim();
if (!trimmed) {
continue;
}
let parsed;
try {
parsed = SessionIndexEntrySchema.safeParse(JSON.parse(trimmed));
} catch {
continue;
}
if (!parsed.success) {
continue;
}
const title = parsed.data.thread_name.trim();
if (!title) {
continue;
}
titlesByThreadId.set(parsed.data.id, title);
}
🤖 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 `@apps/server/src/agents/adapters/codex-agent.ts` around lines 2847 - 2861, The
JSON.parse call within the loop that processes content.split("\n") can throw an
exception when encountering malformed JSON on a line, which propagates to the
outer catch block and discards all subsequent valid entries. Wrap the
JSON.parse(trimmed) call in a per-line try-catch block so that when parsing
fails, only that specific line is skipped with a continue statement, allowing
the loop to proceed with processing remaining lines. This should be done around
the JSON.parse invocation that feeds into SessionIndexEntrySchema.safeParse.

Comment on lines +2910 to +2927
function getThreadListItemTitle(thread: unknown): string | null | undefined {
if (!thread || typeof thread !== "object") {
return undefined;
}

const record = thread as Record<string, unknown>;
const title = record["title"];
if (title === null || typeof title === "string") {
return title;
}

const name = record["name"];
if (name === null || typeof name === "string") {
return name;
}

return undefined;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Uses unknown type and runtime introspection instead of Zod schema.

As per coding guidelines, unknown is forbidden and type introspection must use Zod. Define a schema for the thread list item shape and use .safeParse() instead of manual type checks.

♻️ Proposed refactor using Zod
+const ThreadListItemTitleSchema = z.object({
+  title: z.union([z.string(), z.null()]).optional(),
+  name: z.union([z.string(), z.null()]).optional(),
+}).passthrough();
+
-function getThreadListItemTitle(thread: unknown): string | null | undefined {
-  if (!thread || typeof thread !== "object") {
-    return undefined;
-  }
-
-  const record = thread as Record<string, unknown>;
-  const title = record["title"];
-  if (title === null || typeof title === "string") {
-    return title;
-  }
-
-  const name = record["name"];
-  if (name === null || typeof name === "string") {
-    return name;
-  }
-
-  return undefined;
-}
+function getThreadListItemTitle(thread: z.input<typeof ThreadListItemTitleSchema>): string | null | undefined {
+  const parsed = ThreadListItemTitleSchema.safeParse(thread);
+  if (!parsed.success) {
+    return undefined;
+  }
+
+  const { title, name } = parsed.data;
+  if (title !== undefined) {
+    return title;
+  }
+  if (name !== undefined) {
+    return name;
+  }
+  return undefined;
+}
📝 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
function getThreadListItemTitle(thread: unknown): string | null | undefined {
if (!thread || typeof thread !== "object") {
return undefined;
}
const record = thread as Record<string, unknown>;
const title = record["title"];
if (title === null || typeof title === "string") {
return title;
}
const name = record["name"];
if (name === null || typeof name === "string") {
return name;
}
return undefined;
}
const ThreadListItemTitleSchema = z.object({
title: z.union([z.string(), z.null()]).optional(),
name: z.union([z.string(), z.null()]).optional(),
}).passthrough();
function getThreadListItemTitle(thread: z.input<typeof ThreadListItemTitleSchema>): string | null | undefined {
const parsed = ThreadListItemTitleSchema.safeParse(thread);
if (!parsed.success) {
return undefined;
}
const { title, name } = parsed.data;
if (title !== undefined) {
return title;
}
if (name !== undefined) {
return name;
}
return undefined;
}
🤖 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 `@apps/server/src/agents/adapters/codex-agent.ts` around lines 2910 - 2927, The
getThreadListItemTitle function uses unknown type and manual runtime type
introspection with typeof checks instead of Zod validation. Define a Zod schema
that describes the expected thread object shape with optional title and name
string properties, then replace the manual type checking logic with Zod's
safeParse method. When safeParse succeeds, extract the title or name from the
validated data object, and return undefined if validation fails. This ensures
type safety and consistency with the codebase's validation patterns.

Source: Coding guidelines

Comment on lines +75 to +77
const existingIndex = current.findIndex(
(profile) => profile.baseUrl === baseUrl || profile.name === name,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use baseUrl as the upsert identity; matching by name risks profile corruption.

This can overwrite a different saved server when two profiles share a name, which breaks multi-device profile persistence.

Proposed fix
   const current = readServerProfiles();
   const existingIndex = current.findIndex(
-    (profile) => profile.baseUrl === baseUrl || profile.name === name,
+    (profile) => profile.baseUrl === baseUrl,
   );
📝 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 existingIndex = current.findIndex(
(profile) => profile.baseUrl === baseUrl || profile.name === name,
);
const existingIndex = current.findIndex(
(profile) => profile.baseUrl === baseUrl,
);
🤖 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 `@apps/web/src/lib/server-profiles.ts` around lines 75 - 77, The findIndex
method in the profile matching logic is using both baseUrl OR name as matching
criteria, which risks overwriting different profiles that happen to share the
same name. Change the condition to only match by baseUrl, removing the OR clause
that checks profile.name === name, since baseUrl is the unique identifier that
should determine profile identity for upsert operations.

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.

1 participant