Repository navigation
Conversation
Gesture control now moves tiles: with the tile grid open, pinch anywhere on a tile and carry it onto another tile (they swap) or an empty cell (it moves there); a session tab carried onto the grid joins it there. The overlay asks tile-grid.js what is under the hand (tileAtPoint, tileDropTargetAt) and drops through the mouse drag's own paths (dropSessionOnTile / dropSessionOnSlot, so _reorderTiles stays the one move path). Detach Tiles (App Settings > Appearance, per device, OFF by default, desktop only): - a tile's header let go outside the browser window opens the session as a pop-out (detachSession, which now takes an optional size and screen position), the tile's size, near the drop point, after a 120 ms settle; - dropped on another Codeman window's tiles it moves there: that window docks it through the target's own drop and posts 'tile-adopted' on the window channel, and only the window it was dragged from lets go of its copy (a last tile goes to the welcome screen, never onto that session); - a pop-out's title drags back onto any grid: the session is taken back, the pop-out is asked to close and is released (stops answering roll-calls; one a script cannot close says where the session went), and its stale 'detached' answers are ignored for 2 s; - a tile's menu offers "Open in a new window", and a hand that lets a tile go outside the grid pops it out too. With the setting off nothing changes. No server change. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Ark0N
force-pushed
the
feat/tile-gesture-detach
branch
from
October 10, 2026 05:21
df636c5 to
2c55fe9
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What this adds
Two things for the tile grid (the Tiles view that shows up to 6 sessions side by side). Meant for a later release.
1. Gesture control moves tiles
With the camera overlay on (instance started with
CODEMAN_GESTURE=1, then App Settings → Gesture Control) and the tile grid open:While carried, the tile dims, a small copy of it follows the hand, and the tile or cell under the hand is highlighted (the mouse drag's own highlight). The hand never moves tiles by itself: the overlay asks
tile-grid.jswhat is under the point and drops through the same paths the mouse header drag uses (dropSessionOnTile/dropSessionOnSlot, so_reorderTilesstays the one move path). Hand and mouse cannot disagree about what a drop does.2. Detach Tiles (new setting, off by default)
App Settings → Appearance → Detach Tiles (per device, desktop only,
beta). With it on:A lone or zoomed tile can be dragged out too (it just cannot move inside the grid). With the setting off nothing changes: same draggable rules, same tooltip, no foreign drops taken.
How it works
No server change. A pop-out is just another client of the same session (that is how the tab pop-out already works), and a tile is a
TerminalTilein whichever window holds it. A session stays in one place in the browser at a time.dragend, if no drop target took it (dropEffect: 'none') and the drop point is outside the viewport, the tile opens viadetachSession(id, placement)after a 120 ms settle. Where a browser reports no drop point (0, 0), the page's own record of the pointer leaving at the viewport edge decides (a laterdragoverinside clears it).detachSessiongained an optional placement (size + screen position); without one the window features are byte-identical to before.application/x-codeman-tile(never text, so no text field or terminal can take it as typing). Another window's tiles and empty cells accept a drag that is none of their own and carries that type, dock it through the target's own drop, and posttile-adoptedon the existingcodeman-windowsBroadcastChannel. Only the window the tile was dragged from acts on that (its drag still settling, or ended within 5 s): it drops its copy without moving the keyboard, and if that was its last tile it goes to the welcome screen, never onto that session (two windows sizing one PTY garble it). A claim during the 120 ms settle cancels the pop-out.close-request. The pop-out is now "released": it saysredockedat once, stops answering roll-calls and closes; one a script may not close (opened by typing its URL) shows "Session moved" instead of staying a pop-out that dashboards would mark detached again. The docking window ignores that pop-out'sdetachedanswers for 2 s, which would otherwise take the new tile straight back off.Known limits
src/web/CLAUDE.mdlands first, that line moves with it.Files
src/web/public/tile-grid.js: the Detach Tiles section (setting check, tear-off tracking and settle, foreign docking,tile-adoptedhandling, the solo title handle) and the gesture helpers (tileAtPoint,canGrabTile,tileDropTargetAt,dropOnTileTarget,isOverTileGrid,detachTileAtPoint); the header drag and its tooltip know the settingsrc/web/public/app.js:detachSessionplacement,tile-adoptedon the window channel, the released pop-outsrc/web/public/tab-rail-resize.js: the tile's ⋯ menu entrysrc/web/public/settings-ui.js,index.html,styles.css,i18n.js: the setting (display key, stripped from the PUT, absent from the schema), its row and Help row, the solo title cursor, zh-CN for every new stringpackages/gesture-control/src/codeman/entry.ts+ the rebuiltsrc/web/public/gesture/gesture-codeman.js: tile grabs, tab drops onto the grid, pull-outdocs/architecture-invariants.md(Tile grid → Detach Tiles, gesture source), the gesture README, one line inCLAUDE.md.changeset/tile-gesture-detach.md(minor)Testing
test/tile-grid-detach.test.ts(new, 33 tests, the real tile code in the shared vm harness): off changes nothing; out of the window opens a placed pop-out after the settle; inside or taken by a target opens nothing; the edge-leave fallback; listeners removed on drag end and on a grid close mid-drag; the realdetachSessionpath removes the tile;tile-adoptedhandling (source only, own nonce ignored, stale drags ignored, last tile to the welcome screen, claim cancels a settling pop-out); foreign drops on a tile and an empty cell; a popped-out session docked with its stale answers ignored; unknown ids dock nowhere; own drags never read as foreign; window features; the ⋯ menu (JSDOM); a released pop-out stops answering roll-calls; the gesture helpers; the setting's per-device plumbingtest/tile-grid-i18n.test.tsextended: the two new header hints are harvested from the real code and translate with the existing state-plus-duration pattern; the Help row, the settings row and the pop-out strings translatenpm test(the CI gate): 10,388 passed, 12 skipped, 1 failed. The failure istest/spreadsheet-preview-worker.test.ts> "refuses a shared string of 2,000,001 empty runs before ExcelJS loads": it hit its 60 s timeout on a loaded machine (load average ~8) and passes on its own in 50 s. This PR touches no spreadsheet codenpm run typecheck,lint,format:check,check:frontend-syntax,check:public-assets, andtsc --noEmitinpackages/gesture-control: cleanCODEMAN_INSTANCE, four throwaway shell sessions, Playwright + Chromium, two dashboard pages talking over the real BroadcastChannel): 27/27. A real mouse drag of a header out of the window opened/session/<id>and the tile left the grid; that pop-out's title was a drag handle, and dropping it on the second dashboard's empty cell docked it, closed the pop-out and un-marked it in the first dashboard; a tile whose drag ended as taken elsewhere left the first grid when the second docked it; through the live gesture overlay a carried tile highlighted its target, the drop swapped the two tiles, a stray pinch moved nothing, and a tile carried above the grid opened as a pop-out; with the setting off a drag out of the window opened nothing and the tooltip was unchanged; the settings row shows on desktop, off.DataTransferdrop and the sending side with thedragenda browser sends), and a real camera at the desk.