ref DEVEX-449
## before
In `AgentDevcontainerCard`, the devcontainer's display name truncated
with an ellipsis--but only when the viewport is <768px (parent has
`md:w-full`), which is also when the display name width gets capped at
320px (`max-w-xs`). At larger screen sizes, the display name overlaps
with the rebuild button to the right.
https://github.com/user-attachments/assets/47af30a2-f446-4b5f-bf63-24afb0532b19
## after
Now, the devcontainer display name fills up as much width as possible at
all screen sizes (not just >=768px), and it also gets truncated with an
ellipsis at all screen sizes.
https://github.com/user-attachments/assets/68579438-6fb5-40ea-93cf-c3814dfaac1a
---
(If we have a way to automate resizing screens in Storybook, lmk!)
The trick is to set a `min-width` on the text we want to truncate, see
this CSS Tricks article: https://css-tricks.com/flexbox-truncated-text/
I changed `max-w-xs` to `min-w-[284px]`. In addition to constraining the
display name's width (see "before" above), the `max-width` caused the
rebuild button to wrap to the next line if the screen is too narrow to
show the display name at that width. This behavior on small screens is
preserved. 🙂 However, since the display name's width isn't constrained
anymore, it's written out fully if possible, which causes the rebuild
button to wrap lines on wider screens if the display name is very long.
Implement the module selection wizard step with multi-select toggle and
conflict warnings.
- Add `getTemplateBuilderModules` API client method with optional `base`
parameter for OS filtering
- Add react-query wrapper with `staleTime: Infinity` to prevent
re-fetches on step navigation
- Render a flat grid of `ModuleCard` components with checkbox-style
multi-select
- Show non-blocking conflict warnings when selected modules declare
`conflicts_with` each other
- Map selections to `TemplateBuilderComposeModule[]` and
`SelectedModuleMeta[]` for wizard state
Relates to [DEVEX-285](https://linear.app/codercom/issue/DEVEX-285).
> [!NOTE]
> This PR was authored with Coder Agents.
---------
Co-authored-by: Andrew Aquino <dawneraq@gmail.com>
Implement the base template parameters wizard step, which renders a
configuration form for base template variables using the existing
`TemplateConfiguration` and `ConfigurationField` components.
- Map `TemplateBuilderModuleVariable` to `ConfigurationFieldDefinition`
(switch for bool, text input for string/number)
- Read variable definitions from the cached bases query
- Disable Continue button until all required non-sensitive variables
have values (`baseParametersComplete` helper)
- Step is automatically skipped when the selected base has no parameters
(e.g. Docker)
- Update `toComposeRequest` to include `base_variable_values` in the API
payload
Relates to [DEVEX-284](https://linear.app/codercom/issue/DEVEX-284).
> [!NOTE]
> This PR was authored with Coder Agents.
## Summary
Replaces 9 stale docs paths and 2 stale doc anchors in `site/src/`, and
adds a TS/TSX audit script (`site/scripts/audit-docs-paths.mjs`) plus
unit tests that scan the codebase for paths that resolve via
`coder.com/redirects.json`.
### Redirect-target updates (9)
Each of these paths' `/docs/...` source matches a Next.js redirect rule,
so requests today produce a 302 on `coder.com`. The audit script
identifies them by cross-referencing against `redirects.json`.
- Five update product-code references to `/ai-coder/ai-bridge` (renamed
to `/ai-coder/ai-gateway` in v2.33).
- One updates a commented-out reference to
`/templates#template-filtering` in `TemplatesFilter.tsx`.
- Three update notification-template mock data in
`testHelpers/entities.ts` that pointed at the renamed
`/docs/templates/schedule`.
### Anchor-only updates (2)
These paths are still live on `coder.com` (no redirect), but their
`#fragment` no longer matches a heading on the destination page.
Fragments are evaluated client-side and never sent to the server, so the
audit script does not catch them. Found and verified manually against
the current docs.
- `AuditFilter.tsx`: `/admin/security/audit-logs#filtering-logs` →
`/admin/security/audit-logs#how-to-filter-audit-logs`. The current
heading is `## How to Filter Audit Logs` in
[`docs/admin/security/audit-logs.md`](https://github.com/coder/coder/blob/main/docs/admin/security/audit-logs.md).
- `UserAuthSettingsPageView.tsx`: drops the stale `#openid-connect`
anchor; the path itself (`/admin/users/oidc-auth`) is unchanged. The
page H1 is now `# OpenID Connect`, so the bare path lands at the same
place the anchor used to.
None are user-visible label changes; only the doc target URLs change.
## Audit script
`site/scripts/audit-docs-paths.mjs` cross-references TS/TSX docs-URL
references against `coder.com/redirects.json` and reports anything that
resolves via a redirect (which means stale source). It catches four
forms:
- `docs("/...")` / `docs('/...')` / `` docs(`/...`) ``
- `` docs(`/.../${expr}/...`) `` (literal prefix, flagged as dynamic)
- `"https://coder.com/docs/..."` and other quoted forms
- `](https://coder.com/docs/...)` and `](/docs/...)` markdown-link forms
The full audit (26 findings: 9 in `coder/coder/site/`, 17 in
`coder/coder.com/src/`) lives in [DOCS-253 on
Linear](https://linear.app/codercom/issue/DOCS-253) rather than being
committed to the repo. Re-run locally with:
```bash
node site/scripts/audit-docs-paths.mjs \
--redirects=/path/to/coder.com/redirects.json \
--roots=/path/to/coder/site/src,/path/to/coder.com/src
```
Default output goes to `docs/.audit/redirects-audit-YYYY-MM-DD.md`,
which is gitignored.
## Tests
`site/scripts/audit-docs-paths.test.mjs` has 59 cases covering the four
regexes, `matchRedirect` (exact, `:path*`, `:slug(.*)`, miss),
`findMatchingRedirect`, `stripQueryAndFragment`, `literalPrefix`,
`extractReferences` end-to-end with line numbers and multi-line `docs()`
calls, `buildReport` (empty input, repo grouping and sort order,
fragment annotation, unclassified section), `walk` against a real temp
filesystem (recursion, extension filter, `SKIP_DIRS` pruning,
missing/file inputs, seeded results), and `runCli` (missing-root
warning, real-but-empty root). Run with `pnpm exec vitest run
scripts/audit-docs-paths.test.mjs --project=unit` from `site/`.
## Notes
- User-visible "AI Bridge" label text is not changed here. Renaming the
product surface from "AI Bridge" to "AI Gateway" is tracked separately
by the AI Governance team in AIGOV-233.
- A vitest assertion that no literal path in `site/src/` resolves via a
redirect will land in a follow-up PR (DOCS-257), so future drift fails
fast in CI.
- A generated `DocsPath` type for the `docs()` helper is planned in
DOCS-254.
- The `/docs/templates/schedule` drift in `entities.ts` also appears in
`coderd/notifications/testdata/*.golden` test fixtures and in historical
SQL migrations under `coderd/database/migrations/`. Those are tracked
under DOCS-256 (A2: non-TS audit) and not in scope here.
## Related work
- Linear: DOCS-253 (this PR), parent DOCS-209.
- Companion redirect rule on the coder.com side: coder/coder.com#826.
- Companion fixes for the 17 coder.com findings: coder/coder.com#876
(supersedes the closedcoder/coder.com#827, which was made redundant by
coder/coder.com#832).
<details>
<summary>Implementation plan (Linear DOCS-209)</summary>
| Phase | Scope | Linear | Status |
|---|---|---|---|
| D | Versioned redirect for `/docs/@v2.33.x/ai-coder/ai-bridge` in
`coder.com/redirects.json` | DOCS-255 | coder/coder.com#826 open |
| A1 | TS/TSX audit + autofix in `coder/coder/site/` | DOCS-253 | This
PR |
| A1 follow-up | Same autofix in `coder/coder.com/src/` (3 remaining
findings after coder/coder.com#832) | DOCS-281 | coder/coder.com#876
open |
| A2 | Non-TS audit in `coder/coder` (Go, comments, markdown) | DOCS-256
| Backlog |
| A3 | code-server audit | DOCS-252 | Backlog |
| B | vitest assertion against `redirects.json` | DOCS-257 | Blocked by
A1 |
| C | Generated `DocsPath` type | DOCS-254 | Blocked by B |
</details>
---
Generated by Coder Agent on behalf of @nickvigilante.
---------
Co-authored-by: Coder <coder@users.noreply.github.com>
Implement the first wizard step for selecting a base infrastructure
template.
- Add `getTemplateBuilderBases` API client method and react-query
wrapper
- Render a responsive grid of `TemplateCard` components fetched from
`GET /api/v2/templatebuilder/bases`
- Map `TemplateBuilderBase` to `SelectedBaseMeta` with `hasParameters`
derived from the base variables
- Single-choice selection that dispatches `SET_BASE` to wizard state,
preserving selection on back navigation
Relates to [DEVEX-283](https://linear.app/codercom/issue/DEVEX-283).
> [!NOTE]
> This PR was authored with Coder Agents.
Removes the coder agents PR Insights page (`/agents/settings/insights`) and all of its backend support. The page had previously been hidden and was only reachable via deep link. It had previously been hidden due to the dubious value provided in the current iteration.
Add an AI budget section to the group settings page, gated by the
aibridge feature and the ai-gateway-cost-control experiment. Saves a
per-member monthly budget via the group AI budget endpoints alongside
the group patch: empty is uncapped (deletes the budget), 0 disables,
and any value >= 0 is accepted.
Closes AIGOV-294
The workspace-app and port preview tabs in the Coder Agents right panel
were previously gated behind a `devel` prerelease build check, which
can't be toggled in real deployments.
This replaces that check with a proper `agent-app-tabs` deployment
experiment, registered in `ExperimentsKnown`, so the feature can be
enabled via `CODER_EXPERIMENTS=agent-app-tabs` like any other
experiment. The frontend now reads
`experiments.includes("agent-app-tabs")` from the dashboard instead of
`getPrereleaseFlag(buildInfo) === "devel"`.
Depends on #26208
Adds workspace app and forwarded port tabs to the AgentsPage right panel, so app previews and port views can sit alongside terminals while working in the chat view.
- The new add-tab dropdown lists the agent's apps and a ports submenu: embeddable apps open as iframe tabs, command apps open as renamed terminal tabs running their command, and ports open in a new port preview panel served through the wildcard access URL.
- Tabs persist per chat and are validated against the current workspace state: tabs whose app disappears, stops being embeddable, or whose agent no longer exposes the port forwarding helper are hidden rather than deleted, and reappear if the workspace exposes them again.
- A shared `usePortsData` hook keeps the add-tab control, workspace pill, and existing `PortForwardButton` on the same port queries and refresh cadence.
- ~~App and port tabs are limited to `devel` builds for now; terminal tabs remain generally available. This was done in favour of adding a backend experiment for a frontend-only feature.~~ TODO: I'm just gonna switch to a backend experiment.
Closes CODAGT-346
There was inconsistency with what the form showed and what actually was
sent to the backend. I opted to make it so that explicitly blank values
are always sent rather than have blank values silently changing to the
default value.
Add the template builder wizard route at `/templates/new/builder` with
feature flag gating, step navigation, and wizard state management.
- Read `template_builder.disabled` from deployment config and redirect
to `/templates/new` when disabled
- Five-step wizard registry with skip logic for `base-parameters` (no
params) and `module-settings` (no configurable vars)
- Reducer managing base selection, module selection, variable values,
and template customizations with state preservation across navigation
- Two-column layout with step content area, `SelectionSummary` sidebar,
and back/forward navigation
- Page/PageView separation using `Margins`, `PageHeader`, and standard
layout components
Part 1 of the Template Builder stack. Relates to
[DEVEX-282](https://linear.app/codercom/issue/DEVEX-282).
> [!NOTE]
> This PR was authored with Coder Agents.
Makes the chat context foundation from #26385 live. That PR added the
storage columns, writer queries, and a dormant
`agentapi.ContextDirtyMarker` trigger with no production callers; this
PR wires them together end to end.
When a workspace agent pushes a context snapshot, bound chats now
hydrate to that snapshot's hash, and a later push with a different hash
flips already-pinned chats to dirty (emitting a `context_dirty` watch
event after the transaction commits). Chat creation pins the agent's
latest snapshot when one already exists. The experimental chat API
exposes this as `Chat.Context` (`*ChatContext` with `dirty`,
`dirty_since`, `error`), and a new `PUT
/api/experimental/chats/{chat}/context` endpoint re-pins the agent's
latest snapshot and clears the dirty marker.
`context_dirty_resources` stays NULL (the resource-level diff is
deferred to the UI phase) and the live per-turn context pull is
unchanged.
The end-to-end test provisions a workspace agent via the echo
provisioner, connects it over the Agent API v2.10, and exercises the
full path: an initial push hydrates a bound chat (clean), a second push
with a different hash marks it dirty, the API reports the dirty state,
and the refresh endpoint clears it.
<details>
<summary>Decision log</summary>
- **API shape — sub-struct.** Dirty state is surfaced as
`codersdk.Chat.Context *ChatContext { Dirty bool; DirtySince *time.Time;
Error string }` rather than flat fields, matching the RFC's named
`ChatContext` type and leaving room for future fields (resource diff,
sources). `db2sdk.Chat` populates it when the chat is context-tracked
(`len(ContextAggregateHash) > 0`), dirty, or carries a snapshot error,
and leaves it nil (`omitempty`) otherwise. `Dirty` mirrors
`context_dirty_since` being set.
- **Marker wiring.** The chat daemon is injected directly as the
`agentapi.ContextDirtyMarker`. It is unconditionally constructed (only
its background worker is gated), so the marker is always non-nil and the
wiring matches every other `api.chatDaemon` call site. `agentapi` still
treats a nil marker as "chatd absent", so `PushContextState` stays a
pure write path for any future caller that does not wire chatd in.
- **Refresh is atomic.** `RefreshChatContext` reads the agent's latest
snapshot and re-pins the chat in one repeatable-read transaction, so a
concurrent push cannot land between the read and the write and leave the
chat pinned to a stale hash with the dirty marker cleared.
- **Hydrate + dirty run inside the push transaction.** The fan-out
shares the push's transaction so a concurrent refresh cannot interleave
with the version gate; `context_dirty` watch events publish only after
commit. The pinned hash on dirtied chats is intentionally left unchanged
— the refresh endpoint re-pins it.
- **Dirtied chats keep their pinned hash.** Drift is advisory: a dirty
chat stays usable, and refreshing is the only path that advances the
pinned hash.
- **Test binds `chats.agent_id` directly.** In production the binding is
set lazily during a chat turn (`chatd.persistBuildAgentBinding`); the
test sets it via `dbgen` so it exercises the context flow rather than
turn resolution.
Plan: `coderd/x/chatd` context integration + E2E (sub-struct API,
create-time + push-time hydration, refresh endpoint;
`context_dirty_resources` and the per-turn pull untouched).
</details>
🤖 Generated by Coder Agents on behalf of @kylecarbs
The Bedrock create form required both `access_key` and
`access_key_secret`, blocking deployments that authenticate against AWS
through an IAM role, instance profile, or `AWS_PROFILE`. The backend
already accepts a Bedrock provider that is configured by region alone
(see `codersdk.AIProviderBedrockSettings.IsConfigured`), so the UI was
the only thing standing between the operator and a working IAM-role
provider.
The Yup schema now treats both fields as optional while keeping the
cross-validation that forces the pair to travel together. A descriptive
note under the inputs tells the operator that leaving both blank falls
back to ambient AWS credentials, and links to the [Amazon Bedrock
section](https://coder.com/docs/ai-coder/ai-gateway/providers#amazon-bedrock)
of the AI Gateway providers docs for the credential chain and IAM
permissions. The mapping into `CreateAIProviderRequest` already omits
empty credential fields, so the wire payload sends only `region`,
`model`, and `small_fast_model`, which is enough for `IsConfigured()` on
the backend.
The model and small-fast model fields are now pre-filled with the modern
Sonnet 4.5 and Haiku 4.5 IDs from `codersdk.aiGatewayBedrockModel` /
`codersdk.aiGatewayBedrockSmallFastModel`, matching the legacy
environment seed path. A second docs note under those fields points at
the [AWS Bedrock model
cards](https://docs.aws.amazon.com/bedrock/latest/userguide/model-cards.html)
page so operators can find the canonical model IDs without leaving the
form. This also addresses the AIGOV-411 ask.
Adds `providerFormValuesToCreate` coverage for the no-credential and
whitespace-only paths, plus three new `ProviderForm` stories: one that
verifies the model fields pre-fill, one that submits without static
credentials, and one that verifies a half-typed credential pair stays
blocked.
Closes
[CODAGT-626](https://linear.app/codercom/issue/CODAGT-626/bedrock-ui-requires-access-keys-for-iam-role-setup).
Partial coverage for
[AIGOV-411](https://linear.app/codercom/issue/AIGOV-411/ai-gateway-providers-improve-bedrock-model-fields-in-ui)
(model pre-fill plus docs link; combobox, model ID pattern validation,
and docs site updates remain).
> The Slack thread also flagged a separate edit-time regression: "if I
go to edit an existing provider, all the fields I set are not on the
UI." I did not see that reproduce against the masked-credential edit
story, and the issue description focuses on the create flow, so I left
it for a separate investigation rather than bundling it into this fix.
<img width="1169" height="814" alt="image"
src="https://github.com/user-attachments/assets/08741641-da86-4acc-82ac-ef758f739f58"
/>
<sub>This PR was created by a Coder Agent on behalf of
@dannykopping.</sub>
Add a periodic purge job for `boundary_logs` rows past their retention
threshold, following the same pattern as the existing audit log and
connection log purge jobs in `dbpurge`.
Expose a `--boundary-log-retention` deployment flag (env
`CODER_BOUNDARY_LOG_RETENTION`, YAML `retention.boundary_logs`). Default
is `0` (keep indefinitely). When set to a positive duration, `purgeTick`
deletes rows where `captured_at` is older than the threshold in batches
of 10,000, matching other log purge operations. The `boundary_logs`
label is added to the `records_purged_total` Prometheus counter.
Also removes the random-UUID fallback for `OwnerID` in
`dbgen.BoundarySession`. The previous fallback generated a UUID that
could never satisfy the `boundary_sessions_owner_id_fkey` FK constraint,
masking test setup bugs. Callers must now provide a valid user ID or
accept NULL (the legitimate "user deleted" state).
Adds the `ai-gateway-cost-control` experiment flag to gate new cost
control endpoints and upcoming frontend UI behind an explicit opt-in.
Currently AI Gateway cost control supports the following endpoints:
- `GET/PUT/DELETE /api/v2/organizations/{org}/groups/{group}/ai/budget`
- `GET/PUT/DELETE /api/v2/users/{user}/ai/budget`
Note: the group-level endpoints were already released in v2.34.0 and
remain ungated. Only the user-level endpoints are gated behind this
experiment. Future cost control endpoints and UI should use this
experiment for gating until the feature is stable.
> Generated by Coder Agents on behalf of @ssncferreira
Previously, there could be a gap where the web socket has been connected
and gets the initial message but the build parameters request was still
in flight. This caused two issues:
1. Because we only send initial parameters as a response to a message,
when the message comes first and the build parameters have not resolved
yet, we end up not sending the initial parameters, meaning the form
could be stale until the next edit the user makes.
2. And if the user does make an edit, once we get that response back we
would then send the initial parameters, essentially reverting back to
the initial state since the initial params do not include the user's
edits. So the user would need a second edit to finally sync up.
To resolve both issues, we ignore the web socket's initial message until
we get the build parameters, at which point we decide whether we can use
that initial message (when there are no build params) or if we need to
continue ignoring it and send the initial parameters to get the correct
state then finally render the form.
Adds `POST /api/v2/templatebuilder/compose/template`, a synchronous
endpoint that composes a template from a base and modules, validates it
via a provisioner import job, and creates the template in a single
request.
The handler composes terraform files, bundles them as a tar, inserts the
file with hash-based dedup, creates a template version with an import
job, waits up to 2 minutes for the job to complete, classifies errors
for known failure modes (network-unreachable registry, DNS failures),
then creates the template on success. Canceled and failed jobs return
appropriate error responses.
Also adds `hclwrite.Format` to composed terraform output for canonical
HCL formatting.
Closes https://linear.app/codercom/issue/DEVEX-279
<details>
<summary>Implementation notes</summary>
- SDK types and client method in `codersdk/templatebuilder.go` with
validation tags matching the standard template creation path
(`template_display_name`, `lt=128`)
- `ClassifyProvisionerError` in `coderd/templatebuilder/errors.go`
detects DNS, connection refused, i/o timeout, and TLS handshake failures
and returns actionable messages
- `waitForProvisionerJob` polls with a ramp-up interval schedule (100ms,
200ms, 500ms, then 1s steady) and accepts an `onUpdate` callback for
future SSE streaming
- Audit logging for both template and template version creation
- TOCTOU name uniqueness: early check for fast feedback, DB unique
constraint catch for the race window (returns 409, not 500)
- Swagger annotations for all error responses (400, 404, 409, 504)
</details>
> 🤖 Generated by Coder Agents
> Vibe-coded using Coder Agents, author with limited frontend knowledge,
manually tested.
Adds an `AI Gateway Keys` page under `Admin settings > AI > AI Gateway
Keys` for key management of keys used by standalone AI Gateway replicas
to authenticate into `coderd`.
The page is shown to users with `viewAIGatewayKeys` permission and a
Premium license with AI Gateway enabled.
Adds Storybook coverage.
---------
Co-authored-by: Jake Howell <jacob@coder.com>
> [!NOTE]
> This PR was authored by Coder Agents on behalf of @jeremyruppel.
Part 3 of DEVEX-277 (POST /api/v2/templatebuilder/compose).
Adds SDK types and client method for the compose endpoint:
- `TemplateBuilderComposeRequest` with `BaseTemplateID` and `Modules` (list of `{ID, Variables}`). Registry URL is omitted from the request; it comes from server-side deployment config.
- `TemplateBuilderCompose(ctx, req)` client method that POSTs the request and returns raw `application/x-tar` bytes (matching the `Download` pattern in `codersdk/files.go`).
- Generated TypeScript types updated.
Implement `GET /api/v2/templatebuilder/modules`, which returns the
filtered list of modules available for a given base template. Reads from
the bundled catalog via `LoadModules()` and applies OS-compatibility
filtering based on the `base` query param.
Computed variables (e.g. `agent_id`) are excluded from the API response
at the `ToSDK()` conversion boundary since they are wired automatically
by the builder. The `Computed` field is removed from the SDK type. Adds
`CompatibleWithOS()` to `ModuleManifest` for OS filtering.
Returns 400 for unknown base IDs and 404 when the template builder is
disabled.
Depends on #26116
> [!NOTE]
> This PR was authored by Coder Agents on behalf of @jeremyruppel.
Implement `GET /api/v2/templatebuilder/bases`, which returns the list of
base templates available in the template builder. Reads from the bundled
catalog by cross-referencing `templatebuilder.BaseTemplateIDs()` with
`examples.List()`, enriching each entry with the OS from the `exampleID
-> OS` map.
The endpoint is gated behind the template builder feature flag (returns
404 when disabled) and requires `policy.ActionRead` on
`rbac.ResourceTemplate`.
Depends on #26115
> [!NOTE]
> This PR was authored by Coder Agents on behalf of @jeremyruppel.
## Summary
Fixes the WCAG image-alt failures reported on
https://dev.coder.com/workspaces. The audit flagged ~52 `<img>` elements
without an `alt` attribute, all matching the inner `<img>` rendered by
Radix `AvatarPrimitive.Image` inside our `Avatar` component (selectors
like `.size-full.object-contain`, `.size-[--avatar-lg].rounded-[6px]`,
`.size-[--avatar-sm]`). Two `ExternalImage` callsites on the same page
were also missing `alt`.
## Changes
- `Avatar`: add optional `alt?: string` and forward it to
`AvatarPrimitive.Image`. Default is `""`, which marks the avatar as
decorative and removes it from the accessibility tree. Every callsite on
the workspaces page already renders the human-readable name (owner,
template, organization, user) as adjacent text, so decorative-by-default
is the WCAG-correct behavior. Callers that need a meaningful alt can
override.
- `AvatarData`: thread an optional `alt` through to the internal default
`Avatar`.
- `WorkspacesTable` `IconAppLink` `ExternalImage`: pass `alt=""`. The
wrapping `BaseIconLink` already exposes the app name through an
`sr-only` span on the link.
- `BatchDeleteConfirmation` resource icons `ExternalImage`: pass
`alt=""`. The resource-type label sits next to each icon.
- `WorkspacesPageView.stories.tsx` `AllStates`: add a play function that
scans the rendered canvas and asserts every `<img>` has an `alt`
attribute, to prevent regressions.
## Validation
- `pnpm check`, `pnpm lint`, `pnpm format` clean.
- `pnpm test -- src/pages/WorkspacesPage/WorkspacesPage.test.tsx` passes
(13/13).
- Pre-commit (`make pre-commit`) passes locally.
<details>
<summary>Implementation plan</summary>
### Root cause
The `Avatar` component (`site/src/components/Avatar/Avatar.tsx`)
rendered `AvatarPrimitive.Image` without an `alt` attribute. Every
consumer (`AvatarData`, `TopbarAvatar`, workspace table rows, filter
menus, empty state, batch dialogs, "New workspace" dropdown) inherited
the missing-alt bug, which is why a single page produced ~52 violations.
### Fix
1. Make `Avatar` accept an `alt` prop, default `""`, and forward it to
the underlying `<img>`. Drop-in compatible with every existing call.
2. Mirror the prop on `AvatarData` so callers can label the implicit
avatar without composing their own.
3. Explicitly mark the workspaces-page `ExternalImage` callsites as
decorative because each is paired with adjacent text.
4. Lock the behavior with a Storybook play function so a future
regression on the workspaces page fails CI.
### Why `alt=""` by default
All workspaces-page avatars are rendered next to the corresponding name.
Per WCAG, repeating that name in the image's alt text would only add
noise for screen-reader users. Empty alt removes the image from the
accessibility tree, which is the correct decorative pattern.
</details>
---
_PR opened by Coder Agents on behalf of @tracyjohnsonux._
Callers can now choose when to open and emit the initial message. This
will enable finer testing for some incoming bug fixes related to the
timing of dynamic parameter sockets and requests.
Add a callback to preserve the current behavior for existing tests and make
the transition easier. Future tests can omit the callback and emit the events
under whatever condition they need.
The only behavioral changes are:
- the web socket error test now emits a close error without first
opening to accurately simulate a failure to connect at all.
- add some missing `diagnostics` to some responses (just to be
thorough).
- change one of the IDs to match in two tests (for consistency).
Scaffolds the `coderd/templatebuilder` package for the guided template
builder ([DEVEX-272](https://linear.app/codercom/issue/DEVEX-272),
[RFC](https://www.notion.so/coderhq/RFC-Guided-Template-Creation-Workflow-342d579be59280dfbf8eea2e5006dbda)).
Adds the module catalog types and `go:embed` wiring that the template
builder endpoints will use:
- `codersdk.TemplateBuilderModule`, `TemplateBuilderModuleVariable`, and
related types matching the RFC schema
- Internal `ModuleManifest` type with `go:embed` wiring to bundle
`module.json` files from `coderd/templatebuilder/modules/`
- `LoadModules()` with defensive copy, unexported
`parseModulesFromFS(fs.FS)` for test isolation, `ToSDK()` conversion
- Real `code-server` module manifest as the first catalog entry
- Strict validation: ID uniqueness, version non-empty, variable
type/name validation, `DisallowUnknownFields`, and requiring
`module.json` in every module directory
- Tests via internal `catalog_internal_test.go` (for
`parseModulesFromFS` with `fstest.MapFS` fixtures) and external
`catalog_test.go` (for `LoadModules` and `ToSDK`), covering multi-module
parsing, all variable types, validation errors, nil-slice normalization,
and full SDK field assertions
> [!NOTE]
> Generated with [Coder Agents](https://coder.com/agents) by
@jeremyruppel
---------
Co-authored-by: McKayla はな <mckayla@hey.com>
## Summary
Removes the deprecated `/api/v2/aibridge/interceptions` endpoint and the
Request Logs frontend page, both replaced by the session-based view.
Closes https://linear.app/codercom/issue/AIGOV-266
Closes https://linear.app/codercom/issue/AIGOV-324
## Changes
### Backend
- Remove `GET /api/v2/aibridge/interceptions` HTTP handler and route
- Remove SDK types and client method (`AIBridgeInterception`,
`AIBridgeTokenUsage`, `AIBridgeUserPrompt`, `AIBridgeToolUsage`,
`AIBridgeListInterceptionsResponse`, `AIBridgeListInterceptionsFilter`)
- Remove SQL queries `CountAIBridgeInterceptions` and
`ListAIBridgeInterceptions`
- Remove `searchquery.AIBridgeInterceptions` parser
- Remove dbauthz wrappers, in-memory implementations, metrics, and mocks
for the interceptions list queries
- Remove the `coder aibridge interceptions list` CLI command and golden
files
- Regenerate API docs, swagger, mocks, and metrics
The `/models`, `/clients`, and `/sessions` endpoints stay; the sessions
list page still consumes all three.
### Frontend
- Delete the entire `RequestLogsPage/` directory (page, view, row,
filter, stories, tests)
- Remove the `/aibridge/request-logs` route and its lazy import
- Remove the `getAIBridgeInterceptions` API method,
`paginatedInterceptions` query, and mock interception entities
- `git mv` the shared filter and icon components used by the sessions
pages:
- `RequestLogsPage/RequestLogsFilter/{Client,Model,Provider}Filter.tsx`
→ `AIBridgePage/filters/`
- `RequestLogsPage/icons/AIBridge{Client,Model,Provider}Icon.tsx` →
`AIBridgePage/icons/`
- Drop the `getProviderIconName` hack and the duplicate `anthropic-neue`
icon case now that the FIXME no longer applies
## Commits
1. `refactor: remove interceptions API and request logs view` — the bulk
removal, with explicit renames for the shared filter/icon files.
2. `refactor(site/src/pages/AIBridgePage): drop getProviderIconName
hack` — cleanup of the FIXME that depended on RequestLogsPage existing.
> [!NOTE]
> Generated by Coder Agents on behalf of @dannykopping
OpenAI-compatible provider endpoints need to include the upstream
OpenAI-compatible prefix, typically `/v1`, because Coder appends request
suffixes such as `/chat/completions`, `/responses`, and `/models`. The
generic OpenAI-compatible provider form did not show an example
endpoint, so it was easy to save a host-only URL that looked valid but
would fail when used.
Add `https://provider.example.com/v1` as the Endpoint placeholder for
the OpenAI-compatible provider, matching the documented example URL
shape.
Extracts the workspace app iframe, wildcard warning, and workspace-app
helper functions out of TaskPage into shared `site/src/modules/apps`
modules. Existing agent and app lookups in the task chat helpers,
download-logs dialog, and workspaces table now route through the shared
`workspaceApps` helpers instead of duplicating resource-flattening
logic. The extracted frame preserves the existing preview-only toolbar
behavior, and its open-in-new-tab link gains `rel="noreferrer"` to
harden against tabnabbing.
Relates to CODAGT-346
fixes DEVEX-375
Replaces `getByRole` with async `findByRole`, which returns a promise /
rejects if no matching element is found after a default timeout of
1000ms
Co-written with Coder Agents. Relevant chat responses:
[I couldn't repro locally, so I inquired if there was a commit/PR that
fixed the flake within the past 3 weeks]
>No, this flake has not been fixed. There have been zero commits to
`CreateTokenPage`, `CopyButton`, `CodeExample`, or `useClipboard.ts`
since the failing CI run (13bf0e11f1, May 20).
>`getByRole` is synchronous, so it doesn't wait for the success modal
(containing the "Copy code" button) to render after the `createToken()`
mutation resolves. When the mutation is slow, the DOM still shows the
form (Cancel / Create token), and the query fails.
## Summary
Fixes [CODAGT-415](https://linear.app/codercom/issue/CODAGT-415).
Right-clicking selected text in the web terminal on Windows (and Linux)
showed
the browser's image actions ("Copy image", "Save image as") instead of
copy/paste. The terminal uses xterm.js with the canvas/WebGL renderer,
so the
underlying element is a `<canvas>`, which Chromium and Firefox treat as
an
image. xterm.js tries to retarget the menu by moving a hidden textarea
under
the cursor, but on Windows and Linux the browser's own non-native
context menu
locks onto the canvas before that workaround lands.
## Change
Wrap the terminal in the shared Radix `ContextMenu` so right-click shows
a
custom **Copy** / **Paste** menu instead of the browser default:
- **Copy** reuses the existing copy-on-select clipboard path
(`getSelection()`
+ `copyToClipboard`). It is disabled when there is no selection.
- **Paste** reads the clipboard and uses xterm's `paste()`, which
respects
bracketed-paste mode.
- The menu is gated to non-macOS (`disabled={isMac()}` on the trigger).
macOS
renders native context menus that already expose working copy/paste
across
Chrome, Firefox, and Safari, so its default is left untouched.
## Platform scope
| Platform | Behavior |
| --- | --- |
| Windows (Chromium / Firefox) | Custom Copy/Paste menu (fixes the bug)
|
| Linux (Chromium / Firefox) | Custom Copy/Paste menu |
| macOS (Chrome / Firefox / Safari) | Native menu preserved (already
works) |
## Testing
- `TerminalPage.test.tsx`: on non-macOS, right-click suppresses the
native menu
and shows the Copy/Paste menu; on macOS the native menu is preserved.
- `TerminalPage.stories.tsx`: new `RightClickMenu` story opens the menu
via a
`play` function for real-browser and visual coverage.
- `tsc`, `biome`, and `make pre-commit` (gen/fmt/lint/build) pass
locally.
<details>
<summary>Decision log</summary>
- The issue was originally reported as Windows-only. Hands-on testing
confirmed
macOS is not affected: Chrome, Firefox, and Safari on macOS all show a
working
copy/paste menu. The difference is the menu implementation: macOS uses
native
OS context menus (which pick up xterm's repositioned textarea), while
Chromium/Firefox on Windows and Linux draw their own menu that targets
the
`<canvas>` directly.
- Root cause is the canvas/WebGL renderer plus the unreliability of
xterm's
textarea-repositioning workaround on non-native menus, not the operating
system itself.
- A custom menu (rather than just `preventDefault`) was chosen so users
keep an
explicit copy/paste affordance on the affected platforms. A bare
`preventDefault` removes the menu entirely.
- Scope is gated to non-macOS to avoid regressing the working native
menu on
macOS. Rejected alternatives: suppressing/replacing on all platforms
(regresses
macOS), and Windows-only (misses Linux, which shares the same non-native
menu).
</details>
---
Generated by Coder Agents on behalf of @jaaydenh.
#26124 introduced a regression on `main`: `AgentRow ›
NonStartupScriptError` fails because the refactor replaced
`hasAgentIssues` (which covered both connectivity and script issues)
with `hasConnectivityIssues` only in the `showLogs` condition. For a
`ready` agent with a failed script but no connectivity issues,
`showLogs` becomes false, logs never load, and the failed script tab
never renders. The PR's own behavior table said logs should still
auto-open in this case — it was an implementation oversight, not an
intentional change.
Fix by including `hasScriptIssues` in the `showLogs` condition alongside
`hasConnectivityIssues`, restoring the auto-expand behavior from #25442
without touching connectivity badge styling.
> **Note:** This reached `main` undetected because `test-js` in CI only
runs `--project=unit`; the Storybook interaction tests
(`--project=storybook`) that caught this are not a required check. When
Chromatic is switched off, `--project=storybook` should be added to the
required gate.
Refs #26124, #25442