Chat ACL audit diffs rendered as `[object Object]` because the diff
viewer called `.toString()` on object values. Common chat operations
(archive, share) showed generic "updated chat" descriptions instead of
semantic ones.
Add `chatAuditLogDescription` to derive semantic descriptions from the
audit diff for successful chat writes: "archived/unarchived chat" for
archive toggles, "updated sharing for chat" for ACL-only changes.
Extract diff value formatting into `formatAuditDiffValue`, which renders
object values as deterministic compact JSON with sorted keys, fixing the
`[object Object]` rendering for chat ACLs and any other object-valued
fields. The previous `determineIdPSyncMappingDiff` workaround for IdP
sync mappings was removed because the generic formatting handles it.
Closes CODAGT-513
> Generated by Coder Agents on behalf of @johnstcn
Redesigns the agent desktop panel with a persistent toolbar, zoom modes,
and a detachable pop-out window.
## Changes
**Toolbar** (`DesktopToolbar`)
- Persistent top bar with right-aligned controls: Take/Release control,
Zoom toggle, Detach
- All buttons use consistent `subtle` variant with icon + label
- `h-8` height, `bg-surface-primary` background with bottom border
**Zoom modes**
- Defaults to fit-to-window (`scaleViewport = true`) so the full
1920x1080 desktop is visible
- Toggle to native 100% resolution via toolbar button or keyboard
shortcuts (`Ctrl+0` fit, `Ctrl+1` native)
- noVNC background color overridden from hardcoded `rgb(40,40,40)` to
`--surface-secondary` so letterbox margins match the app theme in light
and dark mode
**Pop-out window**
- New route at `/agents/:agentId/desktop` for a dedicated desktop window
- Opens via toolbar "Detach" button at 50% of screen size, centered
- BroadcastChannel coordination: sidebar shows placeholder with "Bring
back" button
- Closing the pop-out window automatically restores the sidebar panel
**Other**
- `useDesktopConnection` hook accepts `scaleViewport` option, synced to
the RFB instance via a secondary effect
- `DesktopPanelContext` extended with `agent` and `workspace` fields
- Replaces the previous hover-overlay take/release control UX with
toolbar buttons
> Generated by Coder Agents on behalf of @tracyjohnsonux
Changes the "Manage deployment-wide BYOK" link on the AI Providers
settings page (`/ai/settings`) to "View docs", matching the pattern used
on the provisioner keys page (`/organizations/{org}/provisioner-keys`).
### Changes
- Swapped `Link` from `react-router` to `#/components/Link/Link` (uses
`href` instead of `to`)
- Removed `target="_blank"` and `rel="noreferrer"`: the link now
navigates in the same tab, matching the provisioner keys page convention
- Changed link text from "Manage deployment-wide BYOK" to "View docs"
> Generated by Coder Agents on behalf of @tracyjohnsonux
Other-user agent chats showed a banner that implied prompts would run as
the owner, but submitting from that view is forbidden.
This updates the banner to identify the chat owner and makes chats owned
by another user read-only in the UI by disabling the composer and hiding
inline send or edit follow-up actions.
> Mux working on behalf of Mike.
_Disclosure:_ _produced_ _with_ _Claude_ _Opus_ _4\.7_
AI Gateway only supports Anthropic (+Bedrock), OpenAI, and Copilot providers at present. All other types (Vercel, Gemini, etc) will be mapped to OpenAI since they support OpenAI-compatible endpoints.
Fixes CODAGT-484.
- Removed "quota", "billing", "insufficient_quota", "payment required"
from `authStrongPatterns`
- Added `usageLimitPatterns` slice with those patterns
- Added `usageLimitMatch` signal and rule between overloaded and
authStrong in priority
- Added terminal/retry messages for `ChatErrorKindUsageLimit`
- Simplified auth message (removed billing reference)
- Frontend: conditional `!usageLimitStatus.provider` guard on the "View
Usage" Alert
- Added `TestClassify_UsageLimitBeatsAuth` with 5 cases including real
production OpenAI error
- Added `ProviderQuotaExceeded` story asserting no "View Usage" link and
correct `ChatStatusCallout` rendering
> Generated with [Coder Agents](https://coder.com/agents)
Replaces the linear progress bars and text labels in the sidebar footer
usage trigger with SVG donut ring charts that show the section icon
centered inside each ring.
## Changes
- **`SvgRingProgress`**: shared SVG component used by both
`UsageIndicator` and `ContextUsageIndicator`
- Ring colors follow the existing severity system
(normal/warning/exceeded)
- Hover tooltips show "Spend $12.50" and "Workspaces 30/100"
- Dropdown menu content unchanged; full usage details still appear on
click
- Removed dead `summaryValue` field and `size="compact"` variant
- Updated stories to cover ring trigger rendering and dropdown usage
details
> Generated by Coder Agents on behalf of @tracyjohnsonux
Replaces the blocking Dialog modal setup notice with a context-aware
inline banner above the chat input, with different messaging for admins
and members.
## Inline notice banner
The `AgentSetupNotice` component now renders as a `bg-surface-tertiary`
inline box instead of an unclosable `Dialog` modal. The notice sits
above the chat composer using negative margin overlap, and the composer
is forced opaque (`bg-surface-secondary`) when the notice is present so
the banner doesn't bleed through the semi-transparent desktop
background.
Three states based on role and configuration:
- **Admin, no providers or models**: links to both provider and model
setup
- **Admin, missing provider only**: link to provider setup
- **Admin, has providers but no models**: link to model setup only
- **Member, no models available**: generic "your admin is still getting
things set up" message
The admin/member distinction is determined via
`permissions.editDeploymentConfig` and applied in both `AgentChatPage`
and `AgentCreatePage`.
## Conflict resolution notes
During merge with main, the following were adapted:
- Sidebar filter props updated to main's
`sidebarFilters`/`onSidebarFiltersChange` pattern (replacing old
`archivedFilter`)
- Accepted `Sidebar/` -> `ChatsSidebar/` directory refactor from main
- Dropped `hasArchivedChats` query (its sidebar consumer was removed in
the refactor)
- Provider link updated to `/ai/settings` (new AI settings page)
> Generated with the assistance of Coder Agents on behalf of
@tracyjohnsonux
---------
Co-authored-by: jaaydenh <jaaydenh@users.noreply.github.com>
Add a Postgres trigger and matching codersdk constants that cap each
user's secrets in four dimensions: count (50), total stored value bytes
(200 KiB), env-injected stored value bytes (24 KiB), and env name length
(256 bytes). Without these caps a user could overflow the 4 MiB DRPC
agent manifest, the ~32 KiB Windows process env
block, or Linux/macOS ARG_MAX at workspace start. The trigger is the
source of truth on aggregates; the handler maps its check_violation
error into a 400 that names the per-user budget in stored
(post-encryption) bytes. A handler test exercises off-by-one at each cap
across POST and PATCH, plus per-user budget isolation.
Generated with help from Coder Agents.
## Summary
Routes chatd model calls backed by concrete AI Provider rows through the
in-process aibridge transport by default, with deployment options to use
direct provider routing when AI Gateway is disabled or chat AI Gateway
routing is disabled.
- Splits model routing into common, direct provider, and AI Gateway
paths behind a single deployment-mode entry point.
- Builds chatd models through explicit request, route, and options data.
Active API key attribution is passed explicitly instead of being hidden
inside generic model construction.
- For AI Gateway BYOK routes, resolves the user's provider key in chatd,
forwards it through provider-specific auth headers, and sets
`X-Coder-AI-Governance-Token` to the `delegated` marker so aibridge
preserves those headers while still stripping Coder-specific metadata.
- Keeps central provider credentials and deployment fallback credentials
out of forwarded provider auth headers, so AI Gateway central policy
remains authoritative.
- Redacts delegated provider auth from default string formatting to
avoid accidental plaintext logging of user BYOK credentials.
- Covers selected chat models, advisor overrides, title and quickgen
paths, subagent overrides, computer use model selection, and an
integration-style chat turn through the aibridge transport path.
- Persists initiating API key IDs on chat and queued user messages,
including subagent child messages, and fails closed for AI
Gateway-routed model builds without an active key.
- Removes unused `api_key_id` indexes while keeping the persistence
columns and foreign keys.
- Keeps the deployment option available through config and env parsing,
but hides it from CLI help and generated docs.
- Stabilizes the subagent poll fallback test so background CreateChat
processing cannot win the state transition under slower CI environments.
## Tests
- `go test ./coderd/x/chatd -run
'TestAIGatewayProviderAuthForUser|TestAIGatewayProviderAuthRedactsFormatting|TestResolveModelRouteForConfigAIGatewayProviderAuth|TestAIGatewayModelForwardsProviderAuth|TestProcessChat_AIGatewayRoutingUsesDelegatedAPIKey|TestAwaitSubagentCompletion'
-count=1`
- `go test ./coderd/aibridged -run
'TestServeHTTP_DelegatedAPIKey|TestServeHTTP_StripCoderToken' -count=1`
- `git diff --check HEAD~1..HEAD`
- `make lint`
> Mux working on behalf of Mike.
## Problem
When visiting `/ai/settings/governance`, both **AI Governance** and
**Providers** items in the AI settings subnav appear highlighted as
active.
## Cause
`SettingsSidebarNavItem` is built on react-router's `<NavLink>`, which
by default treats a link as active when the current URL **starts with**
the link's `to` path. Since `/ai/settings/governance` starts with
`/ai/settings`, the Providers item is also marked active.
## Fix
Pass `end` on the Providers nav item so it only matches when the path is
exactly `/ai/settings` (the index route). The `SettingsSidebarNavItem`
component already supports this prop for exactly this case.
Co-authored-by: blink-so[bot] <211532188+blink-so[bot]@users.noreply.github.com>
> 🤖 Generated with [Coder Agents](https://coder.com/agents) on behalf of
@tracyjohnsonux
Updates the providers page description to explain that providers power
Coder Agents, AI Gateway, and other LLM features. Adds a "Manage
deployment-wide BYOK" link to the docs.
Uses `<Link>` component and `docs()` helper per project conventions.
Adds the account settings UI for managing user secrets, including the
table, add/edit/delete dialog, Storybook coverage, and route/sidebar
entry.
Also updates the shared `FeatureStageBadge` beta variant with
dedicated beta styling, sizing, and label casing for the Secrets
page.
Stacked on #25370.
_This PR was generated by Coder Agents._
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.
Linear: [DEVEX-355](https://linear.app/coder/issue/DEVEX-355)
Fifth and final PR in a 5-PR stack splitting #25328. Surfaces the AI
settings section in the dashboard chrome and moves the existing AI
Governance page out of `/deployment`.
- `Navbar` / `NavbarView` / `DeploymentDropdown` gain a
`canViewAISettings` prop sourced from the `viewAnyAIProvider` permission
added in PR 2. The deployment dropdown gets a new AI entry that links to
`/ai/settings`.
- `DeploymentSidebarView` drops the AI-related entries that now live
under `/ai/settings`.
- `AISettingsSidebarView` expands to include AI Governance and a
cross-section link to Manage Coder Agents.
- `router.tsx` removes the `/deployment/ai-governance` route and mounts
the matching `/ai/settings/governance` child route under the new AI
settings layout.
- `ChatsSidebar` settings panel repoints the Providers link from
`/deployment/ai-providers` to `/ai/settings`.
<details>
<summary>Stack</summary>
1. #25579 jakehwll/DEVEX-355/01-primitives, primitives
2. #25580 jakehwll/DEVEX-355/02-api, API client and query layer
3. #25581 jakehwll/DEVEX-355/03-components, provider form components
4. #25583 jakehwll/DEVEX-355/04-pages, pages and routes
5. **jakehwll/DEVEX-355/05-section, section reshuffle (this PR)**
Replaces #25328 once the stack lands.
</details>
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.
Linear: [DEVEX-355](https://linear.app/coder/issue/DEVEX-355)
Fourth PR in a 5-PR stack splitting #25328. Wires the new `/ai/settings`
provider management UI.
- `AISettingsLayout` hosts the section under `/ai/settings` with a
sidebar outlet.
- `AISettingsSidebar(View)` shows a single "Providers" nav entry. The
remaining sidebar entries arrive with the broader AI settings section
reshuffle in the next PR.
- `ProvidersPage` lists configured AI providers via the queries added in
PR 2.
- `AddProviderPage` walks through provider-type selection and form
submission, with type-specific credential fields.
- `UpdateProviderPage` edits an existing provider with the same form
components.
- Storybook stories cover each view's loading, empty, populated, error,
and form states using the mock providers from `testHelpers/entities.ts`.
- `router.tsx` mounts the new `/ai/settings` layout with index, `add`,
and `:providerId` child routes. The `governance` child route lands
together with the dashboard navigation changes in the next PR.
Removes the now-unused knip ignore entries for
`src/api/queries/aiProviders.ts` and
`src/pages/AISettingsPage/ProvidersPage/components/addableProviderTypes.ts`,
and drops the matching `@lintignore` tags on `getProviderIcon` and
`MockAIProviders` since the pages and page stories now consume them.
<details>
<summary>Stack</summary>
1. #25579 jakehwll/DEVEX-355/01-primitives, primitives
2. #25580 jakehwll/DEVEX-355/02-api, API client and query layer
3. #25581 jakehwll/DEVEX-355/03-components, provider form components
4. **jakehwll/DEVEX-355/04-pages, pages and routes (this PR)**
5. jakehwll/DEVEX-355/05-section, section reshuffle
Replaces #25328 once the stack lands.
</details>
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.
Linear: [DEVEX-355](https://linear.app/coder/issue/DEVEX-355)
Third PR in a 5-PR stack splitting #25328. Adds the component-level
pieces used by the provider management pages landing in the next PR of
the stack.
- `ProviderForm` + `CredentialField` + a provider type-to-form mapping
for reading and editing the per-type credential and config fields, with
the form API map covered by unit tests.
- `ProviderIcon` resolves the bundled per-provider SVG icons and falls
back to a building glyph for unknown types.
- `ProviderRow` renders a single provider entry for the list view.
- `useUnsavedChangesPrompt` hook intercepts unsaved-form navigation.
- Storybook stories for `ProviderForm`, `ProviderIcon`, and
`ProviderRow` exercise each provider type and form state and consume the
mock providers from PR 2.
Stories now consume `MockAIProviderOpenAI` / `Anthropic` / `Bedrock` so
their per-mock `@lintignore` tags are removed; the `MockAIProviders`
aggregate and the `addableProviderTypes` / `aiProviders` query modules
keep their exclusions for the page stories in the next PR.
<details>
<summary>Stack</summary>
1. #25579 jakehwll/DEVEX-355/01-primitives, primitives
2. #25580 jakehwll/DEVEX-355/02-api, API client and query layer
3. **jakehwll/DEVEX-355/03-components, provider form components (this
PR)**
4. jakehwll/DEVEX-355/04-pages, pages and routes
5. jakehwll/DEVEX-355/05-section, section reshuffle
Replaces #25328 once the stack lands.
</details>
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.
Linear: [DEVEX-355](https://linear.app/coder/issue/DEVEX-355)
Second PR in a 5-PR stack splitting #25328. Adds the frontend layer that
talks to the existing `/api/v2/ai/providers` endpoints already shipped
on `main`:
- API client: `getAIProviders`, `getAIProvider`, `createAIProvider`,
`updateAIProvider`, `deleteAIProvider`.
- React Query wrappers in `queries/aiProviders.ts` with a shared key
helper and matching cache invalidations.
- Mock fixtures for OpenAI, Anthropic, and Bedrock providers in
`testHelpers/entities.ts` for stories and unit tests.
- `viewAnyAIProvider` registered in `permissions.json` so the existing
permissions hook can read it.
- `viewAnyAIProvider` added to `canViewDeploymentSettings` so admins who
can only manage providers still see the deployment dropdown.
The `aiProviders` query module and the per-provider mocks are
temporarily added to the `knip` ignore list / annotated with
`@lintignore`; the next PRs in the stack consume them and remove the
exclusions.
<details>
<summary>Stack</summary>
1. #25579 jakehwll/DEVEX-355/01-primitives, primitives
2. **jakehwll/DEVEX-355/02-api, API client and query layer (this PR)**
3. jakehwll/DEVEX-355/03-components, provider form components
4. jakehwll/DEVEX-355/04-pages, pages and routes
5. jakehwll/DEVEX-355/05-section, section reshuffle
Replaces #25328 once the stack lands.
</details>
> 🤖 This PR was modified by Coder Agents on behalf of Jake Howell.
Linear: [DEVEX-355](https://linear.app/coder/issue/DEVEX-355)
First PR in a 5-PR stack splitting #25328. Adds the small UI primitives
the AI settings stack depends on.
- `FormField` accepts a `description` prop and renders a required
marker. `aria-describedby` is composed from the description, helper, and
error IDs.
- `PageHeader` title, subtitle, and caption forward `className` and
other intrinsic `h1`/`h2`/`span` props to their root elements.
- `AvatarData` gains an opt-in `truncate` prop that clips overflowing
title and subtitle with an ellipsis. Off by default so existing
consumers passing non-text nodes (icons, badges) do not clip silently.
- Bundles the Vercel provider icon (`vercel.svg`) and registers it in
`icons.json` and `externalImages.ts`.
No new pages or routes here; later PRs in the stack consume these
primitives.
<details>
<summary>Stack</summary>
1. **jakehwll/DEVEX-355/01-primitives, primitives (this PR)**
2. jakehwll/DEVEX-355/02-api, API client and query layer
3. jakehwll/DEVEX-355/03-components, provider form components
4. jakehwll/DEVEX-355/04-pages, pages and routes
5. jakehwll/DEVEX-355/05-section, section reshuffle
Replaces #25328 once the stack lands.
</details>
Restyles the Agents page usage trigger to match the new quota meter
presentation by Tracy.
The trigger now shows one compact row per section with the existing
severity colors, the same spend icon used in settings, a server icon for
workspace quota, and right-aligned counters. The related stories were
updated to reflect the new trigger layout.
**Before:**
<img width="318" height="332" alt="image"
src="https://github.com/user-attachments/assets/4c6087bd-7c14-4cb6-b2e7-26bb7a1d3e70"
/>
**After:**
<img width="323" height="353" alt="image"
src="https://github.com/user-attachments/assets/95bfb992-cab8-473e-838d-1dcbe246fe3d"
/>
Relates to CODAGT-197
*Disclaimer: implemented by a Coder Agent using Claude Opus 4.6*
## Summary
Renames "AI Bridge" to "AI Gateway" in the paywall card shown on the AI
Governance settings page when the feature is not entitled.
## Changes
In `PaywallAIGovernance.tsx`:
- Title: `AI Bridge` -> `AI Gateway`
- Description text: `AI Bridge provides...` / `AI Bridge requires...` ->
`AI Gateway provides...` / `AI Gateway requires...`
- Docs link label: `AI Bridge Docs` -> `AI Gateway Docs`
Closes#24333
This was a common setup in `updateWorkspace()` but was not appropriately
ported to `changeWorkspaceVersion()`. Some tests have been added also to
ensure this works 🙂 Simple smooth and easy.
Replace the env-based `BuildProviders` with a DB-backed loader. The database is now the single source of truth for runtime provider configuration; env config arrives via `SeedAIProvidersFromEnv` (run at boot) and `BuildProviders` reads it back as `aibridge.Provider` instances. `cli/server.go` and `enterprise/cli/server.go` both call the same path, so aibridged and aibridgeproxyd see the same provider set.
Per-provider `DumpDir` is replaced by a top-level `CODER_AI_GATEWAY_DUMP_DIR` base; each provider's effective dump path is `<base>/<provider name>`.
Models frequently confuse the search and replace fields in the
edit_files tool (CODAGT-312). Rename the model-facing JSON fields
to old_text/new_text so the intent is unambiguous.
Backend: custom UnmarshalJSON on editFileEdit falls back to
deprecated search/replace when old_text/new_text are empty. The
workspace agent API is unchanged; toSDKFiles maps old_text/new_text
back to search/replace for agent/agentfiles.
Frontend: normalizeEdit in parseEditFilesArgs accepts both
old_text/new_text and search/replace, normalizing to the internal
{ search, replace } representation so streaming diff rendering
works with either field naming convention.
When removing the `/` personal skill trigger, the popover content stayed
mounted during its close transition and briefly rendered the empty
skills state at the viewport origin.
This keeps the menu content mounted for stable Radix positioning,
preserves the last open menu state during the close transition, and adds
a Storybook regression for the backspace path.
> Mux is creating this PR on behalf of Mike.
Generic agent chat tool cards now render an `Input` section before the
existing output viewer, so MCP and workspace MCP tools expose the
arguments sent to the tool. Empty inputs stay hidden, model-intent
wrappers are stripped before display, and the formatted input is the
single source of truth for whether an input block renders.
Refs
https://linear.app/codercom/issue/CODAGT-260/show-mcp-tool-inputs-in-agent-chats
> Mux worked on this on Mike's behalf.
Closes#24183
## Changes
Drops `mx-auto` so README content left-aligns with the header. Bumps
padding from 24px to 32px and widens `max-w` from 800px to 860px for
breathing room.
Applied to both:
- `TemplateDocsPage.tsx`
- `StarterTemplatePageView.tsx`
> Generated with [Coder Agents](https://coder.com/agents)
## Summary
- Compute mobile dropdown bottom offsets in layout-viewport coordinates,
matching the fixed Radix popover wrapper.
- Use `visualViewport.offsetTop` to clamp the above-composer popup
height when iOS WebKit pans the visual viewport for the soft keyboard.
- Align mobile dropdown width/left to the chat composer and add a
Storybook regression for shifted visual viewports.
## Testing
- `cd site && pnpm tsc --noEmit -p .`
- `cd site && pnpm test:storybook
src/pages/AgentsPage/components/ChatMessageInput/ChatMessageInput.stories.tsx`
- `cd site && pnpm lint`
## Manual mobile verification
Start dev mode with `./scripts/develop.sh`, open the forwarded port 8080
URL on a real iPhone in Safari and Chrome, focus the Agents chat input,
type `/`, and verify the personal skills popup appears directly above
the composer, stays within the visible viewport while the keyboard is
open, and scrolls internally for long lists.
Generated by Coder Agents.
On mobile, typing `/` in the chat input could leave the personal-skills
popup partially clipped above the visible viewport. With the soft
keyboard open, Radix's collision detection flipped the caret-anchored
popup above the caret, and the resulting position pushed the top of the
list off-screen.
Add a `.mobile-full-width-dropdown-above-composer` CSS variant in
`site/src/index.css`, driven by a new
`--mobile-dropdown-above-composer-bottom` custom property set from the
existing composer geometry effect in `AgentChatInput.tsx`. The variant
pins the Radix popper wrapper to sit just above the chat input with the
same horizontal padding (`calc(100vw - 2rem)`), and caps `max-height` to
the space between the viewport top and the composer top so the inner
`CommandList` scrolls when the skill list overflows.
Apply the new classes to `PersonalSkillsTriggerMenu`'s `PopoverContent`.
Desktop behavior is unchanged: the new selectors only apply below the
`md` breakpoint, and the caret-anchored `PopoverAnchor` still drives
Radix positioning everywhere else.
Two new Storybook stories cover the mobile geometry:
`MobileAboveChatInput` asserts the popup stays inside the visible
viewport, and `MobileLongListScrolls` asserts the popup is scrollable
when the skill list is taller than the available space.
<details>
<summary>Implementation plan</summary>
The plan file lives at
`/home/coder/.coder/plans/PLAN-28f5e6ed-97dd-4375-a338-60fded8ef8b0.md`
in the agent workspace and was followed end-to-end without scope drift.
Key decisions:
- Did not reuse the existing `.mobile-full-width-dropdown-bottom`
because its formula (`window.innerHeight - composer.bottom`) aligns the
popup's bottom edge with the composer's bottom edge, which overlaps the
composer rather than sitting above it.
- Did not change the existing class's behavior because other dropdowns
(Plus menu, ContextUsageIndicator, ModelSelector, WorkspacePill,
CompactOrgSelector) rely on the current geometry. If the project decides
the overlap pattern is also a bug, those callsites can migrate to the
new variant in a separate change.
- Kept the caret-pinned `PopoverAnchor` span in
`PersonalSkillsTriggerMenu` because it still drives desktop positioning,
and on mobile the CSS overrides the wrapper position entirely (same
pattern as the existing `mobile-full-width-dropdown-bottom` usage).
- Left `CommandList`'s `max-h-72` in place so desktop still caps the
popup at ~18 rem; on mobile the wrapper's CSS-driven `max-height` is the
binding constraint.
</details>
Generated by Coder Agents on behalf of @jaaydenh.
---------
Co-authored-by: Coder Agents <noreply@coder.com>
When the personal skills menu is open and the user clicks outside (e.g.
the send button), the Popover closes via `onOpenChange` but the
`SkillsTriggerPlugin`'s `dismissedTriggerRef` is not set. The next
Lexical update listener call detects the trigger again and briefly
reopens the menu, causing a visible flash.
Addresses this symptom:
https://github.com/user-attachments/assets/0c1442a2-df75-442b-bcf8-4b028dc647b0
Fix by recording the current trigger position in `dismissedTriggerRef`
when the `open` prop transitions from `true` to `false`. This mirrors
what the Escape key handler already does and prevents `refreshTrigger`
from immediately re-opening the menu at the same position.
<details><summary>Implementation details</summary>
- Added a `useLayoutEffect` in `SkillsTriggerPlugin` that tracks `open`
prop transitions via a `prevOpenRef`. When `open` goes from `true` to
`false`, it snapshots the current trigger position into
`dismissedTriggerRef`, matching the pattern the Escape handler uses
(line 225-227).
- Added `OutsideClickDismissesTriggerOnRefocus` Storybook regression
story that verifies the menu stays closed when clicking back into the
editor after an outside-click dismissal.
</details>
---
*PR generated with Coder Agents*
Replace redundant matching Tailwind width and height utilities in
AgentsPage with the `size-*` shorthand. This addresses the AgentsPage
`react-doctor/design-no-redundant-size-axes` findings without changing
rendered dimensions.
Normalize program names in shellparse.Parse to their basename.
Does not rely on filepath.Base because the server may run on either
Linux or Windows where the behavior would differ.
Closes CODAGT-470