Skip to content

Promote develop → main: Sessions board by-bot, CodeMirror/Monaco editors, workspace git hardening#176

Merged
haowei2000 merged 13 commits into
mainfrom
develop
Jul 13, 2026
Merged

Promote develop → main: Sessions board by-bot, CodeMirror/Monaco editors, workspace git hardening#176
haowei2000 merged 13 commits into
mainfrom
develop

Conversation

@haowei2000

Copy link
Copy Markdown
Collaborator

Promotion: develop → main

Rolls up 13 commits merged into develop (PRs #173, #174, #175 plus fixes).

Sessions board

  • Group the Sessions board by bot; show the workdir on cards and in the composer session picker
  • Make drag-to-promote pointer-based (native DnD was dropping silently); handle pointercancel; restore tag truncation

Editors

Workspace / connector

  • Git dubious-ownership recovery, refetch-storm brake, session-workdir root picker
  • Harden git ownership bypass, exclude closed sessions (Codex review follow-up)
  • rustfmt the git repo-detection tests

Chat

  • Stop react-markdown v10 crash from blanking channels

🤖 Generated with Claude Code

haowei2000 and others added 13 commits July 13, 2026 16:56
… drag

- Group by bot: each bot is a header with its session cards under it
  (primary first). The bot name moves off every card into the header.
- Show workdir on the card face (left-truncated, full path on hover),
  with a small primary badge.
- Polish drag-to-promote: source dims, primary card shows a dashed
  droppable hint, hovering surfaces a centered '↑ Make primary' pill.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…session-workdir root picker

Connector: run_git now passes `-c safe.directory=*` so a repo whose files are
owned by a different UID than the connector process (bind-mount / container /
mixed-UID) stays inspectable — git's dubious-ownership guard otherwise fails
even `rev-parse` and it was misreported as E_NOT_A_REPO (which also hid the
History/Changes views). Only a genuine "not a git repository" now maps to
E_NOT_A_REPO; other rev-parse failures surface their real stderr as E_GIT.

Frontend: live-refetch circuit breaker. WS ticks/signals drive up-to-5-request
refetch batches with no error gating, so an offline/erroring connector got
hammered on every frame. After 3 consecutive failed pulls the 400ms debounce
drops to a ~12s probe (without restarting the timer per frame, which would
starve it), recovering the moment a pull succeeds. Manual Refresh and
foreground navigation bypass the throttle; a "live updates paused" hint shows.

Workspace root picker: new DB-only endpoint GET .../workspace/session-workdirs
enumerates a channel's session workdirs. The picker now offers session
workdirs first (each scoping the browse to its owning session so the connector
accepts the subdir as a root) and the connector's allowed_roots as the
fallback set — no connector/security change needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… silently)

The grouped Sessions board kept native HTML5 drag-and-drop for
drag-to-promote, but real drops silently no-op'd for the user (the drop
never fired). Rewrite it on pointer events, matching the reliable
lane-window drag:

- The bot group owns the drag: pointerdown on a non-primary card starts
  it (with setPointerCapture + a small move threshold so a plain click is
  inert), window listeners track the cursor, and the group hit-tests the
  primary card's rect. Release over the primary promotes it.
- Visuals unchanged in spirit: dragged source dims, the primary card
  shows a dashed droppable hint, and a "↑ Make primary" pill while the
  cursor is over it. `busy` (promote in flight) is lifted to the group.

Verified end-to-end via dispatched pointer events (hot ring + pill +
Applied toast + primary moved); works with touch too.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The composer's session-target chip listed each session by its tag
(primary / time / dir basename) but not its working directory. Add a
second line per session row — a folder icon + the cwd (left-truncated,
full on hover; "default" when the connector chose it) — matching the
Sessions board so both surfaces read the same.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
react-markdown v10 (bumped in c4efbb4) removed the `className` prop;
passing it throws inside <Markdown>, which — with no error boundary —
crashed the whole ChannelView to a blank screen for any channel that
had messages. Wrap ReactMarkdown in a styled <div> so the "prose" +
caller classes still apply without feeding a rejected prop.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The workbench File panel's Raw mode was a plain <textarea>. Swap it for
a small, embeddable CodeMirror 6 editor: line numbers, undo history,
bracket matching, and markdown/json syntax highlighting.

