Commit Graph
220 Commits
Author SHA1 Message Date
Phorcys 🐾 1bb0978361 fix(site): centralize UUID generation and fix non-secure crash (#28285)
Fixes OSS-38: `crypto.randomUUID is not a function` when accessing Coder
over plain HTTP.

### Root cause
`crypto.randomUUID` is only defined in secure contexts (HTTPS or
localhost). It is `undefined` when Coder is served over plain HTTP. The
`uuid` package's `v4()` short-circuits to `crypto.randomUUID()` when it
is truthy, and in the production bundle that path compiles down to a
direct `crypto.randomUUID()` call, so `uuidv4()` throws over HTTP.
Observed crash (Template Builder, in the `useMemo` that mints the
telemetry session ID):

```
TypeError: crypto.randomUUID is not a function
  Rt/h<@.../assets/TemplateBuilderPage-*.js
  useMemo@...
```

The same `uuidv4()` path backed the Terminal reconnection token and the
Agent chat tab IDs / reconnection tokens, so those flows were affected
too.

### Fix
Add a single `generateUUID` helper in `site/src/utils/random.ts` that
derives the UUID from `crypto.getRandomValues` (the one `Crypto` member
available in insecure contexts) and only calls `crypto.randomUUID` when
it actually exists. Route all client-side UUID generation through it, so
every call site is fixed at once and future ones stay safe.

### Changes

| Change | Route | Human tested/verified |
|---|---|---|
| `utils/random.ts` — new `generateUUID()` helper | N/A | N/A |
| `TemplateBuilderPage.tsx` — telemetry session ID |
`/templates/new/builder` | [x] |
| `TerminalPage.tsx` — reconnection token |
`/:username/:workspace/terminal` | [x] |
| `AgentChatPageView.tsx` — tab IDs + reconnection tokens |
`/agents/:agentId` | [ ] |
| `useChatDraftAttachments.ts` — draft client IDs | `/agents/:agentId` |
[x] |
| `TaskApps.stories.tsx` — mock app IDs (Storybook) |
`/tasks/:username/:taskId` | [ ] |
| `WorkspaceSharingIndicator.stories.tsx` — mock actor IDs (Storybook) |
`/workspaces` | [ ] |
| Removed unused `uuid` / `@types/uuid` direct deps | N/A | N/A |

`uuid` is still pinned via a `pnpm.overrides` entry because
`mermaid`/`streamdown` depend on it transitively.

### Tests
- `random.test.ts` covers a valid UUID, uniqueness, and the
insecure-context path (with `crypto.randomUUID` stubbed to `undefined`).
- `TerminalPage.test.tsx` now mocks `#/utils/random` instead of the
`uuid` module.

### Validation
- `make fmt`, `biome check`, `tsc --noEmit` clean
- `vitest run` green: `random.test.ts` (6), `TerminalPage.test.tsx`
(16), `useChatDraftAttachments.test.ts` (17), both changed stories (12)
- Reproduced the original crash over HTTP on a production build, top
frame `uuidv4()` in Template Builder.

---

_This PR was generated by Coder Agents on behalf of @phorcys420._
2026-08-19 15:51:42 +00:00
Cian Johnston da99941099 feat(site): add shared DateTimeRangeFilter component (#28255)
A text-expression datetime range picker with From/To inputs accepting
`now`, a clock time (current day), a date (midnight), or a date with a
clock time. Invalid text gets inline errors, underspecified expressions
resolve on blur, and out-of-order boundaries clamp against the other
one. The trigger derives a concise label ("Last 24 hours", "Apr 10",
"Aug 11 - Today", "Apr 17 - 19").

Placed in `site/src/components/DateTimeRangeFilter/` so other pages can
reuse it. Expression parsing and trigger-label derivation live in
`timeRange.ts` next to the component; both are generic over `Date`
pairs. Depends on #28254 for the shared `filterQuery` serialization
helpers.

Part of
[AIGOV-580](https://linear.app/codercom/issue/AIGOV-580/ai-gateway-sessions-page-takes-5-10-seconds-to-load)

---
_Generated by Coder Agents on behalf of @johnstcn._$

---

**Stack:** #28254 (filterQuery fix) \u2192 #28255 (component) \u2192
#28256 (sessions page)
2026-08-19 13:47:06 +01:00
Jake Howell 4d13bef74d feat(site): add Install Coder Desktop item to UserDropdown (#28244)
> 🤖 This PR was modified by Coder Agents on behalf of Jake Howell.

Resolves
[DEVEX-722](https://linear.app/codercom/issue/DEVEX-722/add-install-coder-desktop-button-to-userdropdown).

Adds an **Install Coder Desktop** item to `UserDropdown`, sitting
directly above the existing **Install CLI** option. Because
`UserDropdownContent` is shared, it appears in both the navbar dropdown
and the agents-page sidebar footer.

### Screenshots

macOS/Windows shots are captured with `navigator.platform` overridden
accordingly; the Linux shot is the real workspace platform, where the
Coder Desktop item is hidden. Install Coder Desktop uses a monitor
glyph, and Install CLI now uses a terminal glyph (it was a monitor on
`main`).

| `main` (before) | This PR · macOS/Windows | This PR · Linux / iPad OS
|
| --- | --- | --- |
| ![UserDropdown on
main](https://raw.githubusercontent.com/coder/coder/jakehwll/devex-722-assets/.github/assets/devex-722-userdropdown-main.png)
| ![UserDropdown with Install Coder
Desktop](https://raw.githubusercontent.com/coder/coder/jakehwll/devex-722-assets/.github/assets/devex-722-userdropdown-new.png)
| ![UserDropdown on Linux without the Desktop
item](https://raw.githubusercontent.com/coder/coder/jakehwll/devex-722-assets/.github/assets/devex-722-userdropdown-linux.png)
|

### Behaviour
- **Platform-gated visibility:** shown only on macOS and Windows (the
platforms Coder Desktop ships for), hidden on Linux/other. The Linux
client
([coder/coder-desktop-linux](https://github.com/coder/coder-desktop-linux))
is experimental and not advertised yet, so it stays hidden there.
- **Links to the docs** (`https://coder.com/docs/user-guides/desktop`)
rather than a single install method. The docs page is the canonical hub
covering Homebrew, WinGet, manual release downloads, and source, and it
stays current without frontend changes. The `coder-desktop-*` repo
READMEs don't enumerate install methods; they defer to these same docs.
- **No installed-detection:** consistent with the Install CLI item, we
don't try to detect whether Coder Desktop is already present.

### Changes
- `site/src/utils/platform.ts`: add `isWindows()` and
`supportsCoderDesktop()`.
- `UserDropdownContent.tsx`: render the platform-gated item
(`MonitorIcon`) and switch Install CLI to `TerminalIcon`.
- Unit tests for the platform helpers and Storybook coverage for the
dropdown item (present + correct href on macOS/Windows, absent on
Linux/iPadOS).


Implementation plan & decision log

**Ticket open questions and decisions**

1. *Shown to all users or only supported platforms?* Only supported
platforms (macOS, Windows); hidden elsewhere. The Linux client is
experimental and not advertised yet, so hiding it there is intentional.
2. *Where should it link?* The Coder Desktop docs page. Users install
via many methods (brew, winget, releases, source); the docs are the
single hub that lists them all and are maintained in-repo. The GitHub
release pages / repo READMEs only cover downloads and defer back to the
docs.
3. *Hide when already installed?* No. There is no reliable browser-side
way to detect a native app install, and it mirrors how Install CLI
behaves.

**Implementation**
- Extend `site/src/utils/platform.ts` with `isWindows()` and
`supportsCoderDesktop()` (reusing the existing `isMac()`).
- In `UserDropdownContent.tsx`, conditionally render an `Install Coder
Desktop` `DropdownMenuItem` (external link, `MonitorIcon`, opens in a
new tab) above `Install CLI` when `supportsCoderDesktop()` is true.
- Tests: `platform.test.ts` (OS detection via `vi.stubGlobal`) and
`UserDropdown.stories.tsx` (visibility + href per platform, mocking
platform detection with `spyOn`).



Left as a **draft** pending review, let me know when you'd like it
opened.
2026-08-19 09:11:07 +07:00
Andrew Aquino b721935fc1 chore(site): add generateConnectionSessionId helper function (#27935)
- adds a `generateConnectionSessionId` helper function which reuses our
existing `generateRandomString` logic
- adds tests for `generateConnectionSessionId` in utils/random.test.ts
(utils/random.ts was previously untested)
- renames our existing `generateRandomString` function to be
`generateRandomBase64String`

additional context in
https://github.com/coder/coder/pull/27671#discussion_r3731213459

This PR doesn't change any of our UIs. The new
`generateConnectionSessionId` function is a piece of frontend plumbing
related to DEVEX-663. I originally implemented #27677 so that web
terminal connections to workspaces would be identified by uuids--but
according to the RFC for DEVEX-663, those connection session ids should
be lowercase hexadecimal strings (not uuids).
2026-08-11 09:38:28 -07:00
Thomas Kosiewski 496ed757ed fix: prevent IP leaks via external chat images (#27362) 2026-08-11 11:50:56 +02:00
Thomas Kosiewski 84f4a8bb17 fix(site/src): prevent protocol-relative login redirects (#27363) 2026-08-10 15:35:06 +02:00
Ethan 01c00ce1bf feat(site): add per-template Coder Agents controls (#27514)
Relates to CODAGT-713

Depends on #27285

This replaces the deployment-wide template allowlist UI with per-template Coder Agents controls. Each template's General Settings page exposes `agents_allowed` to organisation and template administrators through the existing template-update permission, whilst AI Settings > Templates gives deployment administrators a bulk management page. The bulk page requires both deployment-config and template-update permissions, reuses the same URL-backed server-side filter as `/templates`, tracks pending updates independently for each template, and surfaces failures through template-specific toasts. The copy refers to new workspace creation because disabling a template does not revoke an existing Coder Agent workspace.

This completes the frontend migration away from the experimental `/template-allowlist` API. #27515 removes the now-unused legacy storage, routes, SDK methods, generated types, and JSON utility. Later PRs add CLI flags and update the platform controls documentation. The stack is intended to merge as a unit.
2026-08-06 14:23:36 +10:00
Jake Howell c98d6bb535 refactor(site/src): migrate shared Pill to Badge (#27638)
> 🤖 This PR was written by Coder Agents on behalf of Jake Howell.

## Summary

Deletes the shared `Pill` component (`site/src/components/Pill/`) and
migrates every consumer to `Badge`. Accepts the intentional visual shift
(`rounded-full` → `rounded-md`, slightly different color tokens). No
`ThemeRole` variants were added to `Badge`.

### Key changes
- Added `site/src/components/Badge/themeRoleToBadgeVariant.ts`, a small
mapper used at call sites that receive a `ThemeRole` (e.g.
`StatusBadge`, `TemplateVersionStatusBadge`).
- Exported `BadgeProps` from `Badge.tsx` so the mapper and status
helpers can reference the variant type.
- Replaced `PillSpinner` with `<Spinner loading />`.
- Renamed `StatusPill` → `StatusBadge` (folder, file, and export) and
updated the audit log and connection log imports.
- Extended `Badge.stories.tsx` with a status + spinner icon pattern
(rather than porting the full Pill story).

### Variant mapping applied
| Pill `type` | Badge `variant` |
|---|---|
| `success` | `green` |
| `error` | `destructive` |
| `warning`, `danger` | `warning` |
| `active`, `notice` | `info` |
| `inactive`, `muted`, `info` | `default` |
| `preview` | `purple` |

### Out of scope
The local Health-page `Pill` in `site/src/pages/HealthPage/Content.tsx`
is a separate component with no shared import; it will be migrated in a
follow-up.

## Validation
- `pnpm check` (biome) passes
- `pnpm lint` (biome + tsc + circular-deps + compiler + knip) passes
- `pnpm test:storybook` for Badge, TemplatePageHeader, and AuditLogRow
stories: 30/30 pass
- Grep for `components/Pill` and `PillSpinner` in `site/src` returns
nothing

<details>
<summary>Implementation plan</summary>

# Migrate Pill to Badge

## Goal

Delete `site/src/components/Pill/Pill.tsx` and migrate every shared-Pill
consumer to `site/src/components/Badge/Badge.tsx`. Accept the visual
shift (`rounded-full` → `rounded-md`, slightly different color tokens).
Do not add ThemeRole variants to Badge.

**Out of scope:** the local Health-page `Pill` in
`site/src/pages/HealthPage/Content.tsx` (separate component, no shared
import). Rename/migrate that in a follow-up.

## Variant mapping

Use this fixed mapping at every call site (Pill `type` → Badge
`variant`):

| Pill `type` | Badge `variant` | Notes |
|---|---|---|
| `success` | `green` | |
| `error` | `destructive` | |
| `warning`, `danger` | `warning` | Pill treats danger like orange
warning |
| `active`, `notice` | `info` | sky / pending |
| `inactive`, `muted`, `info` | `default` | Pill `info` is neutral grey;
Badge `info` is sky, so map Pill `info` to `default` |
| `preview` | `purple` | |

Other remaps:

- `icon={node}` → put `node` as the first child of `Badge`
- `size="lg"` → `size="md"` (closest); add a local `className` only if a
specific layout breaks
- `PillSpinner` → `<Spinner loading />` from
`site/src/components/Spinner/Spinner.tsx`
- Prefer existing wrappers when they already exist (e.g.
`DeprecatedBadge` on the template header)

For ThemeRole-driven helpers (`httpStatusColor`, status getters), add a
tiny shared mapper next to Badge
(`site/src/components/Badge/themeRoleToBadgeVariant.ts`). Use it in
`StatusPill` and any site that still receives `ThemeRole` rather than
hardcoding variants twice.

## Call-site work (ordered easy → hard)

1. **Trivial label chips** (import swap + variant map):
TemplatePageHeader (use `DeprecatedBadge`), VersionRow,
ChangeWorkspaceVersionDialog, PermissionPillsList, IdpPillList,
OrganizationPills
2. **Icon-as-child chips**: Provisioner, ProvisionerTag (local
`BooleanPill` becomes a Badge wrapper), TemplateExampleCard (keep
clickable styles via `className`)
3. **Status + spinner**: TemplateVersionStatusBadge (Badge + children
icons; `PillSpinner` → `Spinner`), utils/workspace.tsx (replace
`PillSpinner` imports only)
4. **Wrappers that own ThemeRole**: StatusPill (switch to Badge +
`themeRoleToBadgeVariant(httpStatusColor(...))`; rename to
`StatusBadge`), Notifications (Badge with severity `className`
overrides)
5. **Delete shared Pill**: remove `Pill.tsx`, `Pill.stories.tsx`, and
the `components/Pill` folder; extend `Badge.stories.tsx` only if a
migrated pattern is missing (icon + spinner status)

## Non-goals / constraints

- Do not change Badge's default shape or add `rounded-full`
- Do not broaden Badge's public API with `type` / `icon` props (that
recreates Pill)
- Do not rename IDP/permission "PillsList" filenames unless needed for
imports
- Modify Badge itself only for the new mapper helper (or stories)

## Validation

- `pnpm check` / `pnpm lint` under `site/`
- Storybook smoke on Badge + affected pages
- Grep for `components/Pill` and `PillSpinner` must be empty in
`site/src`

</details>
2026-08-06 12:36:05 +10:00
Jake Howell 7a4ae2649e refactor(site): replace MUI Stack, TextField, and Link with shared components (#27817)
> 🤖 This PR was modified by Coder Agents on behalf of Jake Howell.

Continue the MUI → Tailwind/shadcn migration across a few auth and
settings surfaces.

- Create token form: MUI `TextField`/`MenuItem` → `FormField`, `Select`,
and `Input` (drops Emotion for section min-width)
- Create organization form: `TextField` → `FormField` / `Textarea`,
matching the organization info form
- SSO security section and external auth: MUI `Link`/`TextField` →
shared `Link`, `Input`, and `Label`
- Permission and IdP pill lists: MUI `Stack` → `flex flex-row gap-2`
- Minor link layout polish on Git device auth / external auth pages

---

_Also removes the now-unused `@emotion/css` dependency from
`site/package.json` (and the lockfile), which was the last usage of it.
This fixes the `knip` CI lint failure ("Unused dependencies:
@emotion/css")._
2026-08-05 14:07:04 +10:00
Michael Suchacz 6b8f820493 feat: remove native chat cost tracking in favor of AI Gateway cost data (#27330)
## Stack Context

This stack makes AI Gateway data and budgets the source of truth for AI
spend controls.

1. Re-back the per-chat cost endpoint with AI Gateway data (#27328,
merged).
2. Remove native chat usage limits (#27329, merged).
3. **This PR, now based on `main`:** remove native chat cost tracking
and its dedicated admin UI.

## Summary

Removes native per-message price calculation, model pricing fields, cost
persistence, aggregate cost queries, and admin cost API types. It also
deletes the Analytics and Spend pages plus their legacy redirects. The
AI Gateway-backed per-chat cost row and compact budget indicators
remain.

The spend documentation is renamed to `spend-management.md` and updated
for the remaining surfaces, group budget APIs, CSV export, upgrade
handling for native pricing and cost history, and the absence of a
deployment-wide spend dashboard. The per-chat cost API documents that
data follows AI Gateway retention and reports zero after all matching
requests are purged.

No schema is dropped in this release. `chat_messages.total_cost_micros`
remains nullable and unwritten so replicas from the previous release can
continue inserting messages during rolling upgrades. #27600 tracks
removal after the compatibility window.

> Mux prepared this PR on Mike's behalf.
2026-08-04 12:27:38 +02:00
Michael Suchacz f0e6ac64b3 feat: remove native chat usage limits in favor of AI Gateway budgets (#27329)
## Stack Context

This stack makes AI Gateway data and budgets the source of truth for AI
spend controls.

1. Re-back the per-chat cost endpoint with AI Gateway data (#27328,
merged).
2. **This PR:** remove native chat usage limits.
3. Remove native chat cost tracking and its dedicated admin UI (#27330).

## Summary

Removes the native usage-limit API, SDK types, SQL, and chat enforcement
for deployment, user, and group chat limits. Compact AI Gateway budget
indicators remain in the Agents sidebar, user menu, and group settings.
Gateway budget rejections and provider quota failures continue to
classify as usage-limit errors, including a 409 response for synchronous
title generation.

Budget-period labels now use the API's UTC boundaries, so users see the
same dates in every browser timezone. The documentation explains the AI
Gateway replacement, its licensing requirements, and the differences
from native limits.

No schema is dropped in this release. The usage-limit table, index, user
and group columns, constraints, audit mappings, and generated scan
fields remain for mixed-version rolling upgrades. #27600 tracks their
removal after the compatibility window.

## Breaking change

Native day, week, and month chat spend limits are removed and are not
migrated. AI Gateway budgets are month-based, group-scoped with per-user
overrides, and require the AI Gateway entitlement. Deployments without
that entitlement no longer have chat spend enforcement.

> Mux prepared this PR on Mike's behalf.
2026-08-04 11:36:49 +02:00
Jeremy Ruppel 218829d444 refactor(site): use uuid package instead of generateUUID helper (#27709) 2026-07-30 16:52:29 -04:00
Jeremy Ruppel cf7f876880 feat(site): add generateUUID helper (#27661)
Adds a `generateUUID()` helper to `site/src/utils/uuid.ts`.

It uses `crypto.randomUUID()` when available, and otherwise falls back
to `crypto.getRandomValues()`, setting the version (4) and variant (RFC
4122) bits before formatting the 16 random bytes into the standard
`8-4-4-4-12` UUID string.

Seriously open to any implementation here, let me know if you have a
favorite!

---
_This PR was created by Coder Agents on behalf of @jeremyruppel._
2026-07-30 10:46:40 -04:00
1a6a8be96c feat: log tailnet tunnels to the connection log (#27423)
Co-authored-by: Chris DiGiamo <cd@anthropic.com>
Co-authored-by: Chris DiGiamo <cdigiamo@anthropic.com>
2026-07-28 15:30:12 -05:00
Jake Howell 448fe10d82 fix: remove @mui/material from formUtils.stories.tsx (#27503)
This pull-request removes the material dependency from
`formUtils.stories.tsx`. Just a lil bit of dead code we can clean up 🙂.
2026-07-28 23:39:28 +10:00
McKayla はな 00d134ebfd chore: remove classic parameter UI (#25014) 2026-07-27 19:02:28 -06:00
Danielle Maywood 507ce504f8 refactor(site): align AgentsPage layout naming with codebase conventions (#27378) 2026-07-22 13:16:14 +01:00
Ehab Younes d5e5b10ff1 feat(site): polish AI budget members table (#26805)
Finishing touches for the AI cost control group members table.

- Spend stays primary-colored until near the budget limit, via a new
  AIBudgetAmount component
- "AI budget period" label shows the current spend window in local
  time, next to the members tab
- Budget tooltip notes the reset date and the group's default limit
- "Budget type" renamed to "Budget group", with a badge for the
  governing group or override
- Distinguishes $0 budget ("None") from no budget ("Unlimited")
- Unattributed spend from another group shows a note instead of a dash
- "Manage AI budget" disabled only when another named group governs
- Replaces UserAISpend with generated UserAISpendStatus, fixing
  limit_source

Closes #26401
2026-07-13 17:33:40 +03:00
Ehab Younes 6b297912a5 feat(site): add AI spend to user dropdown (#26698)
Adds the current user's AI spend progress to the top navbar avatar
dropdown when /api/v2/users/me/ai/spend reports a configured spend
limit. The shared dropdown content accepts an optional profileExtra
slot so the Agents sidebar can opt in later without changing the
default sidebar UI.

Introduces a temporary site API type and a React Query helper for
AIGOV-473 that refetches each time the dropdown opens, plus shared
budget progress helpers used by both the new dropdown bar and the
existing Agents usage indicator. The shared AIBudgetUsage component
moves to site/src/components so the dropdown and group budget UI
format spend identically, including the unlimited case. The avatar
border polls the spend endpoint and is colored by severity while the
dropdown is closed.

Closes AIGOV-473
2026-06-30 19:59:13 +03:00
McKayla はな a78982c238 test: convert chromatic sentinels to pixel equivalents (#26778) 2026-06-29 08:57:57 -06:00
Ehab Younes 75993c2ea0 feat(site): add group member AI budget columns (#26574)
Adds AI budget and Budget type columns to the group members table, shown when
aibridge is enabled and the ai-gateway-cost-control experiment is on. A member's
spend, limit, and source come from an ai_cost_control object embedded in the
group members and groups responses, so no extra request is made.

- Add AI budget and Budget type columns, gated by the aibridge feature and the
  ai-gateway-cost-control experiment
- Read ai_cost_control inline from the group and member lists instead of calling
  a separate spend endpoint
- Share an AIBudgetUsage component (spend vs budget with severity colors) and an
  InfoIconTooltip for the column headers
- When another group governs a member's budget, grey the spend and name that
  group in a tooltip; otherwise render the spend (severity-colored) against a
  white limit
- Resolve a member's effective group in the AI budget override dialog, marking
  only the governing group "(default)" and none when no group governs them
- Defer the override's custom-budget error until the field is touched

Closes AIGOV-291
2026-06-25 15:41:46 +03:00
Ehab Younes 0aa14c7d45 feat(site): add group AI budget column (#26566)
Adds an AI budget column to the organization Groups list showing each
group's current AI spend against its configured limit, or "unlimited"
when no limit is set.

- Column and its spend request are gated behind `aibridge` visibility
  and the `ai-gateway-cost-control` experiment
- Shows loading placeholders while spend is fetched from
  `/api/v2/organizations/{org}/groups/ai/spend`
- Spend severity thresholds extracted into shared `utils/budget.ts`:
  warning at 85%, destructive at or above the limit
- Response type defined locally with a TODO to replace with the
  generated type once the backend endpoint exists

Closes AIGOV-290
2026-06-23 18:56:54 +03:00
Ehab Younes c0b8fa9418 feat(site): add user AI budget override UI (#26402)
Add a per-user AI budget override dialog in the group members menu, scoped to the group's organization.

Refs AIGOV-295
2026-06-22 13:58:53 +03:00
Ethan 7632e73608 refactor(site/src): extract shared workspace-app frame module (#26088)
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
2026-06-12 11:51:20 +10:00
Jaayden Halko eea427f288 fix: inline ports panel in workspace pill on mobile (#25042)
closes CODAGT-326

<img width="335" height="658" alt="Screenshot 2026-06-02 at 18 29 39"
src="https://github.com/user-attachments/assets/ee103047-67ad-403c-b67b-eb2147726db3"
/>


On viewports below the `md` Tailwind breakpoint, the agents-chat
workspace pill becomes full width via the existing
`mobile-full-width-dropdown` CSS hook. The `Ports (X)` item used a Radix
`DropdownMenuSub` which opened a flyout sub-content to the right of the
parent menu, so on mobile the sub-content had nowhere to render and
clipped off the right edge.

Mirror the inline sub-panel pattern already used by the agent chat input
plus menu: lift a `view: "main" | "ports"` state into `WorkspacePill`,
and on mobile swap the same dropdown's contents to a `Back` + ports list
panel instead of opening a flyout. Desktop keeps the existing flyout
sub-menu unchanged.

Closes
[CODAGT-326](https://linear.app/codercom/issue/CODAGT-326/port-forward-menu-is-cut-off-on-mobile-viewports).

Visual coverage is added via two new Storybook stories at the `mobile1`
(375 px) viewport: `MobilePortsInlinePanel` (full interaction +
assertions) and `MobilePortsInlinePanelOpen` (visual / Chromatic capture
stop point).

<details>
<summary>Implementation plan</summary>

### Why

The dropdown content already gets full viewport width on `< 768 px` via
the `mobile-full-width-dropdown` CSS hook in `site/src/index.css`. A
Radix `DropdownMenuSub` opens to the right of its trigger, so on mobile
it has no room and clips. Forcing the sub-content to also be full-width
would overlap the parent and break keyboard / focus flow. The existing
convention for nested menus on mobile in this codebase is the inline
sub-panel (see `plusMenuView` in `AgentChatInput.tsx`).

### `WorkspacePill.tsx`

- Add `view: "main" | "ports"` state and reset to `"main"` on close.
- Extract `usePortsData(workspace, agent, enabled)` and a shared
`PortsList` so desktop sub-content and mobile inline panel share one
renderer.
- Replace `PortsSubMenuItem` with `PortsMenuItem`, which uses
`useIsBelowMdViewport()` to render either:
- **Mobile:** a regular `DropdownMenuItem` whose `onSelect` calls
`event.preventDefault()` (keeps the dropdown open) and switches to the
inline view.
- **Desktop:** the existing `DropdownMenuSub` flyout, behavior
unchanged.
- Add `MobilePortsPanel` rendered inside the parent
`DropdownMenuContent` when `view === "ports"`. Includes a `Back` item
that returns to the main view.
- Add a small reactive `useIsBelowMdViewport()` hook around the existing
`isBelowMdViewport` helper.

</details>

> Created on behalf of @jaayden by Coder Agents.
2026-06-02 14:22:15 +01:00
Michael Suchacz 06526a5822 feat: use AI provider chat APIs (#25415) 2026-05-22 07:53:23 +02:00
Michael Suchacz 356bccddc2 feat: add personal skills settings UI and docs (#25066)
> Mux updated this PR on behalf of Mike.

## Summary

- Add experimental personal skills API helpers and an Agents settings UI
for listing, creating, editing, deleting, and importing SKILL.md
content.
- Add docs, Storybook coverage, and unit tests for backend-compatible
SKILL.md parsing.
- Address review feedback by simplifying frontmatter scalar parsing,
clarifying the UI parser scope, defaulting personal skill queries to
`me`, and patching React Query caches after create, update, and delete.
- Merge latest `main` and resolve the Agents sidebar refactor conflicts.

## Validation

- pre-commit hook
- `go test ./codersdk/workspacesdk -run TestParseSkillFrontmatter
-count=1`
- `go test ./coderd/x/chatd/chattool -run 'Test' -count=1`
- `cd site && pnpm test --
src/pages/AgentsPage/utils/personalSkills.test.ts
src/api/queries/userSkills.test.ts src/utils/fileSize.test.ts
--runInBand`
- `cd site && pnpm lint:types`
- `cd site && pnpm lint:check`
2026-05-22 00:20:10 +02:00
Jaayden HalkoandCian Johnston 92d67888b8 feat: modal chat search popup (#25535)
closes CODAGT-422

<img width="591" height="404" alt="Screenshot 2026-05-21 at 17 33 26"
src="https://github.com/user-attachments/assets/5ef8134a-aca7-4442-bed7-9a31698dc5d6"
/>
<img width="590" height="499" alt="Screenshot 2026-05-21 at 17 33 46"
src="https://github.com/user-attachments/assets/b8e99e53-793c-4480-8411-d90f97f9bcb6"
/>
<img width="376" height="301" alt="Screenshot 2026-05-21 at 17 36 39"
src="https://github.com/user-attachments/assets/d9c4a45c-9094-40a2-ac88-87415c45b358"
/>

---------

Co-authored-by: Cian Johnston <cian@coder.com>
2026-05-21 11:39:58 +01:00
Kayla はな 9810d299d2 chore: make dynamic parameters TemplateEmbedPage the default (#25069) 2026-05-12 18:28:36 +00:00
Kayla はな 638e2220e9 chore: refactor BuildIcon and remove useClassName (#25017) 2026-05-08 14:11:49 -06:00
Danielle Maywood 8ac4b9ab45 refactor: remove unnecessary typeof window checks (#24999) 2026-05-06 22:39:59 +01:00
Kayla はな 57a6421670 fix(site): ignore empty file path segments in template file tree (#24980) 2026-05-05 13:33:59 -06:00
Ben Potter f6eccbab23 feat(site/src): add ports submenu to WorkspacePill in agents chat (#24887)
Adds a **Ports (n) >** submenu item to the `WorkspacePill` dropdown
shown in the `/agents` chat UI when a workspace is attached, sitting
alongside VS Code, Terminal, and other app items.

The submenu shows a **Listening Ports** section with clickable port
links that open in a new tab, a **Shared Ports** section with
sharing-level icons when any ports are shared, and a **Manage sharing**
footer link to the workspace detail page. Port data is fetched on a 5s
polling interval while the dropdown is open and only when the agent is
connected. The trigger is disabled when the workspace is not running.

Also adds `DropdownMenuSub`, `DropdownMenuSubTrigger`, and
`DropdownMenuSubContent` to the shared `DropdownMenu` component for use
here and future consumers.

![ports submenu
demo](https://raw.githubusercontent.com/coder/coder/screenshots/ports-submenu/ports-submenu-demo.gif)

<details>
<summary>Implementation notes</summary>

- `host` for port-forward URL construction comes from
`useProxy().proxy.preferredWildcardHostname`, the same source used by
`PortForwardButton` on the workspace detail page.
- Port queries are gated on `isOpen && agent.status === "connected"` so
no requests fire when the dropdown is closed or the agent is
disconnected.
- Shared ports that overlap with listening ports are deduplicated: they
only appear in the Shared section.
- Port sharing controls (create/update/delete) are intentionally
excluded to keep the agents toolbar lightweight; the "Manage sharing"
link surfaces the workspace page for that.
- The port count badge on the trigger (`Ports (n)`) uses the raw
listening-port count, matching the existing `PortForwardButton` behavior
on the workspace detail page.
</details>

> Generated by [Coder Agent](https://coder.com)
2026-05-04 11:56:51 -05:00
TJandJaayden Halko 95386f526a fix(site): improve agents page mobile view (#24508)
closes DES-22030

## Summary

Mobile view cleanup for the agents page — all changes are behind the
`md:` breakpoint so desktop is unchanged.

**Dropdowns:** Full-width on mobile with dynamic positioning via a
`--mobile-dropdown-bottom` CSS custom property set by a `ResizeObserver`
on the chat input box. Three position variants: `-bottom` (above chat
input), `-top` (below header), `-top-below-header` (below sidebar
header). Viewport branching uses a new `isBelowMdViewport()` helper (`<
768px`) so 640–767 px landscape phones pick the mobile branch instead of
the desktop flyout.

**Layout:** On the main agents page, mobile ordering is header → chat
list → chat input using CSS `order` and `contents` on the content
wrapper. The chat input aligns to the bottom of available space. The
sidebar list uses a top/bottom fade mask on mobile to hint at scrollable
content.

**Header:** Settings, Analytics, sound, and notification icons
consolidated into a single meatball menu dropdown on mobile.
Sound/notification toggles use `e.preventDefault()` to keep the menu
open for state feedback. Chime and notification state is lifted into
`AgentCreatePage` and passed down, so the mobile meatball menu and the
desktop `ChimeButton`/`WebPushButton` stay in sync.

**Workspace pill:** Icon-only on mobile (`size-7` round button with
`StatusIcon`), full pill on desktop. Tooltip hidden on mobile to prevent
ghost tooltip after dropdown close.

**Plus menu:** Workspace picker replaces the flyout with an inline
sub-panel on mobile (back button + search list). Desktop flyout
unchanged. `modal={false}` prevents double-tap when switching between
dropdowns.

**Model selector:** Truncated via `shrink` + `min-w-0` on mobile
(flex-based, no fixed max-width), inline provider/context subtext per
item, tooltip hidden on mobile. Added `open` / `onOpenChange` /
`onTriggerTouchStart` props for external control.

**Consistency:** All back/close buttons normalized to `ArrowLeftIcon`.
Right panel, sidebar settings, header `mobileBack`, and workspace
sub-panel all match the chat top bar pattern.

**Misc polish:** Chat tree nodes use `select-none` +
`-webkit-touch-callout:none` on coarse pointers to suppress the
long-press selection/callout on mobile.

<details>
<summary>Files changed (18)</summary>

- `site/src/index.css` — mobile dropdown CSS with 3 position variants
- `site/src/utils/mobile.ts` — new `isBelowMdViewport()` helper
(`<768px`)
- `site/src/pages/AgentsPage/AgentChatPageView.tsx` — bottom padding
`pb-3`
- `site/src/pages/AgentsPage/AgentCreatePage.tsx` — lift chime + webpush
state; pass handlers to header and buttons
- `site/src/pages/AgentsPage/AgentsPageView.tsx` — `contents` wrapper +
sidebar `border-b`
- `site/src/pages/AgentsPage/components/AgentChatInput.tsx` —
`ResizeObserver` composer ref, `plusMenuView` state, inline workspace
picker, `modal={false}`, mobile branching via `isBelowMdViewport`, bg
- `site/src/pages/AgentsPage/components/AgentCreateForm.tsx` —
`order-last` + `items-end` on mobile
- `site/src/pages/AgentsPage/components/AgentPageHeader.tsx` — meatball
menu (controlled chime/webpush props), `ArrowLeftIcon`, `order-first`,
padding, desktop/mobile branching via `matchMedia`
- `site/src/pages/AgentsPage/components/AgentPageHeader.stories.tsx` —
new Storybook coverage + `play` assertions that toggle state stays in
sync across breakpoints
- `site/src/pages/AgentsPage/components/ChimeButton.tsx` — optional
controlled `enabled` / `onToggle` props
- `site/src/pages/AgentsPage/components/WebPushButton.tsx` — optional
controlled `webPush` / `onToggle` props
-
`site/src/pages/AgentsPage/components/ChatElements/CompactOrgSelector.tsx`
— full-width dropdown class
- `site/src/pages/AgentsPage/components/ChatElements/ModelSelector.tsx`
— truncation, inline subtext, tooltip hidden on mobile, new props
(`open`, `onOpenChange`, `onTriggerTouchStart`)
- `site/src/pages/AgentsPage/components/ChatTopBar.tsx` — full-width
dropdown class
- `site/src/pages/AgentsPage/components/ContextUsageIndicator.tsx` —
full-width dropdown class (mobile branch)
- `site/src/pages/AgentsPage/components/Sidebar/AgentsSidebar.tsx` —
`ArrowLeftIcon`, filter dropdown class, top/bottom fade mask on scroll
area, `select-none` on tree nodes
- `site/src/pages/AgentsPage/components/Sidebar/SidebarTabView.tsx` —
`ArrowLeftIcon`, padding, back button placement
- `site/src/pages/AgentsPage/components/WorkspacePill.tsx` — compact
icon trigger, tooltip hidden on mobile, full-width dropdown class

</details>

> 🤖 Generated by Coder Agents

---------

Co-authored-by: Jaayden Halko <jaayden@coder.com>
2026-04-23 13:41:44 +01:00
Jake Howell 3b561dcea9 fix(site): simplify prerelease css (#24514)
> 🤖 This PR was modified by Coder Agent on behalf of Jake Howell

Removes the bottom-line navbar effect in `<NavbarView />` and cleans up
prerelease CSS.

- Remove `relative` from `<NavbarView />` as it was redundant with the
`sticky` class.
- Refactor `getPrereleaseFlag` to simplify version classification.
- Clean up unused prerelease CSS from `index.css`.

<img width="920" height="83" alt="image"
src="https://github.com/user-attachments/assets/84e351e6-d7a2-4fe2-a331-27c651266256"
/>
2026-04-21 02:11:25 +10:00
Jeremy RuppelandKayla はな a2b9b74f4a fix(site): use hard reload after login to reload metadata (#24239)
After password login, `navigate('/')` performed a client-side SPA
navigation, leaving the pre-authentication `<meta>` tags (including
`userAppearance`) empty in the DOM. The `ThemeProvider` read empty
metadata and fell through to `DEFAULT_THEME` until the API query
resolved, causing a visible flash of the wrong theme. A page refresh
fixed it because the server re-rendered HTML with the session cookie
present.

Replace `navigate('/')` with `window.location.href` so the server
re-renders HTML with all metadata tags populated (`userAppearance`,
`user`, `permissions`, etc.) via the new session cookie. This matches
the pattern already used for API route redirects on the same page. Also
uses `redirectTo` instead of hardcoded "/" so the redirect query
parameter is respected for password login.

Fixes https://github.com/coder/coder/issues/20050

> [!NOTE]
> Generated by Coder Agents

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

- Chose `window.location.href` over invalidating React Query caches
because a hard reload is the only way to get fresh server-rendered meta
tags. The API query approach still has a flash between mount and
response.
- Sanitized redirect URL with `redirectUrl.pathname` (same as existing
`<Navigate>` path) to prevent open redirects via absolute URLs.
- Removed unused `useNavigate` import.

</details>

---------

Co-authored-by: Kayla はな <kayla@tree.camp>
2026-04-17 16:28:03 -04:00
Kayla はな b149433138 chore: complete jest to vitest migration (#24216) 2026-04-10 14:04:24 -06:00
Matt Vollmer 36141fafad feat: stack insights tables vertically and paginate Pull requests table (#24198)
The "By model" and "Pull requests" tables on the PR Insights page
(`/agents/settings/insights`) were side-by-side at `lg` breakpoints, and
the Pull requests table was hard-capped at 20 rows by the backend.

- Replaced `lg:grid-cols-2` with a single-column stacked layout so both
tables span the full content width.
- Removed the `LIMIT 20` from the `GetPRInsightsRecentPRs` SQL query so
all PRs in the selected time range are returned.
- Can add this back if we need it. If we do, we should add a little
subheader above this table to indicate that we're not showing all PRs
within the selected timeframe.
- Added client-side pagination to the Pull requests table using
`PaginationWidgetBase` (page size 10), matching the existing pattern in
`ChatCostSummaryView`.
- Renamed the section heading from "Recent" to "Pull requests" since it
now shows the full set for the time range.
<img width="1481" height="1817" alt="image"
src="https://github.com/user-attachments/assets/0066c42f-4d7b-4cee-b64b-6680848edc68"
/>


> 🤖 PR generated with Coder Agents
2026-04-10 10:48:54 -04:00
Ethan f219834f5c perf(site): add reconnect jitter to reconnectingWebsocket (#24096)
## Motivation

During the April 2 dogfood incident, a pod OOM-kill triggered a
reconnection storm: hundreds of chat-stream and agent-RPC websockets all
attempted to reconnect at the same deterministic backoff intervals (1 s,
2 s, 4 s, …). Because every browser tab computed the same delay, the
surviving replicas received a synchronized wall of new connections at
each retry tick, amplifying the overload that caused the first OOM in
the first place.

The root cause of the memory blowup (chatd serialization cost) is a
separate issue. This change addresses the secondary blast-radius
problem: when N clients reconnect in lockstep, the retry storm itself
becomes a capacity threat.

## Change

The shared `createReconnectingWebSocket` utility now applies symmetric
jitter (default ±30%) to the capped exponential-backoff delay before
scheduling the reconnect timer. With 100 clients and a 1 s base delay,
reconnects spread over the 700 ms–1300 ms window instead of all landing
at exactly 1000 ms, and once retries hit `maxMs` the scheduler still
preserves downward spread instead of collapsing back to a single tick.

Two new options are accepted by callers:

- **`jitter`** (0–1 fraction, default `0.3`) — controls the jitter
window. Values are clamped to `[0, 1]`; `0` preserves exact legacy
timing.
- **`random`** (`() => number`, default `Math.random`) — injectable RNG,
primarily a deterministic test seam. Non-finite output falls back to the
midpoint (`0.5`).

The `retryingAt` timestamp surfaced to `ChatStatusCallout` is computed
from the jittered delay, so the countdown shown to users reflects the
actual retry time. The scheduler also keeps `maxMs` as a hard ceiling on
the final delay and saturates exponential overflow at that cap instead
of dropping to `0ms` retries.

No production callers need changes — the default jitter activates
automatically for all four call sites (`AgentsPage` chat-list watcher,
`AgentChatPage` workspace watcher, `useChatStore` per-chat stream,
`useGitWatcher`). The two downstream tests that asserted exact reconnect
timing now pin `Math.random()` to `0.5` so those expectations stay
deterministic.
2026-04-09 20:31:37 +10:00
Danielle Maywood 3d139c1a24 refactor(site): replace !! with Boolean() for boolean coercion (#24180) 2026-04-09 10:48:54 +01:00
Cian Johnston 461a31e5d8 feat(site): add under-construction navbar stripes for pre-release builds (#24157)
Dev and RC builds now show diagonal warning stripes in the navbar plus a
centered version badge, making it impossible to miss which build you're
running.

**Devel build:** amber "warning" from theme

**RC build:** sky "pending" from theme

> 🤖 Written by a Coder Agent. Will be reviewed by a human.
2026-04-08 20:10:03 +00:00
Danielle Maywood aede045549 chore: bump @biomejs/biome from 2.2 to 2.4.10 (#24074) 2026-04-07 12:22:18 +01:00
Cian Johnston 2a51687ff3 fix: stop amputating RC suffixes from docs URLs (#23903)
Fixes #23897 (docs link only — naming rename is in #23905)

- Fix version stripping logic in both Go (`codersdk/deployment.go`) and
TypeScript (`site/src/utils/docs.ts`) to preserve `-rc.X` suffixes
instead of amputating them along with `-devel`
- Add `v0.0.0` fallback in the TS frontend to match Go backend behavior
for dev builds
- Add tests covering RC, devel, and plain release version strings

> 🤖 Written by a Coder Agent. Will be reviewed by a human.
2026-04-01 13:05:14 +00:00
Kayla はな b9f140e53e chore: remove Language objects (#23866) 2026-03-31 15:26:59 -06:00
code-qtzl f44a8994da fix(site): improve keyboard navigation in help popovers (#23374) 2026-03-31 11:10:33 -06:00
Kayla はな aa81238cd0 chore: modernize all typescript imports (#23511) 2026-03-26 16:04:24 -06:00
Kayla はな 0e69e0eaca chore: modernize typescript api client/types imports (#23637) 2026-03-25 15:21:19 -06:00
Ethan 196c61051f feat(site): structured error/retry UX for agent chat (#23282)
> **PR Stack**
>
> 1. #23351 ← `#23282`
> 2. **#23282** ← `#23275` *(you are here)*
> 3. #23275 ← `#23349`
> 4. #23349 ← `main`

---

## Summary

Replaces raw error strings and infinite "Thinking..." spinners in the
agents chat UI with a structured live-status model that drives startup,
retry, and failure UI from one source of truth.

This branch also folds in the frontend follow-up fixes that fell out of
that refactor: malformed `retrying_at` timestamps no longer render
`Retrying in NaNs`, stale persisted generic errors no longer outlive a
recovered chat status, and partial streamed output stays visible when a
response fails after blocks have already rendered.

Consumes the structured error metadata added in #23275.
Retry-After header handling remains in #23351.

<img width="853" height="493" alt="image"
src="https://github.com/user-attachments/assets/5a4a1690-5e22-4ece-965c-a000fd669244"
/>

<img width="812" height="517" alt="image"
src="https://github.com/user-attachments/assets/e78d28ce-1566-48ca-a991-62c6e1838079"
/>

<img width="847" height="523" alt="image"
src="https://github.com/user-attachments/assets/e5fd7b60-4a3c-4573-ba4c-4e5f6dbfbdc3"
/>

## Problem

The previous AgentDetail chat UI derived startup, retry, and failure
behavior from several loosely connected bits of state spread across
`ChatContext`, `AgentDetailContent`, `ConversationTimeline`, and ad hoc
props. That made the UI inconsistent: some failures were just raw
strings, retry state could only partially describe what was happening,
startup could sit on an infinite spinner, and rendering decisions
depended on local booleans instead of one authoritative model.

Those splits also made edge cases brittle. Invalid retry timestamps
could produce broken countdown text, persisted generic errors could
linger after recovery, and streamed partial output could disappear if
the turn later failed.

## Fix

Introduce a structured live-status pipeline for AgentDetail.
`ChatContext` now normalizes stream errors and retry metadata into
richer state, `liveStatusModel` centralizes precedence and phase
derivation, and `ChatStatusCallout` renders startup, retry, and terminal
failure states with shared copy, provider attribution, status links,
attempt metadata, and guarded countdown handling.

`AgentDetailContent` and `ConversationTimeline` now consume that single
model instead of juggling separate error and stream booleans, while
usage-limit messaging stays on its explicit path. The result is a
timeline that shows consistent state transitions, preserves accumulated
assistant output across failures, suppresses stale generic errors once
live state recovers, and has focused model, store, and story coverage
around those behaviors.
2026-03-26 01:45:39 +11:00
Cian Johnston ef2eb9f8d2 fix: strip invisible Unicode from prompt content (#23525)
- Add `SanitizePromptText` stripping ~24 invisible Unicode codepoints
and collapsing excessive newlines
- Apply at write and read paths for defense-in-depth
- Frontend: warn in both prompt textareas when invisible characters
detected
- Explicit codepoint list (not blanket `unicode.Cf`) to avoid breaking
flag emoji
- 34 Go tests + idempotency meta-test, 11 TS unit tests, 4 Storybook
stories

> This PR was created with the help of Coder Agents, and was reviewed by my human.
2026-03-25 14:09:24 +00:00
Kayla はな e045f8c9e4 chore: additional typescript import modernization (#23536) 2026-03-24 16:04:39 -06:00