GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — feat(site): drag chats onto project folders in the sidebar by ibetitsmike · Pull Request #30795 · coder/coder

Conversation

@ibetitsmike

@ibetitsmike ibetitsmike commented Oct 14, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown
Collaborator

In the sidebar, the owner of a chat can now drag it onto a project folder to move it there, or onto Chats to remove it from its project, with the mouse or the keyboard.

Stack (moving chats between projects, bottom first):

  1. feat: move a chat family between projects in chatd #30893: the move in chatd
  2. feat: move chats between projects through the chat update API #30894: the chat update API takes project_id
  3. feat(site/src): move chats between projects from the chat menu #30895: Move to project in the chat menu
  4. This PR: drag a chat onto a project folder in the sidebar

Why: the menu from #30895 takes three clicks. Dragging a chat onto a folder is the direct way, and it uses the same mutation.

  • Only the root row of a chat that the menu could move is draggable: the owner's root chat while it and its subagents are idle and not archived, with the chat-projects experiment. Pinned chats keep their reorder drag and move through the menu.
  • While a chat is dragged, the targets it cannot move to are muted: its own folder, the folders of other organizations, and Chats for a chat that is already there or whose project did not load. A drop on a folder opens it, so the user sees the chat arrive.
  • While Projects is collapsed, a chat dragged over its header opens it, so the folders can take the drop. The section stays open, as after a click on the header, and a drop on the header itself moves nothing.
  • Screen readers hear the chat title and the folder name during the drag, never an ID. A pinned drag also announces its position.
  • The chat link is the drag handle, on pinned rows too. Space picks a chat up and Enter still opens it. The row adds no tab stop, and no button role wraps its link and buttons.
  • The project drags and the pinned reorder now use @dnd-kit/react 0.5.1 in place of @dnd-kit/core and @dnd-kit/sortable. @dnd-kit/react, @dnd-kit/dom, @dnd-kit/abstract and @dnd-kit/collision have exact version pins, because the library is not at 1.0. Its Accessibility plugin is off, because it would give the chat links a button role, so ChatDragAnnouncer writes the instructions and the live announcements. The SyncDragTargetOnScroll plugin checks the drag target again after the list scrolls, which dnd-kit skips while the pointer is still, and turns dnd-kit's own scrolling off during a keyboard drag, which scrolls each target into view itself.
  • The sidebar no longer shows the organization name next to a folder whose name another organization also uses. The folder's accessible names, the drag announcements and the delete dialog still name it.

What to scrutinize: the drop rules and SyncDragTargetOnScroll in chats/projectDrag.ts, and handleDragEnd() in ChatsPanel.tsx. Most of the ChatsPanel.tsx diff is indentation from the wider DragDropProvider, so read it with whitespace hidden.

The drag preview of a chat over the Billing folder, which shows an outline

Pixel: ChatDraggedOverProjectFolder, ChatDroppedOnProjectFolder (the dropped chat in the folder that the drop opens), LooseChatDragMutesUnavailableTargets, ProjectChatDragMutesItsFolder, UnloadedProjectChatDragMutesChats, SameNamedProjects (two same-named folders with no organization text) and ChatDraggedOverCollapsedProjects (the section that a keyboard drag over its collapsed header opens). After the library change, the dropped row in ChatDroppedOnProjectFolder keeps its focus ring.