- New CodeEditor component; dark theme matches the panel tokens
  (zinc-950 field, indigo focus/caret, tinted "data-coding" syntax hues
  per DESIGN.md). Still renders content as INERT TEXT (no HTML execution),
  so the textarea's XSS-safety property is preserved.
- Uncontrolled-with-sync: CodeMirror owns the doc; external changes
  (path switch, live-push reload) are pushed in only when they differ,
  so typing never loses the cursor/selection.
- Only markdown + json language packs are loaded; everything else is a
  small core module, and the editor is click-gated via React.lazy so it
  downloads only when a user opens Raw mode — off the chat critical path
  (ChatLayout chunk drops ~520 kB; CodeMirror lands in its own chunk),
  consistent with how pdf/hljs are handled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The updateListener forwarded every docChanged to onChange, including the
programmatic dispatches in the path-switch and value-sync effects. That
marked clean server reloads dirty, wrongly enabled Save, and made
FilePanel skip subsequent live-push reloads (it saw phantom unsaved
edits). Tag those internal dispatches with a `sync` annotation and skip
onChange for annotated transactions, so only real user edits set dirty.

Addresses Codex review on #175.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…xclude closed sessions

P1 (security): `safe.directory=*` re-enables trusting a foreign-owned repo's
local config, and `git diff`/`git show` without `--no-ext-diff` let such a repo
run arbitrary commands (diff.external / textconv) as the connector user. Now:
- git_diff/git_show/git_commit_files pass `--no-ext-diff --no-textconv`.
- run_git disables `core.fsmonitor` (a query hook run during index refresh) for
  every invocation. (A `-c diff.external=` override is NOT used — git then tries
  to exec the empty string and aborts the diff; `--no-ext-diff` is the lever.)
- New unix test proves a hostile repo-level diff.external does not execute.

P2: channel_session_workdirs now excludes closed sessions
(`status NOT IN ('terminated','revoked','expired')`), matching the session
switcher, so the root picker no longer offers stale closed-session workdirs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…runcation

Self-review of the sessions PR found two issues:

- BotGroup.startDrag registered pointermove/pointerup on window but never
  handled pointercancel. On touch, an interrupted gesture (system gesture,
  extra touch point, captured element unmounting) fires pointercancel
  instead of pointerup — so the listeners leaked AND the drag state was
  never cleared, leaving the source card dimmed forever and letting a
  stale onMove corrupt the next drag. Add a pointercancel handler (shared
  cleanup) that tears the drag down. Verified: after pointercancel the
  card is no longer dimmed, and a normal drag still promotes.

- SessionChip: the two-line refactor put the tag in a nested flex row as
  `truncate` but dropped the `min-w-0` the old single-line row had, so a
  long tag wouldn't shrink/truncate and pushed the status out. Restore
  `min-w-0`.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
feat(sessions): group Sessions board by bot, show workdir, polish drag
fix(workspace): git dubious-ownership recovery · refetch-storm brake · session-workdir root picker
…49c9c

feat(workbench): CodeMirror 6 editor for Raw mode (+ fix react-markdown v10 crash)
@haowei2000
haowei2000 merged commit 2b67c0b into main Jul 13, 2026
12 checks passed

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be51994acf

ℹ️ About Codex in GitHub

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

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

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

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

) -> Result<std::process::Output, (String, String)> {
tokio::process::Command::new("git")
.arg("-c")
.arg("safe.directory=*")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Block Git hooks when bypassing dubious ownership

When this safe.directory=* bypass is used for git_status, a foreign-owned repo can ship an executable .git/hooks/post-index-change and have it run as the connector user: the git-status docs say status refreshes and writes the index by default, and the githooks docs say post-index-change is invoked when the index is written. I also reproduced this locally with Git 2.43: git -c safe.directory=* -c core.fsmonitor=false status --porcelain=v2 --branch executed that hook unless GIT_OPTIONAL_LOCKS=0 was set, so the ownership bypass still enables arbitrary code execution during read-only status inspection.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant