__tests__fix(connections): stop the add-MCP and recorder OAuth flows failing silently
Review findings on the connections UI. All of them share a shape: the flow
finished, nothing reported an error, and the user was left with a connection
that does not do what they asked for.
B1 The mandatory description was dropped for any branded server. The row was
matched by raw string against the URL the user typed, but the probe echoes
the input while the callback stores the driver's own URL, which for Pylon
carries a trailing slash. The dialog then closed as though it had saved.
Matched on a normalized resource identifier now (the same rule the server
uses), against the URL frozen when the popup opened rather than the still
editable field, and a genuine miss is toasted instead of swallowed.
B2 A popup blocked by Safari or Firefox left isConnecting set, which disables
every control on the recorder credential form: the card was inert until a
page reload. Both blocked-popup paths now clear the flag, drop the pending
key and say what happened.
S3 Refocusing Cedar mid-flow deleted the pending record, so finishing the
sign-in afterwards did nothing. The MCP branch now refetches first and keeps
the record until the row actually reports connected.
S4 Whether a recorder uses MCP OAuth was re-derived at focus time from live
query state, which is undefined mid-refetch; an MCP recorder read as
non-MCP goes to Klavis and 422s. The decision is recorded in the pending
payload when the flow starts.
S5 A token connection saved from the Organization tab silently became a
personal one. addMcpConnection has no org variant, so those forms now say
so; the manual client-app path does have one and is routed to
credentialVault.upsertOrg when the scope says org.
S6 Picking a provider and then pasting a different URL left the old brand name
on the new server. A name this form derived is cleared with the verdict; one
the user typed is kept.
S8 The section's reconnect listener stood down for the whole dialog, though
only the add step ever competes for the message. Ownership now follows the
in-flight sign-in, reported by the step itself.
Also: one ScopeSelect instead of two hand-duplicated selectors whose non-admin
notes had already drifted; a non-admin who owns an org row is no longer told to
ask themselves; providerName reused instead of re-derived; a redundant
capabilities cast dropped; and the catalog comment no longer claims a new entry
needs no server change, which a null providerId disproves.
apps/mail's jest allowlist did not name modules/integrations. Added, with six
tests over the two OAuth completion paths; all six fail against the code before
this change.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 1:06 PMadd-mcp-step.tsxfix(forms): an input is a well, and the add-MCP modal has room to be one
Inputs and textareas were `bg-background` — the app canvas — while the
select next to them was `bg-sunken`. Inside a dialog, whose sheet is also
`background`, that made every text field the same colour as the paper
behind it while the select beside it read as a control. Same for the
"Popular systems" cards in the add-MCP step, which were `bg-card`
(RAISED), so the thing you pick from sat on top of the sheet instead of
into it. All three are one surface now.
`border-input` went with them: Cedar defines `--input` as the sunken
SURFACE, not a border colour as shadcn means it, so that class was
drawing the border in the fill colour.
The add-MCP and connections dialogs take the wider sizes. Tool
permissions loses both its paragraphs — one told you to do the thing the
button beside it does; the button now sits on the label's own row.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>
Claude-Session: https://claude.ai/code/session_01DFkAfWRuLsdTkpxrFhTKYWAug 31, 2026, 12:18 AMadd-system-dialog.tsxfix(forms): an input is a well, and the add-MCP modal has room to be one
Inputs and textareas were `bg-background` — the app canvas — while the
select next to them was `bg-sunken`. Inside a dialog, whose sheet is also
`background`, that made every text field the same colour as the paper
behind it while the select beside it read as a control. Same for the
"Popular systems" cards in the add-MCP step, which were `bg-card`
(RAISED), so the thing you pick from sat on top of the sheet instead of
into it. All three are one surface now.
`border-input` went with them: Cedar defines `--input` as the sunken
SURFACE, not a border colour as shadcn means it, so that class was
drawing the border in the fill colour.
The add-MCP and connections dialogs take the wider sizes. Tool
permissions loses both its paragraphs — one told you to do the thing the
button beside it does; the button now sits on the label's own row.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>
Claude-Session: https://claude.ai/code/session_01DFkAfWRuLsdTkpxrFhTKYWAug 31, 2026, 12:18 AMcredential-form.tsxfix(connections): stop the add-MCP and recorder OAuth flows failing silently
Review findings on the connections UI. All of them share a shape: the flow
finished, nothing reported an error, and the user was left with a connection
that does not do what they asked for.
B1 The mandatory description was dropped for any branded server. The row was
matched by raw string against the URL the user typed, but the probe echoes
the input while the callback stores the driver's own URL, which for Pylon
carries a trailing slash. The dialog then closed as though it had saved.
Matched on a normalized resource identifier now (the same rule the server
uses), against the URL frozen when the popup opened rather than the still
editable field, and a genuine miss is toasted instead of swallowed.
B2 A popup blocked by Safari or Firefox left isConnecting set, which disables
every control on the recorder credential form: the card was inert until a
page reload. Both blocked-popup paths now clear the flag, drop the pending
key and say what happened.
S3 Refocusing Cedar mid-flow deleted the pending record, so finishing the
sign-in afterwards did nothing. The MCP branch now refetches first and keeps
the record until the row actually reports connected.
S4 Whether a recorder uses MCP OAuth was re-derived at focus time from live
query state, which is undefined mid-refetch; an MCP recorder read as
non-MCP goes to Klavis and 422s. The decision is recorded in the pending
payload when the flow starts.
S5 A token connection saved from the Organization tab silently became a
personal one. addMcpConnection has no org variant, so those forms now say
so; the manual client-app path does have one and is routed to
credentialVault.upsertOrg when the scope says org.
S6 Picking a provider and then pasting a different URL left the old brand name
on the new server. A name this form derived is cleared with the verdict; one
the user typed is kept.
S8 The section's reconnect listener stood down for the whole dialog, though
only the add step ever competes for the message. Ownership now follows the
in-flight sign-in, reported by the step itself.
Also: one ScopeSelect instead of two hand-duplicated selectors whose non-admin
notes had already drifted; a non-admin who owns an org row is no longer told to
ask themselves; providerName reused instead of re-derived; a redundant
capabilities cast dropped; and the catalog comment no longer claims a new entry
needs no server change, which a null providerId disproves.
apps/mail's jest allowlist did not name modules/integrations. Added, with six
tests over the two OAuth completion paths; all six fail against the code before
this change.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 1:06 PMscope-select.tsxfix(connections): stop the add-MCP and recorder OAuth flows failing silently
Review findings on the connections UI. All of them share a shape: the flow
finished, nothing reported an error, and the user was left with a connection
that does not do what they asked for.
B1 The mandatory description was dropped for any branded server. The row was
matched by raw string against the URL the user typed, but the probe echoes
the input while the callback stores the driver's own URL, which for Pylon
carries a trailing slash. The dialog then closed as though it had saved.
Matched on a normalized resource identifier now (the same rule the server
uses), against the URL frozen when the popup opened rather than the still
editable field, and a genuine miss is toasted instead of swallowed.
B2 A popup blocked by Safari or Firefox left isConnecting set, which disables
every control on the recorder credential form: the card was inert until a
page reload. Both blocked-popup paths now clear the flag, drop the pending
key and say what happened.
S3 Refocusing Cedar mid-flow deleted the pending record, so finishing the
sign-in afterwards did nothing. The MCP branch now refetches first and keeps
the record until the row actually reports connected.
S4 Whether a recorder uses MCP OAuth was re-derived at focus time from live
query state, which is undefined mid-refetch; an MCP recorder read as
non-MCP goes to Klavis and 422s. The decision is recorded in the pending
payload when the flow starts.
S5 A token connection saved from the Organization tab silently became a
personal one. addMcpConnection has no org variant, so those forms now say
so; the manual client-app path does have one and is routed to
credentialVault.upsertOrg when the scope says org.
S6 Picking a provider and then pasting a different URL left the old brand name
on the new server. A name this form derived is cleared with the verdict; one
the user typed is kept.
S8 The section's reconnect listener stood down for the whole dialog, though
only the add step ever competes for the message. Ownership now follows the
in-flight sign-in, reported by the step itself.
Also: one ScopeSelect instead of two hand-duplicated selectors whose non-admin
notes had already drifted; a non-admin who owns an org row is no longer told to
ask themselves; providerName reused instead of re-derived; a redundant
capabilities cast dropped; and the catalog comment no longer claims a new entry
needs no server change, which a null providerId disproves.
apps/mail's jest allowlist did not name modules/integrations. Added, with six
tests over the two OAuth completion paths; all six fail against the code before
this change.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 1:06 PMsystem-row.tsxMerge origin/staging into fix/dm-container-kind
Both branches built org-shared MCP connections independently, so the merge
picks ONE mechanism and layers the other work on top of it.
KEPT (staging) `metadata.scope` / `organizationId` + callability.ts as the
single visibility rule for every MCP surface. It resolves
name collisions deterministically, narrows in SQL and
enforces in JS, and it is what the discovery-based OAuth
callback and the credential vault already write.
DROPPED (ours) `metadata.orgShared` and the peer-scan in
visible-connections.ts. That module survives as the
agent-facing ADAPTER over findMcpConnections — `ownedByMe`
and the explicit-grant name set — so nothing re-implements
a cross-tenant WHERE.
KEPT (ours) `toolPolicy` (the per-tool ceiling) and `requiresAgentGrant`
(which of a person's agents reach a connection). Staging has
no equivalent; they are a different axis from scope, and the
types now say so.
Settings loses mcp-integration-card.tsx to staging's Systems and credentials
section, and the tool-permissions checklist is ported onto system-row.tsx so
the ceiling is still editable. The agent's own picker keeps its shape and now
reads staging's KNOWN_MCP_PROVIDERS, resolving a provider id through discovery
at click time instead of from a second hard-coded catalogue. `probeMcpServer`
became discovery, so the pre-save tools/list probe is `probeMcpServerTools`.
Also reconciled outside MCP: the Slack Connect webhook scoping (staging's, which
handles externally shared channels) keeping this branch's stronger workspace
resolver; the agent-archive and subagent-write metadata stamps, chained rather
than chosen between; buildAgentDocs' generic folder model with this branch's
`listed` on top; and the conferencing-aware event popover with the extracted
RSVP hook.
Fixes carried by the merge: coach-meeting and coach-weekly had no folder and
would have landed in Active; a duplicate source_workspace_id column; a duplicate
import; the dead org-admin catalogue bypass.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 4:15 PMsystems-and-credentials-section.tsxMerge origin/staging into fix/dm-container-kind
Both branches built org-shared MCP connections independently, so the merge
picks ONE mechanism and layers the other work on top of it.
KEPT (staging) `metadata.scope` / `organizationId` + callability.ts as the
single visibility rule for every MCP surface. It resolves
name collisions deterministically, narrows in SQL and
enforces in JS, and it is what the discovery-based OAuth
callback and the credential vault already write.
DROPPED (ours) `metadata.orgShared` and the peer-scan in
visible-connections.ts. That module survives as the
agent-facing ADAPTER over findMcpConnections — `ownedByMe`
and the explicit-grant name set — so nothing re-implements
a cross-tenant WHERE.
KEPT (ours) `toolPolicy` (the per-tool ceiling) and `requiresAgentGrant`
(which of a person's agents reach a connection). Staging has
no equivalent; they are a different axis from scope, and the
types now say so.
Settings loses mcp-integration-card.tsx to staging's Systems and credentials
section, and the tool-permissions checklist is ported onto system-row.tsx so
the ceiling is still editable. The agent's own picker keeps its shape and now
reads staging's KNOWN_MCP_PROVIDERS, resolving a provider id through discovery
at click time instead of from a second hard-coded catalogue. `probeMcpServer`
became discovery, so the pre-save tools/list probe is `probeMcpServerTools`.
Also reconciled outside MCP: the Slack Connect webhook scoping (staging's, which
handles externally shared channels) keeping this branch's stronger workspace
resolver; the agent-archive and subagent-write metadata stamps, chained rather
than chosen between; buildAgentDocs' generic folder model with this branch's
`listed` on top; and the conferencing-aware event popover with the extracted
RSVP hook.
Fixes carried by the merge: coach-meeting and coach-weekly had no folder and
would have landed in Active; a duplicate source_workspace_id column; a duplicate
import; the dead org-admin catalogue bypass.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 4:15 PMtypes.tsMerge origin/staging into fix/dm-container-kind
Both branches built org-shared MCP connections independently, so the merge
picks ONE mechanism and layers the other work on top of it.
KEPT (staging) `metadata.scope` / `organizationId` + callability.ts as the
single visibility rule for every MCP surface. It resolves
name collisions deterministically, narrows in SQL and
enforces in JS, and it is what the discovery-based OAuth
callback and the credential vault already write.
DROPPED (ours) `metadata.orgShared` and the peer-scan in
visible-connections.ts. That module survives as the
agent-facing ADAPTER over findMcpConnections — `ownedByMe`
and the explicit-grant name set — so nothing re-implements
a cross-tenant WHERE.
KEPT (ours) `toolPolicy` (the per-tool ceiling) and `requiresAgentGrant`
(which of a person's agents reach a connection). Staging has
no equivalent; they are a different axis from scope, and the
types now say so.
Settings loses mcp-integration-card.tsx to staging's Systems and credentials
section, and the tool-permissions checklist is ported onto system-row.tsx so
the ceiling is still editable. The agent's own picker keeps its shape and now
reads staging's KNOWN_MCP_PROVIDERS, resolving a provider id through discovery
at click time instead of from a second hard-coded catalogue. `probeMcpServer`
became discovery, so the pre-save tools/list probe is `probeMcpServerTools`.
Also reconciled outside MCP: the Slack Connect webhook scoping (staging's, which
handles externally shared channels) keeping this branch's stronger workspace
resolver; the agent-archive and subagent-write metadata stamps, chained rather
than chosen between; buildAgentDocs' generic folder model with this branch's
`listed` on top; and the conferencing-aware event popover with the extracted
RSVP hook.
Fixes carried by the merge: coach-meeting and coach-weekly had no folder and
would have landed in Active; a duplicate source_workspace_id column; a duplicate
import; the dead org-admin catalogue bypass.
Co-Authored-By: Claude Opus 5 (1M context) <<email>>Aug 30, 2026, 4:15 PM