Testing

  • Vitest passes on the ChatsSidebar directory and AgentsPageLayout (269 tests), and so do their Storybook tests (173). Biome, oxlint, tsc, dpdm, the React Compiler check and knip are clean, and CI passes.
  • The drag tests check the request that a drop sends, or that it sends none, for keyboard and mouse drags onto folders and Chats. A node identity test fails when a row remounts, and closes its open menu, as its chat status changes. A Tab and Enter test fails when the row wrapper is the handle again or when Enter picks a chat up. The pinned reorder test checks the position announcement and that the dragged link keeps the focus. Two tests drag onto the collapsed Projects header, by mouse and by keyboard, and one scrolls the list under a still pointer before the drop.
  • Checked in Chromium, because jsdom has no layout: a mouse drop on a folder sends the request, a keyboard reorder of the pins keeps the focus on the link, and the end of a drag does not open the chat. A local probe also covered the scroll cases: a wheel turn during a mouse drag, keyboard steps in a scrolled list, and auto-scroll near both edges.
  • Dogfood UAT on a deployment of the whole stack, before the library change, passed for the menu, the visibility rules, the busy 409, the API rules, the instructions of the next turn, live updates, drag and drop, the composer copy and the audit entry. It also read the drag live region: keyboard and mouse drags announce the chat title and the folder name.
  • Remote UAT of the library change compared each failure with 1ada34d. Round 5 found two regressions, fixed in f663f72: a drop with the pointer on a muted folder moved the chat to the folder next to it, and a keyboard drag could not reach a Chats taller than the window. Round 6 found two more, fixed in 76d4073: a wheel turn during a mouse drag left the drop on the folder the pointer had left, and keyboard steps got stuck in a scrolled list. Round 7 found one more, fixed in e6bbcea: after a scroll, a keyboard step could keep the previous folder as the target, so a drop moved the chat there. The live region also no longer changes after a drop. Round 8 verified those fixes and found one minor difference that stays: after a drag ends, a new drag starts only once the 250 ms drop animation is over, because dnd-kit 0.5.1 keeps the drag busy until then (1ada34d starts one at once). Turning the animation off removes that wait, but a local Chromium probe showed that a keyboard drop into a folder then leaves the focus on the page body instead of the moved chat link, so the animation stays.

Review record

  • frontend-review, frontend-deep-review, coder-frontend-review and 13 Codex rounds ran on the unsplit branch, which this PR still carries. Every thread is resolved.
  • Kept: the mouse drag tests use fireEvent: with user-event's pointer API, React reports dnd-kit updates outside act and the drop sends no request.
  • frontend-deep-review and coder-frontend-review ran again on the library change, the collapsed Projects drag and the scroll fix, and pass.
  • jaaydenh: the nested button around the chat link is fixed, and the move to @dnd-kit/react is in d0e9329. That commit is one change: the pins and the project moves share one provider, so neither half builds without the other. A drag onto the collapsed Projects section opens it since 44bfb5c.
  • Follow-ups: where focus goes after a row moves away (handling it in the sidebar would also let the drop animation, and its wait, go), a split of ChatsPanel.tsx (808 lines), and one predicate that the menu and the drag share for the move rule.

Xum opened this PR on behalf of @ibetitsmike.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
ibetitsmike added 8 commits October 14, 2026 15:31
@ibetitsmike
feat: move chats between projects through the chat update API
1f99314
PATCH /api/v2/chats/{chat} now accepts project_id. A UUID moves an idle
root chat into a project of its owner and organization; uuid.Nil removes
it from its project. The project checks are the ones of chat creation,
now shared by validateChatProjectSelection.

The chatstate machine gains a SetProject transition, allowed only from
W, E0 and E1. SetFamilyProject locks the root first and then each
subagent chat, so a busy family returns 409 and the move never overlaps
a step. chatd publishes a project_change watch event for each family
member after the commit.
@ibetitsmike
feat(site/src): move chats between projects from the chat menu
d142c55
The chat menu (sidebar row, top bar and project page row) gets a "Move to
project" submenu for the owner of a root chat that is not archived, behind
the chat-projects experiment. It lists the projects of the chat's
organization from the project list the sidebar already loaded, marks the
current project, and ends with "Remove from project". While the chat or a
subagent runs, the submenu is off and shares the Archive agent hint.

moveChatToProject() updates the cached lists and the chat optimistically,
restores only project_id on failure, and refetches the lists when it
settles. project_change watch events update project_id in other tabs and
refetch the filtered lists, and the project page drops rows whose chat
moved to another project.

The new-chat composer no longer says that a chat cannot move later.
@ibetitsmike
feat(site/src/pages/AgentsPage/components): drag chats onto project f…
29e4461
…olders in the sidebar
@ibetitsmike
fix(site/src): address frontend review of moving chats between projects
41b5af8
- Keep each sidebar row's wrapper mounted when the chat's drag eligibility
  changes, so a status change no longer remounts the row and closes its menu.
