Commit Graph
82 Commits
Author SHA1 Message Date
Codeman maintainer 92797c2619 test(desktop): unit tests for main.rs's decisions and a browser test for the connect page
main.rs was the only source file without tests, and it held three decisions
with real failure modes:

- Which login a connect goes out with. The choice was a closure body inside
  the probe worker; it is now choose_login, tested for every row: typed wins,
  then the Keychain, then the local Codeman's own login, which is used only
  when no saved password was expected and only for the same username.
- What the Keychain keeps afterwards (update_keychain): saved or deleted by
  Remember, never a copy of the local server's login, never a password the
  server wants changed, and the old user's item deleted before the new save.
  The in-memory Keychain store moves out of credentials' test module so other
  test modules can use it.
- When a commit in the main window means the server page is loaded.

The connect page had only substring checks in the repository; the checks that
drove it lived in a scratch script. They are now
test/desktop-connect-page.browser.test.ts: Chromium, the page's real CSP, a
stubbed native side, five ordered walks through every state of both tabs
(first run, logins, failures, redirects, Start, the install wait, the SSH
install, layout, double clicks, keys, a starting server, a WebKit too old).
Listed in BROWSER_TEST_GLOBS, so the nightly browser suite runs it and
`npm test` does not.

Also pins the install one-liner the page shows to the one Copy copies.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-06 03:54:49 +01:00
Codeman maintainer 2aa6016d33 chore: version packages
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 00:04:01 +01:00
Codeman maintainer f36bea86c7 Merge pull request #693: feat: edit history panel for local claude sessions (files edited, diffs, generated images) 2026-11-04 17:04:46 +01:00
Codeman maintainer 76e9f364f9 Merge pull request #735: fix(links): preserve wrapped configuration filenames 2026-11-04 16:55:44 +01:00
Codeman maintainer 7ed660ff11 Merge pull request #737: fix(mobile): show a press on the phone toolbar and accessory keys 2026-11-04 16:47:48 +01:00
Devvyn 5ce8213726 fix(links): preserve wrapped configuration filenames 2026-10-28 21:17:50 +08:00
Codeman maintainer f1f7eec4d6 chore: version packages
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:10:31 +01:00
Codeman maintainer 593b2eb2ea Merge pull request #721: feat(tabs): L-shaped header strip, wrapped rows run under the toolbar 2026-10-28 09:10:09 +01:00
JD 8d75e6bd15 fix(mobile): show a press on the phone toolbar and accessory keys
On every skin but og the skin block outranks the phone :active backgrounds of the toolbar buttons, so Run showed no press at all and the others at most a faint tint, eased in over their 0.2s transition, so a quick tap showed almost nothing. A press now scales and brightens (darkens on the light skins) over 80ms, two properties nothing on these buttons sets at a higher specificity, and honours prefers-reduced-motion.

