From 5d9ff57a1fda14d121651bdd1c92b93598c945c1 Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Fri, 31 Jul 2026 15:00:19 +0300 Subject: [PATCH 01/44] docs(spec): native Connect Client form (spec 091) --- .../checklists/requirements.md | 35 +++++ specs/091-connect-client-form/spec.md | 141 ++++++++++++++++++ 2 files changed, 176 insertions(+) create mode 100644 specs/091-connect-client-form/checklists/requirements.md create mode 100644 specs/091-connect-client-form/spec.md diff --git a/specs/091-connect-client-form/checklists/requirements.md b/specs/091-connect-client-form/checklists/requirements.md new file mode 100644 index 00000000..7b229ad9 --- /dev/null +++ b/specs/091-connect-client-form/checklists/requirements.md @@ -0,0 +1,35 @@ +# Specification Quality Checklist: Native Connect Client Form + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-07-31 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- The preview-before-write rule (FR-003/SC-002) encodes standing user feedback on this exact flow and is enforced structurally, not by convention. +- The no-config-file-reads rule (FR-002/SC-004) preserves the existing tray discipline that all state comes from the core. diff --git a/specs/091-connect-client-form/spec.md b/specs/091-connect-client-form/spec.md new file mode 100644 index 00000000..feaace62 --- /dev/null +++ b/specs/091-connect-client-form/spec.md @@ -0,0 +1,141 @@ +# Feature Specification: Native Connect Client Form + +**Feature Branch**: `091-connect-client-form` +**Created**: 2026-07-31 +**Status**: Draft +**Input**: User description: "Add client must open macOS app form — a native tray menu item and form for connecting AI clients (Claude Code, Cursor, etc.) to the proxy, replacing the current Web-UI-only flow." + +## Context + +The proxy already knows how to connect AI client applications: a client registry (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Codex, Gemini, OpenCode) with per-client status, a no-write diff preview, a config-writing connect action that backs up the client's config first, plus undo and disconnect. Today this is reachable only through the Web UI's Connect modal. The macOS tray app has a native "Add Server…" form but nothing for clients — the user must leave the menu, open a browser, and authenticate, for what is conceptually a two-click action. Field feedback: "Add server, Add client must open macOS app form." + +A standing product rule (learned from earlier feedback on this exact flow) applies: any config-mutating connect flow must show the diff preview and the backup notice BEFORE the action button — the user sees exactly what will be written to their client's config file, and where the backup will land, before anything happens. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Connect a client from the tray (Priority: P1) + +The user picks "Connect Client…" from the tray menu. A native window lists the known AI clients with their current state (connected to this proxy / not connected / not detected). They select Claude Code, immediately see a preview: the exact change that will be made to Claude Code's config file, and a notice that a timestamped backup will be created first. They press Connect, and the row flips to connected. + +**Why this priority**: This is the requested capability — the entire feature exists so this journey never leaves the native app. + +**Independent Test**: With a core running, open the form, select a client whose config exists, verify the preview renders the pending change and backup notice before any write occurs, connect, and verify the client's status updates. + +**Acceptance Scenarios**: + +1. **Given** the tray menu is open, **When** the user chooses "Connect Client…", **Then** a native window opens listing every registry client with name, icon, and current state — no browser is involved. +2. **Given** a selected client, **When** the preview loads, **Then** the user sees the change that would be written (added/modified entry, or new file creation when no config exists) and the backup notice, all before any action button is enabled. +3. **Given** the preview is displayed, **When** the user presses Connect, **Then** the client's config is updated, a backup is created, and the form shows the new connected state without the user re-opening it. +4. **Given** the core is unreachable, **When** the form opens, **Then** it shows a clear "core not running" state instead of an empty list. +5. **Given** a client the registry marks unsupported on this platform, **When** the list renders, **Then** the client appears disabled with the reason, not hidden. + +--- + +### User Story 2 - See every client's connection state at a glance (Priority: P2) + +The user opens the form just to check: which of my AI tools are actually routed through the proxy? The list answers immediately — each client shows connected / not connected / not detected, and for connected ones, which proxy entry name they use. + +**Why this priority**: Complements the tray's Clients presence section (spec 090): presence says who *talked* recently; this form says who is *configured* to talk. + +**Independent Test**: Seed config files for some clients and not others; open the form; verify the three states render correctly without any file access from the tray process itself (states come from the core). + +**Acceptance Scenarios**: + +1. **Given** one connected client, one installed-but-unconnected client, and one client with no config present, **When** the form opens, **Then** the three rows show connected / not connected / not detected respectively. +2. **Given** the form is open, **When** a connect or disconnect completes, **Then** all rows refresh from the core — the tray process never reads or parses client config files itself. + +--- + +### User Story 3 - Undo or disconnect safely (Priority: P2) + +After connecting, the user changes their mind. From the same form they can undo the last connect (restoring the backup) or disconnect (removing the proxy entry), with the same preview-first discipline: the form states what will happen before the destructive button is pressed. + +**Why this priority**: A config-writing feature without a visible way back teaches users not to trust it. + +**Independent Test**: Connect a client, then undo; verify the config returns to its backed-up state and the row returns to its prior state. Disconnect a connected client; verify the entry is removed. + +**Acceptance Scenarios**: + +1. **Given** a client just connected via the form, **When** the user chooses Undo, **Then** the backed-up config is restored and the row reflects it. +2. **Given** a connected client, **When** the user chooses Disconnect, **Then** the form states that the proxy entry will be removed from the client's config before the user confirms, and the entry is removed after. +3. **Given** a client with no backup available, **When** the row renders, **Then** Undo is not offered. + +--- + +### Edge Cases + +- The client's config file does not exist yet: the preview must present this as "a new file will be created at ", not a diff against nothing. +- The client already has an mcpproxy entry pointing elsewhere (stale port/URL): the preview shows a modification; connecting requires the same explicit confirmation, no silent overwrite. +- The connect action can embed the admin credential into the client's config file; the preview notice must say so when it applies. +- The core rejects the write (e.g. the tray is connected with a restricted token): the form surfaces the rejection reason verbatim rather than a generic failure. +- Two rapid connect clicks: the action button disables while a write is in flight. +- The form is opened while the core is starting: it renders a waiting state and populates when the core becomes reachable, without requiring reopen. +- A registry client unknown to this app version (server newer than tray): rendered by name with default icon, fully functional. + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: The tray menu MUST offer a "Connect Client…" item adjacent to the existing "Add Server…" item, opening a native form; no step of the journey may require a browser. +- **FR-002**: The form MUST list all clients reported by the core's client registry with name, icon, platform support, and current state (connected / not connected / not detected), sourced entirely from the core; the tray process MUST NOT read or parse client config files. +- **FR-003**: Selecting a client MUST fetch and display a no-write preview: the exact pending change to that client's config (or new-file creation with its path) and a notice that a timestamped backup is created before any write — both visible before the Connect button can be pressed. +- **FR-004**: When the pending change would embed an admin credential into the client's config, the preview MUST say so. +- **FR-005**: Connect MUST perform the same operation as the existing connect action (backup, then write), and the form MUST refresh all client states from the core afterward. +- **FR-006**: The form MUST offer Undo (restore the pre-connect backup) when the core reports one is available, and Disconnect (remove the proxy entry) for connected clients; each states its effect before a confirming press. +- **FR-007**: The proxy entry name defaults to the standard name; an advanced, collapsed-by-default field allows overriding it, feeding the same preview-then-connect flow. +- **FR-008**: Failures (unreachable core, rejected write, preview error) MUST surface the core's reason text; the action button MUST disable while a request is in flight. +- **FR-009**: An unsupported-on-this-platform client renders disabled with its reason; an unknown client renders generically and remains functional. +- **FR-010**: The form MUST be fully keyboard-navigable and announced correctly by VoiceOver: list navigation, preview content, and action buttons. +- **FR-011**: Opening the form MUST NOT modify anything; the only mutations are the explicit Connect / Undo / Disconnect confirmations. + +### Key Entities + +- **Registry client**: An AI client application the core knows how to configure: identity (name, icon), platform support, detection state, connection state, and — when connected — the proxy entry name in use. +- **Connect preview**: A no-write description of the pending change for one client: change kind (create / add / modify), affected file path, rendered change, backup destination, and whether a credential would be embedded. +- **Connect action result**: Outcome of connect/undo/disconnect: success or a reason, plus the refreshed client state. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: A user can go from opening the tray menu to a connected client in under 30 seconds with no browser involved. +- **SC-002**: 100% of config writes performed through the form are preceded by a rendered preview and backup notice for that exact client — enforced structurally (the Connect control does not exist before the preview has rendered), verified by unit tests of the form's state model. +- **SC-003**: Undo restores the client's config to its exact pre-connect content in the round-trip test. +- **SC-004**: The tray process performs zero client-config file reads (all state via the core), verifiable by the existing tray no-config-access discipline. +- **SC-005**: All three client states and all three actions are covered by unit tests of the form's state model; the visual flow is covered by a screenshot protocol in the spec's verification directory. + +## Assumptions + +- The core's existing connect surface (registry list, per-client status, preview, connect, undo, disconnect) is sufficient; no new backend endpoints are required. The tray talks to it with its existing administrative transport (local socket), which is not subject to the restricted-token gate. +- The form is a window/sheet of the existing native app, consistent with the Add Server form's presentation. +- Client detection semantics (what counts as "not detected") are whatever the core reports today; this feature does not redefine them. +- Bridge/binary installation for clients that need a bridge is out of scope; such clients surface whatever state the core reports. + +## Out of Scope + +- Web UI changes (the Web UI Connect modal remains as is). +- Adding new clients to the registry or changing detection/config-writing logic in the core. +- The "Add Server…" form (already native and shipped). +- Windows/Linux tray parity. +- Automatic connection health checks after connect (spec 090's presence section covers observed traffic). + +## Commit Message Conventions *(mandatory)* + +### Issue References +- ✅ **Use**: `Related #[issue-number]` - Links the commit to the issue without auto-closing +- ❌ **Do NOT use**: `Fixes #[issue-number]`, `Closes #[issue-number]`, `Resolves #[issue-number]` - These auto-close issues on merge + +### Co-Authorship +- ❌ **Do NOT include**: `Co-Authored-By: Claude ` +- ❌ **Do NOT include**: "🤖 Generated with [Claude Code](https://claude.com/claude-code)" + +### Example Commit Message +``` +feat(macos): native Connect Client form + +Related #[issue-number] + +Tray menu item and native form over the core connect surface: status +list, preview-before-write with backup notice, connect, undo, +disconnect. +``` From e5657a2fd3f3b1a3671d253e2dfd1f21d56ea908 Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Fri, 31 Jul 2026 15:04:32 +0300 Subject: [PATCH 02/44] =?UTF-8?q?docs(spec):=20connect=20client=20form=20?= =?UTF-8?q?=E2=80=94=20round-1=20fixes=20(stat-only=20list,=20session=20un?= =?UTF-8?q?do,=20preview=20contract,=20legacy=20sheet=20routing)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- specs/091-connect-client-form/spec.md | 114 ++++++++++-------- .../verification/manual-protocol.md | 40 ++++++ 2 files changed, 105 insertions(+), 49 deletions(-) create mode 100644 specs/091-connect-client-form/verification/manual-protocol.md diff --git a/specs/091-connect-client-form/spec.md b/specs/091-connect-client-form/spec.md index feaace62..1dd0a661 100644 --- a/specs/091-connect-client-form/spec.md +++ b/specs/091-connect-client-form/spec.md @@ -2,119 +2,135 @@ **Feature Branch**: `091-connect-client-form` **Created**: 2026-07-31 -**Status**: Draft +**Status**: Draft (revised after Codex review round 1) **Input**: User description: "Add client must open macOS app form — a native tray menu item and form for connecting AI clients (Claude Code, Cursor, etc.) to the proxy, replacing the current Web-UI-only flow." ## Context -The proxy already knows how to connect AI client applications: a client registry (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Codex, Gemini, OpenCode) with per-client status, a no-write diff preview, a config-writing connect action that backs up the client's config first, plus undo and disconnect. Today this is reachable only through the Web UI's Connect modal. The macOS tray app has a native "Add Server…" form but nothing for clients — the user must leave the menu, open a browser, and authenticate, for what is conceptually a two-click action. Field feedback: "Add server, Add client must open macOS app form." +The proxy already knows how to connect AI client applications: a client registry (Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Codex, Gemini, OpenCode) with per-client status, a no-write preview, a config-writing connect action that backs up an existing client config first, plus undo and disconnect. Today the only preview-first UI is the Web UI's Connect modal; the native app has a legacy dashboard sheet that connects *without* preview. Field feedback: "Add server, Add client must open macOS app form." -A standing product rule (learned from earlier feedback on this exact flow) applies: any config-mutating connect flow must show the diff preview and the backup notice BEFORE the action button — the user sees exactly what will be written to their client's config file, and where the backup will land, before anything happens. +A standing product rule (learned from earlier feedback on this exact flow) applies: any config-mutating connect flow must show the pending change and the backup consequences BEFORE the action button. + +Two platform realities shape this design: + +- The aggregate client list is deliberately cheap (file-existence checks only) so that merely opening a list does not trigger macOS data-access prompts for every client's container. Reading a config's *contents* — needed to know "connected or not" and to build a preview — happens only for a client the user explicitly selects. +- Undo depends on the backup name returned by the connect that created it; the core keeps no cross-session undo state. Undo is therefore an in-form affordance for the connect you just performed (identical to the Web UI's behavior). ## User Scenarios & Testing *(mandatory)* ### User Story 1 - Connect a client from the tray (Priority: P1) -The user picks "Connect Client…" from the tray menu. A native window lists the known AI clients with their current state (connected to this proxy / not connected / not detected). They select Claude Code, immediately see a preview: the exact change that will be made to Claude Code's config file, and a notice that a timestamped backup will be created first. They press Connect, and the row flips to connected. +The user picks "Connect Client…" from the tray menu. A native window lists the known AI clients, showing which have a config file present. They select Claude Code; the form reads its state (connected or not) and shows a preview: the exact entry that will be written, the config file path, whether an existing entry is being replaced (and its current content), and what the safety net is — a timestamped backup for an existing file, or "a new file will be created; Undo removes it" when none exists. They press Connect, and the row flips to connected. **Why this priority**: This is the requested capability — the entire feature exists so this journey never leaves the native app. -**Independent Test**: With a core running, open the form, select a client whose config exists, verify the preview renders the pending change and backup notice before any write occurs, connect, and verify the client's status updates. +**Independent Test**: With a core running, open the form, select a client whose config exists, verify the preview renders the pending entry, path, and backup consequence before any write occurs, connect, and verify the client's status updates. **Acceptance Scenarios**: -1. **Given** the tray menu is open, **When** the user chooses "Connect Client…", **Then** a native window opens listing every registry client with name, icon, and current state — no browser is involved. -2. **Given** a selected client, **When** the preview loads, **Then** the user sees the change that would be written (added/modified entry, or new file creation when no config exists) and the backup notice, all before any action button is enabled. -3. **Given** the preview is displayed, **When** the user presses Connect, **Then** the client's config is updated, a backup is created, and the form shows the new connected state without the user re-opening it. -4. **Given** the core is unreachable, **When** the form opens, **Then** it shows a clear "core not running" state instead of an empty list. -5. **Given** a client the registry marks unsupported on this platform, **When** the list renders, **Then** the client appears disabled with the reason, not hidden. +1. **Given** the tray menu is open, **When** the user chooses "Connect Client…", **Then** a native window opens listing every registry client with name, icon, and config-presence state — no browser is involved, and no client config *contents* are read yet. +2. **Given** the user selects a client, **When** its detailed state loads, **Then** the form shows whether it is currently connected and renders the preview: the entry text that will be written, the config path, and — when an entry already exists — the existing entry being replaced. +3. **Given** the preview is displayed, **When** the user presses Connect, **Then** the write happens (backup first when the file existed), and the form shows the refreshed state without being reopened. +4. **Given** the selected client's config file does not exist, **When** the preview renders, **Then** it states that a new file will be created at the path and that Undo will remove that file (no pre-existing content exists to back up). +5. **Given** the core is unreachable, **When** the form opens, **Then** it shows a clear "core not running" state, retries in the background, and populates when the core becomes reachable without reopening. +6. **Given** a client the registry marks unsupported on this platform, **When** the list renders, **Then** the client appears disabled with the reason, not hidden. --- -### User Story 2 - See every client's connection state at a glance (Priority: P2) +### User Story 2 - See client configuration state at a glance (Priority: P2) -The user opens the form just to check: which of my AI tools are actually routed through the proxy? The list answers immediately — each client shows connected / not connected / not detected, and for connected ones, which proxy entry name they use. +The user opens the form to check which of their AI tools have a config the proxy recognizes. The list answers with the cheap truth (config present / no config found / unsupported), and the user drills into any client to see the authoritative state (connected to this proxy or not, and under which entry name) — an explicit, user-initiated read. **Why this priority**: Complements the tray's Clients presence section (spec 090): presence says who *talked* recently; this form says who is *configured* to talk. -**Independent Test**: Seed config files for some clients and not others; open the form; verify the three states render correctly without any file access from the tray process itself (states come from the core). +**Independent Test**: Seed config files for some clients and not others; open the form; verify list states come from existence checks only; select a client and verify the connected/not-connected resolution appears only then. **Acceptance Scenarios**: -1. **Given** one connected client, one installed-but-unconnected client, and one client with no config present, **When** the form opens, **Then** the three rows show connected / not connected / not detected respectively. -2. **Given** the form is open, **When** a connect or disconnect completes, **Then** all rows refresh from the core — the tray process never reads or parses client config files itself. +1. **Given** clients with and without config files, **When** the form opens, **Then** rows show "config present" / "no config found" respectively, and no config contents have been read. +2. **Given** a selected client whose config contains the proxy entry, **When** its detail loads, **Then** the row shows connected and the entry name in use. +3. **Given** the form is open, **When** a connect or disconnect completes, **Then** the affected client's state refreshes from the core — the tray process never reads or parses client config files itself. --- ### User Story 3 - Undo or disconnect safely (Priority: P2) -After connecting, the user changes their mind. From the same form they can undo the last connect (restoring the backup) or disconnect (removing the proxy entry), with the same preview-first discipline: the form states what will happen before the destructive button is pressed. +After connecting, the user changes their mind. While the form remains open, Undo reverses the connect they just performed: restoring the timestamped backup, or removing the file the connect created. Disconnect (for any connected client, any time) removes the proxy entry after a confirmation that names the file and the entry. **Why this priority**: A config-writing feature without a visible way back teaches users not to trust it. -**Independent Test**: Connect a client, then undo; verify the config returns to its backed-up state and the row returns to its prior state. Disconnect a connected client; verify the entry is removed. +**Independent Test**: Connect a client, then undo; verify the config returns to its pre-connect content (or the created file is removed) and the row reflects it. Disconnect a connected client; verify the entry is removed after confirmation. **Acceptance Scenarios**: -1. **Given** a client just connected via the form, **When** the user chooses Undo, **Then** the backed-up config is restored and the row reflects it. -2. **Given** a connected client, **When** the user chooses Disconnect, **Then** the form states that the proxy entry will be removed from the client's config before the user confirms, and the entry is removed after. -3. **Given** a client with no backup available, **When** the row renders, **Then** Undo is not offered. +1. **Given** a connect just performed in this open form, **When** the user chooses Undo, **Then** the pre-connect state is restored (backup restored, or created file removed) and the row reflects it. +2. **Given** the form was closed and reopened after a connect, **When** the row renders, **Then** Undo is not offered (undo state is scoped to the connect performed in the open form) — Disconnect remains available. +3. **Given** a connected client, **When** the user chooses Disconnect, **Then** a confirmation names the config file and the entry to be removed before anything happens, and the entry is removed after. --- ### Edge Cases -- The client's config file does not exist yet: the preview must present this as "a new file will be created at ", not a diff against nothing. -- The client already has an mcpproxy entry pointing elsewhere (stale port/URL): the preview shows a modification; connecting requires the same explicit confirmation, no silent overwrite. -- The connect action can embed the admin credential into the client's config file; the preview notice must say so when it applies. -- The core rejects the write (e.g. the tray is connected with a restricted token): the form surfaces the rejection reason verbatim rather than a generic failure. -- Two rapid connect clicks: the action button disables while a write is in flight. -- The form is opened while the core is starting: it renders a waiting state and populates when the core becomes reachable, without requiring reopen. -- A registry client unknown to this app version (server newer than tray): rendered by name with default icon, fully functional. +- **Existing entry pointing elsewhere** (stale port/URL): the preview shows both the existing entry and the replacement; connecting requires the same explicit confirmation, no silent overwrite. +- **Credential embedding**: when the pending entry would contain the admin credential, the preview says so explicitly. +- **Malformed config**: a client whose config cannot be parsed shows a "config unreadable" state with the path; Connect is disabled (a write would fail) and the user is pointed at the file. +- **Access denied**: a client whose config the core cannot read shows "access not granted" with remediation guidance; Connect is disabled until access resolves. +- **Preview staleness**: changing any input (selected client, entry-name override) discards the preview and fetches a fresh one; the Connect button is bound to the currently rendered preview. A conflicting change that lands between preview and write surfaces as the core's conflict error and triggers a fresh preview (the form never passes a force/overwrite flag silently). +- **Double-click protection**: action buttons disable while a request is in flight. +- **Unknown client** (core newer than app): rendered by name with a default icon, fully functional. +- **Non-socket transport**: when the app is not talking to the core over its private local socket (administrative transport), the mutating actions are disabled with an explanation; the list and previews remain available if the transport permits. ## Requirements *(mandatory)* ### Functional Requirements - **FR-001**: The tray menu MUST offer a "Connect Client…" item adjacent to the existing "Add Server…" item, opening a native form; no step of the journey may require a browser. -- **FR-002**: The form MUST list all clients reported by the core's client registry with name, icon, platform support, and current state (connected / not connected / not detected), sourced entirely from the core; the tray process MUST NOT read or parse client config files. -- **FR-003**: Selecting a client MUST fetch and display a no-write preview: the exact pending change to that client's config (or new-file creation with its path) and a notice that a timestamped backup is created before any write — both visible before the Connect button can be pressed. -- **FR-004**: When the pending change would embed an admin credential into the client's config, the preview MUST say so. -- **FR-005**: Connect MUST perform the same operation as the existing connect action (backup, then write), and the form MUST refresh all client states from the core afterward. -- **FR-006**: The form MUST offer Undo (restore the pre-connect backup) when the core reports one is available, and Disconnect (remove the proxy entry) for connected clients; each states its effect before a confirming press. -- **FR-007**: The proxy entry name defaults to the standard name; an advanced, collapsed-by-default field allows overriding it, feeding the same preview-then-connect flow. -- **FR-008**: Failures (unreachable core, rejected write, preview error) MUST surface the core's reason text; the action button MUST disable while a request is in flight. -- **FR-009**: An unsupported-on-this-platform client renders disabled with its reason; an unknown client renders generically and remains functional. -- **FR-010**: The form MUST be fully keyboard-navigable and announced correctly by VoiceOver: list navigation, preview content, and action buttons. +- **FR-002**: The form's list MUST come from the core's aggregate client registry (existence-only checks): name, icon, platform support, and config-presence. The tray process MUST NOT read or parse client config files, and opening the list MUST NOT cause client config *contents* to be read. +- **FR-003**: Selecting a client MUST fetch its detailed state (connected / not connected, entry name, access state) and a no-write preview, displayed before the Connect control exists: the entry text to be written, the config file path, the existing entry when one is present, and the safety-net statement — "a timestamped backup of this file will be created alongside it" for an existing file, or "this file does not exist; it will be created, and Undo removes it" otherwise. +- **FR-004**: When the pending entry would embed the admin credential into the client's config, the preview MUST say so. +- **FR-005**: Connect MUST perform the existing connect operation (backup when the file exists, then write) without any silent overwrite flag, and the form MUST refresh the client's state afterward. A conflict reported by the core re-runs the preview instead of retrying. +- **FR-006**: Undo MUST be offered exactly for a connect performed while the form has been open (using that connect's returned backup identity; for a created file, undo removes it) and MUST disappear once used or once the form closes. Disconnect MUST be offered for any connected client, preceded by a confirmation naming the config file and entry. +- **FR-007**: The proxy entry name defaults to the standard name; an advanced, collapsed-by-default field allows overriding it. Any change to it discards and refetches the preview. +- **FR-008**: Failures (unreachable core, rejected write, preview error) MUST present the core's message text unaltered in content (a status-code prefix is acceptable); action buttons disable while a request is in flight. +- **FR-009**: Client states beyond the happy path MUST render with defined labels and affordances: unsupported-on-platform (disabled + reason), config unreadable (Connect disabled + path), access not granted (Connect disabled + remediation), unknown client id (generic rendering, fully functional). +- **FR-010**: The form MUST be fully keyboard-navigable and announced correctly by VoiceOver: list navigation, preview content, and action buttons, with stable accessibility identifiers for testing. - **FR-011**: Opening the form MUST NOT modify anything; the only mutations are the explicit Connect / Undo / Disconnect confirmations. +- **FR-012**: The legacy native dashboard connect control (which connects without preview) MUST be routed into this form, so no native path performs a connect without the preview step. +- **FR-013**: While the core is starting or unreachable, the form MUST show a waiting state and poll (every 2 seconds) until reachable, then populate without user action. ### Key Entities -- **Registry client**: An AI client application the core knows how to configure: identity (name, icon), platform support, detection state, connection state, and — when connected — the proxy entry name in use. -- **Connect preview**: A no-write description of the pending change for one client: change kind (create / add / modify), affected file path, rendered change, backup destination, and whether a credential would be embedded. -- **Connect action result**: Outcome of connect/undo/disconnect: success or a reason, plus the refreshed client state. +- **Registry client (list row)**: identity (name, icon), platform support, config-presence — the cheap, prompt-free view. +- **Client detail (selected)**: connected state, entry name in use, access state (readable / absent / unreadable / denied) — the authoritative, user-initiated view. +- **Connect preview**: the entry text to be written, config path, existing-entry content when present, credential-embedding flag, and the derived safety-net statement (backup vs. create-and-undo-removes). Derivation of "create vs add vs replace" is defined: file absent → create; readable without entry → add; readable with entry → replace; unreadable/denied → no preview, Connect disabled. +- **Connect action result**: success (with backup identity for Undo) or the core's reason; drives the in-form Undo affordance. ## Success Criteria *(mandatory)* ### Measurable Outcomes -- **SC-001**: A user can go from opening the tray menu to a connected client in under 30 seconds with no browser involved. -- **SC-002**: 100% of config writes performed through the form are preceded by a rendered preview and backup notice for that exact client — enforced structurally (the Connect control does not exist before the preview has rendered), verified by unit tests of the form's state model. -- **SC-003**: Undo restores the client's config to its exact pre-connect content in the round-trip test. -- **SC-004**: The tray process performs zero client-config file reads (all state via the core), verifiable by the existing tray no-config-access discipline. -- **SC-005**: All three client states and all three actions are covered by unit tests of the form's state model; the visual flow is covered by a screenshot protocol in the spec's verification directory. +- **SC-001**: Following the manual protocol (`specs/091-connect-client-form/verification/manual-protocol.md`), a user goes from opening the tray menu to a connected client in under 30 seconds with no browser involved (stopwatch step in the protocol). +- **SC-002**: Every config write performed through the form is structurally preceded by a rendered preview for the exact (client, entry-name) pair being written — the Connect control does not exist before that preview has rendered, and input changes destroy it; verified by unit tests of the form's state model. +- **SC-003**: The undo round-trip test restores the client's config to its exact pre-connect content for the existing-file case, and removes the created file for the new-file case. +- **SC-004**: The tray process performs zero client-config file reads (all state via the core); opening the form's list causes zero config-content reads core-side (existence checks only). +- **SC-005**: Unit tests of the form's state model cover: all list states of FR-009, the detail resolution, preview derivation for all four access states, connect success/conflict/failure, session-scoped undo appearance and disappearance, and disconnect confirmation. Visual flow is covered by the manual protocol. +- **SC-006**: An integration test proves a request over the private local socket reaches the gated connect-write route as an administrative caller end-to-end (complementing the existing separate middleware tests). ## Assumptions -- The core's existing connect surface (registry list, per-client status, preview, connect, undo, disconnect) is sufficient; no new backend endpoints are required. The tray talks to it with its existing administrative transport (local socket), which is not subject to the restricted-token gate. -- The form is a window/sheet of the existing native app, consistent with the Add Server form's presentation. -- Client detection semantics (what counts as "not detected") are whatever the core reports today; this feature does not redefine them. +- Backend surface: existing endpoints suffice, with one additive field on the preview response — the existing entry's rendered content when an entry is present (needed for the replace case). No new endpoints; no behavior changes to connect/undo/disconnect. +- The tray's administrative transport is the private local socket; socket callers are treated as administrative by the core (verified: socket connections are tagged and granted admin context; the write gate rejects only restricted agent tokens). TCP fallback exists for reads, so mutating actions require the socket per the non-socket edge case. +- "No config found" is a statement about the config file, not about whether the application is installed; labels say "no config found", never "not installed". +- Disconnect has no exact-diff preview endpoint; a confirmation naming file and entry is the defined and sufficient disclosure. - Bridge/binary installation for clients that need a bridge is out of scope; such clients surface whatever state the core reports. +- The form is a window/sheet of the existing native app, consistent with the Add Server form's presentation. ## Out of Scope - Web UI changes (the Web UI Connect modal remains as is). - Adding new clients to the registry or changing detection/config-writing logic in the core. +- Cross-session (persistent) undo — would require new backend state. +- Application-installation detection (beyond config presence). - The "Add Server…" form (already native and shipped). - Windows/Linux tray parity. - Automatic connection health checks after connect (spec 090's presence section covers observed traffic). @@ -136,6 +152,6 @@ feat(macos): native Connect Client form Related #[issue-number] Tray menu item and native form over the core connect surface: status -list, preview-before-write with backup notice, connect, undo, -disconnect. +list, preview-before-write with backup notice, connect, session-scoped +undo, disconnect. ``` diff --git a/specs/091-connect-client-form/verification/manual-protocol.md b/specs/091-connect-client-form/verification/manual-protocol.md new file mode 100644 index 00000000..04b638cf --- /dev/null +++ b/specs/091-connect-client-form/verification/manual-protocol.md @@ -0,0 +1,40 @@ +# Manual Verification Protocol — Native Connect Client Form (Spec 091) + +Run on the built app (`scripts/build-swift-app.sh`) against a dev core +(isolated data dir + config, high port). Capture screenshots into +`specs/091-connect-client-form/verification/shots/`. + +## Setup + +1. Seed client configs in a scratch HOME (or point the core's client registry + paths at scratch copies): one client with an mcpproxy entry (connected), + one with a config but no entry, one with no config file, one with a + deliberately malformed config (invalid JSON). +2. Start the core; start the app connected over the local socket. + +## Checks + +| # | Check | Expect | Screenshot | +|---|-------|--------|------------| +| 1 | Menu item | "Connect Client…" next to "Add Server…" | `01-menu.png` | +| 2 | List states | config present / no config found / unsupported-disabled rows; no TCC prompts on open | `02-list.png` | +| 3 | SC-001 stopwatch | menu → connected client < 30 s, no browser | note time below | +| 4 | Preview (add) | entry text + path + timestamped-backup statement, before any Connect control | `03-preview-add.png` | +| 5 | Preview (replace) | existing entry AND replacement both visible | `04-preview-replace.png` | +| 6 | Preview (create) | "file will be created… Undo removes it" statement | `05-preview-create.png` | +| 7 | Credential notice | shown when entry embeds the admin credential | `06-credential.png` | +| 8 | Connect + refresh | row flips to connected without reopening | `07-connected.png` | +| 9 | Undo (session) | Undo visible after connect; restores config byte-exact (diff the file); gone after form reopen | `08-undo.png` | +| 10 | Disconnect | confirmation names file + entry; entry removed after | `09-disconnect.png` | +| 11 | Malformed config | "config unreadable" state, Connect disabled | `10-malformed.png` | +| 12 | Core down | waiting state; auto-populates when core starts (≤ 2 s poll) | `11-waiting.png` | +| 13 | Legacy path | dashboard connect control routes into this form (no preview-less native connect remains) | `12-dashboard.png` | +| 14 | VoiceOver | ⌘F5: list rows, preview text, and buttons announced; identifiers stable | note below | + +## Results + +- Date / macOS version / app build: +- SC-001 time: +- Checks passed: +- Waived: +- Notes: From 9db3c41ea082d5979db2bd23e1b57bd03d0464b4 Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Fri, 31 Jul 2026 15:06:58 +0300 Subject: [PATCH 03/44] =?UTF-8?q?docs(spec):=20connect=20client=20form=20r?= =?UTF-8?q?ound-2=20fixes=20=E2=80=94=20write=20precondition,=20OpenCode?= =?UTF-8?q?=20absent=20case,=20sanitized=20existing=20entry,=20protocol=20?= =?UTF-8?q?coverage?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- specs/091-connect-client-form/spec.md | 9 +++++---- .../verification/manual-protocol.md | 5 ++++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/specs/091-connect-client-form/spec.md b/specs/091-connect-client-form/spec.md index 1dd0a661..35c5c459 100644 --- a/specs/091-connect-client-form/spec.md +++ b/specs/091-connect-client-form/spec.md @@ -75,7 +75,8 @@ After connecting, the user changes their mind. While the form remains open, Undo - **Credential embedding**: when the pending entry would contain the admin credential, the preview says so explicitly. - **Malformed config**: a client whose config cannot be parsed shows a "config unreadable" state with the path; Connect is disabled (a write would fail) and the user is pointed at the file. - **Access denied**: a client whose config the core cannot read shows "access not granted" with remediation guidance; Connect is disabled until access resolves. -- **Preview staleness**: changing any input (selected client, entry-name override) discards the preview and fetches a fresh one; the Connect button is bound to the currently rendered preview. A conflicting change that lands between preview and write surfaces as the core's conflict error and triggers a fresh preview (the form never passes a force/overwrite flag silently). +- **Preview staleness**: changing any input (selected client, entry-name override) discards the preview and fetches a fresh one; the Connect button is bound to the currently rendered preview. For the replace case, the write carries a precondition (the previewed existing-entry content); if the entry changed after preview, the core rejects with a conflict and the form re-previews — the form never passes a force/overwrite flag without a matching precondition. +- **Client requiring an existing config** (e.g. OpenCode): when its config file is absent, the core refuses to create one; the form surfaces the core's refusal verbatim and this absent case is non-connectable. The generic create statement applies only to clients the core will create configs for. - **Double-click protection**: action buttons disable while a request is in flight. - **Unknown client** (core newer than app): rendered by name with a default icon, fully functional. - **Non-socket transport**: when the app is not talking to the core over its private local socket (administrative transport), the mutating actions are disabled with an explanation; the list and previews remain available if the transport permits. @@ -88,7 +89,7 @@ After connecting, the user changes their mind. While the form remains open, Undo - **FR-002**: The form's list MUST come from the core's aggregate client registry (existence-only checks): name, icon, platform support, and config-presence. The tray process MUST NOT read or parse client config files, and opening the list MUST NOT cause client config *contents* to be read. - **FR-003**: Selecting a client MUST fetch its detailed state (connected / not connected, entry name, access state) and a no-write preview, displayed before the Connect control exists: the entry text to be written, the config file path, the existing entry when one is present, and the safety-net statement — "a timestamped backup of this file will be created alongside it" for an existing file, or "this file does not exist; it will be created, and Undo removes it" otherwise. - **FR-004**: When the pending entry would embed the admin credential into the client's config, the preview MUST say so. -- **FR-005**: Connect MUST perform the existing connect operation (backup when the file exists, then write) without any silent overwrite flag, and the form MUST refresh the client's state afterward. A conflict reported by the core re-runs the preview instead of retrying. +- **FR-005**: Connect MUST perform the existing connect operation (backup when the file exists, then write), and the form MUST refresh the client's state afterward. For the replace case, the write request MUST carry the previewed existing-entry content as a precondition; the core MUST reject the write with a conflict when the current entry no longer matches, and the form re-runs the preview instead of retrying. The form never sends an overwrite flag without that precondition. - **FR-006**: Undo MUST be offered exactly for a connect performed while the form has been open (using that connect's returned backup identity; for a created file, undo removes it) and MUST disappear once used or once the form closes. Disconnect MUST be offered for any connected client, preceded by a confirmation naming the config file and entry. - **FR-007**: The proxy entry name defaults to the standard name; an advanced, collapsed-by-default field allows overriding it. Any change to it discards and refetches the preview. - **FR-008**: Failures (unreachable core, rejected write, preview error) MUST present the core's message text unaltered in content (a status-code prefix is acceptable); action buttons disable while a request is in flight. @@ -102,7 +103,7 @@ After connecting, the user changes their mind. While the form remains open, Undo - **Registry client (list row)**: identity (name, icon), platform support, config-presence — the cheap, prompt-free view. - **Client detail (selected)**: connected state, entry name in use, access state (readable / absent / unreadable / denied) — the authoritative, user-initiated view. -- **Connect preview**: the entry text to be written, config path, existing-entry content when present, credential-embedding flag, and the derived safety-net statement (backup vs. create-and-undo-removes). Derivation of "create vs add vs replace" is defined: file absent → create; readable without entry → add; readable with entry → replace; unreadable/denied → no preview, Connect disabled. +- **Connect preview**: the entry text to be written, config path, existing-entry content when present (sanitized: credentials, tokens, secret-like values, and credential-bearing URLs masked, matching the preview's existing non-disclosure discipline), credential-embedding flag, and the derived safety-net statement (backup vs. create-and-undo-removes). Derivation of "create vs add vs replace" is defined: file absent → create (or non-connectable for clients the core refuses to create configs for, surfacing the core's refusal); readable without entry → add; readable with entry → replace; unreadable/denied → no preview, Connect disabled. - **Connect action result**: success (with backup identity for Undo) or the core's reason; drives the in-form Undo affordance. ## Success Criteria *(mandatory)* @@ -118,7 +119,7 @@ After connecting, the user changes their mind. While the form remains open, Undo ## Assumptions -- Backend surface: existing endpoints suffice, with one additive field on the preview response — the existing entry's rendered content when an entry is present (needed for the replace case). No new endpoints; no behavior changes to connect/undo/disconnect. +- Backend surface: existing endpoints suffice, with two additive changes and no new endpoints: (1) the preview response gains the existing entry's *sanitized* rendered content when an entry is present (secrets masked; backend tests must prove raw secrets never enter the response), and (2) the connect write request gains an optional expected-existing-entry precondition, rejected with a conflict when the current entry differs (backward-compatible: absent precondition preserves today's behavior). Undo/disconnect behavior is unchanged. - The tray's administrative transport is the private local socket; socket callers are treated as administrative by the core (verified: socket connections are tagged and granted admin context; the write gate rejects only restricted agent tokens). TCP fallback exists for reads, so mutating actions require the socket per the non-socket edge case. - "No config found" is a statement about the config file, not about whether the application is installed; labels say "no config found", never "not installed". - Disconnect has no exact-diff preview endpoint; a confirmation naming file and entry is the defined and sufficient disclosure. diff --git a/specs/091-connect-client-form/verification/manual-protocol.md b/specs/091-connect-client-form/verification/manual-protocol.md index 04b638cf..2811619f 100644 --- a/specs/091-connect-client-form/verification/manual-protocol.md +++ b/specs/091-connect-client-form/verification/manual-protocol.md @@ -17,7 +17,8 @@ Run on the built app (`scripts/build-swift-app.sh`) against a dev core | # | Check | Expect | Screenshot | |---|-------|--------|------------| | 1 | Menu item | "Connect Client…" next to "Add Server…" | `01-menu.png` | -| 2 | List states | config present / no config found / unsupported-disabled rows; no TCC prompts on open | `02-list.png` | +| 2 | List states | config present / no config found / unsupported-disabled rows | `02-list.png` | +| 2b | No content reads on open | run once against a REAL client location under App-Data TCC (e.g. actual Claude Desktop container): opening the list triggers no TCC prompt; alternatively verify via core logs that zero config-content reads occur on list | `02b-tcc.png` or log excerpt | | 3 | SC-001 stopwatch | menu → connected client < 30 s, no browser | note time below | | 4 | Preview (add) | entry text + path + timestamped-backup statement, before any Connect control | `03-preview-add.png` | | 5 | Preview (replace) | existing entry AND replacement both visible | `04-preview-replace.png` | @@ -25,6 +26,8 @@ Run on the built app (`scripts/build-swift-app.sh`) against a dev core | 7 | Credential notice | shown when entry embeds the admin credential | `06-credential.png` | | 8 | Connect + refresh | row flips to connected without reopening | `07-connected.png` | | 9 | Undo (session) | Undo visible after connect; restores config byte-exact (diff the file); gone after form reopen | `08-undo.png` | +| 9b | Undo (created file) | for the no-config client: connect creates the file, Undo removes it (verify file absent afterwards) | `08b-undo-create.png` | +| 9c | Conflict precondition | edit the entry externally between preview and Connect: core rejects with conflict, form re-previews | `08c-conflict.png` | | 10 | Disconnect | confirmation names file + entry; entry removed after | `09-disconnect.png` | | 11 | Malformed config | "config unreadable" state, Connect disabled | `10-malformed.png` | | 12 | Core down | waiting state; auto-populates when core starts (≤ 2 s poll) | `11-waiting.png` | From 10c3c1e4ff2b00403a3f6875d380c33292eaf375 Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Fri, 31 Jul 2026 15:08:52 +0300 Subject: [PATCH 04/44] =?UTF-8?q?docs(spec):=20connect=20client=20form=20r?= =?UTF-8?q?ound-3=20fixes=20=E2=80=94=20opaque=20precondition=20token=20fo?= =?UTF-8?q?r=20all=20change=20kinds,=20protocol=20fixture=20split?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- specs/091-connect-client-form/spec.md | 8 ++++---- .../verification/manual-protocol.md | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/specs/091-connect-client-form/spec.md b/specs/091-connect-client-form/spec.md index 35c5c459..7969cc41 100644 --- a/specs/091-connect-client-form/spec.md +++ b/specs/091-connect-client-form/spec.md @@ -75,7 +75,7 @@ After connecting, the user changes their mind. While the form remains open, Undo - **Credential embedding**: when the pending entry would contain the admin credential, the preview says so explicitly. - **Malformed config**: a client whose config cannot be parsed shows a "config unreadable" state with the path; Connect is disabled (a write would fail) and the user is pointed at the file. - **Access denied**: a client whose config the core cannot read shows "access not granted" with remediation guidance; Connect is disabled until access resolves. -- **Preview staleness**: changing any input (selected client, entry-name override) discards the preview and fetches a fresh one; the Connect button is bound to the currently rendered preview. For the replace case, the write carries a precondition (the previewed existing-entry content); if the entry changed after preview, the core rejects with a conflict and the form re-previews — the form never passes a force/overwrite flag without a matching precondition. +- **Preview staleness**: changing any input (selected client, entry-name override) discards the preview and fetches a fresh one; the Connect button is bound to the currently rendered preview. Every preview carries an opaque, backend-generated precondition token derived from the raw pre-write state (file existence + target entry presence and raw value); the write echoes it, and the core rejects with a conflict when the state has drifted — including a file appearing after an absent-file preview or disappearing after an add/replace preview — upon which the form re-previews. The form never passes a force/overwrite flag without a valid token. - **Client requiring an existing config** (e.g. OpenCode): when its config file is absent, the core refuses to create one; the form surfaces the core's refusal verbatim and this absent case is non-connectable. The generic create statement applies only to clients the core will create configs for. - **Double-click protection**: action buttons disable while a request is in flight. - **Unknown client** (core newer than app): rendered by name with a default icon, fully functional. @@ -89,7 +89,7 @@ After connecting, the user changes their mind. While the form remains open, Undo - **FR-002**: The form's list MUST come from the core's aggregate client registry (existence-only checks): name, icon, platform support, and config-presence. The tray process MUST NOT read or parse client config files, and opening the list MUST NOT cause client config *contents* to be read. - **FR-003**: Selecting a client MUST fetch its detailed state (connected / not connected, entry name, access state) and a no-write preview, displayed before the Connect control exists: the entry text to be written, the config file path, the existing entry when one is present, and the safety-net statement — "a timestamped backup of this file will be created alongside it" for an existing file, or "this file does not exist; it will be created, and Undo removes it" otherwise. - **FR-004**: When the pending entry would embed the admin credential into the client's config, the preview MUST say so. -- **FR-005**: Connect MUST perform the existing connect operation (backup when the file exists, then write), and the form MUST refresh the client's state afterward. For the replace case, the write request MUST carry the previewed existing-entry content as a precondition; the core MUST reject the write with a conflict when the current entry no longer matches, and the form re-runs the preview instead of retrying. The form never sends an overwrite flag without that precondition. +- **FR-005**: Connect MUST perform the existing connect operation (backup when the file exists, then write), and the form MUST refresh the client's state afterward. The write request MUST echo the preview's opaque precondition token (derived backend-side from raw file existence + target entry presence and raw value, for all change kinds — create, add, replace); the core MUST reject the write with a conflict when the current state no longer matches the token, and the form re-runs the preview instead of retrying. The sanitized rendered entry is display-only and never used for comparison. The form never sends an overwrite flag without a valid token. - **FR-006**: Undo MUST be offered exactly for a connect performed while the form has been open (using that connect's returned backup identity; for a created file, undo removes it) and MUST disappear once used or once the form closes. Disconnect MUST be offered for any connected client, preceded by a confirmation naming the config file and entry. - **FR-007**: The proxy entry name defaults to the standard name; an advanced, collapsed-by-default field allows overriding it. Any change to it discards and refetches the preview. - **FR-008**: Failures (unreachable core, rejected write, preview error) MUST present the core's message text unaltered in content (a status-code prefix is acceptable); action buttons disable while a request is in flight. @@ -103,7 +103,7 @@ After connecting, the user changes their mind. While the form remains open, Undo - **Registry client (list row)**: identity (name, icon), platform support, config-presence — the cheap, prompt-free view. - **Client detail (selected)**: connected state, entry name in use, access state (readable / absent / unreadable / denied) — the authoritative, user-initiated view. -- **Connect preview**: the entry text to be written, config path, existing-entry content when present (sanitized: credentials, tokens, secret-like values, and credential-bearing URLs masked, matching the preview's existing non-disclosure discipline), credential-embedding flag, and the derived safety-net statement (backup vs. create-and-undo-removes). Derivation of "create vs add vs replace" is defined: file absent → create (or non-connectable for clients the core refuses to create configs for, surfacing the core's refusal); readable without entry → add; readable with entry → replace; unreadable/denied → no preview, Connect disabled. +- **Connect preview**: the entry text to be written, config path, existing-entry content when present (sanitized: credentials, tokens, secret-like values, and credential-bearing URLs masked, matching the preview's existing non-disclosure discipline), credential-embedding flag, an opaque precondition token binding the preview to the raw pre-write state, and the derived safety-net statement (backup vs. create-and-undo-removes). Derivation of "create vs add vs replace" is defined: file absent → create (or non-connectable for clients the core refuses to create configs for, surfacing the core's refusal); readable without entry → add; readable with entry → replace; unreadable/denied → no preview, Connect disabled. - **Connect action result**: success (with backup identity for Undo) or the core's reason; drives the in-form Undo affordance. ## Success Criteria *(mandatory)* @@ -119,7 +119,7 @@ After connecting, the user changes their mind. While the form remains open, Undo ## Assumptions -- Backend surface: existing endpoints suffice, with two additive changes and no new endpoints: (1) the preview response gains the existing entry's *sanitized* rendered content when an entry is present (secrets masked; backend tests must prove raw secrets never enter the response), and (2) the connect write request gains an optional expected-existing-entry precondition, rejected with a conflict when the current entry differs (backward-compatible: absent precondition preserves today's behavior). Undo/disconnect behavior is unchanged. +- Backend surface: existing endpoints suffice, with two additive changes and no new endpoints: (1) the preview response gains the existing entry's *sanitized* rendered content when an entry is present (secrets masked; backend tests must prove raw secrets never enter the response) plus an opaque precondition token derived from the raw pre-write state (file existence + target entry presence and raw value; the token is a hash/revision, never reversible to secrets), and (2) the connect write request gains an optional precondition-token field, rejected with a conflict when the current state differs (backward-compatible: absent token preserves today's behavior). Undo/disconnect behavior is unchanged. - The tray's administrative transport is the private local socket; socket callers are treated as administrative by the core (verified: socket connections are tagged and granted admin context; the write gate rejects only restricted agent tokens). TCP fallback exists for reads, so mutating actions require the socket per the non-socket edge case. - "No config found" is a statement about the config file, not about whether the application is installed; labels say "no config found", never "not installed". - Disconnect has no exact-diff preview endpoint; a confirmation naming file and entry is the defined and sufficient disclosure. diff --git a/specs/091-connect-client-form/verification/manual-protocol.md b/specs/091-connect-client-form/verification/manual-protocol.md index 2811619f..b434ab61 100644 --- a/specs/091-connect-client-form/verification/manual-protocol.md +++ b/specs/091-connect-client-form/verification/manual-protocol.md @@ -8,8 +8,9 @@ Run on the built app (`scripts/build-swift-app.sh`) against a dev core 1. Seed client configs in a scratch HOME (or point the core's client registry paths at scratch copies): one client with an mcpproxy entry (connected), - one with a config but no entry, one with no config file, one with a - deliberately malformed config (invalid JSON). + one with a config but no entry, one CREATE-CAPABLE client with no config + file (e.g. Cursor), one non-create-capable client with no config file + (OpenCode), and one with a deliberately malformed config (invalid JSON). 2. Start the core; start the app connected over the local socket. ## Checks @@ -26,8 +27,9 @@ Run on the built app (`scripts/build-swift-app.sh`) against a dev core | 7 | Credential notice | shown when entry embeds the admin credential | `06-credential.png` | | 8 | Connect + refresh | row flips to connected without reopening | `07-connected.png` | | 9 | Undo (session) | Undo visible after connect; restores config byte-exact (diff the file); gone after form reopen | `08-undo.png` | -| 9b | Undo (created file) | for the no-config client: connect creates the file, Undo removes it (verify file absent afterwards) | `08b-undo-create.png` | -| 9c | Conflict precondition | edit the entry externally between preview and Connect: core rejects with conflict, form re-previews | `08c-conflict.png` | +| 9b | Undo (created file) | for the create-capable no-config client: connect creates the file, Undo removes it (verify file absent afterwards) | `08b-undo-create.png` | +| 9c | Conflict precondition | between preview and Connect, externally change ONLY a masked credential value inside the existing entry: core rejects with conflict (token detects what sanitization hides), form re-previews | `08c-conflict.png` | +| 9d | Non-create-capable absent | OpenCode with no config: Connect unavailable, core refusal shown verbatim | `08d-opencode.png` | | 10 | Disconnect | confirmation names file + entry; entry removed after | `09-disconnect.png` | | 11 | Malformed config | "config unreadable" state, Connect disabled | `10-malformed.png` | | 12 | Core down | waiting state; auto-populates when core starts (≤ 2 s poll) | `11-waiting.png` | From 4228b7255e53079446eda84bed85979ea6ac0a3a Mon Sep 17 00:00:00 2001 From: Algis Dumbris Date: Fri, 31 Jul 2026 17:44:12 +0300 Subject: [PATCH 05/44] =?UTF-8?q?docs(plan):=20connect=20client=20form=20?= =?UTF-8?q?=E2=80=94=20plan,=20research,=20data=20model,=20contracts,=20qu?= =?UTF-8?q?ickstart?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 2 +- .../contracts/api-deltas.md | 51 +++++++++++ specs/091-connect-client-form/data-model.md | 70 +++++++++++++++ specs/091-connect-client-form/plan.md | 78 ++++++++++++++++ specs/091-connect-client-form/quickstart.md | 36 ++++++++ specs/091-connect-client-form/research.md | 90 +++++++++++++++++++ 6 files changed, 326 insertions(+), 1 deletion(-) create mode 100644 specs/091-connect-client-form/contracts/api-deltas.md create mode 100644 specs/091-connect-client-form/data-model.md create mode 100644 specs/091-connect-client-form/plan.md create mode 100644 specs/091-connect-client-form/quickstart.md create mode 100644 specs/091-connect-client-form/research.md diff --git a/CLAUDE.md b/CLAUDE.md index 87d06d95..93f123c0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -158,6 +158,6 @@ tail -f ~/Library/Logs/mcpproxy/main.log # main log (macOS; Linux: ~/.mcpproxy/ - **Windows installer**: [docs/github-actions-windows-wix-research.md](docs/github-actions-windows-wix-research.md). **Prerelease** (`next` branch + `v*-rc.*` tags, opt-in, off stable channels): [docs/prerelease-builds.md](docs/prerelease-builds.md). ## Recent Changes +- 091-connect-client-form: Added Swift 5.9 (SwiftUI sheet + AppKit menu) + Go 1.25 (core) + existing `internal/connect` package (registry, preview, connect, undo, backup), `internal/httpapi/connect.go` routes; Swift `APIClient` over Unix socket (admin context) - 088-scanner-trust-ui: Added TypeScript 5.9, Vue 3.5 (Composition API, `