- Share one ChatDropArea for project folders and Chats, drawn as an outline.
- Read dnd-kit drag data through type guards instead of casts.
- Group projects with Object.groupBy instead of a module cache.
- Add stories for the Move to project submenu states and a drag over a folder.
- Tidy test fixtures, queries and comments.
@ibetitsmike
fix(site/src): address frontend re-review of moving chats between pro…
8670873
…jects

- Add stories for an unfiled chat's submenu and the blocked menu item.
- Restore plain project fixture IDs in tests.
- Clarify comments on the project grouping, the blocked prop and drop lookup.
- Name the arrow-key check in the keyboard getter and drop a no-op class.
@ibetitsmike
fix(site/src/pages/AgentsPage/components/ChatsSidebar): drag only the…
c49234e
… root row to move a chat family

Subagent rows rendered inside the draggable wrapper of their root, so a
drag that started on a subagent row moved the root chat. The drag handle
is now the root row itself, and the subagent subtree stays outside it.
@ibetitsmike
refactor(site/src/pages/AgentsPage/components/ChatsSidebar/chats): ty…
9bd66d7
…pe dnd-kit drag data guards
@ibetitsmike
test(site/src/pages/AgentsPage/components/ChatsSidebar): cover refuse…
649a0ad
…d moves and drop folder opening

Assert the move failure toast still shows after the menu closes, assert a
drop opens its target folder, and stub the projects request in the row
remount test so it no longer reaches a real server.
@github-actions github-actions Bot assigned ibetitsmike Oct 14, 2026
@ibetitsmike

ibetitsmike commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 14, 2026 •
edited
Loading

Uh oh!

There was an error while loading. Please reload this page.

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-16T21:14:33.512965Z 3f2a42d Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

chatgpt-codex-connector[bot]
chatgpt-codex-connector Bot reviewed Oct 14, 2026
View reviewed changes

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 649a0ad9b7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread site/src/api/queries/chats.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread site/src/pages/AgentsPage/components/ProjectPage/ProjectChatsList.test.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

@ibetitsmike
test(site/src/pages/AgentsPage/components/ProjectPage): check the mov…
fd48f30
…ed chat in the cache, not the DOM

FE1 leaves rendered presence to screenshots. The row menu test now checks
the move request and the project page's own list cache, and the new
ChatMovedToAnotherProject story shows that the page leaves out a cached
chat of another project.
@ibetitsmike

ibetitsmike commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

chatgpt-codex-connector[bot]
chatgpt-codex-connector Bot reviewed Oct 14, 2026
View reviewed changes

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fd48f30247

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread site/src/api/queries/chatProjects.ts Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/tree/ChatTreeNode.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread coderd/x/chatd/chatstate/transition.go Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/chats/ChatsPanel.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread coderd/exp_chats.go Resolved

Uh oh!

There was an error while loading. Please reload this page.

ibetitsmike added 3 commits October 14, 2026 22:10
@ibetitsmike
fix(coderd/x/chatd): drop the project memory index when a chat leaves…
55720de
… its project

A chat moved out of its project, or whose project was deleted, kept the
memory index the model saw there, while the memory tools were gone. A
root chat outside a project now has an empty index, so its next turn
start sends an update that removes those memories. Such a chat reads the
prompt only at turn start, the only step where that removal can be due.
@ibetitsmike
fix(site/src/api/queries): group chat projects without Object.groupBy
993f768
Object.groupBy needs Chrome 117 and Safari 17.4, and the browserslist
supports Chrome 111 and Safari 16.4 with no polyfill, so the Move to
project menu threw on those browsers. lodash groupBy keeps the server
order of the projects.
@ibetitsmike
refactor(coderd/x/chatd): name the memory index history parameter once
7d14cff
syncMemoryIndex and memoryIndexMessage now call the loaded chat messages
history, and the doc comment says plainly when a chat outside a project
reads its prompt rows.
@ibetitsmike

ibetitsmike commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

chatgpt-codex-connector[bot]
chatgpt-codex-connector Bot reviewed Oct 14, 2026
View reviewed changes

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7d14cffa6d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/chats/ChatsPanel.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

ibetitsmike added 2 commits October 14, 2026 22:43
@ibetitsmike
fix(site/src/pages/AgentsPage/components/ChatsSidebar): announce chat…
bb4ba6e
… drags by name

dnd-kit's default announcements read the draggable and droppable IDs, so
a screen reader heard "move:<id>" and "project:<id>" during the new
keyboard drag. The sidebar's DndContext now announces the chat title and
the folder name, and pin drags keep the defaults.
@ibetitsmike
fix(site/src/pages/AgentsPage/components/ChatsSidebar): spell "cancel…
310bf07
…ed" in the drag announcement

User-facing strings in site/src use "canceled". The drag test now reads
the dnd-kit live region by its status role instead of searching for
visually hidden text.
@ibetitsmike

ibetitsmike commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

chatgpt-codex-connector[bot]
chatgpt-codex-connector Bot reviewed Oct 14, 2026
View reviewed changes

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 310bf07153

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread site/src/pages/AgentsPage/components/MoveToProjectMenuItems.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread site/src/pages/AgentsPage/components/MoveToProjectMenuItems.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/ChatsSidebar.test.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

ibetitsmike added 2 commits October 14, 2026 23:20
@ibetitsmike
test(site/src/pages/AgentsPage/components/ChatsSidebar): show menus w…
8f4b47e
…ithout Move to project in stories

The archived, shared and subagent menu cases asserted DOM absence in
Vitest, which the frontend contract (FE1) leaves to screenshots. Three
stories now open those menus with the chat-projects experiment on, and
the Vitest absence checks are removed.
@ibetitsmike
docs(site/src/pages/AgentsPage/components/ChatsSidebar): note the mov…
1fc564f
…e rule above its three stories

The JSDoc on the first story became that story's description in Storybook, where "these menus" had no referent. A plain comment above the group keeps the rule out of a single story's docs.
@ibetitsmike

ibetitsmike commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

chatgpt-codex-connector[bot]
chatgpt-codex-connector Bot reviewed Oct 14, 2026
View reviewed changes

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1fc564f8f0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
Comment thread site/src/pages/AgentsPage/components/ChatsSidebar/ChatsSidebar.test.tsx Resolved

Uh oh!

There was an error while loading. Please reload this page.

@ibetitsmike
test(site/src/pages/AgentsPage/components/ChatsSidebar): refuse the m…
97b9005
…ove after the menu closes

The refused-move test waited for the menu item to leave the DOM before it
checked the toast. The update now rejects only after the select, so the
test still shows that the toast outlives the menu without a DOM check.
@ibetitsmike

ibetitsmike commented Oct 14, 2026

Copy link
Copy Markdown
Collaborator Author

@codex review

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ibetitsmike
ibetitsmike requested a review from jaaydenh October 18, 2026 15:19
@ibetitsmike
refactor(site): move the sidebar drags to @dnd-kit/react
d0e9329
The sidebar drags used @dnd-kit/core, @dnd-kit/sortable, and
@dnd-kit/utilities. They now use @dnd-kit/react 0.5.1, with
@dnd-kit/dom and @dnd-kit/abstract pinned to the same version.

The Accessibility plugin is off because it gives each drag handle
role="button", and the handles are chat links. ChatDragAnnouncer reads
drag events through useDragDropMonitor and writes the instructions and
the live announcements. A pinned drag now also announces the position.

Up and Down still move a keyboard drag to the next folder. After an
optimistic pin reorder, focus goes back to the dragged link. The click
guard after a drop is removed, because the pointer sensor stops the
click after a drag.
@ibetitsmike ibetitsmike changed the title feat(site/src/pages/AgentsPage/components): drag chats onto project folders in the sidebar feat(site): drag chats onto project folders in the sidebar Oct 18, 2026
jaaydenh
jaaydenh requested changes Oct 19, 2026
View reviewed changes

