Commit Graph
3760 Commits
Author SHA1 Message Date
Kyle Carberry a5cc579453 feat: add last_injected_context column to chats table (#23798)
Adds a nullable JSONB column `last_injected_context` to the `chats`
table that stores the most recently persisted injected context parts
(AGENTS.md context-file and skill message parts). The column is updated
only when `persistInstructionFiles()` runs — on first workspace attach
or when the agent changes — so there are no redundant writes on
subsequent turns.

Internal fields (`ContextFileContent`, `ContextFileOS`,
`ContextFileDirectory`, `SkillDir`) are stripped at write time so the
column only holds small metadata. No stripping needed on the read path.

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

- New migration `000456` adds nullable `last_injected_context JSONB`
column.
- New SQL query `UpdateChatLastInjectedContext` writes the column
without touching `updated_at`.
- `persistInstructionFiles()` strips internal fields from parts via
`StripInternal()` before persisting.
- Sentinel path (no AGENTS.md) persists skill-only parts when skills
exist.
- `codersdk.Chat` exposes `LastInjectedContext []ChatMessagePart`
(omitempty).
- `db2sdk.Chat()` passes through the already-clean data.

</details>
2026-03-30 14:11:30 -04:00
Mathias Fredriksson d2c308e481 fix(site/src/pages/AgentsPage): unify scroll restore-guard lifecycle in ScrollAnchoredContainer (#23809)
Two ResizeObserver effects (content and container) each had their own
local restoreGuardRafId but both wrote to the shared
isRestoringScrollRef. Either observer's guard-clear RAF could fire
while the other's pin chain was in-flight, leaving isRestoringScrollRef
prematurely false.

scrollTranscriptToBottom also set isRestoringScrollRef without
cancelling any pending guard-clear, so a stale clear could drop
the flag mid-smooth-scroll animation.

Promote restoreGuardRafId to a single shared ref so all write
paths coordinate through one cancellation point.
2026-03-30 20:17:44 +03:00
Kyle Carberry 953c3bdc0f fix(site): prevent spurious startup warning during pending status (#23805)
## Problem

The `/agents` page frequently shows "Response startup is taking longer
than expected" even while the agent is actively working and messages are
appearing in the transcript.

## Root Cause

There's an inconsistency between `isActiveChatStatus` and
`shouldApplyMessagePart` during `"pending"` status (the state between
agent tool-call turns):

| Component | Treats `"pending"` as... |
|---|---|
| `isActiveChatStatus` | **active** — includes both `"running"` and
`"pending"` |
| `shouldApplyMessagePart` | **inactive** — drops all `message_part`
events during `"pending"` |
| Status handler | clears `streamState` to `null` on `"pending"` |

This creates a dead state during multi-turn tool-call cycles:

1. Agent finishes a turn → status = `"pending"` → `streamState` cleared
to `null`
2. `selectIsAwaitingFirstStreamChunk` returns `true` (status is
"active", stream is null, latest message isn't assistant)
3. Phase = `"starting"` → 15s timer starts
4. Stream parts from the server are **silently dropped**
(`shouldApplyMessagePart()` returns `false` for `"pending"`)
5. `streamState` stays `null` — phase is stuck at `"starting"`
6. Meanwhile, durable messages (tool calls, tool results) appear
normally in the transcript
7. After 15s → "Response startup is taking longer than expected" fires

## Fix

Narrow `selectIsAwaitingFirstStreamChunk` to only check `chatStatus ===
"running"` instead of `isActiveChatStatus(chatStatus)`. `"running"` is
the only status where the transport actually accepts stream parts, so
it's the only status where we should be showing the "starting"
indicator.

`isActiveChatStatus` is left unchanged since its other caller
(`shouldSurfaceReconnectState`) correctly needs to include `"pending"`.
2026-03-30 12:46:51 -04:00
Danielle Maywood 3f8e3007d8 fix(site): write WebSocket messages to React Query cache (#23618) 2026-03-30 15:56:08 +01:00
Danielle Maywood 6a2f389110 refactor(site/src/pages/AgentsPage): use createReconnectingWebSocket in git and workspace watchers (#23736) 2026-03-30 14:05:05 +01:00
Danielle Maywood 027f93c913 fix(site): make settings and analytics headers scrollable in Safari PWA (#23742) 2026-03-30 13:55:35 +01:00
Hugo Dutka 509e89d5c4 feat(site): refactor the wait for computer use subagent card (#23780)
Right now, when an agent is waiting for the computer use subagent, it
shows a VNC preview of the desktop that spans the full width of the
chat. It also displays a standard "waiting for <subagent name>" header
above it. See https://github.com/coder/coder/pull/23684 for a recording.

This PR refactors that preview to be smaller and changes the header to a
shimmering "Using the computer" label.


https://github.com/user-attachments/assets/0db5b4dc-6899-419b-bf7f-eb0de05722f1
2026-03-30 14:51:14 +02:00
Mathias Fredriksson 378f11d6dc fix(site/src/pages/AgentsPage): fix scroll-to-bottom pin starvation in agents chat (#23778)
scheduleBottomPin() cancelled any in-flight pin and restarted the
double-RAF chain on every ResizeObserver notification. When content
height changes on consecutive frames (e.g. during streaming where
SmoothText reveals characters each frame and markdown re-rendering
occasionally changes block height), the inner RAF that actually sets
scrollTop is perpetually cancelled before it fires. The scroll falls
behind the growing content.

Two fixes:

1. Make scheduleBottomPin() idempotent: if a pin is already in-flight,
   skip. The inner RAF reads scrollHeight at execution time so it
   always targets the latest bottom. User-interrupt paths (wheel,
   touch) still cancel via cancelPendingPins().

2. Add overscroll-behavior:contain to the scroll container. Prevents
   elastic overscroll from generating extra scroll events that could
   flip autoScrollRef to false.
2026-03-30 12:36:42 +00:00
Mathias Fredriksson f2845f6622 feat(site): humanize process_signal and show killed on processes (#23590)
Replace the raw JSON dump for process_signal with the standard
ToolCollapsible + ToolIcon + ToolLabel pipeline, matching process_list
and other generic tools. A thin ProcessSignalRenderer promotes soft
failures (success=false, isError=false) so the generic renderer shows
the error indicator. ToolLabel distinguishes running, success, and
failure states. TerminalIcon used for consistency with other process
tools.

When a process is killed via process_signal, the execute and
process_output blocks show a red OctagonX icon with signal details
on hover. The killedBySignal field is set on MergedTool during the
existing cross-message parsing pass, no new abstractions.

Stories for process_signal (10) and killed indicators (8). Unit
tests for the cross-tool annotation logic (3). Humanized labels
and TerminalIcon for process_list.
2026-03-30 15:35:39 +03:00
Jeremy Ruppel 076e97aa66 feat(site): add client filter to AI Bridge Session table (#23733) 2026-03-30 08:15:45 -04:00
548a648dcb feat(site): add AI session thread page (#23391)
Adds the Session Thread page

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Jake Howell <jacob@coder.com>
2026-03-30 08:03:52 -04:00
Danielle Maywood 4c97b63d79 fix(site/src/pages/AgentsPage): toast when git refresh fails due to disconnection (#23779) 2026-03-30 12:35:23 +01:00
Danielle Maywood 7a5fd4c790 fix(site): align plus menu icons and add small switch variant (#23769) 2026-03-30 11:18:18 +01:00
Danielle Maywood 0b07ce2a97 refactor(site): move AgentChatPageView to correct directory (#23770) 2026-03-30 10:49:21 +01:00
Jaayden Halko 0e78156bcd fix: create scrollable proxy menu list (#23764)
This allows the proxy menu to scroll for very large numbers of proxies
in the menu.

<img width="334" height="802" alt="screenshot"
src="https://github.com/user-attachments/assets/f0e45b9c-5b77-43da-b566-28d0572fd56b"
/>
2026-03-30 09:39:30 +01:00
TJandJaayden Halko 78986efed8 fix(site): hide table headers during loading and empty states on workspaces page (#23446)
## Problem

When the workspaces table transitions between states (loading →
populated, or populated → empty search results), the table column
headers visibly jump. This happens because the Actions column's width is
content-driven: when workspace rows are present, the action buttons give
it intrinsic width, shrinking the Name/Template/Status columns. When the
body is empty or loading, the Actions column collapses to zero, and the
other columns expand to fill the space.

## Solution

Hide the header content during loading and empty states using
`visibility: hidden` (Tailwind's `invisible` class), which preserves the
row's layout height but hides the text. This prevents the visual jump
since headers aren't visible during the states where column widths
differ.

- **Loading**: first column shows a skeleton bar matching the body
skeleton aesthetic; other columns are invisible
- **Empty search results**: all header content is invisible
- **Populated**: headers display normally

---------

Co-authored-by: Jaayden Halko <jaayden@coder.com>
2026-03-29 23:28:30 -07:00
Kyle Carberry 4d2b0a2f82 feat: persist skills as message parts like AGENTS.md (#23748)
## Summary

Skills are now discovered once on the first turn (or when the workspace
agent changes) and persisted as `skill` message parts alongside
`context-file` parts. On subsequent turns, the skill index is
reconstructed from persisted parts instead of re-dialing the workspace
agent.

This makes skills consistent with the AGENTS.md pattern and is
groundwork for a future `/context` endpoint that surfaces loaded
workspace context to the frontend.

## Changes

- Add `skill` `ChatMessagePartType` with `SkillName` and
`SkillDescription` fields
- Extend `persistInstructionFiles` to also discover and persist skills
as parts
- Add `skillsFromParts()` to reconstruct skill index from persisted
parts on subsequent turns
- Update `runChat()` to use `skillsFromParts` instead of re-dialing
workspace for skills
- Frontend: handle new `skill` part type (skip rendering, hide
metadata-only messages)

## Before / After

| | AGENTS.md | Skills |
|---|---|---|
| **Before** | Persist as `context-file` parts, reconstruct from parts |
In-memory `skillsCache` only, re-dial workspace on cache miss |
| **After** | Persist as `context-file` parts, reconstruct from parts |
Persist as `skill` parts, reconstruct from parts |

The in-memory `skillsCache` remains for `read_skill`/`read_skill_file`
tool calls that need full skill bodies on demand.

<details><summary>Design context</summary>

This is the first step toward a unified workspace context
representation. Currently:
- Context files are persisted as message parts (works)
- Skills were only in-memory (inconsistent)
- Workspace MCP servers are cached in-memory (future work)

Persisting skills as parts means a future `/context` endpoint can query
both context files and skills from the same message parts in the DB,
without depending on ephemeral server-side caches.
</details>
2026-03-29 21:48:17 -04:00
Danielle Maywood 588beb0a03 fix(site): restore mobile back button on agent settings pages (#23752) 2026-03-28 23:12:49 +00:00
Michael Suchacz bfeb91d9cd fix: scope title regeneration per chat (#23729)
Previously, generating a new agent title used a page-global pending
state, so one in-flight regeneration disabled the action for every chat
in the Agents UI.

This change tracks regenerations by chat ID, updates the Agents page
contracts to use `regeneratingTitleChatIds`, and adds sidebar story
coverage that proves only the active chat is disabled.
2026-03-29 00:01:53 +01:00
Danielle Maywood a399aa8c0c refactor(site): restructure AgentsPage folder (#23648) 2026-03-28 21:33:42 +00:00
Danielle Maywood 565cf846de fix(site): fix exec tool layout shift on command expand (#23739) 2026-03-28 16:27:54 +00:00
Kyle Carberry a2799560eb fix: use Popover for context indicator on mobile viewports (#23747)
## Problem

The context-usage indicator ring in the agents chat uses a Radix UI
`Tooltip`, which only opens on hover. On mobile/touch devices there is
no hover event, so tapping the indicator does nothing.

## Fix

On mobile viewports (`< 640 px`, matching the existing
`isMobileViewport()` helper), render a `Popover` instead of a `Tooltip`
so that tapping the ring toggles the context-usage info. Desktop
behavior (hover tooltip) is unchanged.

- Extract the trigger button and content into shared variables to avoid
duplication
- Conditionally render `Popover` (mobile) or `Tooltip` (desktop) based
on viewport width
- Both `Popover` and `PopoverContent` were already imported in the file
2026-03-28 12:26:23 -04:00
Michael Suchacz 73bde99495 fix(site): prevent mobile scroll jump during active touch gestures (#23734) 2026-03-28 16:24:12 +01:00
Kyle Carberry a708e9d869 feat: add tool rendering for read_skill, read_skill_file, start_workspace (#23744)
These tools previously fell through to the `GenericToolRenderer` which
showed a wrench icon and the raw tool name. Now they get dedicated icons
and contextual labels.

## Changes

**ToolIcon.tsx** — new icon mappings:
- `read_skill` / `read_skill_file` → `BookOpenIcon`
- `start_workspace` → `PlayIcon`
- `web_search` → `SearchIcon`

**ToolLabel.tsx** — contextual labels:
- `read_skill`: "Reading skill {name}…" → "Read skill {name}"
- `read_skill_file`: "Reading {skill}/{path}…" → "Read {skill}/{path}"
- `start_workspace`: "Starting workspace…" → "Started {name}"
- `web_search`: "Searching \"{query}\"…" → "Searched \"{query}\""

**tool.stories.tsx** — 12 new stories covering running, completed, and
error states for all four tools.

<details>
<summary>Visually verified in Storybook</summary>

All 10 story variants verified:
- ReadSkillRunning / ReadSkillCompleted / ReadSkillError
- ReadSkillFileRunning / ReadSkillFileCompleted / ReadSkillFileError
- StartWorkspaceRunning / StartWorkspaceCompleted / StartWorkspaceError
- WebSearchRunning / WebSearchCompleted / WebSearchNoQuery
</details>
2026-03-28 11:16:11 -04:00
Danielle Maywood 399080e3bf fix(site/src/pages/AgentsPage): preserve right panel tab state across switches (#23737) 2026-03-28 00:00:17 +00:00
Danielle Maywood 50d9d510c5 refactor(site/src/pages/AgentsPage): clean up manual ref-sync callback patterns (#23732) 2026-03-27 23:09:51 +00:00
eda1bba969 fix(site): show table loader during session list pagination (#23719)
This patch shows the loading spinner on the AI Bridge Sessions table
during pagination. These API calls can take a noticeable amount of time
on dogfood, and without this it shows stale data while the fetch is
fetching.

Claude with the assist 🤖

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Kayla はな <mckayla@hey.com>
2026-03-27 19:09:42 -04:00
Danielle MaywoodandKayla はな 808dd64ef6 fix(site): reduce dropdown menu font size on agents page (#23711)
Co-authored-by: Kayla はな <mckayla@hey.com>
2026-03-27 23:09:27 +00:00
Kayla はな 04f7d19645 chore: additional typescript import modernization (#23722) 2026-03-27 16:41:56 -06:00
71a492a374 feat: implement <ClientFilter /> to AI Bridge request logs (#22694)
Closes #22136

This pull-request implements a `<ClientFilter />` to our `Request Logs`
page for AI Bridge. This will allow the user to select a client which
they wish to filter against. Technically the backend is able to actually
filter against multiple clients at once however the frontend doesn't
currently have a nice way of supporting this (future improvement).

<img width="1447" height="831" alt="image"
src="https://github.com/user-attachments/assets/0be234e2-25f2-4a89-b971-d74817395da1"
/>

---------

Co-authored-by: Jeremy Ruppel <jeremy.ruppel@gmail.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-27 17:18:28 -04:00
Charlie VoiselleandKayla はな 8c494e2a77 fix(site): sever opener in openAppInNewWindow for slim-window apps (#23117)
Split out from #23000.

## Problem

`openAppInNewWindow()` opens workspace apps in a slim popup without
`noopener`, leaving `window.opener` intact. Since workspace apps can
proxy arbitrary user-hosted content under the dashboard origin, this
exposes the Coder dashboard to tabnabbing and same-origin DOM access.

We cannot simply pass `"noopener"` to `window.open()` because the WHATWG
spec mandates that `window.open()` returns `null` when `"noopener"` is
present — indistinguishable from a blocked popup — which would break the
popup-blocked error toast.

## Solution

Use a two-step approach in `openAppInNewWindow()`:

1. Open `about:blank` first (without `noopener`) so we can detect popup
blockers via the `null` return
2. If the popup succeeded, sever the opener reference with `popup.opener
= null`
3. Navigate the popup to the target URL via `popup.location.href`

This preserves popup-block detection while eliminating the security
exposure.

## Tests

A vitest case in `AppLink.test.tsx` asserts that `open_in="slim-window"`
anchors do not carry `target` or `rel` attributes (since slim-window
opening is handled programmatically via `onClick` / `window.open()`, not
anchor attributes).

---------

Co-authored-by: Kayla はな <kayla@tree.camp>
2026-03-27 15:35:10 -04:00
Kyle Carberry 6b77fa74a1 fix: remove bold font-weight from unread chats in agents sidebar (#23725)
Removes the `font-semibold` class applied to unread chat titles in the
`/agents` sidebar. The unread indicator dot already provides sufficient
visual distinction for unread chats.
2026-03-27 13:36:49 -04:00
Danielle Maywood 60065f6c08 fix(site): replace inline delete confirmations with modals (#23710) 2026-03-27 17:02:18 +00:00
Kyle Carberry bcdc35ee3e feat: add chat read/unread indicator to sidebar (#23129)
## Summary

Adds read/unread tracking for chats so users can see which agent
conversations have new assistant messages they haven't viewed.

## Backend Changes

- Adds `last_read_message_id` column to the `chats` table (migration
000439).
- Computes `has_unread` as a virtual column in `GetChatsByOwnerID` using
an `EXISTS` subquery checking for assistant messages beyond the read
cursor.
- Exposes `has_unread` on the `codersdk.Chat` struct and auto-generated
TypeScript types.
- Updates `last_read_message_id` on stream connect/disconnect in
`streamChat`, avoiding per-message API calls during active streaming.
- Uses `context.WithoutCancel` for the deferred disconnect write so the
DB update succeeds even after the client disconnects.

## Frontend Changes

- Bold title (`font-semibold`) for unread chats in the sidebar.
- Small blue dot indicator next to the relative timestamp.
- Suppresses unread indicator for the currently active chat via
`isActive` from NavLink.

## Design Decisions

- Only `assistant` messages count as unread — the user's own messages
don't trigger the indicator.
- No foreign key on `last_read_message_id` since messages can be deleted
(via rollback/truncation) and the column is just a high-water mark.
- Zero API calls during streaming: exactly 2 DB writes per stream
session (connect + disconnect).
- Unread state refreshes on chat list load and window focus. The
`watchChats` WebSocket optimistically marks non-active chats as unread
on `status_change` events, but does not carry a server-computed
`has_unread` field. Navigating to a chat optimistically clears its
unread indicator in the cache.
2026-03-27 12:15:04 -04:00
Ehab Younes 97a27d3c09 fix(site/src/pages/AgentsPage): fire chat-ready after store sync so scroll-to-bottom hits rendered DOM (#23693)
`onChatReady` now waits for the store to have all fetched messages (not
just query success), so the DOM has content when the parent frame acts
on the signal. Removed the `scroll-to-bottom` round-trip from
`onChatReady`, the embed page no longer needs the parent to echo a
scroll command back.

Also moved `autoScrollRef` check before the `widthChanged` guard in
`ScrollAnchoredContainer`'s ResizeObserver. The width guard is only
relevant for scroll compensation (user scrolled up); it should never
prevent pinning to bottom when auto-scroll is active. Also tightened the
store sync guard to `storeMessageCount < fetchedMessageCount`.
2026-03-27 18:01:38 +03:00
Kyle Carberry 4ed9094305 perf(site): memoize chat rendering hot path (#23720)
Addresses chat page rendering performance. Profiling with React Profiler
showed `AgentChat` actual render times of 20–31ms (exceeding the
16ms/60fps
budget), with `StickyUserMessage` as the #1 component bottleneck at
35.7%
of self time.

## Changes

**Hoist `createComponents` to module scope** (`response.tsx`):
Previously every `<Response>` instance called `createComponents()` per
render, creating a fresh components map that forced Streamdown to
discard
its cached render tree. Now both light/dark variants are precomputed
once
at module scope.

**Wrap `StickyUserMessage` in `memo()`** (`ConversationTimeline.tsx`):
Profile-confirmed #1 bottleneck. Each instance carries
IntersectionObserver
+ ResizeObserver + scroll handlers; skipping re-render avoids all that
setup.

**Wrap `ConversationTimeline` in `memo()`**
(`ConversationTimeline.tsx`):
Prevents cascade re-renders from the parent when props haven't changed.

**Remove duplicate `buildSubagentTitles`** (`ConversationTimeline.tsx` →
`AgentDetailContent.tsx`): Was computed in both `AgentDetailTimeline`
and
`ConversationTimeline`. Now computed once and passed as a prop.

<details>
<summary>Profiling data & analysis</summary>

### Profiler Metrics
| Metric | Value |
|--------|-------|
| INP (Interaction to Next Paint) | 82ms |
| Processing duration (event handlers) | 52ms |
| AgentChat actual render | 20–31ms (budget: 16ms) |
| AgentChat base render (no memo) | ~100ms |

### Top Bottleneck Components (self-time %)
| Component | Self Time | % |
|-----------|-----------|---|
| StickyUserMessage | 11.0ms | 35.7% |
| ForwardRef (radix-ui) | 7.4ms | 24.0% |
| Presence (radix-ui) | 2.0ms | 6.5% |
| AgentChatInput | 1.4ms | 4.5% |

### Decision log
- Chose module-scope precomputation over `useMemo` for
`createComponents`
  because there are only two possible theme variants and they're static.
- Did not add virtualization — sticky user messages + scroll anchoring
  make it complex. The memoization fixes should be measured first.
- Did not wrap `BlockList` in `memo()` — the React Compiler (enabled for
  `pages/AgentsPage/`) already auto-memoizes JSX elements inside it.
- Phase 2 (verify React Compiler effectiveness on
`parseMessagesWithMergedTools`)
and Phase 3 (radix-ui Tooltip lazy-mounting) deferred to follow-up PRs.

</details>
2026-03-27 14:28:27 +00:00
Kyle Carberry d973a709df feat: add model_intent option to MCP server configs (#23717)
Add a per-MCP-server `model_intent` toggle that wraps tool schemas with
a
`model_intent` field, requiring the LLM to provide a human-readable
description of each tool call's purpose. The intent string is shown as a
status label in the UI instead of opaque tool names, and is
transparently
stripped before the call reaches the remote MCP server.

Built-in tools have rich specialized renderers (terminal blocks, file
diffs,
etc.) and don't need this. MCP tools hit `GenericToolRenderer` which
only
shows raw tool names and JSON — that's where model_intent adds value.

The model learns what to provide via the JSON Schema `description` on
the
`model_intent` property itself — no system prompt changes needed.

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

### Architecture

Inspired by the `withModelIntent()` pattern from `coder/blink`, adapted
for
Go + React. The wrapping is entirely in the `mcpclient` layer — tool
implementations never see `model_intent`.

**Schema wrapping** (`mcpToolWrapper.Info()`): When enabled, wraps the
original tool parameters under a `properties` key and adds a
`model_intent`
string field with a rich description that teaches the model inline.

**Input unwrapping** (`mcpToolWrapper.Run()`): Strips `model_intent` and
unwraps `properties` before forwarding to the remote MCP server. Handles
three input shapes models may produce:
1. `{ model_intent, properties: {...} }` — correct format
2. `{ model_intent, key: val, ... }` — flat, no wrapper
3. Malformed — falls through gracefully

**Frontend extraction**: `streamState.ts` extracts `model_intent` from
incrementally parsed streaming JSON. `messageParsing.ts` extracts it
from
persisted tool call args.

**UI rendering**: `GenericToolRenderer` shows the capitalized intent
string
as the primary label when available, falling back to the raw tool name.

### Changes
- Database: `model_intent` boolean column on `mcp_server_configs`
- SDK: `ModelIntent` field on config/create/update types
- API: pass-through in create/update handlers + converter
- mcpclient: schema wrapping in `Info()`, input unwrapping in `Run()`
- Frontend: extraction from streaming + persisted args
- UI: intent label in `GenericToolRenderer`, toggle in admin panel
- Tests: 6 new tests (schema wrapping, unwrapping, passthrough,
fallback)

### Decision log
- **Option lives on MCPServerConfig, not model config**: Built-in tools
  already have rich renderers; only MCP tools benefit from model_intent.
- **No system prompt changes**: The JSON Schema `description` on the
  `model_intent` property teaches the model inline.
- **Pointer bool on update request**: Follows existing pattern (`*bool`)
  so PATCH requests don't reset the value when omitted.

</details>
2026-03-27 14:23:25 +00:00
Danielle Maywood 0ec0f8faaf fix: guard per-chat cancelQueries to prevent 'Chat not found' race (#23714) 2026-03-27 13:08:58 +00:00
Matt Vollmer 9e33035631 fix: optimistic pinned reorder and post-drag click suppression (#23704)
## Summary

Fixes two issues with pinned chat drag-to-reorder in the agents sidebar:

1. **Missing optimistic reorder**: After dragging a pinned chat to a new
position, the sidebar flashed back to the old order while waiting for
the server response, then snapped to the new order. Now the react-query
cache is updated optimistically in `onMutate` so the reorder is visually
instant.

2. **Post-drag navigation on upward drag**: Dragging a pinned chat
**upward** caused unintended navigation to that chat on drop. The
browser synthesizes a click from the final `pointerup`, and the previous
suppression listener was attached too low in the DOM tree to reliably
intercept it after items reorder.

## Changes

### Optimistic cache reorder (`site/src/api/queries/chats.ts`)

`reorderPinnedChat.onMutate` now extracts all pinned chats from the
cache, reorders them to match the new position, and writes the updated
`pin_order` values back via `updateInfiniteChatsCache`. The server
response still reconciles on `onSettled`.

### Document-level click suppression (`AgentsSidebar.tsx`)

- Moved click suppression from the pinned container div to
`document.addEventListener('click', handler, true)`. This fires before
any element-level handlers regardless of DOM reordering during drag.
Scoped via `pinnedContainerRef.contains(target)` so it only affects
pinned rows.
- Replaced `recentDragRef` (boolean cleared by `setTimeout(100ms)`) with
`lastDragEndedAtRef` (`performance.now()` timestamp checked against a
300ms window). This eliminates the race where the timeout fires before
the synthetic click arrives.

---

PR generated with Coder Agents
2026-03-27 07:57:23 -04:00
Ethan 83b2f85d63 feat(site/src/pages/AgentsPage): add ArrowUp shortcut to edit last user message (#23705)
Add a keyboard shortcut (ArrowUp on empty input) to start editing the
most recent user message, mirroring Mux's behavior. The shortcut reuses
the existing history-edit flow triggered by the pencil button.

Extract a shared `getEditableUserMessagePayload` helper so the pencil
button and the new shortcut both derive the edit payload identically.
Derive the last editable user message during render in
`AgentDetailInput` from the existing store selectors, keeping the
implementation Effect-free and React Compiler friendly.
2026-03-27 21:43:31 +11:00
Jaayden Halko 86c3983fc0 feat: add AI Governance seat capacity banners (#23411)
## Summary

Add site-wide banners for AI Governance seat usage thresholds:

1. **90% capacity warning (admin-only):** When actual AI Governance
seats are ≥90% and <100% of the license limit, admins see:
   > "You have used 90% of your AI governance add-on seats."

2. **Over-limit banner (admin-only):** When actual seats exceed the
license limit, admins see a prominent warning:
> "Your organization is using {actual} / {limit} AI Governance user
seats ({X}% over the limit). Contact sales@coder.com"
   - Uses floor whole percentage (Go int division / `Math.floor`)
   - Includes a clickable `mailto:sales@coder.com` link
2026-03-27 05:51:51 +00:00
Michael Suchacz 2312e5c428 feat: add manual chat title regeneration (#23633)
## Summary

Adds a "Generate new title" action that lets users manually regenerate a
chat's title using richer conversation context than the automatic
first-message title path.

## Changes

### Backend
- **New endpoint:** `POST
/api/experimental/chats/{chatID}/title/regenerate` returns the updated
Chat with a regenerated title
- **Manual title algorithm:** Extracts useful user/assistant text turns
→ selects first user turn + last 3 turns → builds context with gap
markers → renders prompt with anti-recency guidance → calls lightweight
model → normalizes output
- **Helpers:** `extractManualTitleTurns`,
`selectManualTitleTurnIndexes`, `buildManualTitleContext`,
`renderManualTitlePrompt`, `generateManualTitle` — all private, with the
public `Server.RegenerateChatTitle` method
- **SDK:** `ExperimentalClient.RegenerateChatTitle(ctx, chatID) (Chat,
error)`
- Persists title via existing `UpdateChatByID` and broadcasts
`ChatEventKindTitleChange`

### Frontend
- API client method + React Query mutation with cache invalidation
- "Generate new title" menu item (with wand icon) in both TopBar and
Sidebar dropdown menus
- Loading/disabled state while regeneration is in-flight
- Error toast on failure
- Stories updated for both menus

### Tests
- `quickgen_test.go`: Table-driven tests for all 4 helper functions
(turn extraction, index selection, context building, prompt rendering)
- `exp_chats_test.go`: Handler tests (ChatNotFound,
NotFoundForDifferentUser, NoDaemon)

## Design notes
- The existing auto-title path (`maybeGenerateChatTitle`, `titleInput`)
is completely unchanged
- Manual regeneration uses richer context (first user turn + last 3
turns + gap markers) vs the auto path's single first message
- Endpoint is experimental and marked with `@x-apidocgen {"skip": true}`
2026-03-27 01:47:19 +01:00
Michael Suchacz f35f2a28e6 refactor(site/src/pages/AgentsPage): normalize transcript scrolling to top-to-bottom flow (#23668)
Converts the Agents page transcript from reverse-scroll
(\`flex-col-reverse\`) to normal top-to-bottom document flow with
explicit auto-scroll pinning.

The previous \`flex-col-reverse\` approach (from #23451) required
browser-specific workarounds for Chrome vs Firefox \`scrollTop\` sign
conventions, and compensation logic that could fight user scroll intent
during streaming. This replaces it with standard scroll math
(\`scrollHeight - scrollTop - clientHeight\`) while keeping the proven
\`ScrollAnchoredContainer\` observer machinery.

Changes:
- \`AgentDetailView.tsx\`: layout flip to \`flex-col\`, standard bottom
detection, explicit prepend restoration via \`pendingPrependRef\`
snapshot, sentinel moved inside content wrapper, initial mount bottom
pin, \`scrollToBottomRef\` imperative API for send/edit, user-interrupt
guard with \`isNearBottom\` check
- \`AgentDetailView.stories.tsx\`: all scroll stories updated for
normal-flow semantics, new \`ScrollAnchorPreservedOnOlderHistoryLoad\`
story with deterministic \`IntersectionObserver\` mock
- \`AgentsSkeletons.tsx\` + \`AgentDetailLoadingView\`: removed
\`flex-col-reverse\` so loading state matches the real transcript layout
- \`AgentDetail.tsx\`: replaced \`scrollTop = 0\` on send/edit with
imperative \`scrollToBottomRef\` call

Supersedes #23576 (which was reverted in #23638). Same behavioral goals
but keeps scroll logic local to \`ScrollAnchoredContainer\` rather than
extracting a separate hook.
2026-03-27 01:31:17 +01:00
Kayla はな 4fab372bdc chore: modernize utils/ imports (#23698) 2026-03-26 16:20:25 -06:00
Kayla はな aa81238cd0 chore: modernize all typescript imports (#23511) 2026-03-26 16:04:24 -06:00
Hugo Dutka f87d6e6e82 feat(site): rich preview for the spawn_computer_use_agent tool (#23684)
Adds preview cards for the `spawn_computer_use_agent` tool. Spawning
that agent now renders a rich saying "Spawning computer use sub-agent".
The "waiting for" tool now displays an inline desktop preview which can
be clicked to reveal the desktop in the sidebar.


https://github.com/user-attachments/assets/e486ca0e-a569-4142-bb12-db3b707967b8
2026-03-26 21:52:03 +00:00
Matt Vollmer 113aaa79a0 feat: add pinned chats with drag-to-reorder (#23615)
https://github.com/user-attachments/assets/bd5d12a1-61b3-4b7d-83b6-317bdfb60b3c

## Summary

Adds pinned chats to the agents page sidebar with server-side
persistence and drag-to-reorder. Users can pin/unpin chats via the
context menu, and pinned chats appear in a dedicated "Pinned" section
above the time-grouped list.

## Database

Migration `000453_chat_pin_order`: adds `pin_order integer DEFAULT 0 NOT
NULL` column on `chats` (0 = unpinned, 1+ = pinned in display order).
Three SQL queries handle pin operations server-side using CTEs with
`ROW_NUMBER()`:

- `PinChatByID`: normalizes existing orders and appends to end
- `UnpinChatByID`: sets target to 0 and compacts remaining pins
- `UpdateChatPinOrder`: shifts neighbors, clamps to `[1, pinned_count]`

All queries exclude archived chats. `ArchiveChatByID` clears `pin_order`
on archive. The handler rejects pinning archived chats with 400.

## Backend

Pin/unpin/reorder go through the existing `PATCH
/api/experimental/chats/{chat}` via the `pin_order` field on
`UpdateChatRequest`. The handler routes based on current pin state:
`pin_order == 0` unpins, `> 0` on an already-pinned chat reorders, `> 0`
on an unpinned chat appends to end.

## Frontend

- `pinChat` / `unpinChat` / `reorderPinnedChat` optimistic mutations
using shared `isChatListQuery` predicate
- Sidebar renders Pinned section above time groups, excludes pinned
chats from time groups
- Pin/Unpin context menu items (hidden for child/delegated chats)
- `@dnd-kit/core` + `@dnd-kit/sortable` for drag-to-reorder with
`MouseSensor`, `TouchSensor`, and `KeyboardSensor`
- Local pin-order override prevents flash on drop; click blocker
prevents NavLink navigation after drag

---
*PR generated with Coder Agents*
2026-03-26 16:52:02 -04:00
Mathias Fredriksson f3a8096ff6 perf(site): move useSpeechRecognition into compiled path (#23689)
The hook lived at src/hooks/, outside the React Compiler scope.
It returned a new object literal every render, causing three
handler guards and two downstream JSX guards in AgentChatInput
(233 cache slots) to always miss.

Move the hook to src/pages/AgentsPage/hooks/ where the compiler
processes it. The compiler auto-memoizes the return object, so
manual useMemo is unnecessary.

Also replace ctorRef.current render-time access with a useState
lazy initializer. The ref access caused a CompileError that
would have prevented compilation. Browser API availability is
constant, so useState captures it once.
2026-03-26 22:34:34 +02:00
Jeremy Ruppel beece6d351 fix(site): make AI Bridge sessions query key more specific (#23687)
Seeing what appears to be a react-query caching issue on dogfood. Not
able to repro on my workspace, so this is sort of a shot in the dark 🤷
2026-03-26 16:13:02 -04:00
Jeremy Ruppel 58f744a5c1 fix(site): remove noisy tooltips from AI Bridge session row (#23690)
The provider and client icons both had a tooltip containing the exact
same content as the badge. This removes the tooltips
2026-03-26 15:58:10 -04:00