A browser test presses every phone bar control on all seven skins and pins the computed transform and filter, so a later rule that takes the press away again shows up in the browser suite.
2026-10-28 01:04:15 -04:00
Codeman maintainer ecf28fa3be chore: version packages
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 09:37:13 +01:00
timkjr 285aa0b2b2 fix(tabs): keep the plain wrap when the strip scrolls, add a browser test
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.
2026-10-26 21:35:04 -05:00
Saqeb Akhter c0d27079d7 test: register palette keyboard browser regression (johoja12/Codeman#21) 2026-10-26 01:16:02 +04:00
Codeman maintainer 3e06f3c141 chore: version packages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-24 17:19:51 +02:00
Codeman maintainer c7b6226cb0 Merge pull request #692: feat(header): opt-in GPU and CPU clock readings after CPU and MEM
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>
2026-10-24 15:25:02 +02:00
Devvyn e1d6471808 feat(header): opt-in GPU and CPU clock readings after CPU and MEM 2026-10-23 21:36:10 +08:00
RandalixandClaude Opus 5.5 95e2272f89 feat(edit-history): edit history panel for local claude sessions
Part 1 of #643 (local sessions only; remote follows as its own PR). An
opt-in, per-device header button opens a panel with the active session's
edits, newest first: every Edit/Write/MultiEdit/NotebookEdit of the main
agent and its subagents with its diff, plus images a Bash call rendered or
the agent sent. Remote, Docker and non-claude sessions answer `unsupported`.

- Reads only on request: `?sync=1` when the panel opens; while it is open on
  that session it re-reads after an edit-capable `transcript:tool_end` or a
  `transcript:complete` from the existing transcript watcher. No hook, timer,
  second watcher or new SSE event; a wiring guard pins it.
- Main transcript: the path the transcript watcher follows (hook-reported),
  else `<id>.jsonl` under the session's own CLAUDE_CONFIG_DIR override or
  the server's config dir. A config dir set outside Codeman is found with
  the next hook; until then the panel says so.
- Store: dataPath('edit-history')/<id>.json, 0600 via tmp+rename, capped at
  500 entries and 4 MiB of diff text per session, deleted with the session,
  blocked in isSensitivePath, never fed to /api/search. Every route goes
  through findSessionOrFail with req.
- Images only through existing file surfaces: file-raw inside the
  workspace, the attachment route with notify:false outside it.
- textContent rendering, bounded byte-offset reads, marker-class header
  button, hidden on phones (locked in the mobile policy test).

Docs: Working-With-Files, Settings-Reference, architecture invariants, API
reference, CLAUDE.md key pattern.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-23 14:00:48 +02:00
Devvyn fa2d75c951 feat(header): resizable toolbar divider that collapses the usage readings as it narrows 2026-10-23 19:01:06 +08:00
Codeman maintainer 002de32a08 chore: version packages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 20:33:07 +02:00
Codeman maintainer 3652670a7d Merge pull request #645: fix(terminal): isolate scroll batches and support tracked Codex scrolling
The remote-history PATH bootstrap that rode along in this PR was left out
at merge (it belongs in its own PR that routes every remote tmux call
through one wrapper), so 242bbfcd's title overstates what lands here.

Conflicts in CLAUDE.md and docs/architecture-invariants.md resolved by
keeping this PR's rewrite of the wheel/touch paragraphs and re-adding
#664's touch-forwarding sentence to both.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:29:53 +02:00
Codeman maintainer 6fb9c4ed8a fix(mobile): hand a sideways-opening drag back to the scroll past the swipe's drift limit
Merge-time fix for #674: the first travel past the tap slop decided the
axis for the whole contact, so a scroll that opened on a thumb arc
(+9 px across, +5 px down, then straight down) locked as 'swipe'. Every
later touchmove returned before the scroll code and SwipeHandler gave
the swipe up at its 50 px drift limit, so the gesture did nothing. Past
SwipeHandler.maxVerticalDrift the drag is now handed to 'y' (and the
published axis with it), so it scrolls; a clean sideways swipe, a
vertical scroll and a long-press drag are unchanged.

The source pin in test/session-swipe-gesture.test.ts moves with the
code and also pins the drift value, and a new browser test drives the
real touch listeners: the thumb-arc drag ends as a scroll (it fails
without the fallback, axis stays 'swipe') and a clean sideways swipe
stays a swipe with nothing scrolled. The architecture note and
src/web/CLAUDE.md now say a swipe drag is consumed only up to that
limit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-22 19:18:51 +02:00
Saqeb Akhter 242bbfcd31 fix(terminal): capture remote history and preserve isolated wheel travel (#7) 2026-10-22 02:01:17 +04:00
Codeman maintainer e63bb65b36 chore: version packages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-18 10:03:55 +02:00
Devvyn c5de8383e7 feat(cli-registry): GitHub Copilot CLI as a run mode
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.
2026-10-18 00:47:46 +08:00
Codeman maintainer ce809eac41 chore: version packages
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 13:23:48 +02:00
Codeman maintainer 5eccaca3bc Merge pull request #616 from opticon454/fix/deepseek-install-path
fix(deepseek): find pnpm when installing the terminal profile, and install it with dsh

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:59:51 +02:00
Codeman maintainer 688bdfb970 fix(#616): install pnpm only when none is on PATH
The DeepSeek install command asked npm for dsh and pnpm in one go. On a
machine that already has pnpm through corepack (`corepack enable` puts a
pnpm shim in node's own bin directory, which is npm's global bin for nvm
and Homebrew installs), `npm install -g pnpm` refuses to overwrite that
shim with EEXIST, and because both packages were in the same command the
whole install failed: dsh was not installed either. Measured with npm 10
against a scratch prefix holding a corepack-style shim. On master the same
machine installed dsh fine and the profile install used the existing pnpm.

The command is now `npm install -g @deepseek-ai/dsh && (command -v pnpm
>/dev/null || npm install -g pnpm)`, so pnpm is installed only when the
install environment has none, and a failed dsh install still fails the
command. The two hints that embed it follow (the launch-table drift test
requires that), and the catalogue is regenerated; install.sh withholds
the command for a launcher-only CLI, so its block is unchanged.

The test now runs the real stock string through /bin/sh with a fake npm
on a PATH it controls, for the three cases: no pnpm, an existing pnpm,
and a failing dsh install.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-16 01:37:41 +02:00
Ludo Fourrage 96f6cdea8c fix(tmux): reset capture styles before live output resumes 2026-10-14 18:07:32 +00:00
Codeman maintainer 7eaa39a67e chore: version packages (1.41.1)
One consolidated patch changeset for the 24 PRs in this release (the PRs'
own changesets folded in), CHANGELOG, package and plugin manifest versions,
and the CLAUDE.md version line.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 19:22:13 +02:00
Codeman maintainer a6ce52d3e7 Merge pull request #582: feat(tabs): pin tabs to a Focus section on the vertical rail
Conflicts resolved against #528:
- CLAUDE.md Owner tab layouts: #582's Focus sentences and #528's
  tabGroupId / spawnedTabsFollowParent sentences are both kept.
- i18n.js: both features' ZH_CN entries kept.
- tab-layout-browser.js: the section-model JSDoc documents #528's
  `options.parents` and #582's `focusRefs` return field.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:13:03 +02:00
Codeman maintainer 0e11012470 Merge pull request #527: fix(terminal): page a hollow Codex transcript with PageUp/PageDown from the first wheel event
Conflict resolved in terminal-tile.js's @dependency header: #601's
SHELL_LOAD_BASE_QUERY/SHELL_LOAD_QUERY and #527's pageKeysForGesture and
app.tabAlerts are all listed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 18:06:24 +02:00
Codeman maintainer 7ba1e51c51 Merge pull request #588: perf(terminal): tab switch to a fullscreen Claude tab loads only its current screen
Conflict resolved in config/test-suites.ts: both new browser test globs kept.
2026-10-13 17:49:16 +02:00
Codeman maintainer c041a418e0 feat(mcp): codeman mcp, a Model Context Protocol server for any MCP client
Agents pick tools from MCP clients and registries, and Codeman had no MCP
surface: an assistant in Claude Desktop, Cursor, Codex or Gemini CLI could
not start or drive Codeman sessions without hand-written HTTP calls.

`codeman mcp` is a stdio MCP server and a CLIENT of the existing HTTP API
(it reuses codeman agent's transport and guards; no new route). Seven
tools: list_sessions, start_session, send_prompt, wait_for_session,
read_output, interrupt_session, delete_session.

- Dual-era: answers the legacy initialize handshake (2024-11-05 to
  2025-11-25) and the 2026-07-28 revision (per-request _meta,
  server/discover, resultType, and the ttlMs/cacheScope hints a modern
  tools/list must carry: Claude Code 2.1.296 drops every tool without
  them).
- Never guesses a server: the URL comes from --url or CODEMAN_API_URL.
  Unconfigured, it still lists its tools and every call says how to
  configure it.
- Prompting, interrupting and deleting are limited to sessions the process
  started (or, inside a Codeman pane, that session's own workers) unless
  --allow-any-session; never its own session. Control bytes are refused
  before any request.
- Waits default to 45 s (Codex's MCP tool timeout is 60 s), max 600 s.
- read_output returns text only: a client that prefers structuredContent
  otherwise showed the model metadata instead of the output.
- config/mcp-registry/server.json and package.json mcpName prepare the
  official MCP registry listing; sync-plugin.mjs keeps its version.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-13 13:23:56 +02:00
Aamer Akhter 3ea1e03aaf fix(terminal): one touch contact is one paging gesture; bound slow ticks and wobble
Review of #527 (Ark0N, 2026-10-11):

1. A slow touch drag paged a full screen per line. touchmove calls the
   fallback once per whole cell, so a slow drag's calls were further apart
   than the 150 ms gesture gap and each pre-paid a page (12 lines at 170 ms
   sent 12 PageUps). The touch paths now tag every call, touchmove and
   momentum frames alike, with a per-contact touchId (incremented on
   touchstart), and pageKeysForGesture never splits a contact by time; a
   new contact is a new gesture.
2. A pre-paid debt now survives up to PAGE_KEY_DEBT_MS (1 s) of quiet when
   the next event goes the same way, so slow small wheel ticks (40 px every
   250 ms, a Firefox 1-line notch) pay it back instead of each pre-paying.
   A reversal inside the gap (or inside one contact) drops the debt and
   accumulates without pre-paying, so a +/-3 px wobble no longer pages
   back and forth. A reversal after the gap still pages at once. Main pane
   and tiles share the helper, so both get it.
3. A wheel consumed by the dialog check now logs page-keys-dialog-open.
4. The Dashboard wiki names newer Codex beside OpenCode; the invariants
   paragraph describes the new gesture boundaries.

Tests: unit regressions through the real _maybePageCliTranscript, helper
cases, tile/main parity for ticks and wobble, and a Chromium test that
drives the real initTerminal touch listeners (12 PageUps before, 1 after).
2026-10-10 22:50:06 -04:00
Aamer Akhter cde90a465b feat(tabs): pin tabs to a Focus section on the vertical rail
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.
2026-10-10 13:43:20 -04:00
Devvyn b7be8db80b fix(deepseek): find pnpm when installing the terminal profile, and install it with dsh 2026-10-10 14:27:54 +08:00
Codeman maintainer df1a493b8c perf(terminal): tab switch to a pane with no scrollback loads only its screen
After a session's first load per page, a tab switch fetched a 1 MiB tail of
the server's byte stream. For a fullscreen claude pane (alternate screen,
tmux `history_size` 0) that tail is old repaints of one frame, and an idle
tab parsed it twice (the cached copy, then the fresh one).

Every terminal response now records `paneHistoryLines` per session, and
`selectSession` takes `full=1` whenever the last report was 0. For such a
pane that capture is the visible frame, a few KB, which is also what a page
load already shows. A response without the field forgets the entry, so
unknown never counts as empty; a pane that starts keeping history goes back
to the tail on the next switch. Shells and the refresh path are unchanged.

Measured with real prod payloads through the real client in chromium:
median switch 1.26-1.66 s -> 0.10-0.19 s, parse ~1.1-1.5 s -> 2-3 ms.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-10 06:25:00 +02:00
Codeman maintainer 2881ef5fa5 Merge pull request #580: feat(tabs): search sessions by name on the vertical rail 2026-10-10 03:54:39 +02:00
Aamer Akhter 7c3877016b feat(tabs): search sessions by name on the vertical rail
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.
2026-10-09 20:55:32 -04:00
DevvynandClaude Sonnet 5.5 8da4a07a60 fix(viewer): make in-document links in rendered markdown scroll to their heading
marked emits no heading ids and, with <base href="/">, a bare #section href points at the dashboard root, so [Install](#installation) in the File Viewer did nothing. The shared click delegate now resolves fragment links against the rendered document: GitHub-style slugs in data-md-anchor (never ids, so a heading cannot capture an app element), case-insensitive and percent-decoded, repeats numbered -1/-2, # = top, explicit ids supported, an unmatched fragment ignored instead of navigating.

Tests: slug/assign/find unit tests (CI gate) and a real-browser test clicking links in a File Viewer document, which fails without the change.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrzFKEdBLwVfu6ev2ZscJS
2026-10-09 17:01:44 +08:00
Codeman maintainer 4fe843a94e fix(tiles): page a hollow tile's CLI transcript and report its clicks (#555 parity)
#555 made the primary pane page opencode's transcript with PageUp/PageDown
from the wheel, because opencode draws in place on the alternate screen and
leaves the browser's buffer with no scrollback. A TerminalTile (a grid tile,
the split's Pane B) left every wheel to xterm, so in an opencode tile the
wheel scrolled nothing, or only stale rows.

The tile now runs the primary pane's own gates aimed at itself (its terminal,
its session, never the active one): xterm's tracking mode, the Claude
forwarding gate, then the hollow-buffer test. A wheel that passes them is
consumed in the capture phase and turned into PageUp/PageDown through the
shared pageKeysForTravel math, coalesced per tile (40 ms, 512 bytes, the twin
of the primary pane's queue) and sent ephemeral on the tile's own socket.
Every other wheel stays with xterm as before, the shell history pull
included. The file names no CLI: the mode rules stay in terminal-ui.js, and
terminal-tile.js joins the frontend no-id-branching guard.

A plain port of the primary's baseY === 0 test would almost never fire in a
grid. A tile's first capture is taken at the PTY's previous size (usually the
taller primary pane's) and written into a shorter xterm, and its own
row-shrinking fits (zoom-out, divider drags, tile count changes) push more
rows above the screen. The tile counts those rows as its own overflow: all of
them after a load whose capture held a single screen (the server's
captureRows), plus whatever a local fit or a PTY geometry report pushes up,
reset by a clear and clamped to baseY. The paging gate gets baseY minus that
count. Output that scrolls real lines still counts as history, so the tile
stops paging there.

#555's other half, stripping opencode's mouse DECSETs so a drag selects text,
is server-side and already reached tile sockets. It also left the tile's
xterm unable to encode opencode's clicks, so the tile now installs the
primary pane's desktop click report (bubble phase, gated on the session's
cliMouseTracking, the tile's own link hover and selection). Both listeners,
the flush timer and the page-key state are torn down in destroy().

Still out of scope, as the fileoverview now says: touch paging (tiles have
no touch path) and SGR wheel forwarding to Claude's fullscreen renderer
(tile-grid-plan follow-up 4), so a fullscreen Claude tile keeps leaving the
wheel to xterm.

Tests: test/terminal-tile-scroll.test.ts drives a real tile in the vm
harness (session targeting, every no-page case, accumulation, the cap,
coalescing, byte parity with the primary pane, the overflow discount through
a load, a fit, a geometry report and a clear, the click report and destroy);
the discount cases fail with it removed. The fake xterm gains opt-in row
emulation. test/terminal-tile-scroll.browser.test.ts checks the same model
against a real xterm with trusted wheel events (browser suite, not the gate).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-09 07:35:29 +02:00
Codeman maintainer 0868286661 Merge #502: read-only XLSX spreadsheet preview in the file-preview overlay
# Conflicts:
#	CLAUDE.md
2026-10-09 05:48:05 +02:00
DevvynandClaude Sonnet 5.5 90649fc363 fix(ui): make the Run dropdown scrollable
The menu opens upward (bottom: 100%) with no max-height or overflow, so with the stock CLIs plus custom endpoint entries it was taller than the room above the toolbar and its top was off-screen and unreachable, worst on phones. Cap it to the space between the header and the toolbar (dvh, vh fallback), scroll inside it with overscroll containment, and stop its overflow:auto children (history, saved URLs) being squashed.

Tests: a CI-gate source guard and a real mobile-browser test (touch swipe reaches the last entry), which fails without the CSS.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrzFKEdBLwVfu6ev2ZscJS
2026-10-06 19:06:39 +08:00
Aamer Akhter c36be7bb94 Merge remote-tracking branch 'origin/master' into pr/cod-455-xlsx-preview
# Conflicts:
#	CLAUDE.md
#	src/web/public/styles.css
2026-10-05 21:19:54 -04:00
Codeman maintainer 2063d15c20 Merge pull request #537 from opticon454/feat/git-status-indicator
feat(ui): git status indicator in the bottom bar, with a panel of uncommitted and unpushed work

# Conflicts:
#	config/test-suites.ts
#	docs/api-reference.md
2026-10-05 19:51:43 +02:00
Codeman maintainer fed3a0897a Merge pull request #536 from opticon454/feat/doctor-in-settings
feat(settings): codeman doctor in Settings → System → Diagnostics

# Conflicts:
#	config/test-suites.ts
2026-10-05 19:51:42 +02:00
Codeman maintainer d9c760609f Merge pull request #535 from opticon454/feat/case-custom-path
feat(cases): create a new case in a custom folder
2026-10-05 19:51:41 +02:00
Aamer Akhter 97cb5b5799 feat(tabs): edit groups in the vertical rail
The grouped vertical rail can now be edited from the browser: groups are
created, renamed, reordered and deleted, and tabs are moved between them, by
menu, keyboard or pointer drag. Every edit is saved through the existing
PUT /api/tab-layout; there are no server changes.

Saving (tab-layout-browser.js, pure):
- Edits are named operations (createGroup, renameGroup, deleteGroup,
  reorderGroup, moveRef) applied to the rail at once, mirroring the server
  model: a moved session takes the sessions that still follow it, and a
  hand-moved child is marked placement 'manual'. normalizeLayout now keeps
  placement and updatedAt, since whole layouts are written back.
- createEditCoordinator keeps ONE PUT {baseVersion, layout} in flight. Edits
  made in the same turn share a write; edits made while one is in flight go
  out on the version it returns. A 409 replays the operations onto the
  layout the server returned and retries (bounded); an operation that no
  longer applies is dropped and reported. A 400 re-reads first; any other
  failure reports and re-reads.
- dropOperation maps a finished drag to one operation, or null for a drop
  that changes nothing.

Wiring (app.js, tab-rail-resize.js):
- The session row menu gains Move up/down, Move to <group>, Move to
  Ungrouped and Move to new group in the vertical rail. Before the first
  group exists it offers only "Move to new group", which is how a flat rail
  becomes grouped; the header strip's menu is unchanged.
- A group header opens its menu with Shift+F10 / ContextMenu, right-click or
  a hover glyph (a non-focusable aria-hidden span, so the treeitem still
  holds no interactive child): Rename, New group, Move group up/down,
  Delete. F2 renames inline. A web tab row's Shift+F10 opens its settings
  plus the same moves.
- The menu closes on Escape (consumed before the global Escape handler, focus
  back to its row or header), a pointer outside, Tab, focus leaving it, a
  resize, a second open and any full re-render.
- Inline group rename shares the session rename's ownership handle, so only
  the current editor releases the render guard. Enter or blur commits,
  Escape cancels, IME composition keys are left to the IME, and the label
  becomes a flex slot so the editor gets the full width while typing.
- Pointer drag (mouse and pen) in the grouped rail only: rows before/after a
  row or into a group, a header drag reorders groups. Escape cancels; the
  click that ends a drag neither selects nor toggles. The flat rail and the
  header strip keep their HTML5 drag untouched.
- A tab:layoutChanged read is deferred while a write is in flight and run
  once it settles; a read otherwise rebases unsaved edits. On pagehide,
  unconfirmed edits go out in a keepalive PUT and into sessionStorage, and
  replay after reload (a no-op when the keepalive landed).
- New strings have zh-CN entries; group names reach the DOM only as text.

Unchanged: the flat rail's markup when no group exists, the tree semantics
and single roving tab stop, sessionOrder and Alt+N.

Tests: test/tab-layout-editing.test.ts (operations, coordinator, drop
mapping, menus, rename, dismissal, SSE deferral, reload recovery, flat-rail
identity) and test/tab-layout-editing.browser.test.ts (real pointer drags,
editor paint, menu Escape), listed in BROWSER_TEST_GLOBS.
2026-10-04 20:10:32 -04:00
Aamer Akhter ab96e74e69 Merge remote-tracking branch 'origin/master' into pr/cod-455-xlsx-preview
# Conflicts:
#	CLAUDE.md
#	config/test-suites.ts
2026-10-04 20:08:01 -04:00
DevvynandClaude Sonnet 5.5 bfc164a262 feat(ui): git status indicator in the bottom bar, with a panel of uncommitted and unpushed work
Optional and per-device (showGitStatus, default off). GET /api/sessions/:id/git-status is
read-only and offline (no fetch, --no-optional-locks), skips remote and Docker sessions, caps its
lists, and single-flights concurrent polls. The toolbar indicator shows uncommitted files,
commits not pushed, or a check; clicking opens a draggable panel in the style of the Files window.

Which repositories: the enclosing one when there is one; otherwise every repository up to two
levels below the working directory (capped, skipping dot-folders and node_modules, never
following symlinks), each in a collapsible section, with the indicator summing them. A repository
that merely sits above the workspace and is the home folder or higher (a dotfiles repo) is
ignored. Git-supplied text is only ever written with textContent.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JrzFKEdBLwVfu6ev2ZscJS
2026-10-05 07:19:11 +08:00
DevvynandClaude Sonnet 5.5 d9174a7a03 feat(settings): codeman doctor in Settings -> System -> Diagnostics
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-05 07:03:40 +08:00