@jaaydenh jaaydenh left a comment

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when the projects section is collapsed, dragging a chat onto the collapsed projects section should expand the projects section. Currently, it is not possible to drag a chat to a project when the projects section is collapsed which could be confusing for users attempting to drag to move a chat to a project for the first time.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
ibetitsmike added 4 commits October 19, 2026 06:56
@ibetitsmike
fix(site): keep sidebar drops on the target under the pointer
f663f72
Two drags regressed in the move to @dnd-kit/react.

A drop with the pointer on a muted folder moved the chat into the
folder next to it. When no enabled target is under the pointer, the
default collision detector falls back to the dragged row's overlap.
The folders and Chats now use pointerIntersection, so a drop counts
only for the target under the pointer, as before. @dnd-kit/collision
0.5.1 becomes a direct dependency for it.

A keyboard drag could not reach Chats when Chats is taller than the
window. The arrow key scrolls the next target into view, but dnd-kit
measures the targets again only after the drag has moved, and it does
not recheck a position that has not changed. The targets now measure
themselves right after the scroll.
@ibetitsmike
feat(site/src/pages/AgentsPage/components/ChatsSidebar): open the col…
44bfb5c
…lapsed Projects section under a chat drag

A chat dragged over the collapsed Projects header now opens the section,
so the folders can take the drop. The section stays open, like a click on
the header. Until that drag ends, the header stays a target that moves
nothing, so a drop there leaves the chat in place and the live region
says it did not move. Keyboard drags reach the header with Up and the
folders with Down. Pinned rows still never target Projects.
@ibetitsmike
fix(site/src/pages/AgentsPage/components/ChatsSidebar): keep the drag…
76d4073
… target under the drag while the list scrolls

A wheel turn during a mouse drag scrolled the list under the still
pointer, but the drop stayed on the folder that had been under it.
dnd-kit 0.5.1 measures the targets again after a scroll, but its
collision check skips an unchanged drag position. A new plugin checks
the targets again on each scrolled frame.

In a scrolled list, a keyboard drag stuck on one folder because
dnd-kit's Scroller scrolled the list after each step, away from the
drag's still position. The plugin turns Scroller off during a keyboard
row drag, which already scrolls each target into view itself.
@ibetitsmike
fix(site/src/pages/AgentsPage/components/ChatsSidebar/chats): keep ke…
e6bbcea
…yboard drag targets and the drop announcement in step

After a scroll, measuring the targets again for a keyboard step made
dnd-kit check collisions at the old drag position. That check could
change the target, and dnd-kit ignores collisions until React renders
after a target change, so the target under the moved drag was lost: Up
from the tall Chats read "not over", and the next Down kept the folder
above as the target. The keyboard step now turns the collision checks
off until the move has set the new position, then checks once.

dnd-kit can also report a new target after a drag ends, until React
renders the end, which replaced the drop announcement. The announcer
ignores those reports.
@ibetitsmike

ibetitsmike commented Oct 19, 2026

Copy link
Copy Markdown
Collaborator Author

@jaaydenh Done in 44bfb5c. A chat dragged over the collapsed Projects header now opens the section, so the folders can take the drop.

  • The section stays open after the drag, as if the header had been clicked.
  • A drop on the header itself moves nothing, and the live region says the chat did not move.
  • Keyboard drags reach the header with Up. Down then goes to the first folder.
  • Pinned chats never move to projects, so they do not open it.

76d4073 and e6bbcea also fix scroll bugs from the library change that UAT found: a wheel turn during a mouse drag left the drop on the folder the pointer had left, and in a scrolled list keyboard steps got stuck or kept the previous folder as the target.

Xum replied on behalf of @ibetitsmike.

All reactions

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

