Commit Graph
15971 Commits
Author SHA1 Message Date
Samuel VolinandMatt Vollmer f44a3b59d7 feat: Premium Page CTA updates and form handling (#28226)
* Premium page has form for signing up for a trial
* Input validation and normalization for trial form fields
* api endpoint definition to make this processes seamless 
* license page updates to access `?success=true` queryparam and confetti

<img width="1512" height="833" alt="Screenshot 2026-08-17 at 10 08
41 PM"
src="https://github.com/user-attachments/assets/1766e724-c63a-4da9-b2bd-a98e6b66cc9b"
/>

<img width="1510" height="874" alt="Aug-17-2026 22-12-38"
src="https://github.com/user-attachments/assets/3a2a0f35-1176-4290-aa0d-b3f7d66c15d6"
/>


```mermaid
sequenceDiagram
    autonumber
    actor Owner
    participant UI as UI<br/>TrialRequestForm + PremiumPage
    participant API as API<br/>api.ts + coderd middleware
    participant Coderd as Coderd<br/>postTrialLicense + trialer
    participant Licensor as Licensor<br/>v2-licensor + Postgres + pubsub + cache

    Note over UI: mutation.status = "idle"

    Owner->>UI: fills 10 fields, checks acknowledgement
    UI->>UI: Yup validate, strip "acknowledged", submit
    UI->>API: POST /api/v2/licenses/trial<br/>cookie + X-CSRF-TOKEN
    Note over UI: mutation.status = "pending"

    API->>API: verify CSRF, apiRateLimiter,<br/>apiKeyMiddleware sets actor
    API->>Coderd: dispatch

    Coderd->>Coderd: audit.InitRequest, authorize license:create,<br/>httpapi.Read validates, HasLicense is false
    Coderd->>Licensor: POST trial request<br/>deployment_id unspoofable, 10s deadline
    Licensor-->>Coderd: 200, raw signed JWT

    Coderd->>Coderd: ParseClaimsIgnoreNbf, uuid.Parse
    Coderd->>Licensor: InsertLicense, updateEntitlements,<br/>publish PubsubEventLicense, commit audit log
    Licensor-->>Coderd: database.License
    Coderd-->>API: 201 codersdk.License
    API-->>UI: 201 codersdk.License

    Note over UI: mutation.status = "success"
    UI->>Licensor: invalidate entitlements + licenses keys
    UI->>Owner: navigate /deployment/premium?success=true
    Note over Owner: confetti fires on the success param
```

---------

Co-authored-by: Matt Vollmer <matthewjvollmer@outlook.com>
2026-08-21 09:54:54 -04:00
Michael Suchacz 58c1248cf3 fix(site/src): repair failing storybook interaction tests and pixel captures (#28417)
The Storybook suites fail on main in two independent ways: three
interaction tests in the locally-run Vitest suite (`pnpm vitest run
--project=storybook`, which does not run in CI), and three story
captures in CI's pixel snapshot job, whose failures do not fail the
GitHub check (the job PATCHes `status=failed` to pixel.coder.com and
still exits 0).

Vitest interaction failures:

* `ModelForm > Cost Estimate Shows Loading While Debounce Pending` has
been broken since it landed in #28298. In add mode the model identifier
field is the Known Model autocomplete, which only commits typed text to
`form.values.model` on option selection or dropdown close, so typing
free text left the form's model empty and the pricing section never
entered its loading state. The story now selects the catalog option.
This also fixes the same story's capture failure in the pixel job.
* The `SecretsPageView` import error stories raced the dialog enter
animation: `findByText` resolves on DOM presence, and under
parallel-suite CPU load `toBeVisible()` can run while the Radix dialog
content is still at `opacity: 0` from its `fade-in-0` animation. These
now poll with `waitFor(...toBeVisible())`, matching the file's other
import stories.

Pixel capture failures:

* `NavbarView > ForMCPCreateOnlyAdmin` / `ForMCPDeleteOnlyAdmin` inherit
the meta's tablet+desktop matrix but click the "Admin settings" button
that only exists in the desktop navbar, so their tablet captures always
fail. They now pin `pixelWithDesktop` like every other admin story in
the file.
* `AgentsPageLayout > OrganizationScopedMCPServers` fails intermittently
because `CompactOrgSelector` option names are timing-dependent: when an
org icon fails to load (as in the pixel static server), the avatar
fallback initials render and join the option's accessible name (`"MY My
Organization 2"`), breaking exact-name queries and polluting
screen-reader output. The decorative avatar is now `aria-hidden`, which
was verified red-green by forcing unloadable icons locally.

> [!NOTE]
> Shux (AI agent) authored this PR on behalf of @ibetitsmike.

<!-- shux-attribution: model=claude-opus-4-6 thinking=high -->
2026-08-21 08:19:17 +02:00
Michael Suchacz 05fcd06979 fix(site): auto-select MCP server after successful OAuth (#28155) 2026-08-21 05:43:54 +02:00
Ehab Younes 903d7b756d feat: add workspace agent session count protocol (#28125)
Phase 2 of the Scalable Approach for Adding New IDE Session Types RFC,
tracked in #27410.

Adds a session_counts map to Stats on the Agent API, so an agent can report
a count per app name instead of the four fixed fields, which are now marked
deprecated. Bumps the API to v2.11.

- coderd normalizes names on ingest, caps distinct entries, and sums the
  overflow under unknown, so an arbitrary client name cannot blow up
  cardinality.
- Agents predating v2.11 set the fixed fields instead, never both, and the
  ingest path falls back to them.

Nothing sends session_counts yet. The agent starts populating it in #28338.
2026-08-21 03:14:55 +03:00
TJ fd09112e28 fix(site): right-align DateTimeRangePicker dropdown to match other dropdowns (#28428)
Follow-up to #28392. The `DateTimeRangePicker` popover opened with
`align="start"` (left-aligned to the trigger), while the other dropdowns
on the page use `align="end"`. This changes the `PopoverContent`
alignment to `align="end"` for consistency.

One-line change; no behavior or layout changes inside the dropdown
itself.

---

Generated by Coder Agents on behalf of @tracyjohnsonux.
2026-08-20 23:54:53 +00:00
Ehab Younes c767cc5198 refactor: normalize workspace agent session metadata (#28124)
Phase 1 of the Scalable Approach for Adding New IDE Session Types RFC,
tracked in #27410.

Gives agent session accounting one canonical vocabulary for app names, so
later work can report a count per app without the agent and the server
disagreeing about what an app is called.

- Adds codersdk/appname.go: NormalizeAppName for storage and lookup, and
  AppFamilyName for the places that need a bounded value, such as a
  Prometheus label or the Connection_Type enum.
- agentssh.Server keeps session counters in a map keyed by normalized name
  instead of three fixed atomics, dropping entries at zero.
- Drops MagicSessionType. Sessions carry plain app names, and unrecognized
  ones flow through as themselves rather than collapsing to unknown.

Wire behavior is unchanged: the agent still reports the four session_count_*
fields over Agent API 2.10.
2026-08-21 02:22:18 +03:00
TJ 3ffb266af6 fix(site): align settings header style and paywall spacing (#28418)
Settings pages rendered the premium upsell banner at different vertical
offsets and the IdP sync pages used a lighter heading style than the
rest of the settings UI.

Three causes:

1. The IdP Sync (`/organizations/:org/idp-sync`) and Organization IdP
Sync (`/deployment/idp-org-sync`) pages hand-rolled their headers with a
bare `<h1 className="text-3xl m-0">` (missing `font-semibold`) and a
`flex flex-col gap-12` wrapper (48px above the banner).
2. Workspace Proxies and Appearance wrapped `SettingsHeader` in flex
columns with their own `gap` (`gap-4`, `gap-8`), which stacked on top of
the header's built-in `pb-6`, pushing the banner to 40px and 56px
offsets respectively while Groups sits at 24px.
3. `SettingsHeaderDescription` used `leading-normal` (21px lines) while
the inline `Link` component ("View docs") renders a 24px-tall
`inline-flex leading-6` box, inflating the line box on pages whose
description contains a docs link and shifting content below by ~3px.

Changes:

- Replace the custom header markup on both IdP sync pages with the
shared `SettingsHeader`, `SettingsHeaderTitle`, and
`SettingsHeaderDescription` components; the Export Policy button moves
into the `actions` slot.
- Hoist `SettingsHeader` out of the gap wrappers on Workspace Proxies
and Appearance so the header's `pb-6` is the single source of
header-to-content spacing; the gap now applies only within the entitled
content.
- Set `SettingsHeaderDescription` to `leading-6` so description line
height matches the inline `Link`, whether or not a docs link is present.

All affected pages now start content, including the premium paywall,
24px below the header description.

_Created by Coder Agents on behalf of @tracyjohnsonux._
2026-08-20 23:08:51 +00:00
TJ 1c139402d0 feat(site/src): use DateTimeRangePicker on the AI sessions page (#28426)
Follow-up to #28392. Wires the new `DateTimeRangePicker` into the AI
Gateway sessions page, replacing the text-expression
`DateTimeRangeFilter`, and removes the redundant "Filters" preset
button.

- The picker trigger sits in the filter row alongside the search field
and the User/Provider/Client/Model dropdowns.
- The preset queries (All sessions, My sessions) behind the Filters
button duplicate the search field and the user dropdown, so the button
is dropped by passing no presets. Other pages using the shared `Filter`
component are unaffected.
- No API or URL contract changes: the filter query still stores resolved
`started_after`/`started_before` timestamps. The picker's preset id is
display-only component state, shown only while the URL range still
matches what the preset resolved to, so shared links keep rendering as
custom ranges.
- Clearing the search resets the picker to the default window, labeled
"Last 24 hours".
- The picker trigger chevron now uses the shared animated
`ChevronDownIcon`, rotating on open like the other dropdown triggers.
- The old `DateTimeRangeFilter` component is left in place (still
referenced by its stories); removing it can be a separate cleanup.

Storybook: the sessions filter story now asserts the Filters button is
gone and exercises opening the picker; existing stories updated to the
new value shape. `biome`, `tsc`, and the Storybook interaction tests for
the page pass.

---

Generated by Coder Agents on behalf of @tracyjohnsonux.
2026-08-20 23:08:00 +00:00
dylanhuff-at-coder 952c0ce5e6 fix(enterprise/coderd): identify audit rows by content instead of order in diff tests (#27904)
`TestUserSecretAuditDiffRedaction` flakes when create and update audit
rows receive equal timestamps and `GetAuditLogsOffset` returns the tied
rows in either order.

Identify audit rows by action, or by diff content when both actions are
writes, instead of relying on slice position. Fixes coder/internal#1625.

Reviewed and updated by Coder Agents on behalf of @dylanhuff-at-coder.
2026-08-20 15:43:58 -07:00
TJ af05849c9c feat(site/src/components): add date-time range picker with quick picks (#28392)
Adds a frontend-only `DateTimeRangePicker` component: a date range
picker with quick picks (Last 15 min, Last hour, Today, This week) and
per-boundary time selection. The dropdown opens as just the quick-pick
list; the calendar and From/To time fields only appear after selecting
"Custom range". Quick picks commit immediately, custom ranges commit via
Apply.

No backend changes. The value is always resolved dates:

```ts
interface DateTimeRangeValue {
  start: Date;
  end: Date;
  preset?: string; // display metadata only, never sent to the backend
}
```

Quick picks resolve to concrete `start`/`end` at selection time;
`preset` exists solely so the trigger can keep rendering the preset
label. Consumers send `start`/`end` to the API in UTC via
`Date.toISOString()`.

Composed from existing primitives (`Calendar`, `Popover`, `Select`,
`Input`, `Button`); the `Calendar` component is unchanged. Includes 9
Storybook stories with play-function coverage: quick-picks-only open
state, immediate preset commit, custom panel expansion, apply flow,
invalid time validation, end-before-start validation, and cancel
discarding the draft.

<details>
<summary>Design notes and follow-ups</summary>

## Mockup deviations

- Added an Apply/Cancel footer to the custom panel. The design mockup
does not show one, but a draft range needs an explicit commit point.
- Trigger uses the shared `Button` outline variant to match the design
system.

## Settled decisions

- The API receives resolved timestamps, not preset tokens.
- All values sent to the backend are UTC; the component works in
browser-local time and conversion happens at the API boundary.

## Follow-ups (separate PRs)

- Wire the component into the pages that need it, with query-param
persistence where ranges should be shareable.
- The existing `DateTimeRangeFilter` (text-expression based, used by the
AIBridge sessions filter) is a candidate for migration to this design.
- Possible polish per design feedback: masked/segmented time input,
optional seconds, 24-hour locale support.

</details>

---

Generated by Coder Agents on behalf of @tracyjohnsonux.
2026-08-20 22:32:41 +00:00
david-fraley e7eea0f06f feat: update premium paywall copy and link each page to its feature docs (#28339) 2026-08-20 17:06:21 -05:00
TJ cfc7b825c2 fix(site/src/components/MultiSelectCombobox): match Input height when empty (#28409)
The empty `MultiSelectCombobox` rendered ~46px tall next to the fixed
40px `Input`, breaking form rows that place them side by side, most
visibly the IdP sync mapping fields (`/organizations/{org}/idp-sync`,
IdP org sync, template/parameter forms).

## Root cause

#27821 removed MUI's `CssBaseline` and enabled Tailwind preflight
(`corePlugins.preflight` was previously `false`). Preflight sets `input
{ line-height: inherit }`, so the combobox's inner cmdk input started
inheriting the wrapper's `text-sm` 24px line instead of the browser
default. With its `py-2.5` padding the empty combobox computed to 24 +
20 + 2 = 46px, while `Input` stays pinned at `h-10`.

This is a sibling of the `ModelSelector` regressions fixed in #28399,
but in the shared combobox primitive, so that fix could not reach these
pages.

## Fix

- Pin the inner cmdk input to `leading-5` and reduce the empty-state
padding to `py-2` (20 + 16 = 36px row).
- Make the wrapper a `flex flex-col justify-center` so the row centers
within `min-h-10`, giving exactly 40px when empty while the selected
state still grows with its badges.

## Validation

- Red-green verified with a temporary geometry probe story: combobox
wrapper 46px before, 40px after (Input 40px in both). The probe was not
committed (FE10) and no new story ships with this PR per review.
- `pnpm test:storybook` for `MultiSelectCombobox`, `IdpSyncPageView`,
`IdpOrgSyncPageView`, and `DynamicParameter`: all pass.
- `biome check` and `tsc -p .` clean.

> 🤖 This PR was written by Coder Agents on behalf of @tracyjohnsonux.
2026-08-20 21:55:59 +00:00
Michael Suchacz 9168dbf4d3 refactor(coderd/x/chatd): unify LLM model-call construction behind a resolver (#28145)
Centralizes every LLM client build in chatd behind one resolver and
makes all model calls behave the same, instead of preserving per-flow
quirks.

## Why

`chatd` built LLM clients in five places (standard turn, compaction
override, quickgen, title override, advisor), each repeating config
parsing, route resolution, client construction, and debug wrapping with
slightly different behavior. An earlier iteration of this PR centralized
construction but preserved every historical quirk behind
purpose-specific spec constructors and policy flags. Review concluded
the quirks themselves were the problem.

## What changed

- `Server.resolveModelCall` (`modelcall.go`) is the single pipeline from
a `modelCallSpec` to a ready client plus call metadata. Callers build
spec literals inline; the only spec fields are data: model source (chat
config, explicit config row, or fixed provider/model pair for computer
use), requested reasoning effort, chatd-scoped routing, and build
options.
- Uniform behavior for every call:
- Provider options are always derived. Whole-chat summaries, turn status
labels, and chat-model compaction summaries previously sent none; they
now follow the config like everything else.
- `MaxOutputTokens` always defaults to 32k when the config leaves it
unset. The compaction summary call nulls the cap after copying the
template because it is non-streaming and the Anthropic SDK rejects
non-streaming requests whose cap implies a completion over ten minutes.
- Debug recording always wraps the client when chat debug is enabled,
labeled with the resolved provider/model. The debug-rebuild paths
(`manualTitleDebugSpec`, `quickgenDebugSpec`) are deleted; debug helpers
now only create and finalize run records, since run association flows
through the generate context, not the model.
- Clients are always constructed with the configured model string and
unmutated route, so AI Gateway validation sees the name exactly as
configured. The resolved identity feeds metadata, logs, and debug labels
only.
- `resolvedModelCall.newCall` / `newObjectCall` are the only production
constructors of `fantasy.Call` / `fantasy.ObjectCall`; object calls now
carry the config's sampling parameters.
- Net: -240 lines.

## Validation

- Full `./coderd/x/chatd/...` suite green (PostgreSQL-backed).
- Red-green toggles verified for the three key unifications (option
derivation, token default, summary cap removal).
- ARCHITECTURE.md: prose the refactor made stale is deleted; no new
resolver documentation is added.

> Mux acted on Mike's behalf for this PR.
2026-08-20 20:54:52 +00:00
Michael Suchacz 6f1d225d2e fix(site): improve chat slash menu enter handling, anchoring, and placement (#28411)
When the last token of a follow-up prompt is an absolute filesystem path
(e.g. `check /var/log/syslog`), the skill slash menu opened with "No
skills match that query." and swallowed Enter, so the prompt could not
be submitted without adding a trailing space or quoting the path.
Follow-up composers always pass slash commands, so the menu opened for
any `/`-token; the Enter handler then consumed the keypress
unconditionally while the menu was open.

Three fixes (CODAGT-956):

- Enter with no selectable match now dismisses the menu and lets the
same keypress fall through to the submit handler. While a skill source
is still loading (personal skills fetching, or workspace skills
unknown), a zero-match Enter stays consumed so a pending fetch cannot
let a would-be skill trigger submit as raw text; a no-match Tab still
falls through so keyboard users can leave the composer.
- The menu is anchored to the composer box instead of a caret-derived
rect, so it no longer follows the cursor while the query is typed.
- The menu renders directly above the chat input at exactly the
composer's width on desktop, matching its pinned-above-composer
presentation on small viewports. The caret-rect plumbing (live caret
measurement, fixed 1px anchor span, scroll/resize listeners) is removed.

Storybook interaction coverage was updated accordingly:
`FilteredEmptyEnterClosesAndSubmits` (replaces
`FilteredEmptyKeepsMenuOpen`), `TrailingPathEnterSubmits`,
`EnterWhileSkillsLoadingDoesNotSubmit`, `MenuStaysAnchoredWhileTyping`,
and `OpensAboveAnchorAtAnchorWidth`. Each new story was verified red
against the reverted fix and green with it. Remote dogfood UAT validated
the Enter/anchoring/placement scenarios and regressions in the real UI
across two rounds (round 2 covering the composer-width presentation).

Fixes
https://linear.app/codercom/issue/CODAGT-956/slash-menu-improvements

> 🤖 This pull request was created by Mux on behalf of @ibetitsmike.

<!-- mux-attribution: model=claude-opus-4-6 thinking=high -->
2026-08-20 22:35:35 +02:00
Ben PotterandMichael Suchacz bb7f2f2332 fix(site/src): show chat instead of the right panel on small viewports (#28387)
On small viewports, the agent chat page showed the wrong panel. The
right panel filled the screen and hid the chat.

before: 


https://github.com/user-attachments/assets/fa3986b8-949a-4aad-8e98-d64316ade3f5

after:


https://github.com/user-attachments/assets/bc56359d-2ffe-4f95-b47b-638261379da8

## Changes

- Below the `lg` breakpoint, the page shows one panel at a time. A right
panel left open on a wide window used to hide the chat when the window
narrowed. The page now shows the chat.
- The panel preference is not changed by a resize. When the window
widens, the panel comes back. When the user taps a tab while narrow, the
panel opens as before.

## Tests

- Unit tests for the new `useRightPanelNarrowSuppression` hook cover
mount, both breakpoint crossings, and the explicit override.
- A new mobile-viewport story asserts the chat is visible when a panel
was left open.

<details>
<summary>Decision log</summary>

- The right panel's open state persists in localStorage across all
viewport sizes. `max-lg:hidden` on the chat made that stale state hide
the chat on narrow windows.
- The narrow suppression is transient state, not a write to the
persisted preference. A temporary resize keeps the user's desktop layout
intact.
- The hook adjusts state during render (the React "adjusting state when
a prop changes" pattern) instead of an effect, per FE8.
- Verified all resize sequences live against a dev deployment: narrow
with a panel open, widen to restore, drill in while narrow, and explicit
close while narrow.

</details>

🤖 This PR was created by [Coder
Agents](https://coder.com/docs/ai-coder/agents) on behalf of @bpmct.

---------

Co-authored-by: Michael Suchacz <203725896+ibetitsmike@users.noreply.github.com>
2026-08-20 21:54:59 +02:00
Bobby Ho 6348c83142 feat: register public clients without a secret (#28046)
Third in the stack, split out of #27873. This is where dynamic client
registration first produces a public client.

- A registration requesting token_endpoint_auth_method `none` issues no
secret and persists the client_type derived in #28043. The response
omits client_secret entirely, per RFC 7591 §3.2.1.
- Discovery still advertises only the two secret-based methods. `none`
is withheld until the token endpoint honors it, so a conforming client
is not told to attempt an exchange that would be rejected.
- The app and its secret are written in one transaction. They were two
independent inserts, so a failed second one left a committed app that
could never authenticate while still holding a registration access
token. Pre-existing, but a public client's missing secret row makes the
orphaned case unspottable by inspection.
- registration_client_uri uses url.JoinPath, fixing a trailing-slash
access URL producing //oauth2/clients/{id}.
- Docs state both limits above, so the page does not describe a flow
that returns 400 until #28047 lands.

A client registered here cannot obtain a token yet; #28047 adds that.
Dynamic client registration is off by default, so none of this is
user-visible until then.

Refs
https://linear.app/codercom/issue/ENG-3029/oauth2-support-public-client
2026-08-20 12:49:47 -07:00
Cian Johnston edd8bf8b9b refactor(aibridge): extract bedrock mantle SigV4 signing to bedrocksig (#28407)
Refactors the mantle SigV4 signing middleware into a new shared
`intercept/bedrocksig` package. 

Prepares for OpenAI protocol support on the mantle provider (follow-up
PR). 

---
Generated by Coder Agents on behalf of @johnstcn.
2026-08-20 20:39:49 +01:00
Callum Styan c3cd692f86 feat(scaletest/notifications): add opt-in --reuse-users (#28289) 2026-08-20 12:24:58 -07:00
Nick Vigilante c6deadbc6c docs: fix broken .style/ link and lift details block out of list item [DOCS-661] (#28180)
## What

Two small docs fixes, split out of the offlinedocs Fumadocs migration
(#27390) so that PR stays a focused tooling change and these land on
their own review track.

- `docs/about/contributing/documentation.md`: the Vale style-guide link
pointed at `docs/.style/style-guide/`, which is excluded from the
published docs corpus, so the relative link 404s on the live site.
Repointed to an absolute github.com URL.
- `docs/ai-coder/ai-gateway/monitoring.md`: lifted the "Possible client
values" `<details>` block out of the list item so it renders as a
top-level accordion, and restored a dropped blockquote continuation
marker (`>`).

## Why

Both are genuine docs bugs on their own:

- The `.style/` link is broken on the live site today, because that
directory is intentionally excluded from the published corpus.
- The `<details>` nested inside a list item renders inconsistently;
lifting it to a sibling block is portable across renderers.

They are also prerequisites for the offlinedocs Fumadocs migration,
whose stricter build-time sync hard-fails on unmapped inter-doc links
and only converts a `<details>` to an accordion when it is a list
sibling. Landing them here first lets the migration PR drop these two
files on its next rebase.

## Validation

- `make fmt/markdown` and `make lint/markdown`: clean (via
pre-commit-light).
- Docs-only change; no CI or build config touched.

> This PR was created with AI assistance (Coder Agents).
2026-08-20 19:09:46 +00:00
Michael Suchacz 9d6ef1e701 docs: streamline agent instructions (#28243)
## Summary

Streamlines the agent instruction files, cutting per-session token
overhead by roughly 65%.

The core design change: `AGENTS.md` no longer `@`-imports WORKFLOWS.md,
package.json, the eight detailed guides, or `AGENTS.local.md` into every
session. Guidance moves from guaranteed-in-context to load-on-demand
through a routing table, betting that one-line guardrail summaries plus
links are enough to make agents pull the right doc when a task needs it.

- `AGENTS.md` (251 -> 80 lines): a routing table to the canonical docs
in `.claude/docs/`, the essential command table, and one-line guardrails
for the architectural footguns (SQLC workflow plus audit table, OAuth2
error conformance, chatd architecture-doc rule, Swagger annotations,
`InTx` discipline, test concurrency, workflow `permissions: {}`,
mandatory git hooks, commit/PR title format).
- `site/AGENTS.md` (364 -> 89 lines): keeps the FE1-FE10 contract
summary, TypeScript LSP navigation, pnpm command table, React Compiler
boundaries, accessibility rules, and the pre-PR `frontend-review`
requirement. Drops the generic React tutorial sections that restate
framework documentation.
- The persona and relationship prose ("Our relationship", foundational
rules) is deliberately removed. The behavioral gates worth keeping for
every agent survive as one-liners: get explicit permission before
excepting a rule, discuss architectural decisions before implementing,
and answer questions instead of jumping to implementation.
- Unique operational content is relocated rather than deleted:
Playwright failure-artifact paths and the CI `playwright-artifacts-`
naming convention move to `.claude/docs/AGENT_FAILURES.md`; the
`React.useId()` rule, the `key={String(booleanState)}` remount footgun,
date-as-prop Storybook determinism, and the suppression-comment rule
(updated from `eslint-disable` to `biome-ignore` to match current
tooling) move to `.claude/docs/FRONTEND_PATTERNS.md`.

Content reflects current main: the chatd rule says agents leave TODOs
and the human PR author writes the architecture doc, and MUI/Emotion are
described as removed, not deprecated.

Note for Claude Code users: `CLAUDE.md` symlinks to `AGENTS.md`, so
detailed workflow docs (hook internals, the new-feature checklist,
`AGENTS.local.md`) are no longer auto-loaded into every session; agents
must follow the links.

## Validation

- `pnpm exec markdownlint-cli2` on all four touched files: 0 errors
- `pnpm exec markdown-table-formatter --check` on all four touched
files: pass
- `make lint/emdash`: pass
- `make lint/agents`: pass
- All relative links verified to resolve

> Mux created this PR and description on behalf of Mike.
2026-08-20 21:05:08 +02:00
Michael Suchacz 7970cd6d11 fix(site/src/pages/AgentsPage/components/ChatElements/tools): stop completed process_output rows animating forever (#28414)
Since #28300, `process_output` tool rows in the Agents chat transcript
fed the result payload's `running: true` flag into the row's active
state. That flag is a frozen snapshot of whether the polled process was
still alive, so for long-lived background processes (a dev server that
never exits) every completed poll kept its shimmer label and spinner
animating forever, even after the agent went idle.

`ToolCall.Root` now receives the actual tool-call status, so the shimmer
and spinner stop when the poll completes. The `running: true` snapshot
still controls what it was meant to: label tense ("Checking npm start"
while alive vs "Checked npm start" after exit) and kill-signal badge
visibility. The existing `ProcessOutputStillRunningResult` story now
also asserts the running spinner is absent on a completed poll (verified
red-green: reintroducing the bug fails exactly that story), and the
intent story asserts the spinner is present while the tool call is
genuinely running.

> [!NOTE]
> Mux (AI agent) authored this PR on behalf of @ibetitsmike.

<!-- mux-attribution: model=claude-opus-4-6 thinking=high -->
2026-08-20 20:51:59 +02:00
Callum Styan 854991099c feat(scaletest): add create-users scaletest subcommand (#28287) 2026-08-20 11:46:44 -07:00
Yevhenii Shcherbina 211f490910 refactor: share the AI Gateway price book contract (#28310)
Follow-up to the review discussion on the price book refresh automation:
https://github.com/coder/coder/pull/28146#discussion_r3806439966

## Problem

The price seed row struct was duplicated three times, so a JSON tag
change had to be repeated in each copy:

- `scripts/aibridgepricesgen.priceRow`
- `scripts/aibridgepricesdiff.priceRow`
- `coderd/aibridge/prices.seedRow`

The `(provider, model)` key and its parsing were duplicated too.

## Change

Add `coderd/aibridge/prices/pricebook` holding the shared contract:
`Row`, `Key`, `Row.Key()`, and `Parse`. The package depends only on the
standard library, so the scripts can import it without pulling in the
database layer that `coderd/aibridge/prices` brings.

Consumer-specific behaviour stays where it is: upstream conversion and
validation in the generator, comparison and Markdown rendering in the
diff tool, DB seeding and runtime lookup in the seeder.

The batch SQL upsert still extracts JSON fields by name, so it remains a
separate contract boundary. The note about keeping it in sync now lives
on the shared `Row`.

No generated artifacts change; the serialized schema is identical.

---

<sub>Opened by Coder Agents on behalf of @evgeniy-scherbina.</sub>
2026-08-20 14:04:23 -04:00
Danielle Maywood c940284e18 fix(site): freeze motion animations during Pixel captures (#28396) 2026-08-20 17:43:32 +00:00
Michael Suchacz b14bce12bf fix(site/src/pages/AgentsPage/components): fix model selector trigger layout on form-style pages (#28399)
Selecting a model on form-style pages (deployment AI settings such as
`/ai/settings/coder-agents`, the personal agent settings override rows,
and the advisor override) rendered a broken trigger: the provider icon
pinned to the far-left edge, the model name floating in the center, and
the chevron on the right. At desktop widths the field also collapsed to
content height (~22px) next to the 40px Clear/Save buttons.

Two defects in the shared `ModelSelector` trigger:

1. #28330 added the provider icon as a third direct flex child of the
trigger button. Form call sites override the trigger with
`justify-between` plus a width wider than its content, which spread
icon, label, and chevron apart. The icon and label now form a single
flex child so they stay adjacent under any `justify-*` override; the
chevron stays a direct child because the `[&>svg]` trigger styles target
it, and the label still truncates via `min-w-0`.
2. The base classes carried composer-pill responsive variants
(`md:h-auto`, `md:w-auto`, `md:shrink-0`). tailwind-merge resolves
conflicts per modifier, so `md:h-auto` silently defeated the call sites'
`h-10` at the `md` breakpoint (and `md:w-auto` defeated `w-[22rem]` on
the advisor row). The pill's responsive sizing now lives at its call
site, so plain `h-10`/width overrides win everywhere. The composer pill
is visually unchanged.

Also fixes the provider icon shrinking next to long model names: the
icon's `span` wrapper was a plain flex item whose automatic minimum size
collapses (preflight's `img { max-width: 100% }` scales the icon down as
the wrapper shrinks). The wrapper is now a `shrink-0` flex container, so
the label truncates while the icon keeps its 12px size (verified
red-green with a temporary probe: 7.3px without the fix).

Adds `FormFieldTriggerKeepsIconAndLabelAdjacent` and
`FormFieldTriggerTruncatesLongModelName` stories reproducing the
form-style trigger for visual snapshot coverage. Both defects were
verified red-green during development with temporary geometry probes
(icon-label gap 63.5px and height 30px without the fixes; gap 4px and
height 40px with them); per FE10 the committed story carries no geometry
assertions and the pixel snapshot guards the layout.

Also recolors the advisor "Uses / turn" and "Max tokens" field labels
from `content-placeholder` to `content-secondary` on the same settings
surface.

Remote dogfood UAT ran against this branch across desktop and narrow
viewports and passed.

> 🤖 Mux acted on behalf of @ibetitsmike to create this PR.
2026-08-20 19:24:10 +02:00
TJ 93d5f82849 fix(site/src/pages/AgentsPage/components/ChatsSidebar): hide kebab while archiving spinner shows (#28393)
Since #28326 made the row actions trigger (kebab) persistently visible
on the selected chat, it overlapped the archiving spinner: both render
right-aligned in the same 28px box (the kebab is `absolute inset-0` over
the timestamp/spinner slot), so archiving the selected chat produced a
garbled spinner-plus-dots glyph. The overlap could previously only occur
transiently on hover; the persistent kebab made it guaranteed on the
active row.

Render the kebab only when the row is not showing the archiving spinner,
restoring the intent of the existing `ArchivingShowsSpinnerOnly` story
(which only covers a non-selected row, so it never caught this).

<details>
<summary>Diagnosis and validation</summary>

**Diagnosis**

- The only right-side loader in a sidebar row is the `Spinner` rendered
by `ChatTreeNode` when `isArchiving && archivingChatId === chat.id`.
- #28326 added `isActiveChat && "opacity-100"` to the kebab trigger,
which shares the same `w-7` box as the spinner. The spinner branch never
suppressed the trigger, only the timestamp span had the hover/active
`hidden` classes.
- Result: on the selected chat, `EllipsisVerticalIcon` and the 8-leaf
`Spinner` render on top of each other for the entire archive mutation.

**Fix**

- `ChatTreeNode.tsx`: gate the `DropdownMenu` on `!isArchivingThisChat`
so the spinner and trigger are mutually exclusive. Row context-menu
actions remain available and are already disabled while archiving.

**Validation**

- `pnpm test:storybook
src/pages/AgentsPage/components/ChatsSidebar/ChatsSidebar.stories.tsx`:
53 passed
- `make pre-commit`: pass (lint/ts, lint/go, gen, fmt, build)

</details>

_Generated by Coder Agents on behalf of @tracyjohnsonux._
2026-08-20 16:42:35 +00:00
TJ b605446016 feat(site/src/pages/AgentsPage): dim unselected sidebar chat titles (#28382)
## Summary

Subtle visual tweak to the agents sidebar chat list. Chat titles remain
`text-content-primary`, but titles for chats that are not currently
selected now
render at 85% opacity. The selected chat keeps full opacity (and its
existing
`font-medium` weight), making the active chat stand out more clearly.

## Changes

- `ChatTreeNode.tsx`: apply `opacity-85` to the title span when the chat
is not
  active, keeping `font-medium` for the active chat.

`opacity-85` is a valid Tailwind 3.4 utility and is already used
elsewhere in
the codebase.

---

_This PR was generated by Coder Agents on behalf of @tracyjohnsonux._
2026-08-20 09:35:42 -07:00
Susana Ferreira d989ae36cd docs: clarify who can set AI model prices and how they are keyed (#28401)
The docs named the `ai_model_price:update` permission but never said
which role holds it, and nothing anywhere explained that a price is
keyed by provider type and model, so every configured provider of that
type shares it. This names the Owner role, documents the keying and why
`openai-compat` cannot be priced, and repeats both in the CLI's
`--provider` descriptions and `update` help.

> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by
@ssncferreira
2026-08-20 17:35:30 +01:00
Jake Howell d3b3aeaabc docs: refresh workspace-management screenshots with current UI (#28356)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

Refreshes the six stale screenshots on the
[Workspaces](https://coder.com/docs/user-guides/workspace-management)
page (`docs/user-guides/workspace-management.md`) with the current Coder
UI, recreating equivalent dummy data (matching template and workspace
names) so the images stay recognizable.

Linear: DOCS-55

## Updated images

| Image | Section |
|-------|---------|
| `creating-workspace-ui.png` | Creating workspaces |
| `workspace-update.png` | Updating workspaces |
| `workspace-automatic-updates.png` | Automatic updates |
| `user-guides/workspace-bulk-actions.png` | Bulk operations |
| `schedule.png` | Starting and stopping |
| `templates/template-variables.png` | Repairing workspaces |

Prose is unchanged; this is a screenshot-only refresh.

## Notes / limitations

The originals came from production-like deployments with
AWS/Azure/Kubernetes templates, multiple users, and aged data. These
were reproduced on a local `scripts/develop.sh` (Docker) instance, so:

- Template/workspace names, states (Running/Stopped/Failed), the
"Selected 3 of 5" bulk selection,
`use_kubeconfig=True`/`namespace=default`, and the auto-update =
`Always` setting all match.
- Instance-derived values cannot match: "used by" counts show `0
developers`, build times show `Unknown`, the connection-latency pill has
no value, and the workspace name field is disabled by the template.

<details>
<summary>How these were produced (decision log)</summary>

Followed the `dogfood` skill:

1. Started a local instance with `./scripts/develop.sh`
(`v2.36.0-devel`, matching the docs version).
2. Created Docker-backed templates named to match the originals:
`data-warehouse`, `analytics`, `api-services`, `frontend-web`,
`pod-share-jupyter`, `aws-linux-ephemeral`, `azure-aks-pod`, plus
`kubernetes2` ("Develop in Kubernetes"). The JetBrains module was
dropped from the Docker template to avoid a plan-time error;
`kubernetes2` uses a Coder-only variant declaring the real `namespace`
and `use_kubeconfig` variables so it imports with `use_kubeconfig=true`.
3. Created workspaces: `admin/my-frontend` (outdated → update available,
with a schedule), `admin/dev` (auto-update = Always), and
`kirby/{data-science, aws-large, azure-space, ephem-demo (Failed), kirbs
(Stopped)}`.
4. Captured each page with a headless Playwright script, hiding dev-only
chrome (the sticky deployment banner, the navbar `::before` hazard
stripe, and the `-devel` version chip) and cropping settings pages to
the form. Images are 2x for crispness.
5. Each screenshot was visually verified against the original for
content parity.
</details>
2026-08-20 23:26:20 +07:00
Bobby HoandClaude Opus 5 dea3a2944a feat(coderd/rbac): compare scopes by permission coverage (#28167)
Split out of #28045
([PLAT-479](https://linear.app/codercom/issue/PLAT-479)) so the coverage
rules can be reviewed on their own. First of three. No behavior change:
both functions are added without production callers, and the OAuth2
authorize endpoint consumes them in the next PR.

**ScopesCover(allowed, requested)** reports whether every permission the
request grants is also granted by the allowlist. It expands both sides
and compares permissions rather than names, so coder:workspaces.access
covers workspace:read without ever naming it.

The fail-closed rules are the part worth reviewing:

- Coverage models site-level grants only. A scope carrying org or user
permissions, a negative permission, or a narrowed allow list is refused
on either side rather than compared on the part that is modeled.
Answering "covered" from the fraction that was read would report
authority that was never examined.
- An unknown name on either side is an error, not a false, since a
caller cannot tell "not covered" apart from "could not decide".
- A wildcard request is covered only by a wildcard grant. Enumerating
every workspace action that exists today does not cover workspace:*,
because the wildcard also authorizes the actions added tomorrow.

**CanonicalScopeName** maps the aliases IsExternalScope accepts, all and
application_connect, onto the api_key_scope enum spellings.
IsExternalScope answers whether a name may be requested, not how it is
spelled once persisted, so anything storing a validated name has to
canonicalize in between. Both sides of ScopesCover must already be
canonical, which its parameter names restate at every call site.

Covered by 17 coverage cases, guard cases that drive each unmodeled
shape through both sides of the comparison, and an exhaustive sweep over
the external catalog: coder:all covers all of it, and every catalog name
covers itself.

Stack: this PR, then negotiate and persist the scope, then the consent
page and invalid_scope redirect.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-20 09:16:04 -07:00
TJandCoder Agents e58a603949 fix(site): prevent duplicate menu when right-clicking chat kebab (#28391)
Right-clicking the kebab (⋮) button on a chat in the agents sidebar was
showing two menus: the kebab's own dropdown **and** the row-level
right-click context menu.

The kebab lives inside the row's \`ContextMenuTrigger\`, so the
\`contextmenu\` event on the button bubbles up and opens the row's
context menu on top of the dropdown that the button already owns.

Fix: swallow \`contextmenu\` on the kebab \`Button\` so only its
dropdown is associated with the button. Right-clicking anywhere else on
the row still opens the row context menu as before.

> Generated with Coder Agents on behalf of @tracyjohnsonux.

---------

Co-authored-by: Coder Agents <noreply@coder.com>
2026-08-20 15:56:36 +00:00
Jake Howell 05b78a0902 fix(site): scroll per-script logs selector on overflow (#28353)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

Resolves
[DEVEX-721](https://linear.app/codercom/issue/DEVEX-721/the-logs-selector-per-startup-script-needs-to-scroll-on-overflow).

Cap the overflow menu height and let it scroll, matching the existing
pattern already used by `DownloadSelectedAgentLogsButton` (`max-h-56
overflow-y-auto`). No component swap needed, the `DropdownMenu` was the
right primitive, it just lacked height/scroll constraints.

```diff
-<DropdownMenuContent align="end">
+<DropdownMenuContent align="end" className="max-h-56 overflow-y-auto">
```

<img width="504" height="325" alt="image"
src="https://github.com/user-attachments/assets/363d9bc0-e3c3-4355-8d05-abb8c650a37a"
/>

<details>
<summary>Investigation / decision log</summary>

- The "logs selector per startup script" is the overflow kebab menu in
`site/src/modules/resources/AgentRow.tsx`. When per-script log tabs
don't fit horizontally, `useKebabMenu` moves the extras into a Radix
`DropdownMenu` rendered as `DropdownMenuRadioItem`s.
- `menuContentClass` (`site/src/components/DropdownMenu/menuClasses.ts`)
is `z-50 min-w-48 overflow-hidden ...` with no max-height, so an
overlong menu is clipped instead of scrolling.
- `DownloadSelectedAgentLogsButton.tsx` already solves the identical
problem on its own menu with `className="max-h-56 overflow-y-auto"`, so
we reused that for consistency.
- Considered switching to the `Select` component (which has
`SelectScrollUpButton`/`SelectScrollDownButton` chevrons) but that's a
larger re-model of the trigger + items and reconciliation with the
`Tabs`/`useKebabMenu` wiring. Deferred in favor of the minimal fix.

</details>
2026-08-20 22:22:32 +07:00
Tyler 376217423d fix(site/src/pages): use RotateCcwIcon for update actions (#28183)
Closes
[DES-22067](https://linear.app/codercom/issue/DES-22067/update-and-restart-action-uses-a-cloud-icon).

The "Update and restart" / "Update and start" actions used `CloudIcon`,
which reads as a cloud/network concept instead of an update-and-restart
build action.

Switch them to `RotateCcwIcon`, which the UI already uses for:

- the `Restart` button on the workspace page
- the `Update` action in `WorkspaceOutdatedTooltip`,
`AgentOutdatedTooltip`, and `SubAgentOutdatedTooltip`

Call sites changed:

- `WorkspacePage/WorkspaceActions/Buttons.tsx` - `UpdateButton`
- `WorkspacesPage/WorkspacesTable.tsx` - "Update and start workspace"
and "Update and restart workspace" primary actions
- `WorkspacesPage/WorkspacesPageView.tsx` - batch "Update" menu item

The `requireActiveVersion` variant still uses `PlayIcon`, since that
path starts the workspace on the active version rather than offering a
choice. Worth a separate design pass if that should change too.

<details>
<summary>Investigation notes</summary>

Verified against `origin/main` (`af90d8e2be`): no prior PR referenced
DES-22067 and all three `CloudIcon` usages were still present. `grep`
confirms no `CloudIcon` usages remain in `site/src` after this change.

Icon conventions found in the codebase:

- `RotateCcwIcon`: restart, update (outdated tooltips), retry
(RetryButton, TasksTable, TaskPage, HealthLayout)
- `RefreshCwIcon` / `RefreshCcwIcon`: softer "reload data" flows
(terminal, secrets, prebuilds invalidate, inbox, git panel)

`data-testid="bulk-action-update"` is preserved on the batch update
item, so e2e coverage is unaffected.

</details>

---

Generated by Coder Agents on behalf of @designertyler.
2026-08-20 10:17:03 -05:00
Ethan cbb36e613d ci(.github): skip Playwright apt installation (#28292)
Playwright's `--with-deps` flag performs a live apt refresh and install
in the E2E and Storybook jobs, even though the runner images already
contain the required system libraries.

Install only Playwright's pinned Chromium build in CI so apt mirror
failures cannot block browser setup.

Closes DEVEX-792
2026-08-20 16:14:16 +01:00
Jake Howell 47ffa87fa5 fix: <BuildingTemplateLoader /> icon sizes (#27733) 2026-08-20 21:44:27 +07:00
Michael Suchacz 8eb1e43f0b feat: apply reasoning effort and surface thinking blocks for Google chat models (#28273)
## Problem

Selecting a reasoning effort on a Google (Gemini) chat model either got
dropped or broke the request outright, and even when the request
succeeded, Gemini chats never showed thinking blocks:

- The native `fantasygoogle` path had no case in `applyReasoningEffort`
at all, so effort was silently ignored. With only `include_thoughts`
configured, Gemini 3+ models default to no thinking, so chats produced
zero reasoning tokens.
- The path chat generation actually uses today routes Google providers
through aibridge as openai-compat clients and forwarded the raw effort
string as `reasoning_effort`. Google's OpenAI-compatible endpoint
validates that value against the model's supported thinking levels
instead of clamping, so out-of-range efforts fail the whole request with
HTTP 400. Verified against the live Gemini API: `gemini-3.1-pro-preview`
accepts low/medium/high and rejects none/minimal/xhigh/max. Because the
config's default effort is sent whenever the user selects nothing, a
config whose default falls outside the model's supported set failed
every no-selection message.
- Google's OpenAI-compatible endpoint never emits thought text for
`reasoning_effort` requests. Thoughts require
`extra_body.google.thinking_config` with `include_thoughts`, which is
mutually exclusive with `reasoning_effort`, and even then the thought
deltas come back inline in `content` (marked
`extra_content.google.thought`, wrapped in `<thought>` markers) rather
than in the `reasoning_content` field OpenAI-compatible clients parse.
So no reasoning parts were ever produced, persisted, or rendered for
Gemini chats (verified live in UAT).

## Fix

Reasoning effort:

- Map the global reasoning effort scale onto Google's `thinking_level`
in `applyReasoningEffort`, gated to Gemini 3+ models and clamped to each
model's supported subset (3.0 Pro launched with LOW/HIGH only, 3.1 Pro
added MEDIUM, Flash accepts all four through 3.6 while 3.7 and the
latest flash aliases drop MINIMAL, image models accept HIGH plus MINIMAL
for flash). Pre-Gemini-3 models reject `thinking_level`, so effort
degrades to a no-op for them and a config-pinned level is dropped at
call time instead of failing every generation.
- Add `thinking_level` to `ChatModelGoogleThinkingConfig` so admins can
pin a level for configs without user-selectable effort. A resolved
per-turn effort overrides the pinned level; an explicit
`thinking_budget` wins over both since the Google API rejects requests
that set `thinking_budget` and `thinking_level` together, and config
validation now rejects that combination up front. The admin panel picks
up the new field automatically through the generated model-options
schema.

Thinking blocks on the openai-compat path (the path chat generation
uses), translated in both directions at the existing Gemini transport
seam:

- chatd's OpenAI-compat request patch swaps `reasoning_effort` for an
equivalent `extra_body.google.thinking_config` with `include_thoughts`
enabled: `thinking_level` on Gemini 3+ (clamped to the model's supported
subset), `thinking_budget` on earlier models (effort `none` maps to
budget 0, which disables thinking). Non-Gemini models and non-Google
routes are untouched.
- The same transport rewrites Google's responses so thought output
surfaces as `reasoning_content`, which fantasy already converts into
reasoning parts: streaming bodies are rewritten per SSE line (preserving
streaming latency), JSON bodies in place, with the
`<thought>`/`</thought>` markers stripped at the transitions.
- aibridge's chat-completions interception preserves the `extra_body`
passthrough that openai-go's typed params silently drop, forwarding it
to Google upstreams only, so the chatd -> aibridge -> Google route
carries the thinking config end to end.

## Validation

- `go test ./coderd/x/chatd/... ./aibridge/... ./internal/googleopenai
./codersdk` pass; golangci-lint clean on touched packages.
- New unit tests cover the effort-to-level mapping for every effort
value, per-model clamping on both paths, the effort-to-thinking_config
request swap (level, budget, pinned-config precedence, non-Gemini
passthrough), SSE and JSON thought-response rewriting (fixtures mirror
live captures, including tool-call and no-thought streams), an
end-to-end fake-SSE test through the fantasy pipeline proving reasoning
deltas emerge with markers stripped, and aibridge `extra_body`
preservation (including proof the typed round trip drops it).
- Verified live against `generativelanguage.googleapis.com` on
`gemini-3-flash-preview` and `gemini-2.5-flash` through the real
chatprovider stack: reasoning deltas stream through with markers
stripped and answer text intact.
- Live UAT on a dogfood deployment (remote Coder Agents chat, 4 rounds):
effort clamping verified end to end (rounds 1-2), the missing-thoughts
limitation isolated (round 3), and the fix validated (round 4):
expandable, readable thinking blocks render for
`gemini-3.1-pro-preview`, `gemini-3-flash-preview`, and
`gemini-2.5-flash` at effort=high; no `<thought>` marker leaks;
effort=none produces no block and no 400; multi-turn history replay
works with a thinking block on each turn.
- `make gen` regenerated `typesGenerated.ts` and
`chatModelOptionsGenerated.json`; `pnpm -C site lint:types` passes.

> 🤖 Mux acted on Mike's behalf to create this pull request.

<!-- mux-attribution: model=claude-opus-4-6 thinking=high -->
2026-08-20 16:36:57 +02:00
Ben Potter 4ed03f5ee2 chore: refresh README links and streamline quickstart and support sections (#28386)
Points README readers to the right places and cuts redundancy.

## Links

- Dev Containers now links to the docs, so people land on setup steps
instead of the envbuilder repo
- Why Coder now links to the docs
(https://coder.com/docs/about#why-coder) instead of the marketing page
- Removed JetBrains Gateway Plugin (retired)
- Removed the community Terraform provisioning link (outdated)
- Added AI Gateway to the docs section, since it's not discoverable
otherwise
- Added Coding Agents, pointing to the registry modules for running
Claude Code, Codex, and OpenCode isolated in Coder workspaces
- Added the Terraform provider (coderd) for declaratively managing Coder
deployment configuration as code
- Added the Coder Agents Chat Action under community integrations
- Moved Coder Discord to the top of the community list and clarified
it's where to get support and hear about new product updates first

## Structure

- Merged the redundant Quickstart and Install sections into one install
→ try locally → production flow, with a note up top pointing to the
install guides. `coder server` now appears once per purpose instead of
three times
- Windows install now says "latest binary or installer" and links to
GitHub Releases
- Support section now spells out what goes where: issues for bugs and
feature requests, Discord and GitHub Discussions for community support
and showcasing what you built, and dedicated support included in Coder
Premium (coder.com/pricing)

🤖 Generated with Coder Agents on behalf of @bpmct
2026-08-20 14:10:36 +00:00
Danielle Maywood 5480299cef fix: serialize empty MCP tool properties as {} not null for OpenAI (#28380)
## Summary

Fixed a bug where MCP tools with an empty `properties` schema had their
tool definitions sent to OpenAI with `"properties": null`, causing
OpenAI to reject the request.

## Problem

When an MCP server reports a tool with an empty input schema, e.g.:

```json
{
  "type": "object",
  "properties": {},
  "required": []
}
```

Coder was forwarding the tool definition to OpenAI as:

```json
{
  "type": "object",
  "properties": null
}
```

OpenAI's function-calling API rejects `properties: null`, making these
tools unusable with OpenAI models.

## Root cause

The empty `properties` object collapsed to Go `nil` at two layers:

1. `agent/x/agentmcp/manager.go` `toolInputSchemaMap` stripped the
present-but-empty `"properties": {}` (guarded by `len(properties) > 0`),
so the wire copy lost it entirely.
2. `coderd/x/chatd/chatloop/chatloop.go` `buildToolDefinitions` embedded
`info.Parameters` directly into the input schema. When nil,
`encoding/json` marshals it as `null`.

## Fix

- `buildToolDefinitions` now substitutes an empty object when
`Parameters` is nil, mirroring the existing `required` guard (the sink,
protects all tool sources).
- `toolInputSchemaMap` now preserves an empty `properties` object
instead of dropping it (the upstream producer).

## Tests

Added regression tests (failing before, passing after) covering both
layers and asserting the schema serializes to
`{"type":"object","properties":{}}`:

- `TestBuildToolDefinitionsNilPropertiesBecomesEmptyObject`
- `TestToolInputSchemaMapPreservesEmptyProperties`
-
`TestWorkspaceMCPToolInfosFromResources/EmptyPropertiesYieldsEmptySchema`

---

*This PR was generated by Coder Agents.*
2026-08-20 15:56:58 +02:00
Susana Ferreira ab6601086a fix: gate group settings page and fix unauthorized group patch (#28314)
## Problem

A user without `group:update` (e.g. the `Coder Agents User` role) could
open the group settings page by direct URL and edit the budget. Saving
then failed with a misleading error:

> Failed to add or remove non-existent group member — execute
transaction: update group by ID: unauthorized: rbac: forbidden.

The authorization failure was misclassified as a missing group member,
and the settings page was not gated on the required permission.

## Changes

- Return the standard vague 404 for authorization failures in the group
PATCH handler, so the response is accurate and does not leak resource
existence.
- Use a generic message for the remaining 400 branch, since it covers
various transaction failures rather than only member add/remove.
- Gate the group settings page on `group:update`. The settings tab is
already hidden without the permission, but the page was reachable by
direct URL and rendered an editable form that always failed on save. It
now shows the standard permission dialog.

Fixes
[AIGOV-603](https://linear.app/codercom/issue/AIGOV-603/hide-group-budget-controls-without-permission).

> [!NOTE]
> Generated by Coder Agents, reviewed by @ssncferreira.
2026-08-20 14:51:27 +01:00
Susana Ferreira 363d7fc4d9 feat: add CTAs to AI budget admin notifications (#28138)
## Problem

AI budget admin notifications tell an admin that a user crossed a budget
threshold, but give them nothing to act on: no link to the user's
budget, and no pointer to the docs.

## Changes

- Add a **Manage user AI budget** action to both admin templates,
linking to the effective group's members page filtered to the affected
user, where the per-user override is set.
- Link to the AI budget documentation from both notification bodies.
- Say when the block lifts in the limit-reached notification ("blocked
until `{{period_end}}`").
- Add an `organization_name` label, needed to build the members page
URL.
- Grant the AI Gateway daemon subject read access to organizations,
which the label lookup requires.
- Add an authorization test for the notification path, which also covers
the existing group read grant.

<details>
<summary>Screenshots</summary>

**Admin Notifications in Coder UI**

<img width="456" height="695" alt="admin_notifications"
src="https://github.com/user-attachments/assets/a579f91c-55cc-497c-87f4-235f15493725"
/>

**Admin Notification in Email**

<img width="660" height="399" alt="admin_notifications_email"
src="https://github.com/user-attachments/assets/39b876fb-f512-41e5-997f-a5269cd26ddc"
/>
</details>

Related to internal Slack threads:
* https://codercom.slack.com/archives/C096PFVBZKN/p1786051630688569
* https://codercom.slack.com/archives/C04M9CNGHGF/p1786453662306149

> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by
@ssncferreira
2026-08-20 14:38:33 +01:00
Danielle Maywood 015d6a6e06 feat(site): add provider icon to chat model selector (#28330) 2026-08-20 13:35:53 +00:00
Ben Potter 54e64c2228 chore: remove retired go report from README (#28385)
this said "expired"

<!--

If you have used AI to produce some or all of this PR, please ensure you
have read our [AI Contribution
guidelines](https://coder.com/docs/about/contributing/AI_CONTRIBUTING)
before submitting.

-->
2026-08-20 08:32:35 -05:00
Susana Ferreira d58788cb23 fix: clarify AI model price configuration (#28372)
## Description

Model prices displayed in the Agents model form are read-only because
they are managed by AI Gateway. Add guidance and a link to the model
price configuration documentation, and rename the related documentation
subsection to **Configure model prices**.

> [!NOTE]
> Generated with Coder Agents by @ssncferreira.
2026-08-20 12:55:51 +00:00
Hugo Dutka 36bae07a9b test(cli): wait for OAuth server readiness (#28358)
The OAuth GitHub default provider test treated the config URL as proof
that the restarted HTTP server was ready. On macOS, the first SDK
request could reach the listener before it was ready to serve and fail
with `readLoopPeekFailLocked`.

Poll `AuthMethods` through the Coder SDK with the existing bounded retry
helper before asserting its response, both before and after the restart.

Closes
https://linear.app/codercom/issue/ENG-3257/flake-testserveroauth2githubdefaultproviderallowedorg

<sub>Generated by Coder Agents.</sub>
2026-08-20 13:13:53 +02:00
Susana Ferreira 50ecd6959f feat: add a source filter to the AI model price list (#28329)
## Description

With prices from the price book and prices set through the API both in the table, `list` showed only the price in effect and gave no way to tell where it came from. There was also no way to see the price book's entry for a model that had been overridden, since the listing resolves to the custom price.

This adds a `source` column to the output and a `--source` filter for narrowing to one source, or to every row with `all`.

Note: `--source` selects rows rather than models. A model carrying both a price book price and a custom one appears under either value, so the two filtered listings do not sum to the unfiltered one. `--source all` reports both rows at once.

## Changes

- Add `Source` to `codersdk.AIModelPrice` and `AIModelPricesFilter`, and map it in `db2sdk`, which previously dropped it.
- Filter `GetAIModelPrices` by source before resolution, so the price book's row for an overridden model stays reachable.
- Accept `all` as a source, which reports every price a model holds instead of resolving to one.
- Accept a `source` query parameter on the list endpoint, rejected with a 400 when outside the enum.
- Add `--source` to the list command and a `source` column to its output.
- Document the column and the filter, and lead the price section with `coder exp ai-model-prices list`.
- Distinguish a deployment's default prices from the price book that ships with each release.

Closes https://linear.app/codercom/issue/AIGOV-593

> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira
2026-08-20 11:56:58 +01:00
Danielle Maywood 4a7a0a3348 feat: label process_output rows with the process command (#28300) 2026-08-20 11:56:14 +01:00
Danielle Maywood eed420a0eb fix(site/src/pages/AgentsPage): clear streamed message when a chat errors (#28277) 2026-08-20 11:56:06 +01:00
Susana Ferreira 19d1ee9fc8 feat: support custom prices for models in the price book (#28328)
## Description

The experimental CLI could only price models the price book does not cover, so an operator with a negotiated rate for a model Coder already prices had no way to record it. The restriction existed because a single row served both the price book and prices set through the API, and the startup seeder would overwrite an override on the next restart.

Each source now keeps its own row (https://github.com/coder/coder/pull/28204), so that no longer applies. This removes the restriction: a price set through the CLI takes effect over the price book for any model, and the book stays underneath it.

## Changes

- Remove the validation that rejected models the price book covers.
- Delete `prices.IsDefaultPriced` and the index behind it, which existed only for that check.
- Generalize the `coder exp ai-model-prices update` help.
- Document that a custom price overrides the price book and does not follow its updates.
- Rework the spend approximation callout around what a custom price can and cannot correct.

Closes https://linear.app/codercom/issue/AIGOV-594

> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira
2026-08-20 11:46:38 +01:00
Susana Ferreira 9390e51422 feat: distinguish default and custom AI model prices (#28204)
## Description

Prices in `ai_model_prices` come from two places: the price book embedded in each Coder release, and prices set through the experimental CLI (https://github.com/coder/coder/pull/27926). Until now, they were indistinguishable, so the startup seeder re-applied the book over every row, and a price someone set would silently revert on the next restart.

This adds a `source` column with values `default` and `custom`, and keeps a row for each. Readers resolve a model to its custom price when one exists and fall back to the book, including the cost path that prices an interception.

Keeping both rows means the price book stays current underneath an override, so a future delete can restore it.

Note: the endpoint still only accepts models the price book does not cover. Overriding a price the book already carries is handled by the follow-up issue https://linear.app/codercom/issue/AIGOV-594.

## Changes

- Add a migration for the `ai_model_price_source` enum and a `source` column, backfilled to `default` and then `NOT NULL`. `source` joins the primary key, so a model can hold one row per source.
- Take a `source` parameter on `UpsertAIModelPrices`. Each source keeps its own row, so the price book and a custom price never overwrite each other.
- Resolve to the custom price in `GetAIModelPriceByProviderModel` and `GetAIModelPrices`, so both the interception cost snapshot and the list endpoint use it.
- Seed the price book as `default`, and write prices set through the endpoint as `custom`.

Closes https://linear.app/codercom/issue/AIGOV-592

> [!NOTE]
> Initially generated by Claude Opus 5, modified and reviewed by @ssncferreira
2026-08-20 11:36:58 +01:00
Michael Suchacz 2f996d765a feat: remove task management MCP tools (#28354)
Removes the six task-management tools from the MCP tool surface in
`codersdk/toolsdk`:

- `coder_create_task`
- `coder_delete_task`
- `coder_list_tasks`
- `coder_get_task_status`
- `coder_send_task_input`
- `coder_get_task_logs`

Both the coderd-hosted MCP server (`coderd/mcp`) and the CLI `coder exp
mcp server` derive their tool list from `toolsdk.All`, so removing the
registry entries removes the tools from both servers with no server-side
changes.

The MCP server documentation (`docs/ai-coder/mcp-server.md`) is updated
to drop the task-management entry from the tool overview.

## What stays

- `coder_report_task` is intentionally kept: it is the agent-side
status-reporting mechanism (`WithTaskReporter`), already excluded from
the hosted MCP server, and required by `coder exp mcp server`, Claude
Code configuration, and scaletest.
- The `codersdk` Task SDK methods (`CreateTask`, `DeleteTask`, `Tasks`,
`TaskLogs`, etc.) and the Tasks feature (CLI, UI, API) are untouched;
this PR only removes MCP tool registration.
- The task-only `taskIDDescription` helper is deleted; `userDescription`
stays (used by `coder_create_workspace`).

The code change is deletion-only: 970 removed lines across `toolsdk.go`
and its test file, no added behavior.

## Validation

- `go build ./...`, `go test ./coderd/mcp/`, and targeted
`golangci-lint` pass. `go test ./codersdk/toolsdk/` passes all tests;
the package-level goleak failure (agentssh goroutines) reproduces
identically on unmodified `main` and is unrelated to this diff.
- `pnpm run lint-docs` passes on the docs change.
- Remote dogfood UAT ran against the toolsdk change and passed:
`tools/list` on both the hosted MCP endpoint and `coder exp mcp server`
confirms the six tools are absent and the remaining surface is intact
(34 hosted tools; CLI adds `search`/`fetch`), a `tools/call` of
`coder_get_authenticated_user` succeeds, and calling a removed tool
returns `-32602 unknown tool`. `coder_report_task` still registers in
the CLI server when `CODER_MCP_APP_STATUS_SLUG` is set.
- The failing `Pixel / Review` commit status is unrelated: this PR
contains no `site/` changes, and the same status fails on the last 8
`main` commits including this PR's merge base (`676b9bb06bf`).

> Mux acted on Mike's behalf to create this pull request.
2026-08-20 12:07:30 +02:00