selectSession() toggles .active at once while _syncTabStripCorner() only runs in
the debounced render, so a cap set after measuring left a newly selected first
tab wider than the room and dropped the first row below the toolbar until then.
max-width changes nothing on a narrower tab, so the cap is simply always on, and
the clear-before-measure and finish-the-animation loops go. The no-usable-room
test uses 8 tabs: at 40 the scroll fallback fires first and it passed at any
threshold.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The wrapped header strip fell back to the plain wrap whenever its first
tab was wider than the room beside the toolbar. A selected tab is wider
(its gear and close show), so selecting the first tab flipped the whole
strip back: the toolbar took its own column again and, with long names,
every tab took a line of its own. Selecting any other tab brought the
full-width rows back.
The first tab is now trimmed to the room instead (tab-corner-fit, a
max-width from --tab-corner-first-max, its name ellipsized). Only a
toolbar that leaves less than TAB_CORNER_MIN_FIRST_TAB (120px) beside
it keeps the plain wrap, which does not depend on the selection.
Two measuring details make the trim hold:
- the room comes from the rounded-up float width, less the tab's 2px
margin and a pixel for subpixel layout (a tab at the exact width still
wrapped under the float);
- the first tab is measured with its trim cleared and its gear/close
width transitions finished, or a grown room reads the last trim as the
width and a selection change is judged by the width the tab is leaving.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The name rule now matches safePathSchema (letters, digits, space and _ - . ~,
no .., no leading dot, at most 255 bytes) so every folder it makes can be
linked. A file as the parent is a 400, errno text is no longer returned,
showHidden is forwarded like browse, and Create is disabled while the request
runs. Adds multi-user scoping tests.
The picker could only choose an existing folder. POST /api/filesystem/mkdir
creates one folder inside a folder the picker may browse (same roots,
ownership and blocked-tree rules as browse), and PathPicker gains an opt-in
allowNewFolder that adds a New folder button and selects the result.
Set --rv-content-max: none on the File Viewer's rendered document, so
ASCII-diagram blocks (capped by that variable with a 720px fallback the
Response Viewer sets) follow the window like the rest of the document,
and pin it with a third assertion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review follow-up for the L-shaped header strip:
- _syncTabStripCorner() keeps the plain wrap when the plain strip is
already past --tab-strip-max-height, checked before .tabs-corner goes
on: the float scrolled with the tabs while the toolbar stayed put, so a
scrolled strip put tabs under #headerRight, and a re-render of a
scrolled two-rows strip sized the float from the scrolled frame. The
"- scrollTop" in contentTop is gone (always 0 now).
- A strip just under its max-height that passes it once the float is in
falls back to the plain wrap as well.
- test/tab-strip-corner.browser.test.ts (real Chromium, real header and
styles.css), listed in BROWSER_TEST_GLOBS.
- src/web/CLAUDE.md Tab layouts entry describes the L-shaped classic strip.
- Session update no longer resets "How often" while typing: updateGitFetchStatus
(session-ui.js) leaves a control alone while it is document.activeElement, and
autoSaveGitFetch writes the clamped interval back itself (Enter saves with the
field still focused).
- Partial override shows the app values: enabled = session ?? app switch,
interval = session ?? app interval ?? 300 (session-ui.js updateGitFetchStatus).
- runFetch (git-fetch.ts) arms the spawn with what the core.sshCommand read left
of timeoutMs, and answers ETIMEDOUT without spawning when nothing is left, so a
git that never answers costs one timeout, not two.
- fetchEnvFor keeps a GIT_SSH from the server's environment (repoSshCommand ||
base.GIT_SSH), fileoverview and CLAUDE.md line updated to say so.
- i18n.js: the zh-CN "Git fetch on an interval" description scopes the only
writing git command to the Git window.
- styles.css: the disabled/busy rules are scoped to .git-status-actions
.btn-icon-sm (moved beside .git-status-actions), and the busy pulse stops
under prefers-reduced-motion.
- Comments that called the override per case now say per session: session.ts
(field, restore and getter docs, setGitFetchSettings), git-fetch-scheduler.ts:2,
server-timing.ts:78, server.ts (cleanup and restore), app.js _onSessionUpdated,
session-ui.js (openSessionOptions and updateGitFetchStatus).
- Tests: test/git-fetch-session-options-ui.test.ts (new, JSDOM: typing survives
an SSE update, the Enter write-back, both partial cases); test/git-fetch.test.ts
(GIT_SSH kept; one bound for the read and the fetch, stub git on PATH).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A wrapped classic tab strip stopped at the toolbar's left edge on every row,
so the space under the header chips stayed empty and long tabs took a row
each. Once the strip wraps, the toolbar now sits over the strip's top-right
corner and the strip lays its tabs out inline around a float sized to the
toolbar's box (_syncTabStripCorner), so only the rows beside the toolbar stop
short of it and every later row runs the header's full width.
- Wrap is still measured with the toolbar in flow (the corner is dropped
before the measure), so the single-row strip is unchanged.
- A ResizeObserver on the toolbar re-decides wrap + corner and redraws the
lineage routes when a reading or the divider changes its size.
- Falls back to the plain strip when the first tab cannot fit beside the
toolbar (it would otherwise leave an empty band).
- Classic arrangement, desktop, header host only; state/case/ledger, the
sidebar and the tab rail keep their own layouts.
Fetch is the only git command Codeman runs that writes, and the only one that runs a
repository's own fetch-post hook, so it stays behind a click and off by default. The Git
status window's header row gains Fetch; App Settings -> Bottom bar gains the switch and
the interval, synced because the scheduler acts on them, and Session Options -> Git
overrides both per case. The scheduler reads settings fresh each tick, times the interval
from the start of a run, never overlaps runs, and re-checks the Docker and remote rules
every tick before any git runs. Nothing but remote-tracking refs moves: HEAD, the index
and the working tree are proven unchanged against real git while origin/main advances.
Tests: src/git-fetch.ts and the scheduler, the per-case settings route, the synced/display
setting split, the two new i18n rows, and the Fetch block of the Git status browser suite.
The corner grip and Expand size the window correctly, but the document
kept a fixed 960px reading column from before the resize feature
existed, so text stopped reflowing once the window grew past it.
Pinned by new rule 13 in file-preview-window.test.ts.
Co-Authored-By: Claude Code <noreply@anthropic.com>
Opening another session in the case you are already working in meant re-picking the case and the mode in the toolbar. An opt-in + now sits with each tab's gear and close icons (App Settings, Tabs, New Session Button on Tabs: per device, off by default everywhere). It opens a menu of the CLIs that tab's case can start, each shown with its logo and name as in the Run menus, and a pick starts one session in that tab's case, or the managed worktree it runs in, and in its named group, without changing the Run button's mode or case.
The CLIs offered follow the Run menu's own rule for the case, so a Docker case lists what its container has rather than what the host has. A tab outside any named group sends no group, so the case's default session group places the new session the way it places a toolbar Run. Wherever a tab has a row menu (the vertical rail, the sidebar) that menu gains "New session here…", the keyboard route to the same picker, since Enter on the + itself does nothing. On a phone the + joins gear and close on the active tab only, and the label reserve grows to 68px while the setting is on, so the tab's centre stays off the icons.
The shared tab layout menu no longer closes when focus leaves an item for nowhere: Safari never focuses a clicked button, so pressing an item blurred the focused one with no related target and the click was lost. This is the tile count menu's rule, and it fixes the named-group menu as well.
Every keyboard open and close resized the PTY, and Claude Code's inline renderer answers each height change by clearing the screen and reprinting only the transcript tail that fits, so rows above that tail were lost from tmux for good. Measured on Claude Code 2.1.306 with tmux 3.2a: five keyboard cycles (62x38 to 62x15 and back) left 75 of 100 numbered lines in the pane's history; with the rows held the PTY stays 62x38, no SIGWINCH reaches the pane and all 100 remain (#672).
While the keyboard is up the rows captured at the show transition are held through getTerminalDimensions, columns still follow the box, and the screen, now taller than its box, is translated up by --kb-clip so the last content row sits at the bottom of the visible area. This is the one documented exception to the columns-only geometry rule: there the extra rows came from another device and nothing kept the composer in view, here they are this device's own rows held on purpose and the clip keeps the composer visible. The local-echo overlay's setRowLimit narrows the renderer's existing totalRows bound, so one place decides where a long draft stops growing downward.
Only inline renderers hold, through a new registry capability keyboardHold: 'inline' (Claude alone) published to the page as window.__codemanKeyboardHoldModes. Full-height layouts such as codex, Claude's fullscreen mode (mouse tracking on, unmeasured), and remote or docker panes resize as before. The hold is released on keyboard close, a shape change, a tab switch, a session update, a reconnect, or the setting turned off. Per-device setting Keep Size Under Keyboard, default on.
#689's density steps hide every `.header-system-stats .stat-label` once the
toolbar is narrowed, and #692's readings (GPU, VRAM, GPU MHz, CPU MHz) have
nothing else naming them: at tight, dense and tiny the header showed "40%"
beside CPU's "12%", "3.2G" beside MEM's "7.9G", and two bare "1800 MHz" /
"4200 MHz" with no way to tell the GPU clock from the CPU one. At Compact +
tiny, where values are hidden too, the two clocks (no ring) were left as
0-px items that still took an 8 px flex gap each. Neither PR shows it alone.
- The four host items carry `stat-item--host`, excluded from the density
label hide, so they keep their labels at every step.
- `stat-item--ringless` (static on the two clocks, toggled by
`_updateHostSensors` on VRAM while its total, and so its ring, is unknown)
is hidden at Compact + tiny instead of staying an empty item.
Tests: the browser test drives every density in all three styles against
the real markup and styles.css (it fails on the previous files), and the
unit test pins the VRAM ringless toggle.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflict in config/test-suites.ts resolved by keeping both sides: #689's
header-divider and #692's header-host-sensors browser tests are both
listed in BROWSER_TEST_GLOBS.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The palette's "New session" row now shows the agent it will start and
lets Left/Right (or the row's arrows) switch it. The agents come from the
injected CLI catalog, filtered to enabled and installed entries, so no CLI
id list lives in the palette. The pick is a one-shot launch through run()
with a temporary _runMode swap: the Run button's mode is left unchanged.
Conflict in CLAUDE.md's State Files line resolved by keeping #661's new
config-backup entries and this PR's "case default groups" in state.json.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #665.
- A markdown link converted to a preview link became `a.rv-path`, so its
label ("Our incident record") took the path style: monospace, 0.92em
and `word-break: break-all`, which split prose mid-word on a phone. The
anchor now also carries `rv-path-link`, and
`.rv-text a.rv-path.rv-path-link` resets font and word-break, as the
File Viewer does for its labelled document links.
- The first new test adds `/tmp/a.json?raw=true` (pins the `?query`
strip) and `/tmp/` (pins the trailing-slash folder check, since
findFilePathLinks finds no folder in a bare `/tmp/`), and checks the
new class.
- The `_linkifyFilePaths` JSDoc and docs/architecture-invariants.md say
the function also rewrites anchors whose href is a scheme-less,
non-`//` path, on purpose without FILE_PATH_LINK_PATTERN's root and
extension filter.
- Mobile Guide: "a web link" in the response viewer opens a tab.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Merge-time fix for #659.
- Alt+Left/Right (File Viewer back/forward) is a display-only entry in
DEFAULT_SHORTCUTS, so the Ctrl+? overlay lists it, and a row in the
help modal's Panels group, pinned in help-modal-shortcuts.test.ts.
zh-CN entry for the new label; the "Restore size" entry is dropped,
since nothing shows that string any more.
- The wiki row moves from "Sessions and tabs" to "Everything else".
- The grip comment in styles.css says only phones hide the grip;
tablets show and use it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Case settings gains a "Default session group" selector (Ungrouped plus every
named tab group of the owner). A new session of the case lands in that group
in the same versioned layout write that registers it, so it is drawn in place
with no Ungrouped-to-group jump.
- Stored per owner and case in state.json (caseDefaultGroups), as the group's
stable id or null for Ungrouped; read and written only through
TabLayoutService under the owner lock.
- Precedence in sessionCreated: explicit tabGroupId, then the parent
placement, then the case default, else Ungrouped. A deleted or foreign id
never fails a spawn and is forgotten only after the creating write lands.
- POST /api/quick-start (local, linked, remote, Docker) and
/api/ralph-loop/start pass their case; POST /api/sessions infers it
lexically from workingDir. The create response carries tabLayout when a
case default placed the session.
- GET/PUT /api/cases/:name/default-group; deleting a case (or an admin
deleting a user's case folder) forgets the value.
- The selector mirrors desktop and phone, reverts on a refused save, and is
redrawn from every adopted tab layout; zh-CN strings added.
A phone in landscape is wider than 599px, so the 44px targets now follow (pointer: coarse) instead of the width. A balanced title broke the zh-CN text inside 入侵; :lang(zh) keeps CJK runs whole and breaks at the title's spaces and colon.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The card is capped to the room below the toast stack's top and
scrolls inside itself. The stack is fixed and cannot scroll, so on a
phone in landscape the update toast's buttons sat below the screen
with only the × left, which records the notice unread. The accent bar
becomes a background layer, which stays put while the content scrolls.
- Small amber text and the primary button use --notice-ink, darkened a
step on the four light skins, where --yellow measured 3.9 to 4.4:1 for
the 10.6px eyebrow (now 5.1 or better, the button label 5.7 or better).
- The update toast that carries a notice frames itself in the accent
colour and leaves the amber to the warning section inside it.
- The × uses --text-dim (2.4:1 on og before, 5.5 now).
- Phones: every button fills its row (a wrapped Dismiss no longer sits
stray at the left) and is 44px tall, and the × gets a 44px box.
- The title balances its lines, and the card matches the plain toast's
420px so a toast stacked under it lines up.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A hand-maintained src/web/public/security-notices.json carries warnings
about things outside Codeman that its users should act on. The first:
OpenCode's report of unauthorized access to its legacy Console, which
may have exposed provider API keys saved there.
- Pure core in constants.js (CodemanSecurityNotices): select() keeps the
notices that apply now (inclusive local-day dates, not yet seen, https
links, malformed entries skipped), and one naming a CLI only when
window.__codemanCliAvailable[cli] === true. Unknown means not
installed: a warning about software you do not have is noise. The CLI
id is data in the JSON; no code branches on it. plan() decides the
surface, so a load shows each notice once.
- An existing install on a new version gets the notice INSIDE the
release's update toast: the highlights as before, a warning section,
and the notice's link next to "See what's new". With no notice the
update toast is the plain one it always was.
- Everyone else (a fresh install, a same-version reload) gets its own
card in the toast stack: shield, "Security notice" eyebrow, title,
message, the link, Dismiss and the ×; amber-to-red accent bar, theme
tokens only (dark and light skins), full width on phones, an
opacity/transform entrance and none under prefers-reduced-motion,
role=alert.
- A notice is recorded as seen only when the user closes it, dismisses it
or follows its link, never when it appears. Storage blocked shows
nothing. The notice's own text is textContent and carries its own
zh-CN, so i18n.js leaves it alone; the card's own copy translates.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Copilot (`copilot`, npm @github/copilot) as a stock CLI entry with the same launch options as the other agent CLIs: model (--model), resume (--resume), continue (--continue) and allow-all (--yolo, clamped off for non-granted multi-user owners). Adds a resolver and GET /api/copilot/status probe, the agent-image package, Run-menu/mobile/logo entries, skill docs and tests.
The worktree API needs a front door that works from the place cases are
already picked. The toolbar case picker gains a third action row,
"Git worktrees…" (the phone case sheet gets a Worktrees button next to
Create New Case; no header button anywhere), opening one dialog per case:
- the repository's worktrees, with the managed ones (.claude/worktrees)
offering Open (start another session in it) and Remove…, an inline
confirmation with "Also delete its branch" (git branch -d) whose git
refusal is shown verbatim through textContent;
- a New worktree form (name, optional branch and start point) and a
"Start sessions with" picker over every enabled, installed run mode,
built from the registry catalogue as data, never an id branch.
Launching goes through run() with options.worktree and the same temporary
_runMode swap the custom-model entries use, so every mode keeps its own
path: claude and shell create through POST /api/sessions with the
worktree as workingDir, every other CLI through quick-start's worktree
field. Tabs are named w<n>-<worktree> and the Run button's mode is left
as it was.
Close Session offers "Kill & remove worktree" for a session that runs in
a Codeman-managed worktree; the removal waits for the server to confirm
the session is gone (closeSession now resolves true/false), and a dirty
tree keeps the worktree with git's reason in a toast that opens the
dialog. zh-CN strings and toast patterns for all of it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflicts resolved against #605 (and #602's test mock):
- CLAUDE.md Prompt uploads: #606's rewrite is kept and #605's bounded
workspace probe sentence follows the remote (SSH) refusal.
- test/routes/session-routes.test.ts: the remote-pane-history and
bounded-path-probe mocks are kept beside #606's _resetPasteRateBuckets
import, and both PRs' paste-image tests are kept.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Conflict resolved in docs/api-reference.md: #607's prompt cache section and
#528's tabGroupId section are both kept.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
One conflict, in i18n.js SKIP_SELECTOR: master added '.tab-name' itself
(the same fix this branch made for zh-CN tab names), so master's line is
kept and this branch's duplicate comment dropped.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Folder paths in the terminal and the response viewer are now links
(rooted or ~/ without an extension, relative with a trailing slash); a
click opens a grid of the folder's supported files, newest first, with
subfolder chips and a parent button. A tile opens the file preview,
which pages through the folder with ‹ › / ←/→; the preview of any file
gets a "Show folder" button.
Server: POST /api/sessions/:id/folders grants + lists a folder (local
fs or one ssh round trip for a remote case) under the attachment guard;
GET …/folders/:folderId/files/:name/(raw|preview|thumbnail) serves one
file by bare name through resolveServableAttachmentPath, so browser
requests still never carry a path. Grants live in their own small
registry so a big folder cannot evict the session's attachments. The
attachment raw route's body is now the shared serveServableRaw helper.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A session or web-tab ref in the owner tab layout may now carry
`focus: true`. "Add to Focus" in a row's menu (right-click on a web tab)
sets it; the vertical rail then draws a shortcut to that tab in a pinned,
collapsible "Focus" section at its top, while the row itself stays where
it is. A click on a shortcut selects its tab, a right-click or the menu
key offers "Remove from Focus".
Model: `TabRef.focus?: true`, parsed strictly (only `true` or absent) and
carried through normalization, every pure edit and the legacy-order
projection; `setTabFocus()` is the pure helper. TabLayoutService needed
no change and no route was added: the flag rides the existing
`PUT /api/tab-layout`. Browser: `copyRef`/`normalizeLayout` keep `focus`
the way they keep `placement` (the browser writes whole layouts, so a
dropped field would be erased on every save), `setFocus` is a named
operation through `createEditCoordinator` and replays on a 409, and
`focusRefs()` (also on `project()`) lists what the rail can draw.
The section is its own element in #tabRail above #sessionTabs, so it
works on the flat rail (no named groups) as well as the grouped one, and
shortcuts are never rows: no Alt+N badge, no tree item, no lineage
anchor, no drag target. It is a list with one tab stop and
Up/Down/Home/End between shortcuts. Collapse is per-device localStorage
under its own key. A shortcut whose session closed or whose web tab is
not open drops out; the server already prunes deleted refs. zh-CN
strings added.
A phone-only toolbar Attach button hands the agent a photo or a video with the
keyboard down. It owns its own file input, positioned over the button so iOS
anchors the picker popover to it; the picked paths land on the prompt as unsent
text, so Compose adopts them together with anything already typed and Use
terminal keyboard hands a single-line draft back the other way.
Videos take a second path on the same route: ?kind=video streams the file to
disk under MAX_PASTE_VIDEO_BYTES (1GB, env CODEMAN_MAX_PASTE_VIDEO_BYTES)
instead of buffering it, sniffs the container from the first bytes as they
arrive, drains a mislabeled body unwritten and unlinks on any failure. It draws
on its own 3/min per-(IP, session) rate bucket beside the image path's 30/min:
at 1GB each, 30/min would be 30GB of disk a minute. Without the flag the route
is byte-identical to the image-only version, same 50MB cap and same bucket.
One client classifier, _promptAttachKind (MIME first, then the name's
extension), drives every pick and paste surface: the Attach button, terminal
drop, the tile grid's drop, the Compose and paste-dialog pickers and the
clipboard handlers. A pick that lands while the tile grid owns the terminal
goes to the PTY rather than the parked main-terminal overlay.
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>
Since the sidebar filter and the rail search share _applyTabListFilter(),
the sidebar's filter box also marks a .tab-cluster it emptied in the
by-case tab layout and rewrites its count. But the only rule hiding
.tab-cluster.tab-filtered-out was the rail-scoped one, so in the sidebar
the emptied box stayed painted with its label and a count of 0 while its
rows were hidden.
The sidebar rule now hides the marked case box as well. It stays scoped
to html[data-session-list="sidebar"], so a leaked class still cannot hide
anything on the header strip. An alerted row still counts toward its box
(the owner's call on #580), so a box holding a row that needs the user
never hides.
Tests: a unit test renders the sidebar in the by-case layout and checks
the emptied box is marked with a 0 count, that an alerted row keeps its
box on screen and counted, and that clearing restores the totals; a
stylesheet check pins the scoped selector; the browser test measures the
shipped styles.css in Chromium: the marked box is not painted in the
sidebar layout, an unmarked one is, and the header layout hides nothing.
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.