Conflict in docs/wiki/Settings-Reference.md resolved by keeping #565's Apply
wording and adding Copilot (and COPILOT_HOME, which the bot's review found
missing from this list).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A "Search sessions" box at the top of the vertical tab rail narrows the
list to the tabs whose name matches (case-insensitive substring; a web
tab matches by its title). It searches every group, collapsed ones
included: while a search runs the projection draws every group open and
the header will not toggle, and the stored per-device collapse state is
left alone. Groups with no match hide, an empty result says "No sessions
match", and the flat rail (no groups) filters the same way. Escape or
the clear button empties it; leaving the vertical orientation resets it.
It is a view filter only: rows get the same tab-filtered-out class the
sidebar filter box uses, through one shared _applyTabListFilter() over
the pure CodemanTabSearch matcher in constants.js. Grouping, order,
Alt+N badges and drag are untouched, nothing is persisted or sent to
the server. The sidebar keeps matching name plus working directory.
In the grouped tree, hidden rows and the headers of emptied groups leave
the roving walk and posinset/setsize, and the tab stop moves onto a
visible item. zh-CN strings added.
Every animation setting now has its own App Settings section, right after
Appearance (owner: easier to find). It holds the Entrance Theme (the former
Entrance Animations row), Tile Animations, and an Open lab button that closes
settings and opens the per-surface lab (?animlab=1). Appearance keeps the skin,
identity and tab settings. New animation settings go in this section;
test/app-settings-structure.test.ts pins it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tiles become the fifth entrance surface (data-tile-anim), switched on in App
Settings > Appearance > Tile Animations and OFF by default: the default is the
grid's own quick fade (`settle`), exactly as before.
A styled tile plays in two beats: its frame enters as it mounts (fly out of
its session tab, dealt from the Tiles button, CRT power-on, beam down from its
tab, cascade, pop, soft), and its screen then plays the terminal pane style of
the entrance theme when its first capture lands, through the same
html[data-term-anim] rules on .tile-body. Each style has its own exit when the
Tiles button closes the grid (back into the tabs, a CRT switch-off, ...).
Picking an Entrance Animations theme presets the tile style (new Launch theme:
tiles fly from the tabs); the theme readout ignores the tile row, so changing
it never shows "Custom". Frames move transform and opacity only (one fit and
one PTY resize per tile), a reload's restore always settles, and nothing moves
under reduced motion. The lab (?animlab=1) gets a Tile grid group, a cascade
order picker and in-place replay / close + reopen.
Also removes the terminal pane's `boot` entrance style (owner decision); a
saved `boot` falls back to off.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Since 1.40.0 every agent tab draws its CLI logo through the run-mode-dot
slot, and there was no way to turn that off. App Settings → Appearance →
Tabs now has "CLI Logos on Tabs" (showTabCliLogos), right after Tall Tabs.
- Modelled on tabTwoRows: a per-device display key in the server-settings
merge and an optional boolean in the .strict() SettingsUpdateSchema,
loaded and saved by the id appSettingsShowTabCliLogos. Default ON on
every device; only an explicit false turns it off (tabCliLogosEnabled).
- CSS only, no tab re-render: applyTabOrientation() and the pre-paint
script in index.html stamp html[data-tab-logos="on"|"off"] from the
same stored blob (so a reload never flashes the logos), and one rule in
styles.css hides .session-tab .tab-harness and .home-sessions-harness.
The rows space their children with flex gap, so nothing is left behind.
- A live flip resizes every agent tab with no render behind it, so
applyTabOrientation() then re-takes the strip's one-row wrap decision
(updateTabOverflowMode) and re-anchors the lines drawn from tab rects;
a header that changes height reaches the PTY through the terminal
container's ResizeObserver, as any header change does.
- Covered: the header strip, vertical rail, sidebar, grouped rail, ledger
and case clusters and phone chips (all render the same tab markup) and
the desktop home rail. Untouched: tile and split headers, the Run menus,
the welcome launchers. The phone overview rows, the command palette and
the tab action menu draw no logo. The shell's SH pill and the status dot
stay.
- zh-CN for the label and the description.
test/tab-cli-logos-setting.test.ts drives the real openAppSettings() and
saveAppSettings() in JSDOM (the saved PUT body must pass the schema), the
server-settings merge, the defaults on desktop and phone, the live stamp
and its re-measure without a re-render, the real pre-paint script (on,
off, the phone key, the catch fallback), the CSS rule's exact selectors,
and the translations.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot CLI keeps its user MCP list in ~/.copilot/mcp-config.json (COPILOT_HOME moves it) but is
not a Codeman run mode, so it has no registry entry. Add the copilot-json dialect (mcpServers,
tools ["*"], type local/http/sse, checked against `copilot mcp add` 1.0.94) and declare Copilot as
a sync-only target in src/mcp-sync-targets.ts, listed after the registry CLIs. A server switched
off with `copilot mcp disable` is recorded in settings.json (disabledMcpServers), not on the
entry: sync reads that list so it is not copied, and reports the target unreadable if the file
is not valid JSON instead of guessing.
Switches such as MCP server sync only unlock their controls once saved, and
Save closed the modal, so the user had to reopen Settings to continue. Apply
runs the same save, keeps the modal open and refreshes the dependent groups
(MCP sync, custom model endpoints, CLI management) in place.
- Case picker: the "New or link a case…" and "Case settings…" rows that
replaced the translated + and gear buttons get zh-CN entries reusing the
buttons' wording, plus the list's "No cases match".
- App Settings, Bottom bar: the whole group translates as one (heading, the
four Git status rows with #543's max repositories and git timeout, and
their descriptions), so it never reads half English. The Git panel's two
button names, which the new "Git status" key reaches, read naturally too.
- Toasts: the three "Could not open a new window for this ..." errors and
the dictation-closed warning.
- Spreadsheet preview: "Spreadsheet preview failed (<status>)" gets a
pattern. Worker refusals map their error code to one user sentence each in
the renderer (the raw message goes to the console), feature ids read as
words (pivot tables, external links), and the notice bar translates each
item before the join; a number format's code passes through untouched.
The worker and core are unchanged, so their pinned strings and the
asset version hash stay as they were.
- Header Stats Style Tiles: the connection tile's value word reads in
Chinese through scoped "Connection tile: <word>" keys, never bare-word
keys ("retry" is also the orchestrator's Retry button, "LIVE" a resume
list badge). The indicator cache now includes the UI language, so a
language switch repaints it on the next update.
- Tile grid: the "Loading…" label was CSS content text the translator
cannot reach; it is now content: attr(data-loading-label), written through
the translator when the tile is built and each time it starts loading.
Tests pin every new string (zh differs, no English left, English unchanged)
and fail without these changes: the tile harvest sees the loading label and
the rename field, a CSS guard keeps words out of tile generated content, the
Bottom bar group, the case picker rows and toasts read from their source,
the connection tile across a language switch, and the spreadsheet error
codes, notice bar and status line.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A lone repository git could not read rendered as a clean, empty one. The
panel took its single-repository view whenever the overview held one row,
and the error row only exists in the list view, so it showed "Nothing
uncommitted / No remote configured" with an empty header while the
indicator said "? 1". The single-repository view now needs a readable
repository and an untruncated overview; anything else takes the list view
(headed "1 repository"), and the tooltip names the unreadable repository
instead of saying "no branch".
The same condition covers a limit of 1 in a folder of several projects,
now that max repositories can go down to 1: the one row shown keeps the
"Showing the first" notice instead of looking like the only repository.
A repeated timeout query parameter reaches the route as an array, and
calling trim() on it answered 500 with an internal message, before the
ownership check. The route now treats a non-string timeout as "default",
like an empty or absent one, and the route test pins it.
The browser test gains the lone-unreadable-repository case (error row,
no "Nothing uncommitted", "? 1", tooltip names the repository) and the
truncated single-row case. Both fail against the unfixed panel.
The git timeout input steps by 1, not 5: the save accepts any whole
number of seconds and step 5 flagged values like 7 as invalid.
Docs: api-reference says repoLimit is only present in the
folder-of-projects case, the Settings Reference and Working With Files
glyph lists mention "? N", and the module header says the repository
count is the caller's maxRepos.
The PR's own changeset is removed; its text goes into the single
combined release changeset.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
App Settings now refuses a Default Codex model that the server would reject,
before anything is written to localStorage. SettingsUpdateSchema is .strict()
and checks codexModel with ^[a-zA-Z0-9._\-/]*$, so a value like gpt-oss:20b
400'd the whole settings PUT while the toast still said "Settings saved", and
because the bad value was already in the local blob every later save from that
device failed the same way. The client check uses the same pattern, shows an
error toast, focuses the field and keeps the modal open. The toast has a zh-CN
translation in i18n.js.
src/web/codex-launch-defaults.ts gets an @fileoverview (fill only unset fields,
re-validate persisted values, callers decide scope, never writes Codex config
files), as every module in src carries one.
Both new Codex rows in index.html carry has-field, like every other App
Settings field row, so on phones the input and the select stack under their
label instead of squeezing it into a narrow column.
The Agent CLIs wiki paragraph said the defaults apply to every local launch.
Scheduled (cron) codex jobs are built without a codexConfig and never get
them, while Resume goes through POST /api/sessions and does, so the sentence
now names the Run menu, Resume, POST /api/sessions and /api/quick-start, and
says cron jobs do not use them.
The Settings Reference lists the two new rows in the Agents & CLIs table. The
neighbouring "Bypass approvals and sandbox" row described Pi's project trust;
it is the Codex --dangerously-bypass-approvals-and-sandbox toggle, so its note
says that now.
The PR's own changeset is removed: the release writes one consolidated
changeset at COM, and the PR's text overstated the scope (it included cron).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The welcome overview drew every CLI as its own gradient pill (one colour,
gradient and glow per CLI, plus skin overrides that re-tinted three of them),
all at the same size with the same generic play icon, wrapping into an
unaligned cluster with Cloudflare Tunnel styled as one more launcher. Nothing
said which action mattered.
Now the screen has one obvious thing to press:
- A single primary button for the first AGENT in the registry catalog (Claude
Code on a stock install), in the accent fill the toolbar's Run button uses,
with the CLI's real logo on a small light disc (a brand mark straight on the
accent turns muddy) and the same translatable "Run <label>" text.
- Every other enabled CLI as a slim, uniform pill under it, in the Compact
header's language: control-bg surface, control-border, small type, the
logo from the shared run-mode-dot slot, the bare name, and "Run <label>" as
tooltip and accessible name. Catalog order, centred, wrapping when needed.
- Cloudflare Tunnel as a quiet text link under the launchers (same id,
handler and cloudflared gate; text colour carries the active and connecting
states), still directly above the QR it reveals.
The primary is chosen by catalog order and kind, never by an id, so with
Claude disabled the next agent takes the slot and a custom CLI listed first
gets it like any other. The buttons carry no per-CLI class any more: the id
travels only as data-mode and the logo slot, and all the per-CLI welcome
gradients and their skin overrides are gone. Everything is token-driven, so
it follows every skin; OG gets its own toolbar Run blue with light text
because its --accent-ink is an unused placeholder. Focus rings sit offset
from the fills, hover motion is off under prefers-reduced-motion, and the
phone rules (reached only with the phone overview off) span the primary and
keep the chips wrapping.
Tests: run-mode-ui pins catalog order across primary and chips, the
kind-based primary choice (Claude off, shell listed first, custom agent
first, shell only), the logo slot and absence of id classes, the token-only
CSS with no .welcome-btn rule left, and the tunnel link's place between the
launchers and the QR.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The owner asked to drop the "+" (Add Case) and gear (case settings) buttons
beside the toolbar case picker. Their two actions now close the picker's own
list instead, as "New or link a case…" and "Case settings…" rows in a sticky
footer, so nothing becomes unreachable: on desktop the "+" was the only door to
Add Case (create, link, clone, manage), and the gear the only one to the
per-case Agent Teams / 1M Opus overrides.
The rows are part of the arrow-key walk (after the last case) and Enter runs
them, they also show when nothing matches the filter, and the case settings
popover still opens anchored to the case group. The picker input gets its
right-hand corners back, and the dead .btn-case-add / .btn-case-settings rules
(desktop, phone and two skin selector lists) are gone. Phones keep their own
case sheet and gear.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The owner's picks after testing the 1.36.0 beta:
- Header Stats Style defaults to Compact (two pills with rings) instead of
Tiles. The resolver, the pre-paint stamp and the App Settings option all
agree; an unknown value now reads as compact.
- The Tiles header button is ON by default everywhere but handhelds (their
defaults object keeps it off, and the button still needs a 1180px window).
The Ctrl+Shift+G gate in tileShortcutFor() now resolves an absent key
through the device defaults too, so the chord and the button cannot
disagree; before, it required a stored true.
- Tab Layout defaults to Classic (the single strip, as before). By state, By
case and Ledger stay available as opt-ins. The tile-grid beta the owner used
last had only this layout, and it is the one they wanted back.
zh-CN option labels follow the new "(default)" markers; docs and wiki updated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only the stepper half of #428 lands here. Its other half, the phone case
picker search, shipped separately in #488 (1.33.1) and is kept as it is on
master, so every file of this merge starts from ours and only the stepper
changes are ported onto it:
- index.html: the second `− 1 +` group (#shellCount) after Run Shell is gone.
- session-ui.js: incrementShellCount/decrementShellCount are removed and
runShell() reads the toolbar's one stepper through _readTabCount(), the
helper master grew since #428 was opened (same clamp and absent-element
fallback as #428's _toolbarInstanceCount()).
- run-mode-ui tests stub #tabCount instead of #shellCount, plus #428's
"toolbar instance count" block adapted to _readTabCount() and a markup check.
- wiki: "the instance counter", singular.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner feedback 1: "give me the hover info to right click over the tile
button to adjust it". The only hint was the native title, which the
browser shows late, small and unstyled.
The Tiles button now has its own hover card, under it in the count
menu's panel style:
- "Tiles · N" with the remembered count (live: a pick in the menu
changes it while the card is up), "Click: open the grid" ("close the
grid" while it is open), "Right-click: choose 2, 4 or 6 tiles", and
when the count does not fit the window what opens instead ("This
window fits 4 tiles: a click opens 4"; open, what fits). Shown from
the keyboard it adds "Shift+F10: the same menu from the keyboard".
- Shows 300 ms after a pointer that hovers rests on the button, or after
a keyboard focus (:focus-visible); never for a touch pointer or a
device that cannot hover (plus a CSS @media (hover: none) backstop),
never on a hidden button, never while the count menu is open. A short
fade on opacity and transform, none under reduced motion; it takes no
pointer.
- Hides on pointer leave, blur, Escape, a scroll and a resize, and on a
press, a click or a right-click on the button (capture phase, so it is
gone before the menu or the grid opens, and it stays gone while the
pointer rests there). openTileCountMenu hides it too, and the focus
the menu's Escape puts back on the button brings no card back.
- It replaces the button's native title (two tooltips never stack); the
aria-label stays and aria-describedby points at the card, which always
exists and is kept current, its keyboard line included while hidden, so
screen readers hear the same text without a hover. Text is diffed
against the last English, as the rest of the grid chrome, so the zh-CN
translator is not fought on every refresh.
- zh-CN for every line (平铺 · N, 单击, 右键单击, Shift+F10, the fits
note); no other header button changes (its styles are .tile-hint only).
Tests: tile-grid-hint.test.ts (install and aria wiring, the delay, each
show and hide path, content per state and count, the menu rule, the
translator, the CSS); the count menu and i18n tests read the accessible
name instead of the removed title, and the i18n harvest covers the card
(F10 joins the key names that stay Latin). The vm harness gains
removeAttribute.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The tunnel Upload URL page (upload.html) has been broken since the response envelope landed in 458fb81c: it reads j.filename and j.files while the server answers { success, data: { filename } } and { success, data: { files } }, so every upload reported "Saved: undefined" and the recent list stayed empty. Nothing else reads ~/.codeman/screenshots/, and handing a file to an agent goes through POST /api/sessions/:id/paste-image into the session's own workspace, so the page, its Settings row, the suffix branch of the tunnel row helper (now folded into its one caller) and the Upload URL i18n key go.
The three /api/screenshots routes keep working unchanged and log one deprecation warning per process on first use, naming paste-image as the replacement. Per docs/versioning-policy.md they are removed in a later MAJOR, after at least one MINOR release that carries the warning; the docs, CLAUDE.md and the multi-user plan say so.
Static caching: with upload.html gone no HTML is served by @fastify/static any more (every page has its own no-cache route; on a built tree only the precompressed index.html.gz artifact is reachable, as application/gzip, and nothing requests it). The .html branch of setHeaders was therefore dead and goes with the test that fetched upload.html to reach it; a comment now says a new static HTML page needs its own route. The index.html no-cache assertion on the route stays.
A global `.btn-icon-header:hover { transform: rotate(45deg) }`, meant for
the settings gear, turned every header icon button on hover, so the folder,
Tiles, Split and the rest swung their rounded hover background into a
diamond. Three buttons had already cancelled it one by one (the font-size
buttons, notifications, the sidebar toggle).
The rule is gone, and with it those three overrides. Hover motion now moves
the icon only:
- the settings gear's icon turns 45 degrees (one tooth, so it lands on the
same shape);
- the Tiles button's four squares spread apart, each toward its corner;
- the folder cross-fades to an open folder (a second drawing in its SVG,
`.icon-folder-closed` / `.icon-folder-open`);
- every other icon just takes the hover colour.
Pointer devices only (`@media (hover: hover)`, so a tap cannot leave an icon
stuck mid-motion), and the transitions are off under reduced motion.
Owner request: the Tiles and folder buttons "weirdly turn" on hover.
Checked live on a dark and a light skin (rest, mid, end frames). Pinned by
test/header-icon-hover.test.ts, mutation-checked five ways (the button
rotation back, the open drawing missing, the motion not hover-gated, a
square spreading toward the wrong corner, reduced motion keeping its
transition). Gate: 507 files, 9798 tests passed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner decision 10 ("give me then the option to choose only HOW many
tiles, 2,4,6 default is 6 so the menu is easier"; asked where it lives:
"Click opens 6"). The click still opens the grid at once; right-click,
Shift+F10 or the Menu key on the button opens a small menu of three
counts, each drawn as the grid's own layout (2x1, 2x2, 3x2), the
remembered one checked. It replaces the session picker, which is gone
(method, markup hook, CSS, zh-CN entries).
- A pick is remembered per device in codeman:tile-count (default 6;
codeman:tile-grid stays ids only) and opens that many tiles. The click
and Ctrl+Shift+G then open with it, at most what the window fits.
- Which sessions: the rule the click already had (the grid this tab
last had, else an open split's two, else tab order, the active one
included and focused), trimmed from the end (the focused one kept) or
filled from tab order to the count. A remembered grid comes back with
its tiles first, in their cells, holes filled first, then tab order
(owner answer, superseding decision 8's "exactly the stored set"). A
page-load restore still brings back exactly what was stored.
- With the grid open a pick re-forms it: a count change is a shape
change under the cell model's rule (reformTileCells), the focused tile
always kept, every joining tile mounted and laid out before any of
them connects, so each fits once and sends one PTY resize.
- Ctrl/Cmd+click on a tab with the grid closed opens the count in total,
that session among them and focused (owner answer: N, not N+1).
- A count the window cannot fit is greyed out with the reason; a
remembered one stays checked, the keyboard starts on the largest that
fits. Arrows, Home/End, Enter or Space; Escape closes the menu alone
(the global handler gives it the key first, like the tab-group menu)
and puts the keyboard back on the Tiles button; Tab and a click
elsewhere close it.
- zh-CN for every new string (N 个窗格, 窗格数量, the titles, the Help
modal row); the i18n test harvests the menu now, with a session named
"6 tiles" as the user-text trap.
Tests: tile-grid-picker.test.ts becomes tile-grid-count-menu.test.ts
(the button checks kept, every picker check carried over to the menu,
plus keyboard, remembered count, re-form, Ctrl/Cmd+click and the batch
connect); the open-set, cap, restore, shortcuts, split-coexistence and
i18n expectations follow the count; the Help modal test escapes its
label (the new one has parentheses). The vm harness tracks
document.activeElement and makes SVG elements.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Four rebindable registry chords in the Tiles group move the focused
tile: it trades places with the neighbour the Alt+Shift+Arrow focus
chords pick (tileInDirection), through the same _reorderTiles path as
the header drag, and keeps the focus. Nothing at an edge.
They go through tileShortcutFor()/runTileShortcut() like the other
tile chords, so every xterm key handler swallows them while they
apply: only while the grid is open, a zoomed grid included (a no-op
there, so the keys never reach the CLI), and never in a text field
other than xterm's own textarea, where Ctrl+Shift+Arrows select by
word.
Ctrl+Shift+Arrows because every other two-modifier arrow chord is
taken: Ctrl+Alt switches workspaces (GNOME, Xfce, some Windows
graphics drivers), Ctrl+Alt+Shift moves a window to another workspace
(GNOME, Cinnamon, Xfce), Super belongs to the desktop, Alt is the
browser's back and forward, Alt+Shift focuses tiles. No browser,
GNOME, KDE, macOS or Claude Code default uses Ctrl+Shift+Arrows; it
costs a terminal editor's word selection inside a tile while the grid
is open.
The Help modal lists the chords and the header drag; the shortcut
overlay lists the registry. zh-CN entries for every new string.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The grid holds at most TILE_GRID_MAX (6) tiles, but the file header of
tile-grid.js, the comment over its section in index.html and the grid
block in styles.css still said 1 to 9. The tile header descriptions in
_buildTileHeader, .tile-header and the chrome test listed `⋯ ×` without
the zoom button, and the glyph-size rule still spoke of four glyphs and
the plus that owner decision 9 removed. Comments only.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner request: translate "Run SH" and the rest of the Run family, plus
the two leftovers from the last report.
Run: one pattern turns "Run <code>" (Run CC, Run SH, Run OC, Run CX ...,
and any registry CLI's shortBadge) into "运行 <code>"; the mode codes and
product names stay, and exact entries still win ("Run Shell" was already
运行 Shell, "Run OMP" 运行 OMP, "Run PI" takes the "Run Pi" entry). New
entries: "Terminal / Shell" (the run menu's shell item) and "Send Enter"
(the phone toolbar's Enter button title). The phone overview's Run button
already showed 运行 beside a mode word kept as typed.
Help modal and shortcut overlay: "Tabs", "Toggle Session Sidebar", both
"Copy Selection" rows, "Focus Tabs", and "Wheel" (滚轮, a mouse input like
Click). Key names stay English: the Help modal's Home key, and every key
the overlay renders, now carry data-i18n-skip, because "Home" is also a
dictionary word (the Home button) and showed as 主页 in the key column.
The invariants' paneExit section gains the badge's translation rule
(from the previous commit): its updates compare with the remembered
English, never the DOM.
Tests: i18n-exit-run-help covers every Run label _applyRunMode can show
(its hard-coded ones and Run <shortBadge> for every stock CLI), the
toolbar titles, the Help modal through the real translator in JSDOM (no
English outside the key column, the Home key kept while the word Home
elsewhere still translates, Wheel translated), every shortcut registry
group and label, and that the overlay's keys are skipped.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Drawing only the selected tab's family hid every other connection until you
clicked into one. All families are drawn again (still as routed trees); the
selected tab's families (what it spawned, and the family it was spawned into)
get .lineage-family--focus: 2.5px instead of 1.5px, full opacity instead of
0.75, a larger end dot, and drawn last so nothing covers them.
Lanes follow strip order (cycling through CodemanLineage.MAX_LANES, 3), never
the selection, so a family's lines never move when you click a tab.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Lineage lines used to draw one glowing dashed bezier per parent/child pair,
hanging below the tab strip, for every family at once. With a parent on row 3
of a wrapped strip and ten children below it, the curves crossed every lower
row's tab names and ran through the terminal text.
- One rounded orthogonal tree per spawning tab: every route starts at the
parent, so siblings share a trunk. 1.5px, solid, 1px dark outline; only a
working child's branch is dashed.
- Routes run only through the gaps between tab rows, joined by a spine left of
every row (computeLineageTree/computeLineageRows in constants.js), so they
never cross a tab or reach the terminal. The vertical rail gets the same tree
on its existing left track.
- Only the selected tab's family is drawn (what it spawned, plus its parent and
siblings). Selection redraws, and the strip's size transitionend redraws once
more, since the active tab widens for ~150ms after the selection redraw.
- The routing room is reserved by .session-tabs.lineage-tree, keyed on whether
any lineage exists, never on the selection, so a tab switch never resizes the
header or the PTY.
- Colours stay per spawning tab, now claimed in strip order for every family so
selection order never decides who gets the skin blue.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner feedback: the Header buttons group's keywords named neither Split
nor Tiles. Both are now in the group's data-search. The filter
(_filterSettings) matches each chip by its own data-search and its label,
and never by the wrapper's keywords (which would light up every header
chip for "split"), so the two chips also get their own: "tiles tile grid
side by side several sessions" and "split pane side by side two
sessions". Before, only the label words matched; "tile grid" found
nothing. Pinned by running the real filter over the real markup (JSDOM).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Owner decision 8 ("when I hit the tiles button, open the tiles already!").
A click with the grid closed now opens it straight away, and Ctrl+Shift+G
runs the same function (toggleTileGrid), so the two cannot drift. What
opens comes from one pure helper, tileGridOpenSet (constants.js):
a. the grid this tab last had, if any of its sessions survive, opened
exactly (an open split closes and its sessions do not join);
b. else an open split's two sessions, Pane A focused;
c. else the open sessions in tab order (the picker's list: no detached
ones), up to what the grid takes here (the cap of 6, fewer when the
window fits fewer), the active session always among them and focused.
A click with the grid open still closes it.
The picker moved to right-click (oncontextmenu, browser menu suppressed).
With the grid open it is preselected with the current tiles, and Open
replaces them. Ctrl/Cmd+click on a tab with the grid closed opens the
toggle's set plus that session. The button's title, the Help modal and
the wiki say right-click chooses which sessions.
Docs: decision 8 and an as-built entry in the spec (Entry points too),
CLAUDE.md, the invariants (#tile-grid, Opening), the wiki's Tile Grid and
Keyboard Shortcuts pages.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- CLAUDE.md: a Tile grid paragraph beside the split-pane one (parking, the
one load queue, the selection and close rules, chords, dividers, auto-join,
the exited-agent case), tile-grid.js (7.6) in the load order, the
desktop-gated header markers and the Tiles picker in the z-index stack.
- docs/architecture-invariants.md#tile-grid: the mechanisms and the reason
behind each rule; the split section now says where a waiting grid load
differs and that every capture carries a deadline.
- docs/wiki/Tile-Grid.md: the user manual page (turning it on, the ways in, a
tile's header, keys, leaving, persistence, Split), linked from the sidebar,
The Dashboard, Keyboard Shortcuts and Settings Reference.
- The Help modal lists the tile chords (pinned in help-modal-shortcuts.test).
- docs/tile-grid-plan.md: status updated, and an "as built" list of where PR 2
went another way than the spec.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
showTileGridButton gets the full per-device treatment Split has: a header
chip in App Settings beside Split, its load and save lines, OFF by default
(and in the handheld defaults), a member of the displayKeys merge policy,
stripped from the settings PUT and never declared in the .strict()
SettingsUpdateSchema (sending it would 400 the whole save).
The setting also gates the Ctrl+Shift+G toggle (the applied default while the
owner's answer is pending; one line in tileShortcutFor to change): OFF, the
chord is inert and reaches the terminal like any unbound key; ON, it opens and
closes the grid where one can open. A grid that is open however it was opened
(Ctrl/Cmd+click, a dropped tab, "Open group as tiles") keeps all its chords,
the toggle that closes it included.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A Tiles button beside Split in the header, opt-in through the per-device
showTileGridButton setting (read in applyHeaderVisibilitySettings; the
settings checkbox, displayKeys membership and schema exclusion follow with
persistence) and hard-gated like Split: hidden by its --hidden marker, a JS
width check with a live media listener, a @media (max-width: 1179px) backstop
and never in a solo window. While the grid is open the button closes it and
reads as pressed.
Closed, it opens a picker: a checkbox per open session in tab order (never one
popped out to its own window; one with no PTY is offered, its tile shows the
Attach overlay), names as text, preselected with the grid this tab last left,
else the active session and an open split's two. Boxes past what the window
can fit are disabled with the count shown, and Open opens the grid on the
checked sessions, focusing the active one if checked. Escape and an outside
click close it; its close method is idempotent and the global Escape handler
calls it.
A tile's + lists the open sessions not yet tiled; picking one adds it and
focuses it (a human selection). A grid that already holds what the window fits
disables the entries. "New session in this case" waits for auto-join.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
tile-grid.js (load order 7.6) adds the grid to CodemanApp: openTileGrid,
closeTileGrid, addTile, removeTile and _selectTiledSession, over a
<section class="tile-grid"> that is a SIBLING of .terminal-wrap and takes its
place under .main.tiles-active. Every tile is a TerminalTile with the grid's
one load queue, TILE_SCROLLBACK, a bounded load and its own per-device font
size (codeman-tile-font-size; Ctrl +/- sizes the tiles while the grid is open).
Layout comes from computeTileLayout; one ResizeObserver on the section refits
each tile (xterm and PTY together) on the trailing edge.
Opening parks the main terminal: _cleanupPreviousSession runs once (its
snapshot is right at that moment, and it closes the main socket), and
activeSessionId always names the focused tile's session, so the panels follow
focus. With the main socket closed, every main-terminal path that would write
the focused tile's output into the hidden xterm, fetch a capture for it,
resize it or reopen its socket now stands aside through _tilesOwnTerminal():
the SSE terminal, clear and refresh handlers, the dropped-output recovery,
the completion/error writelns, retryConnection and handleInit (both re-arm the
tiles instead; handleInit keeps live tiles and drops dead ones), sendResize,
throttledResize, the history re-pull, and the WebGL long-task observer, which
watches the whole page and must not count tile renders toward the main
terminal's sticky WebGL disable. The header connection state comes from the
tile sockets.
Closing destroys every tile, invalidates the main terminal's cached content
(snapshot, codeman-xs key, buffer cache) for every tiled id, since it predates
the grid, and replays the focused session fresh in the single view.
_focusedPane() answers with the focused tile and _forEachTile reaches every
grid tile. A tile whose socket stops for good is removed (4003, 4004, 4010) or
keeps its "session ended" marker (4009).
No entry point yet: the grid is opened from the shortcut registry in a later
commit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Close Session was bound to Ctrl+W by default. Ctrl+W is delete-word in
every shell, readline prompt and agent CLI, so muscle memory killed the
session (its tmux pane and CLI, with no confirm) mid-sentence, and with
the split pane open it was not even the pane being typed in.
Close Session now has no default key: the capture-phase handler lets
Ctrl+W through and xterm sends ^W to whichever pane is focused. The
action stays in the registry and can be bound in App Settings ->
Shortcuts; the shortcut overlay shows it as not bound. The Help modal,
CLAUDE.md, the split and tile-grid specs and three wiki pages stop
advertising Ctrl+W as kill. Owner decision (tile-grid decision 5).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Settings (per device): Git status: max repositories (1-50, default 12) and git timeout (5-120 s, default 30, was a fixed 10). Both go to /git-status and /git-diff as maxRepos / timeout query parameters, clamped server-side (an empty value means the default). A repository whose git status fails stays in the list with the reason instead of being dropped silently, shows as '? N' in the indicator, and the truncation line now names the limit and the setting. The discovery cache is keyed by the limit.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrzFKEdBLwVfu6ev2ZscJS
Pure move and rename, no behavior change. The split pane's second
terminal (SplitTerminalPane) moves out of terminal-split.js into its own
terminal-tile.js (load order 7.4) as TerminalTile, so the tile grid can
reuse it. terminal-split.js keeps the split orchestration (picker,
divider, auto-collapse) and constructs a TerminalTile for Pane B.
Tests follow the class: split-pane-terminal-unit becomes
terminal-tile-unit, and the Shift+Enter guard and the two browser suites
read terminal-tile.js / window.TerminalTile. The browser suites match
master (one pre-existing environmental failure in both).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Compact now draws every reading the same way, ring then label then
value: CPU and MEM get an accent ring (red past 80%, like their value)
in place of the sparklines, the plan windows keep their green, yellow
or red rings, the dot separators go, and both pills share one label and
one value style. The sparkline markup, history and CSS are removed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Tab Grouping becomes Tab Layout (tabArrangement: 'state' | 'case' |
'ledger' | 'classic', default 'state'), the first row of App Settings,
Appearance, Tabs, so the old and the new strip are one choice apart.
- By case (option A): each case's tabs sit in one .tab-cluster box in
first-appearance order, labelled with the case and its count, coloured
by a stable hash into the session palette. Membership is
_mobileOverviewCaseFor(), the home screens' own match. Inside a box with
company a generated w75-api-gateway reads w75; the -<case> stays in the
DOM in a .tab-name-case span only .tabs-clusters hides. The incremental
render path rebuilds only when the cluster structure key changes. The
rail and the sidebar get a labelled section per case; phones dissolve
the boxes into the chip row. Drag stays inside one box.
- Ledger (option B): CSS only on .tabs-ledger, desktop header strip: an
auto-fill column grid of equal cells in mono type with a 3px status
bar. Its markup is identical to classic's.
- State Order (tabStateOrder: 'urgent-first' | 'urgent-last'): flips the
by-state groups so needs you can be the bottom row.
- By state: the label column is measured to the widest label on screen
and the labels are right-aligned in it, instead of a fixed 92px gutter
that left short labels far from their tabs.
- Tiles: a three-row grid (label, value, bar) with pixel line-heights in
the bundled JetBrains Mono, 36px like the header. The bar used to lie
over a fixed 28px tile, and a taller system mono (SF Mono) pushed the
value into it. The WS tile's grid moved onto an inner .connection-tile
span because JS writes the indicator's display inline. Compact uses the
same font and a matched WS size.
Tests: test/tab-clusters.test.ts (new), plus the rename and the reversed
order in test/tab-triage.test.ts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Two directions from Discussion #426, each a per-device setting and the
new default.
Tab Grouping (tabGrouping: 'state' | 'none', default 'state', option C):
tabs are grouped needs you (red, plus failed sessions), waiting (yellow),
working and idle (also ended, exited panes and web tabs), most urgent on
top. The desktop header strip draws a row per group with its label and
count in a left gutter; the flat vertical rail and the sidebar draw a
section per group; tablets keep their scrolling row with inline dividers;
phones keep the chip row in group order without headings. Classification
is the home screens' own (_mobileOverviewState/_mobileOverviewExit), the
fold into four groups is pure in CodemanTabTriage (constants.js). It is
flex `order` plus aria-hidden heading/break elements reconciled in place
after both render paths, never a DOM reorder, so Alt+N, the keyboard walk
and drag keep reading tab order; a drop is refused across groups. Named
groups in the vertical rail take precedence.
Header Stats Style (headerStatsStyle: 'classic' | 'compact' | 'tiles',
default 'tiles', option G): tiles give WS, CPU, MEM and each plan window
a label-over-value tile with a bar underneath; compact is one WS/CPU/MEM
pill with sparklines plus a plan-ring pill; classic is the header as
before. Desktop only (classic below 768px and in solo windows). The
clustered styles move #connectionIndicator into #headerSystemStats and
WS stays out of a hidden System Stats pill. The extra parts are always
rendered and hidden by default in CSS, so classic is unchanged.
Tests: test/tab-triage.test.ts, test/header-stats-style.test.ts; three
source pins in test/tab-rail-order.test.ts follow renamed lines.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>