@ibetitsmike
ibetitsmike requested a review from jaaydenh October 19, 2026 10:45
ibetitsmike added 5 commits October 19, 2026 11:11
@ibetitsmike
docs(coderd/x/chatd): describe moving chats between projects
9f84197
Add the SetProject transition to the transition list and the execution
state diagram, note that a root chat's project can change after
creation, and describe how a moved chat's memory index is brought up to
date at its next turn.
@ibetitsmike
Merge branch 'mike/move-chats-chatd' into mike/move-chats-api
6d26347
@ibetitsmike
docs(coderd/x/chatd): describe project moves in the chat update endpoint
a57182f
The PATCH /api/v2/chats/{chat} section said the endpoint emits no state
transitions unless the request changes archived. A project_id change now
applies SetProject to the chat family, so list its supported states and
refusals.
@ibetitsmike
Merge branch 'mike/move-chats-api' into mike/move-chats-menu
9197d76
@ibetitsmike
Merge branch 'mike/move-chats-menu' into mike/move-chats-to-projects
70519d3
jaaydenh
jaaydenh approved these changes Oct 19, 2026
View reviewed changes
ibetitsmike added 5 commits October 19, 2026 11:40
@ibetitsmike
Merge origin/main into mike/move-chats-chatd
f83d0f0
@ibetitsmike
Merge the updated layer below into mike/move-chats-api
0093b17
Brings in origin/main through mike/move-chats-chatd. Chat creation on
main now checks project access with chatd.ChatProjectUsableBy, so
validateChatProjectSelection uses that check for both creation and
moves.
@ibetitsmike
test: cover moving a chat into a project shared with its owner
de152c0
A move accepts the same projects as chat creation, which now include
projects shared with the chat's owner. Extend the owner-check test to
share the project and move the chat, and describe the rule on
UpdateChatRequest.ProjectID.
@ibetitsmike
Merge the updated layer below into mike/move-chats-menu
6ee95a9
@ibetitsmike
Merge the updated layer below into mike/move-chats-to-projects
242ea84
ibetitsmike added a commit that referenced this pull request Oct 19, 2026
@ibetitsmike
feat: move a chat family between projects in chatd (#30893)
0968f97
chatd can now move a root chat, together with its subagent chats, into a
project, to another project, or out of its project. Nothing calls this
yet. #30894 adds the API.

**Stack** (moving chats between projects, bottom first):
1. **This PR**: the move in chatd
2. #30894: `PATCH /api/v2/chats/{chat}` takes `project_id`
3. #30895: **Move to project** in the chat menu
4. #30795: drag a chat onto a project folder in the sidebar

**Why:** today a chat keeps the project it was created with. A move must
follow the family rules of archiving, because a step that runs during a
move would still have the memory tools of the old project.

- `chatstate.Tx.SetProject` runs only from W, E0 and E1, like
`SetArchived`. `SetFamilyProject()` locks the root row before its
subagents, in the same order as archiving, so a busy family gets
`ErrTransitionNotAllowed` and nothing changes.
- A move to the project the chat is already in changes nothing, even
while the chat runs. The check reads the locked root row, so it waits
for a move that is still in flight.
- Only the root row stores `project_id`, and subagents use the project
of the root. After the commit, chatd publishes a new `project_change`
watch event for each chat of the family. The move keeps `updated_at`, so
lists keep their order.
- A root chat outside a project now has an empty memory index. When a
chat leaves its project, or its project is deleted, its next turn start
commits one memory index update that removes the memories the model saw
there.

**What to scrutinize:** the lock order and the same-project check under
the lock in `SetFamilyProject()` (`chatstate/family.go`), the turn-start
condition in `memoryIndexMessage()` (`memory.go`), and whether the
`ARCHITECTURE.md` text matches them.

**Generated** (`make gen`): `queries.sql.go`, `querier.go`, `dbmock` and
`dbmetrics` come from `queries/chats.sql`. `coderd/apidoc`,
`docs/reference/api/schemas.md` and `typesGenerated.ts` come from the
new watch event kind, which is also why two `chats.test.ts` fixtures
gain `project_change: false`.

**Architecture doc:** `coderd/x/chatd/ARCHITECTURE.md` lists
`SetProject` among the transitions and in the execution state diagram
(W, E0 and E1 back to themselves). It also says that a root chat's
project can change after creation, and that a moved chat's next turn
starts with a memory index update from the old project to the new one.

## Testing

- `make pre-commit` passes. `go test` passes for
`coderd/x/chatd/chatstate`, `coderd/database/dbauthz`
(`TestMethodTestSuite`) and the chatd memory tests.
- `TestSetFamilyProjectLocksRootFirst` fails with 55P03 when a subagent
row is locked first. `TestSetFamilyProjectKeepsTheCurrentProject` covers
the same-project move during a run. The transition matrix test gains
`SetProject` cases for W, E0 and E1.
- `TestMovedChatUsesNewProject` runs two real turns around a move: the
second turn uses the instructions and memories of the new project, and
`OutOfProject` fails without the memory index removal.

> Xum opened this PR on behalf of @ibetitsmike.
ibetitsmike added a commit that referenced this pull request Oct 19, 2026
@ibetitsmike
feat: move chats between projects through the chat update API (#30894)
dfdd6a5
`PATCH /api/v2/chats/{chat}` now takes `project_id`. A project ID moves
the chat into that project, and `uuid.Nil` removes it from its project.

**Stack** (moving chats between projects, bottom first):
1. #30893: the move in chatd
2. **This PR**: the chat update API takes `project_id`
3. #30895: **Move to project** in the chat menu
4. #30795: drag a chat onto a project folder in the sidebar

**Why:** #30893 can move a chat family, and the UI layers above need an
endpoint to call.

- A move accepts the same projects as chat creation, through
`validateChatProjectSelection()`, which this PR extracts from
`postChats()` without a change in behavior.
- A project that the chat's owner cannot use gives 404, even for an
admin. The owner must own the project or hold a grant on it
(`chatd.ChatProjectUsableBy`, the check that chat creation uses since
#30439). So an admin cannot put a member's chat into a project that the
member has no access to and expose its memory.
- A child chat, an archived chat or a disabled `chat-projects`
experiment gives 400. A family that is not idle gives 409 ("Cannot move
an active chat. Interrupt or wait for the chat to finish first."). A
project deleted between the check and the move gives 404.
- The move is the last write in `patchChat()`, so a refused move keeps
the earlier writes of the same request, as a refused archive does.

**What to scrutinize:** the access check in
`validateChatProjectSelection()` and the order of the refusals in
`patchChat()` (`coderd/exp_chats.go`).

**Architecture doc:** the `PATCH /api/v2/chats/{chat}` section of
`coderd/x/chatd/ARCHITECTURE.md` lists the `SetProject` states of a move
and its refusals, and no longer says that only an `archived` change
emits transitions.

**Generated** (`make gen`): `coderd/apidoc`,
`docs/reference/api/chats.md`, `docs/reference/api/schemas.md` and
`typesGenerated.ts` come from `UpdateChatRequest.ProjectID` in
`codersdk/chats.go`.

## Testing

- `make pre-commit` passes, and so does `go test ./coderd -run
TestPatchChatProject` (14 subtests).
- `TestPatchChatProject/AnotherOwnersProject` gets 404 for another
user's project, then moves the chat after the project is shared with the
chat's owner. It fails without the access check, and also with an
owner-only check. `MoveBackWaitsForAPendingMove` ends in the wrong
project when the handler skips the move before the lock.
`ActiveFamilyReturnsConflict` checks the 409 for a running, interrupting
or action-waiting root and for a running subagent.
`PublishesProjectChange` and `AuditsTheMove` cover the watch event and
the audit entry.
- Untested: the 404 when the project is deleted between the check and
the move.

> Xum opened this PR on behalf of @ibetitsmike.
Base automatically changed from mike/move-chats-menu to main October 19, 2026 14:22
@ibetitsmike
ibetitsmike requested a review from nickvigilante as a code owner October 19, 2026 14:22
ibetitsmike added a commit that referenced this pull request Oct 19, 2026
@ibetitsmike
feat(site/src): move chats between projects from the chat menu (#30895)
2cade24
The chat menu in the sidebar, the top bar and the project page gets a
**Move to project** submenu. It lists the projects of the chat's
organization, marks the current one with a check mark and
`aria-current`, and ends with **Remove from project**.

**Stack** (moving chats between projects, bottom first):
1. #30893: the move in chatd
2. #30894: the chat update API takes `project_id`
3. **This PR**: **Move to project** in the chat menu
4. #30795: drag a chat onto a project folder in the sidebar

**Why:** this is the first way for a user to move a chat, through the
API from #30894.

- The submenu shows only for the owner of a root chat that is not
archived, with the `chat-projects` experiment. While the chat or a
subagent runs, it looks disabled and does not open, as the API would
return 409. It stays keyboard focusable, so keyboard and screen reader
users reach the Archive agent hint it names.
- `moveChatToProject` updates the cached lists and the chat entity at
once. Its rollback restores only `project_id`, so it keeps the fields
that watch events wrote while the request ran. A refused move shows the
server message in a toast, even after the menu closes.
- Other tabs follow the `project_change` watch event. A move keeps
`updated_at`, so `project_id` comes only from that event, and the open
chat refetches so the last event cannot win over the committed project.
First list fetches that started before the move restart.
- The project page hides a chat that moved away. The composer's project
picker no longer says "Can't move to a project later".

**What to scrutinize:** the `project_change` branch in
`AgentsPageLayout.tsx` and `mergeWatchedChatSummary()` in
`api/queries/chats.ts`.

| Move to project submenu | Move is off while the chat runs | Project
page after the move |
| --- | --- | --- |
| ![The Move to project submenu shows the current project Launch with a
check mark, then Billing and Remove from
project](https://github.com/user-attachments/assets/43326137-b994-405d-a72f-82418b1275b9)
| ![Move to project and Archive agent are disabled, with the hint to
interrupt or wait for the
agent](https://github.com/user-attachments/assets/cb9121f7-c25c-42b6-bdd5-d6a668efb412)
| ![The Billing project page lists the moved
chat](https://github.com/user-attachments/assets/25f3c65e-620e-4fff-a7ed-16ac5d48e73e)
|

Pixel: new `ChatTopBar` submenu stories, `ProjectPage`
`ChatMovedToAnotherProject`, and the `ChatsSidebar` menus without **Move
to project** (archived, shared, subagent) and
`MoveToProjectOffWhileSubagentRuns`, which focuses the blocked item. The
open `CompactProjectSelector` stories lose the hint.

## Testing

- `make pre-commit` passes. Vitest passes on the 5 touched specs (437
tests), and the Storybook tests of the 4 touched story files pass (134).
- The open chat in `AgentsPageLayout.test.tsx` ends on the older project
when two moves arrive in reverse order and it does not refetch. The
`resetUnloadedChatLists` tests fail when the project list family or the
unloaded-only check is removed.
- A blocked **Move to project** takes keyboard focus, has the hint as
its description and does not open. The test fails when the trigger is
`disabled` or the open guard is removed.
- Untested: `aria-current` on the current project, which
`site/AGENTS.md` leaves to the stories.
- Codex: the keyboard reach of a blocked **Move to project** is fixed.
Seven other findings are declined in their threads: client race and
cache sync hardening, and story and doc nits on patterns or text this PR
keeps from main.

> Xum opened this PR on behalf of @ibetitsmike.
@ibetitsmike
Merge main into mike/move-chats-to-projects
ebf6425
@ibetitsmike
ibetitsmike merged commit 71435e1 into main Oct 19, 2026
27 checks passed

Uh oh!

There was an error while loading. Please reload this page.

@ibetitsmike
ibetitsmike deleted the mike/move-chats-to-projects branch October 19, 2026 14:34
@github-actions github-actions Bot locked and limited conversation to collaborators Oct 19, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Reviewers

@chatgpt-codex-connector chatgpt-codex-connector[bot] chatgpt-codex-connector[bot] left review comments
@jaaydenh jaaydenh jaaydenh approved these changes
@nickvigilante nickvigilante Awaiting requested review from nickvigilante

Uh oh!

There was an error while loading. Please reload this page.

Assignees

@ibetitsmike ibetitsmike

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

There was an error while loading. Please reload this page.

2 participants

@ibetitsmike @jaaydenh
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.