The workspace-app and port preview tabs in the Coder Agents right panel
were gated behind the `agent-app-tabs` deployment experiment. This
removes the experiment entirely and renders the app and port tabs
unconditionally, so the add-panel dropdown, workspace-app tabs, and port
preview tabs are always available alongside terminals.
## Changes
- Remove the `ExperimentAgentAppTabs` constant, its `DisplayName()`
case, and its `ExperimentsKnown` registration in
`codersdk/deployment.go`, then regenerate
`site/src/api/typesGenerated.ts`, `coderd/apidoc/docs.go`,
`coderd/apidoc/swagger.json`, and `docs/reference/api/schemas.md`.
- Drop the frontend experiment gate in `AgentChatPageView.tsx`
(including the now-unused `useDashboard`/`experiments` usage) so
persisted app and port tabs are no longer filtered out.
- Remove the `appExperimentEnabled` prop from `RightPanelAddTabControl`
and render the add-panel dropdown unconditionally; update the stories
accordingly.
This reverses the gating introduced in #26395.
note: the diff is tiny if you hide whitespace changes
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.
<img width="628" height="353" alt="image"
src="https://github.com/user-attachments/assets/16fbf58f-282a-4629-b0de-45160117adb6"
/>
Linear:
[DES-22051](https://linear.app/codercom/issue/DES-22051/adjust-workspace-list-icon-and-agent-link-affordance)
## Problem
[#23374](https://github.com/coder/coder/pull/23374) swapped the
underlying `Tooltip` for a `Popover` under the `HelpTooltip` →
`HelpPopover` rename, turning the trigger from a hover surface into a
click surface.
On the workspaces list, `WorkspaceOutdatedTooltip` is rendered inside a
`<TableRow>` wired up by `useClickableTableRow`, whose `onClick`
navigates to the workspace page. Other interactive children in the same
row already stop propagation (checkbox, actions cell, agent badge). The
outdated tooltip didn't, so clicking the info icon bubbled up to the
row's `onClick` and navigated away before the popover could open.
## Fix
Stop click and keydown propagation on both trigger variants
(`HelpPopoverTrigger asChild` span and `HelpPopoverIconTrigger`) in
`WorkspaceOutdatedTooltip`. The popover still opens because Radix
composes its own click handler on top of the user-provided one via
`composeEventHandlers`; `stopPropagation()` does not set
`defaultPrevented`, so Radix's toggle still runs.
## Regression coverage
Added an `InsideClickableRow` story that mounts the tooltip inside a
`useClickableTableRow` row with a tracked `onRowClick`, clicks the
trigger, asserts the popover dialog opens, and asserts `onRowClick` was
not called. Verified locally that the new story fails on `main` (1 call
to `onRowClick`) and passes with this fix.
<details>
<summary>Decision log</summary>
- Fix lives on the component (rather than at the `WorkspacesTable.tsx`
call site) so every consumer is safe by default. The popover is
interactive in a way the parent shouldn't have to know about, and the
existing call site in `TaskPage.tsx` is unaffected because its parent
has no click handler.
- `onKeyDown` propagation is also stopped so the trigger keeps working
when activated via keyboard, and to satisfy
`lint/a11y/useKeyWithClickEvents` on the `<span>` trigger variant.
- Considered pushing the guard down into the `HelpPopoverIconTrigger` /
`HelpPopover` primitives so every consumer is covered — left as a
separate follow-up since today only `WorkspaceOutdatedTooltip` is
embedded in a `useClickableTableRow` row, and a blanket change would
need a broader audit.
</details>
- Module selection summary now has `rounded-sm` corners to fit the icon
- Modules without configuration now say so with a check mark
- Correct "Additional settings" label
Add vertical overflow scrolling to the TemplateBuilder wizard step
content areas and make the selection sidebar sticky.
## Changes
- **Scroll on inner content**: Each step's inner content area (below the
heading and any tabs/search) gets `max-h-[calc(100vh-Npx)]
overflow-y-auto`, keeping headings and tab bars visible while the
content scrolls.
- Base template select and module select grids: 420px offset (accounts
for navbar, page header, card padding, tab bar, and nav controls)
- Base template parameters and module settings: 340px offset
- Customizations step: no scroll, flows naturally
- **Sticky sidebar**: The selection summary sidebar uses `sticky top-0
self-start` to stay visible while scrolling.
- **Scroll reset on navigation**: `window.scrollTo(0, 0)` in
Back/Continue handlers so users start at the top of each step.
> 🤖 Generated by Coder Agents
<details><summary>Implementation plan</summary>
The approach uses `max-h` with `overflow-y-auto` on the inner content
divs of each step component, placed below the step heading and any
tabs/search controls. This keeps the heading fixed while the content
scrolls. The offset values account for the vertical space consumed by
the navbar, page header, card border/padding, and navigation controls.
The `DashboardFullPage` flex-fill approach was explored but abandoned
because the viewport-constrained card was too short to show content on
smaller screens. The standard `Margins` + `pb-12` layout with per-step
`max-h` provides a better balance.
</details>
The chat title menu used a horizontal ellipsis (meatball) and a
different set of items than the sidebar chat-row menu. Designers asked
to normalize both menus on the sidebar shape since the sidebar is the
more recent design.
The chat top bar now uses the same `EllipsisVerticalIcon` (kebab) as the
sidebar row and exposes the same items in the same order: Pin/Unpin
agent, Rename chat, Archive agent, and Archive & delete workspace.
Labels are sentence case throughout. The standalone "Generate new title"
item is removed because the rename dialog's Generate button covers the
same workflow, which is what the sidebar already exposes. The kebab now
sits inline with the title rather than off to the right, so it reads as
a title menu instead of a global action.
Both menus render from a single `ChatActionsMenuItems` component so they
cannot drift in the future. The shared component is polymorphic over
`Item` and `Separator`, which lets the sidebar drive both its kebab
(`DropdownMenu`) and its right-click context menu (`ContextMenu`) from
the same JSX.
<details>
<summary>Implementation notes</summary>
- `ChatActionsMenuItems.tsx`: new shared component. Renders the
Pin/Unpin, Rename, Archive, Archive & delete workspace, and Unarchive
items off a flat set of flags (`isArchived`, `isPinned`, `isChildChat`,
`hasWorkspace`, `isArchiving`) and zero-arg handlers, plus polymorphic
`Item`/`Separator` components.
- `ChatTopBar.tsx`: replace the inline `EllipsisIcon` + duplicated
dropdown body with the kebab trigger and a `<ChatActionsMenuItems>`
call. Move the dropdown trigger into the title area so it sits next to
the title text; switch `DropdownMenuContent` to `align="start"` so the
menu opens flush with the trigger's left edge. Replace the old
`onRegenerateTitle`/`isRegenerateTitleDisabled` props with `onPinAgent`,
`onUnpinAgent`, `onOpenRenameDialog`, `isPinned`, and `isChildChat`.
- `ChatTreeNode.tsx`: delete the inline `renderMenuItems` helper and
call `<ChatActionsMenuItems>` from both the `DropdownMenuContent` and
the `ContextMenuContent`. Pin handlers are pre-bound to `chat.id`;
`onOpenRenameDialog` is pre-bound to the chat object.
- `AgentsPageView.tsx`: lift the rename-chat dialog state up here and
expose it through `AgentsOutletContext.onOpenRenameDialog`, so the
sidebar row menu and the chat top bar open the same dialog instance.
- `ChatsSidebar.tsx`: accept optional controlled
`chatPendingRename`/`onChatPendingRenameChange` props with
internal-state fallback (controlled-or-uncontrolled pattern). Existing
stories and tests are untouched.
- `AgentChatPage.tsx`: consume `requestPinAgent`, `requestUnpinAgent`,
and `onOpenRenameDialog` from the outlet context, and pass new
`handlePinAgentAction`, `handleUnpinAgentAction`,
`handleOpenRenameDialog`, `isPinned`, and `isChildChat` props through
`AgentChatPageView` to the top bar.
- Stories: `ChatTopBar.stories.tsx` replaces the `GenerateTitle` story
with `RenameChatItem`, `PinAgentItem`, `UnpinAgentItem`, and
`ChildChatHidesPinAction`. Updated label expectations to sentence case
across stories. `AgentChatPage.stories.tsx` updated for the new "Archive
agent" label.
</details>
<details>
<summary>Decision log</summary>
- **Source of truth.** Per the designer, the sidebar menu is the
canonical list; the chat title menu is what should change.
- **One menu body.** A shared `ChatActionsMenuItems` component drives
both menus so they cannot drift. Reviewers only need to read the menu
items once.
- **Icon parity.** Both menus now use `EllipsisVerticalIcon` (kebab).
The old meatball was the only `EllipsisIcon` use in this surface.
- **Kebab position.** Moved next to the title so the kebab reads as a
per-chat action against the title, not a global top-bar action. Share
and Toggle-panel stay on the right of the bar.
- **"Generate new title" removed, not preserved as extra.** Keeping it
would have re-introduced the inconsistency the designer is asking to
remove. The rename dialog's Generate button already runs the propose
flow and lets the user accept or edit the suggestion. The
auto-regenerate code path (`requestRegenerateTitle`,
`regeneratingTitleChatIds`, the title spinner) is left in place because
removing it expands the diff and the regenerate plumbing may be re-used;
only the menu trigger is gone.
- **Single dialog instance.** Two separate `RenameChatDialog` instances
would have worked, but lifting the state to `AgentsPageView` keeps a
single source of truth and lets the top bar open the dialog the user
already knows. `ChatsSidebar` keeps internal-state fallback so existing
stories and tests do not need to thread state.
- **`isChildChat`.** Pin/Unpin is hidden for child chats to match
`ChatTreeNode`, which only renders the Pin item when `!isChildNode`.
</details>
<sub>Opened by Coder Agents on behalf of @tracyjohnsonux.</sub>
Adds a search input and provider filter dropdown above the Models table
on `/ai/settings/models`, and moves the `Default` badge to sit beside
the model name.
## Changes
- **Search**: text input matches against model display name, model
identifier, and provider label (case-insensitive).
- **Provider filter**: select dropdown listing every configured
provider, plus an `All providers` default.
- Filters apply before pagination and reset to page one when changed.
- New empty state when filters return no matches.
- Pagination footer now shows the filtered total, with `(filtered from
N)` when filters are active.
- `Default` badge moved from the Status column to inline next to the
model name.
- Stories cover the new search, provider filter, and no-match empty
state.
## Screenshots
Please see the Storybook stories under
`pages/AISettingsPage/ModelsPage/ModelsPageView` for `Default`,
`SearchByName`, `FilterByProvider`, and `NoMatchingModels`.
## Verification
- `pnpm --dir site exec biome check
src/pages/AISettingsPage/ModelsPage/`
- `pnpm --dir site exec tsc -p . --noEmit`
- `pnpm --dir site test:storybook -- --project=chromium
src/pages/AISettingsPage/ModelsPage/` (8/8 ModelsPageView stories pass;
the unrelated `MCP Tool Completed` failure under
`AgentsPage/components/ChatElements` reproduces on `main`)
---
> [!NOTE]
> Opened by Coder Agents on behalf of @tracyjohnsonux.
Hides UI, CLI and API related to AI Gateway key management +
`/api/v2/ai-gateway/serve` endpoint.
API endpoints and CLI commands are still working they are just not
visible.
Adds the current user's AI spend progress to the top navbar avatar
dropdown when /api/v2/users/me/ai/spend reports a configured spend
limit. The shared dropdown content accepts an optional profileExtra
slot so the Agents sidebar can opt in later without changing the
default sidebar UI.
Introduces a temporary site API type and a React Query helper for
AIGOV-473 that refetches each time the dropdown opens, plus shared
budget progress helpers used by both the new dropdown bar and the
existing Agents usage indicator. The shared AIBudgetUsage component
moves to site/src/components so the dropdown and group budget UI
format spend identically, including the unlimited case. The avatar
border polls the spend endpoint and is colored by severity while the
dropdown is closed.
Closes AIGOV-473
Adds an "Alternatives to create a template" section below the navigation
controls on the base infrastructure selection step of the Template
Builder wizard. The box is only shown on the first step and contains
four outline buttons linking to other template creation paths:
- **Start from scratch** -> Coder docs tutorial (external)
- **Upload an existing template** -> `/starter-templates` (internal)
- **Browse community templates** -> Registry templates (external)
- **Use template agent skill** -> Registry skills (external)
External links open in a new tab with an external-link icon.
Closes [DEVEX-564](https://linear.app/codercom/issue/DEVEX-564)
> [!NOTE]
> This PR was authored by Coder Agents on behalf of @jeremyruppel.
<details>
<summary>Implementation plan</summary>
### 1. `TemplateAlternatives.tsx` (new)
- Presentational component rendering a bordered container with label and
four `Button` components (variant `outline`, size `sm`)
- External links use `ExternalLinkIcon` from lucide-react and open in
`_blank`
- Internal link uses React Router `Link`
### 2. `TemplateBuilderPageView.tsx` (modified)
- Import and render `<TemplateAlternatives />` below navigation
controls, conditionally when `currentStep.id === "base-infra"`
### 3. `TemplateAlternatives.stories.tsx` (new)
- Default Storybook story for the component
</details>
Configuring only a GitHub Copilot provider left the Agents page stuck on
"set up a provider then add a model", even with a provider and models
configured. The catalog dropped any provider type that NormalizeProvider
did not recognize, so a Copilot-only deployment looked identical to an
empty one and never unlocked the page.
The Agents harness cannot use Copilot: it needs a per-request token only
an official Copilot client can mint, and the harness is not one. Instead
of dropping such providers, the catalog now reports them as unsupported
so the UI can explain the dead end and point elsewhere, rather than ask
for setup that already happened. The providers stay usable through the
AI Gateway proxy.
Support is derived from the provider type, not stored, so there is no
migration. codersdk.IsAgentsUnsupportedProviderType is the single source
of truth, consulted by the chatd catalog and, through the generated
AgentsUnsupportedProviderTypes list, the frontend.
The diff also carries unrelated modernization of nearby db2sdk and
chatprovider helpers (slices.SortFunc, strings.Cut, range-over-int).
Closes CODAGT-627
Refs CODAGT-256
Refs CODAGT-682
Closes#24731
This pull-request attempts to pull the workspace when it has been
deleted, as to not show a generic error on the `<WorkspaceBuildPage />`.
This secondary query for the workspace is only initiated if we get an
error from the build itself, this is to ascertain if there actually was
a workspace here previously or if we are actually 404ing.
Future improvements might be to `410 Gone` these workspaces/builds.
The templates list page "New template" button and the empty-state "View
all starter templates" button now link to `/templates/new/builder` when
the template builder is enabled in the deployment config, falling back
to `/starter-templates` when disabled.
The `TemplateBuilderPage` now checks `createTemplates` permission and
redirects to `/templates` if the user lacks access. The
`templateBuilderBases` query is also gated on `enabled` to avoid a 404
when the feature is disabled.
<details>
<summary>Implementation details</summary>
- `TemplatesPage` fetches `deploymentConfig()` (only when user has
`createTemplates` permission) and computes `templateBuilderEnabled`
using `=== false` to default safely while loading.
- `templateBuilderEnabled` is threaded through `TemplatesPageView` and
`EmptyTemplates` to conditionally set link targets.
- `TemplateBuilderPage` uses `useAuthenticated()` to check
`permissions.createTemplates` before rendering.
- Added Storybook story variants for the builder-enabled state.
</details>
> Generated by Coder Agents on behalf of @jeremyruppel
Adds an `official` prop to both `TemplateCard` and `ModuleCard` in the
TemplateBuilder. When true (the default), a `BadgeCheckIcon` from
lucide-react is rendered inline next to the card name.
- `official` defaults to `true` since all current templates/modules are
official
- Pass `official={false}` for community entries to hide the badge
- Added `Community` and `CommunitySelected` stories for both components
> Generated by Coder Agents on behalf of @jeremyruppel
<img width="678" height="262" alt="Screenshot 2026-06-29 at 9 38 44 AM"
src="https://github.com/user-attachments/assets/51d1ddad-2bae-4ec3-a1bb-e518c86fa50c"
/>
Adds a curated priority ordering for modules in the template builder
module picker. Modules in the priority list appear first in the
specified order; unlisted modules appear after in their original order.
## Changes
- Added `MODULE_PRIORITY` constant in `ModuleSelectStep.tsx` with the
preferred display order
- Added `sortByPriority()` helper to sort modules against the priority
list
- Not-yet-imported module IDs (`codex`, `antigravity`, `kasmvnc`) are
included but commented out
- Both the module grid and category tabs respect the new ordering
- Search results still use fuzzy relevance ordering
> Generated by Coder Agents on behalf of @jeremyruppel
closes DEVEX-517
alternative to #26759
This PR is mainly about the type style overrides I added to
`MemoizedMarkdown` in BaseTemplateParametersStep.tsx.
I did also shoehorn some other misc styling updates into this PR, but
they're only scoped to template builder so I'll keep it as 1 PR instead
of making a Graphite stack:
- 1b98ac374f: adjusted `StepDivider`'s
left margin which I missed during #26757
- f1eb921048: refactored
templates/modules' avatars to use an `AvatarData` component, so that the
text appears to the right of the avatar instead of below it
Groups the agent-related AI settings pages under a new **Coder Agents**
parent in the sidebar, with a continuous left rule connecting the
children and an active-segment indicator that lights up the rule where
the current sub-item sits.
The new nav order:
- AI Governance
- AI Gateway keys
- Providers
- Coder Agents
- Models
- MCP servers
- Templates
- Spend
- Instructions
- Lifecycle
All target pages already exist on main (Danielle's recent migrations of
Models, MCP servers, Templates, Instructions, Lifecycle, Spend, and
Coder Agents into AI Settings). This PR only changes the sidebar visual
structure: the children move into an indented group with a `border-l
border-l-border` rule, and the active child paints a
`border-l-content-primary` segment over that rule via `-ml-px` so the
rule and indicator share a column instead of stacking.
<details>
<summary>Design notes</summary>
Concept 1 from the earlier exploration: always-expanded with indents,
the parent is its own page. Chosen because it adds no expand/collapse
state, no "which child is the default" question, and no animation work;
the parent reuses the existing nav-item, and the children sit in a
wrapper `div` with a left rule. The site bundle ships without Tailwind's
preflight, so the wrapper and sub-item borders are paired with
`border-solid` to actually paint, matching the pattern already in
`Sidebar.tsx`.
</details>
---
_This PR was prepared by Coder Agents on behalf of @tracyjohnsonux._
- `site/src/pages/AISettingsPage/ProvidersPage/components/providerFormApiMap.ts`):
`providerFormValuesToCreate` returns `type: "bedrock"` when the form
type is `bedrock`, instead of `type: "anthropic"` with Bedrock settings.
- Updated `providerFormApiMap.test.ts` to expect `type:
"bedrock"` and `MockAIProviderBedrock` fixture in `entities.ts` to
`type: "bedrock"`. Added Bedrock fixtures to `ModelsPageView` stories
asserting the AWS Bedrock icon renders correctly.
Refs: [CODAGT-549](https://linear.app/codercom/issue/CODAGT-549/bedrock-models-use-anthropic-styling-and-icons)
> 🤖
Migrates the CreateUserPage tests from vitest to Storybook play-function
stories.
The old `CreateUserPage.test.tsx` rendered the full page through
`renderWithAuth` and MSW, which is slow and contributes to `test-js`
timeout flakes. The new stories seed the react-query cache directly and
assert the same behavior in `play` functions, so they run in the
Storybook test lane instead of the vitest `unit` project.
Coverage is preserved: a success story asserts the success toast after
creating a user, and an error story asserts that an API failure surfaces
in the form's error alert.
Relates to CODAGT-686
Relates to https://github.com/coder/internal/issues/1598
Update the group AI budget settings UI to match the unlimited,
no-budget, and finite-budget states. The field now uses a USD input
suffix, the label reads "Monthly limit per member", and unlimited or $0
budgets show explanatory helper text with an info alert.
Adds an avatar URL field to the admin **Edit user** page, available only
for users whose login type is `password` or `none`.
For identity-provider login types (`github`, `oidc`) the avatar is
synced from the IdP on every login, so the field is hidden and the API
ignores any submitted avatar to avoid confusing overwrites.
The field reuses the same emoji picker + URL input (`IconField`) already
used for template, group, and organization icons.
A follow-up PR will add the same control to the self-service Account
settings page.
<details>
<summary>Implementation plan & decisions</summary>
**Goal:** Let an admin set/clear a user's avatar from the Edit user
page, gated to `password`/`none` login types.
**Backend**
- Add `avatar_url` to `codersdk.UpdateUserProfileRequest`.
- `putUserProfile` applies the submitted avatar only for
`password`/`none`; otherwise it preserves the existing (IdP-synced)
value.
- Regenerated TS types and API docs via `make gen`.
**Frontend**
- `EditUserForm` renders an `IconField` ("Avatar URL") when the login
type allows it.
- `EditUserPage` passes the avatar value and a `canEditAvatar` flag.
- `AccountPage` round-trips `avatar_url` so the shared request type
doesn't wipe avatars on the self-service path.
**Gating** is enforced in both the UI (field hidden) and the backend
(submitted value ignored for IdP login types).
**Tests/stories:** backend `TestUpdateUserProfile` covers apply
(password) and ignore (SSO); `EditUserForm` stories cover the
shown/hidden states with interaction tests.
</details>
---
> Generated by Coder Agents on behalf of @aslilac.
ref DEVEX-517
Some small preliminary UI spruce-ups to get each step closer to the
design before implementing the main feature for DEVEX-517. See commit
messages for individual changes.
The most prominent change is making it so that every step gets wrapped
in this rounded/bordered/padded container. Previously it was present
only in `ModuleSettingsStep` and `TemplateCustomizationsStep`; but
missing from `BaseInfraSelectStep`, `BaseTemplateParametersStep`, and
`ModuleSelectStep`
<img width="1840" height="1191" alt="image"
src="https://github.com/user-attachments/assets/f143a6b1-abdf-4103-8041-cd6e1431c856"
/>
Renames the `last_used_at` column to `last_heartbeat_at` in `ai_gateway_keys` table.
`ai_gateway_keys` table has not been released yet.
All references updated.
Adds a new enterprise-only `GET /api/v2/ai-gateway/serve` endpoint that standalone AI Gateway replicas use to connect to `coderd` over a DRPC-over-WebSocket transport, mirroring the existing in-memory path used by the embedded AI Bridge daemon.
- The endpoint upgrades the HTTP connection to a WebSocket, multiplexes it with yamux, and finally serves the three DRPC services (Recorder, MCPConfigurator, Authorizer).
- The `X-AI-Governance-Gateway-Key` header is used for authentication.
- The key is looked up by its hashed secret
- Missing or revoked keys return `401`.
- API version negotiation is enforced via a new `aibridged/proto` version (`v1.0`).
- Incompatible versions return `400`.
- `FeatureAIBridge` entitlement is required.
- Key liveness (`last_used_at`) is recorded immediately on connection and refreshed every 60 seconds while the session remains open.
- When key liveness detects the key was deleted (no rows where updated) session is closed.
#### Small refactors
* The three DRPC service registrations are extracted into `aibridgedserver.Register`, shared by both the in-memory and WebSocket paths.
* The literal `256 * 1024` used as the yamux-aligned WebSocket read limit is replaced with the named constant `drpcsdk.YamuxDefaultStreamWindowSize` in all call sites.
* as noted in review comment https://github.com/coder/coder/pull/26506#discussion_r3461905223 order of `SetReadLimit` and `WebsocketNetConn` calls was fixed.
<!-- Authored by Coder Agents on behalf of @Emyrk. -->
Adds an opt-in `CODER_DANGEROUS_OIDC_EMAIL_FALLBACK` flag (alias
`--dangerous-oidc-email-fallback`) for IdP brokers that do not issue a
stable `sub` for the same user across connections.
Adds DB methods`GetAIGatewayKeyIDByHashedSecret` and `UpdateAIGatewayKeyLastUsedAt`.
`GetAIGatewayKeyIDByHashedSecret` - returns AI Gateway key ID by hashed secret value.
`UpdateAIGatewayKeyLastUsedAt` - updates last used timestamp for given AI Gateway key.
Used by standalone AI Gateway for authentication and keeping track of currently used keys.
relates to GRU-69
Modifies replicasync to handle discovering NATS enabled primary replicas explicitly, and passing that info to the NATS Pubsub.
This PR adds a new deployment value to explicitly represent the host or IP that the replica can be reached on. It isn't wired up to the CLI, but piggybacks on the DERP config for now.
We learn the NATS port directly from NATS at runtime, and propagate it thru replicasync to learn all peers for clustering.
Tool errors caused orchestrators to abandon spawned agents. Bare error
responses and the close_agent name framed delegation as one-shot: one
transient failure or timeout ended the work, and the orchestrator had no
way to recover or reuse agents.
Renames close_agent to interrupt_agent with a hidden backward-compatible
alias. wait_agent and message_agent return structured payloads instead
of bare errors, so the orchestrator can retry after a timeout, recover
from an error status, or redirect an idle agent. Adds list_agents so
orchestrators can rediscover spawned agents. Adds root-only
orchestration guidance for error recovery.
## Problem
In `/agents`, the sticky user-message truncation sometimes does not
update as new content arrives. While pinned to the bottom with the
transcript overflowing, several messages (or a streaming response) can
land and the sticky bubble keeps a stale clip height, overflowing and
overlapping the content below it. It only snaps back once you scroll
manually.
## Root cause
`StickyUserMessage` recomputes its clip height (`--clip-h`) and push-up
`top` in an `update()` driven by three triggers: a scroll listener, a
window-resize listener, and a `ResizeObserver` meant to catch the
transcript growing.
The observer watched `scroller.firstElementChild`, but in
`ChatScrollContainer` the scroller's first child is the `flex-1 basis-0`
spacer that pins content to the bottom, not the content wrapper. That
spacer collapses to `0px` the moment the transcript overflows (exactly
when truncation engages) and then never resizes again, so the observer
goes silent.
The other triggers do not cover this case either: in `flex-col-reverse`
the `scrollTop` stays at `0` while pinned to the bottom, so no scroll
event fires as content grows. The result is a stale `--clip-h` until the
next manual scroll.
## Fix
- Observe the real content wrapper instead of the collapsing spacer. The
wrapper is tagged with `data-chat-scroll-content` (it contains both the
committed timeline and the streaming live tail), and the sticky code
resolves it via `sentinel.closest(...)`, falling back to the previous
node only if the marker is absent.
- Recompute the scroller geometry (`scrollerTop`/`scrollerHeight`)
inside `update()` on every tick instead of caching it at effect setup,
so the clip and push-up math cannot drift when the scroller moves or
resizes without a window resize (for example the composer growing). This
also removes the now-redundant `onResize` handler.
No change to the sticky visuals or the rAF throttling.
## Testing
- New story `StickyUserMessageClipUpdatesWhilePinned` grows the
transcript while pinned (no scroll dispatched) and asserts the clip
tracks the new geometry, plus structural guards that the observed node
is the content marker and not the `aria-hidden` spacer.
- Verified as a true regression guard: with the fix reverted the new
story fails; with the fix it passes. The existing
`StickyUserMessagePinsOnScroll` is unaffected.
- `biome check`, `tsc -p .`, React Compiler check, emdash check, and
`vitest --project=storybook` for this stories file all pass (48/48).
<details>
<summary>Decision log</summary>
- Considered centralizing the per-message scroll/resize/observer wiring
into a single coordinator in `ConversationTimeline` (it already
centralizes sentinels) to cut N observers/listeners down to one.
Deferred as a follow-up to keep this PR a surgical, low-risk fix; this
change alone resolves the staleness.
- Chose a semantic `data-chat-scroll-content` marker over reusing the
`chat-timeline-wrapper` test id so runtime behavior does not depend on a
test-only attribute. The marker sits on the wrapper that contains both
the timeline and the live tail, so streaming growth is observed too.
- Kept the `scroller.firstElementChild` fallback so other
`ConversationTimeline` consumers and stories without the marker keep
working.
</details>
---
Filed via Coder Agents on behalf of @kylecarbs.
ref DEVEX-491
Adds a `useFuzzySearch` hook based on the logic in IconsPage.tsx, and
Storybook stories for `ModuleSelectStep` to verify filter tab count
behavior.
c16e0d9516 and
b6ee21875c co-written with Claude Code
The workspaces table shortcuts row selected `resources[0].agents[0]`, so
a sub-agent that ended up first (for example the Claude/Task sub-agent
created on a workspace) could replace the parent agent's launcher icons,
and which apps showed depended on agent ordering.
Select the parent agent (`parent_id === null`) of the first non-hidden
resource instead, matching the convention already used on the workspace
detail page (`Workspace.tsx`). This keeps the shortcuts row
deterministic and excludes sub-agent apps.
Refs
[DEVEX-459](https://linear.app/codercom/issue/DEVEX-459/aggregate-workspace-table-shortcuts-across-all-agents)
<details>
<summary>Decision context and scope</summary>
Per the discussion on DEVEX-459, this is the agreed short-term fix:
> In the short term, we should display only apps from the parent agent
and make the behavior deterministic, rather than the current reported
behavior of showing apps from the first discovered agent.
Out of scope (tracked as a longer-term backlog item on DEVEX-459):
- Aggregating app shortcuts across multiple agents.
- Changing the 4-slot cap (`WORKSPACE_APPS_SLOTS`).
For workspaces with multiple top-level agents, the first parent agent's
apps are shown. This is deterministic but not aggregated.
A `ParentAgentApps` Storybook story was added (sub-agent listed first)
with a `play` function asserting the parent agent's app renders and the
sub-agent's app does not.
</details>
---
This PR was created by Coder Agents on behalf of @uzair-coder07.
closes DEVEX-532
## other changes
just cleaning up typographic styles a bit to match Figma better
- create `TemplateBuilderTitle`/`TemplateBuilderSubtitle` components for
h2+p elements at the top of steps
- left-align switch's description with its label