Skip to content

feat: setting to anchor sent messages at the top of the timeline - #14773

Closed
connorch wants to merge 78 commits into
pingdotgg:mainfrom
connorch:connorch/add-thread-scroll-anchor-setting
Closed

connorch wants to merge 78 commits into
pingdotgg:mainfrom
connorch:connorch/add-thread-scroll-anchor-setting

Conversation

@connorch

@connorch connorch commented Oct 2, 2026

Copy link
Copy Markdown

When you send a message, the timeline pins to the bottom and the reply streams in right above the composer. Reading a long answer that way means watching the bottom edge. T3 Code already holds the first message of a thread near the top so its turn reads downward; there was no way to get that for every message.

This adds a client setting, Settings → General → Sent message position (Bottom / Top, default Bottom). With Top, every direct send reuses the existing first-message anchoring: the message holds at the top of the viewport and the reply streams below it. Mobile gets the same choice as a device-local switch under Settings → Thread behavior → Sent messages at top.

Behavior notes:

  • Messages that queue behind a running turn keep the current position; they aren't the live turn.
  • The anchor still releases on tool activity, exactly as the first-message anchor does today (fix(web): stop tool calls from leaving a blank page in threads #7971 upstream), so this is "first-message behavior for every send".
  • resolveChatListAnchoredEndSpace now matches the anchor at any index; it was the only place encoding the first-message rule.

Made by Fable 5.1 using Claude Code.

🤖 Generated with Claude Code

connorch and others added 30 commits August 5, 2026 23:33
Conductor workspaces started with no dependencies installed and no way to
launch the app, since Conductor does not read the worktree setup script in
t3.json. The run script also needs a wrapper: Conductor stops a run script by
signalling only the process it spawned, and `vp run dev` sits above a
dev-runner whose Vite and server grandchildren survive that, holding the
workspace's ports so the next run cannot bind.

Adds .conductor/settings.toml mirroring the t3.json setup step, plus three run
scripts (web, desktop, share) that go through .conductor/dev.sh, which runs the
dev-runner in its own process group and forwards the stop to the whole tree.
Ports need no wiring because the dev-runner already derives a stable pair from
the worktree path, so run_mode stays concurrent.

Written by Claude Opus 5 in Claude Code.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
* feat: sidebar mode select with Connor worktree modes

Replace the Sidebar V2 beta toggle with a five-way sidebar mode select
(Default, Flat, and three Connor modes that group threads by git
worktree). Worktree groups carry rename, last-viewed-thread navigation,
and a prioritized attention indicator (question/approval, failed,
working, unread).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: project grouping and sort controls in Connor Stack mode

Stack mode (connor-1) now nests worktree cards and local-checkout
threads under collapsible project sections, sharing the Default
sidebar's project sort (last user message / created at / manual),
thread sort (which orders rows inside expanded worktrees), and
project-expansion store. The header gains the same sort menu and
add-project affordances as the Default sidebar.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: drag-and-drop project reordering in Connor Stack manual sort

Manual project sorting in Stack mode now supports dragging project
sections, using the same @dnd-kit setup and shared projectOrder
preference as the Default sidebar. A post-drop click guard keeps the
drag from also toggling the section's expansion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: hide projects in Connor Stack mode

Right-clicking a project section offers Hide project (with an Undo
toast) alongside new-thread and copy-path items. Hidden projects leave
the list until the sort menu's new Filter switch shows them, where they
render dimmed with an eye-off badge and an Unhide action. Both the
per-project flags and the filter persist in the ui-state store, keyed
by the same preference aliases as project expansion.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat: retire the Tree and Focus sidebar experiments

Connor Mode is now just the Stack take: the mode select offers Default,
Flat, and Connor Mode, and the Tree/Focus variant components, their
expansion state, and the variant plumbing are gone. Stored "connor-2" /
"connor-3" values decode to "connor-1" instead of failing the settings
blob, which would have reset every client setting.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
* fix(server): avoid worktree force-remove timeouts on large trees

- Rename the worktree dir aside and prune the git registration instead of letting `git worktree remove --force` delete files in-band, so removal stays fast for large trees (e.g. with node_modules)
- Delete the renamed directory in a detached background fiber
- Refuse to force-remove the main working tree or a locked worktree
- Stop suppressing the cleanup-failure toast in useThreadActions so it surfaces correctly

* Add delete-worktree action to Connor Stack sidebar

- Add "Delete worktree" context menu item that confirms then deletes all threads in the group
- Add deleteOrphanedWorktree option to useThreadActions to skip the per-thread confirm when the caller already confirmed
- Include gitignored paths in workspace search results, marked with an `ignored` flag instead of being filtered out
- Skip noisy junk directories (node_modules, dist, .venv, etc.) when scanning ignored paths
- Rank ignored file matches alongside regular fuzzy search results
- Render ignored files with muted styling in the file browser tree
- Add `enableChangeRequestAutomerge` to the source control provider interface, implemented for GitHub (via `gh pr merge --auto`) and stubbed as unsupported for Azure DevOps, Bitbucket, and GitLab
- Add `sourceControlEnableAutomerge` RPC method, contract types, and auth scope
- Add web UI: Automerge menu item and combined "Commit, push, PR, & Automerge" action in GitActionsControl, gated to GitHub with an open PR
- Delete worktree trash directories with a spawned `rm -rf` instead of `fs.remove`, keeping large tree walks off the libuv threadpool and tolerating files rm can't unlink
- Sweep and clean up any stale `.{name}.removing-*` trash dirs left behind by earlier failed removals, not just the one just renamed aside
- Add integration test covering sweep of stale trash directories
…ckout card (#8)

* refactor(web): drop repo icon from worktree cards, align Connor sidebar indent

Remove the ProjectFavicon from Connor-mode worktree cards (the project
section header already shows it) and indent each project's collapsible
content so worktree card and thread row text lines up with the project
title in the trigger.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(web): reveal Connor project chevron on hover in the icon slot

The project trigger's chevron no longer sits permanently left of the
favicon; it shares the icon slot and swaps in on hover. Content indent
updated to keep row text aligned with the narrower title offset.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* refactor(web): render Connor local-checkout threads as a Current Checkout card

Replace the flat local-thread rows with the same card UI worktrees use:
partitionThreadsForConnorSidebar now returns one unified group list with
a per-project local-checkout group (fixed "Current Checkout" title, no
rename/delete, local-mode new threads) listed above the worktree groups.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- resolveQuickAction now offers "View PR" for merged PRs, including after a squash merge leaves the branch ahead of default
- GitActionsControl tints the quick action green (open) or purple (merged), matching GitHub's PR state colors
- Rename "Current Checkout" group to "Root Checkout" in Connor sidebar, and show branch color by PR state
- Add --merged/--merged-foreground CSS variables for light and dark themes
#9)

- Resolve the authenticated GitHub account via `gh auth status` and use it as the branch prefix (e.g. `octocat/fix-reconnect`), falling back to `t3code/` when signed out or `gh` is unavailable
- Wire GitHubCli into ProviderCommandReactor and update integration/unit test harnesses accordingly
- Rename open-PR success tint to a dedicated `success-strong` token (green-700) instead of reusing `success`
- Keeps `success` (emerald) for other uses while the open-PR button tracks GitHub's green
- Show "Merged" instead of "View PR" when the quick action targets a merged PR, since it now reads as a status chip rather than an action
- Update tests to match the new label
- New Cmd+T command creates a thread in the active thread/draft's workspace (worktree or current checkout), falling back to chat.new routing with no context
- Adds startNewThreadInCurrentWorkspace helper with test coverage
- Registers chat.newInWorkspace in shared keybindings and contracts
…ups (#12)

- Add cloud icon + tooltip on sidebar groups running on a non-primary machine
- Local checkout stays unmarked as the default
install-desktop-local.sh now branches on the host OS. macOS keeps the
existing /Applications unpack; Linux builds an arch-matched AppImage,
stages it into ~/Applications (T3_APPIMAGE_DIR overrides), and registers
the desktop entry and icon extracted from the image under ~/.local/share
so the t3code:// handler and launcher survive rebuilds and rebases.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
- Add server-side WorktreeArchiveService to snapshot .context, archive threads, and remove the worktree; unarchive reverses each step
- Persist archive records via a new WorktreeArchives repository/migration and expose vcsArchiveWorktree/vcsUnarchiveWorktree/vcsListWorktreeArchives RPCs
- Add a History page and sidebar entry point for browsing and restoring archived worktrees
- Restructure thread/group row action buttons to occupy the same grid cell as the status dot/indicator they replace on hover, instead of sitting alongside them
- Remove opacity/hover-reveal from group +/archive buttons (now shown via the stacked cell instead)
- Archiving a worktree's sole thread now archives the whole worktree, since the server archives its threads as part of that
The status dot / time label stacked under the hover-revealed archive
and + buttons transitions to opacity < 1 on hover, which promotes it
above the buttons in paint order and swallows their pointer events.
Make the decorative indicator spans pointer-events-none so the buttons
underneath get hover styles and clicks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Stamp the active thread's server-timestamped visit time (completedAt when settled, requestedAt when still running) so sidebar unread indicators clear on view and later completions have a correct baseline
…15)

- Replace the inline "No threads yet" list item for empty projects with a tooltip on the project header, shown only when the section has no worktrees or threads.
- Add useResizableHeight hook for drag-to-resize panels, persisted per-window in localStorage
- Add a top-edge drag handle to ChatComposer that resizes the prompt editor, clamped to viewport height
- ComposerPromptEditor reads height bounds from CSS variables so the drag handle can override the default min/max
)

The empty right panel showed a centered card grid: hard to scan and far
from the toggle button that opens the panel. Surfaces now render as ghost
tabs in the tab strip, and collapse to icon ghosts once real tabs exist,
so every surface stays one click away in the same spot. Multi-instance
surfaces (terminal, browser) keep their ghost while open; singletons drop
theirs. On narrow panels the ghosts fold into the existing + menu.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Add "Mark unread" action to worktree group context menu
- Resolves the group's last thread and marks it unread via markThreadUnread
…ktrees (#19)

* feat(server): provision a git-ignored .context scratch dir in new worktrees

Every worktree createWorktree makes now gets a .context/ directory seeded
with an empty placeholder file (the workspace file tree only surfaces
directories that contain files). The ignore entry goes into the repo's
shared info/exclude, so it covers the main checkout and all worktrees
without touching the project's tracked .gitignore.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(server): point Claude sessions at the .context scratch dir

When the session cwd contains the t3code-provisioned .context directory,
append a note to the Claude Code preset system prompt telling the agent
to put plans and other not-for-git files there. The note is only added
when the directory actually exists, so non-worktree checkouts without
one are unaffected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* feat(server): point Codex sessions at the .context scratch dir

Mirror of the Claude change: when the session cwd contains the
t3code-provisioned .context directory, the Codex developer instructions
gain a note telling the agent to put plans and other not-for-git files
there. Default mode only - Plan Mode forbids writing files and plans
flow through proposed_plan blocks instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
- Add cursor-pointer to right-panel ghost surface tab, ghost surface icon button, and add-panel-surface menu trigger
- Add Beta setting `openLinksInPreviewPattern`: a regex that routes matching link clicks (chat markdown and terminal links) straight to the integrated preview browser instead of the system browser or context menu
- Add `urlMatchesPreviewLinkPattern` helper with tests for empty/invalid/changing patterns
- Add moveSurface to rightPanelStore for reordering open surfaces
- Wire dnd-kit sortable drag handling into RightPanelTabs
- Reorder the surface-add actions list (Terminal/Diff before Browser)
connorch and others added 24 commits August 17, 2026 23:51
Threads that share a worktree render as one card in the default sidebar. A card is a read-time view over the flat, server-ordered thread list: members gather behind their highest-ranking member, drag moves the card as a block, and pin/unpin fan out to every member from every entry point. Toggle under Settings > General > Worktree cards.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Worktree cards all shared one gray well, so a stack of cards read as a
single block and told you nothing about which worktree a thread belonged
to.

Each card now draws a 1px outline in a color derived from its card key:
an FNV-1a hash picks one of 36 hues, walked with a coprime stride so
worktree paths that differ only in a trailing character land far apart on
the wheel. Lightness and chroma are fixed, so no worktree shouts louder
than another. Nothing is stored; every client paints a worktree the same.
The outline eats one of the card's inset pixels, so the row body does not
move, and a card stacked directly under another pays the list's row gap
again so the two outlines stay as far apart as two plain threads.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
271 upstream commits. Conflicts resolved upstream-first, then fork
features re-applied:

- Migrations: fork WorktreeArchives moves from slot 53 to 54; upstream's
  053_PullRequestFilesViewed takes 53. The fork migration repairs databases
  that recorded slot 53 as WorktreeArchives by running the skipped upstream
  migration, same pattern as the earlier 41/44 slot moves.
- Composer: upstream replaced the Lexical editor with Tiptap. The fork's
  transcript paste (thread_transcript blocks become composer cards) is
  ported into the Tiptap handlePaste path; the Lexical plugin is deleted.
  The drag-resize CSS variables move to the Tiptap editor class.
- useThreadActions: upstream's undo for archive/unpin/settle merged with the
  fork's worktree-sibling navigation after archive, explicit orphaned
  worktree deletion, and worktree-scoped pin/unpin fan-out. Undo of a
  worktree unpin re-pins with the same scope.
- Thread action menu: fork's hasTranscript/worktreeSiblingCount alongside
  upstream's projectFilter.
- ipc.ts: upstream deleted the unused EnvironmentApi interface; the fork's
  setAutomerge lives in client-runtime and is unaffected.
- RightPanelTabs: fork's ghost tabs replaced the launcher upstream restyled;
  launcher edits dropped, PullRequestGlyph icons kept.
- Contracts: StoredSidebarMode uses SchemaTransformation.transform after the
  Effect rc.115 upgrade removed transformOrFail.
- threadTranscript: accepts upstream's new "reasoning" role and drops those
  messages from copied transcripts.
- GitActionsControl: automerge icon comes from PullRequestGlyph.merged, the
  restricted lucide import is gone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Restores .gitignore to upstream.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Restores the prompt editor sizing to upstream.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Upstream already stamps visits at turn completion and snooze wake.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…panel action

Restores the source-control provider files, contracts, and client state to upstream. The merged-PR quick action tint stays.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Drops the rename-aside, background rm -rf delete, and trash sweep. The .context provisioning stays.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Restores upstream's legacySidebarEnabled boolean. The worktree cards toggle stays and now keys off the legacy flag.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Removes the archive service, persistence, migration 054, RPCs, History page, and client state. Restores the touched files to upstream.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
chore: drop fork features no longer in use
Upstream brought 181 commits. Conflicts resolved upstream-first:

- Codex: the .context scratch dir note now rides in turn/start
  additionalContext (t3_code_scratch_dir), since upstream moved all T3
  context out of developer_instructions.
- GitActionsControl: the open/merged PR tint became Button variants
  (success, merged) because no-restyle now fails lint.
- RightPanelTabs: kept the fork's ghost-tab launcher and re-applied
  upstream's token and tooltip tweaks.
- sendQueuedMessage: upstream's root-level queued sender now appends
  queued thread transcripts, as the chat view send path already did.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…g migrations

Databases that ran the removed WorktreeArchives migration record it as slot
54, which upstream now uses for ProjectionThreadsAutoSettleDisabledAt. The
Migrator skips by id alone, so the column never landed and thread listing
failed with "no such column: auto_settle_disabled_at". Drop the stale ledger
row first so upstream's idempotent 54 runs in its place.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The default diff file state setting could only fold every file or none. Readers who tick files off
as viewed in a pull request wanted the reverse of that pattern: viewed files folded, the rest open.

The `diffFilesCollapsed` client setting is now a three-way mode (`all`, `none`, `viewed`) with the
old boolean decoding to its equivalent mode. The pull request Code tab derives each file's default
fold from its viewed tick under the new mode, and the settings dropdown offers it as "Collapse
viewed". Thread diffs carry no viewed marks, so the mode leaves them expanded.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
feat(web): add a collapse-viewed default for diff files
The empty browser tab listed every local server in the environment, which
made it hard to find the dev server for the code being worked on when many
worktrees run servers at once. It now defaults to servers started from
terminals of threads in the active worktree, with a toggle to show all.

Rows now show the address a click opens and the thread that started the
server.
…ervers

feat(web): scope browser empty-state servers to the current worktree
@connorch

connorch commented Oct 2, 2026

Copy link
Copy Markdown
Author

Filed against the wrong repo by mistake; this belongs on the connorch fork.

@connorch connorch closed this Oct 2, 2026
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XXL 1,000+ changed lines (additions + deletions). labels Oct 2, 2026
// Transcript blocks become composer cards, not prompt text. Pulled
// out before tokenizing: a transcript body can carry mention-shaped
// tokens that must not turn into chips.
const onTranscriptsPasted = onPasteTranscriptsRef.current;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/ComposerPromptEditorTiptap.tsx:1021

Large copied thread transcripts are staged as pasted-text.txt instead of becoming transcript draft cards. ComposerPromptEditorTiptap receives paste events through onPasteCapture, but onComposerPaste calls preventDefault() and stopPropagation() for pastes at least 32 KiB or exceeding the prompt limit before handlePaste reaches onPasteTranscripts at line 1021. The capture handler must allow transcript extraction to run before folding these pastes into attachments.

Also found in 1 other location(s)

apps/web/src/components/chat/ChatComposer.tsx:6910

onPasteTranscripts cannot receive a large transcript paste because the editor's onPasteCapture first runs onComposerPaste, which folds text at 32 KiB (or when the prompt limit would be exceeded), calls preventDefault()/stopPropagation(), and stages it as pasted-text.txt. Thus realistic long copied threads never reach the transcript extractor and are sent as an attachment rather than transcript cards.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ComposerPromptEditorTiptap.tsx around line 1021:

Large copied thread transcripts are staged as `pasted-text.txt` instead of becoming transcript draft cards. `ComposerPromptEditorTiptap` receives paste events through `onPasteCapture`, but `onComposerPaste` calls `preventDefault()` and `stopPropagation()` for pastes at least 32 KiB or exceeding the prompt limit before `handlePaste` reaches `onPasteTranscripts` at line 1021. The capture handler must allow transcript extraction to run before folding these pastes into attachments.

Also found in 1 other location(s):
- apps/web/src/components/chat/ChatComposer.tsx:6910 -- `onPasteTranscripts` cannot receive a large transcript paste because the editor's `onPasteCapture` first runs `onComposerPaste`, which folds text at 32 KiB (or when the prompt limit would be exceeded), calls `preventDefault()`/`stopPropagation()`, and stages it as `pasted-text.txt`. Thus realistic long copied threads never reach the transcript extractor and are sent as an attachment rather than transcript cards.

// Lexical editor (see registerComposerTranscriptPaste); this callback
// receives the extracted blocks as draft cards.
const onPasteTranscripts = (transcripts: ThreadTranscriptDraft[]) => {
addComposerDraftTranscripts(composerDraftTarget, transcripts);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium chat/ChatComposer.tsx:5667

Stashing a draft after onPasteTranscripts drops composerTranscripts from the stash and leaves those transcripts attached to the cleared live composer, so restoring the stash loses the transcript while a later prompt can still send it unintentionally. Update the stash serialization and draft-clear path to persist and remove composerTranscripts alongside the other draft content.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/chat/ChatComposer.tsx around line 5667:

Stashing a draft after `onPasteTranscripts` drops `composerTranscripts` from the stash and leaves those transcripts attached to the cleared live composer, so restoring the stash loses the transcript while a later prompt can still send it unintentionally. Update the stash serialization and draft-clear path to persist and remove `composerTranscripts` alongside the other draft content.

</button>
}
/>
<TooltipPopup>{title}</TooltipPopup>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/RightPanelTabs.tsx:1151

Hovering an open device tab now shows only title, so users cannot see the selected device's host label or OS/version from the tab bar. The replacement tooltip at this path removed the DeviceTabTooltip content, which was the only tab-bar access to that identifying information; restore DeviceTabTooltip for device tabs.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/RightPanelTabs.tsx around line 1151:

Hovering an open device tab now shows only `title`, so users cannot see the selected device's host label or OS/version from the tab bar. The replacement tooltip at this path removed the `DeviceTabTooltip` content, which was the only tab-bar access to that identifying information; restore `DeviceTabTooltip` for device tabs.

Comment on lines +3101 to +3106
const contextDirPath = path.join(worktreePath, WORKTREE_CONTEXT_DIRECTORY_NAME);
yield* fileSystem.makeDirectory(contextDirPath, { recursive: true });
yield* fileSystem.writeFileString(
path.join(contextDirPath, WORKTREE_CONTEXT_PLACEHOLDER_FILE),
"",
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟠 High vcs/GitVcsDriverCore.ts:3101

provisionContextDirectory overwrites a tracked .context/_.txt with an empty file, immediately dirtying the new worktree and losing that worktree's checked-out contents. The ignore entry does not protect tracked files; only write the placeholder when it does not already exist.

   const contextDirPath = path.join(worktreePath, WORKTREE_CONTEXT_DIRECTORY_NAME);
-    yield* fileSystem.writeFileString(
-      path.join(contextDirPath, WORKTREE_CONTEXT_PLACEHOLDER_FILE),
-      "",
-    );
+    const placeholderPath = path.join(contextDirPath, WORKTREE_CONTEXT_PLACEHOLDER_FILE);
+    if (!(yield* fileSystem.exists(placeholderPath))) {
+      yield* fileSystem.writeFileString(placeholderPath, "");
+    }
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/vcs/GitVcsDriverCore.ts around lines 3101-3106:

`provisionContextDirectory` overwrites a tracked `.context/_.txt` with an empty file, immediately dirtying the new worktree and losing that worktree's checked-out contents. The ignore entry does not protect tracked files; only write the placeholder when it does not already exist.

: cardKeys.reduce((sum, key) => {
const memberIndex = key === active.key ? undefined : indices.get(key);
const memberRect = memberIndex === undefined ? undefined : rects[memberIndex];
return sum + (memberRect?.height ?? fallback) + 1;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/Sidebar.drag.ts:221

Dragging a multi-row pinned/active worktree card into settled computes its gap 46px too short, shifting every row below the target to the wrong position. For the active member, memberIndex is undefined, so line 221 uses the destination fallback (slimHeight, 36px) instead of the source card-row height (82px); use the active member's source-section height for that term.

-              return sum + (memberRect?.height ?? fallback) + 1;
+              return sum + (memberRect?.height ?? (key === active.key && (active.section === "pinned" || active.section === "active") ? cardHeight : fallback)) + 1;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/Sidebar.drag.ts around line 221:

Dragging a multi-row pinned/active worktree card into `settled` computes its gap 46px too short, shifting every row below the target to the wrong position. For the active member, `memberIndex` is undefined, so line 221 uses the destination `fallback` (`slimHeight`, 36px) instead of the source card-row height (82px); use the active member's source-section height for that term.

* When set, pasted `<thread_transcript>` blocks are pulled out of the paste
* before the editor sees it and handed here instead of landing as raw text.
*/
onPasteTranscripts?: (transcripts: ThreadTranscriptDraft[]) => void;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/ComposerPromptEditorTiptap.tsx:150

Pasting the same <thread_transcript> twice creates duplicate drafts with the same serialized id, so ComposerTranscriptCards renders duplicate React keys and removing either card deletes both; if left in place, sending includes the transcript twice. Deduplicate transcript IDs before passing them to onPasteTranscripts (or in addTranscripts) so each ID produces only one draft.

Also found in 1 other location(s)

apps/web/src/composerDraftStore.ts:3883

addTranscripts removes existing entries whose IDs occur in the input but then appends the entire input unchanged. Pasting the same &lt;thread_transcript&gt; block twice yields two drafts with the same embedded ID; React renders duplicate keys, one remove action deletes both cards, and sending appends the transcript block twice instead of treating the ID as one transcript.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ComposerPromptEditorTiptap.tsx around line 150:

Pasting the same `<thread_transcript>` twice creates duplicate drafts with the same serialized `id`, so `ComposerTranscriptCards` renders duplicate React keys and removing either card deletes both; if left in place, sending includes the transcript twice. Deduplicate transcript IDs before passing them to `onPasteTranscripts` (or in `addTranscripts`) so each ID produces only one draft.

Also found in 1 other location(s):
- apps/web/src/composerDraftStore.ts:3883 -- `addTranscripts` removes existing entries whose IDs occur in the input but then appends the entire input unchanged. Pasting the same `<thread_transcript>` block twice yields two drafts with the same embedded ID; React renders duplicate keys, one remove action deletes both cards, and sending appends the transcript block twice instead of treating the ID as one transcript.

threadKey: scopedThreadKey(scopeThreadRef(activeThread.environmentId, threadIdForSend)),
messageId: messageIdForSend,
});
if (shouldAnchorFirstMessage || settings.chatTurnAnchor === "top") {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/ChatView.tsx:8257

When chatTurnAnchor === "top"``, a later metadata, attachment, or startThreadTurnfailure removes the optimistic message but leaves the timeline inanchoring-new-turn` with an anchor for that deleted message. Because the failure path never clears the anchor or restores the scroll position, subsequent live-follow and timeline positioning remain stuck until navigation or another send; clean up the pending anchor and restore scrolling when removing the optimistic message.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatView.tsx around line 8257:

When `chatTurnAnchor === "top"``, a later metadata, attachment, or `startThreadTurn` failure removes the optimistic message but leaves the timeline in `anchoring-new-turn` with an anchor for that deleted message. Because the failure path never clears the anchor or restores the scroll position, subsequent live-follow and timeline positioning remain stuck until navigation or another send; clean up the pending anchor and restore scrolling when removing the optimistic message.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium

if (feedbackCommand && multipleModelSelections === null) {

A Codex feedback command with a pasted transcript is submitted as feedback without the transcript context, and submitCodexFeedback then clears the draft, silently discarding the transcript. Require composerTranscripts.length === 0 in this feedback-command guard so transcript-bearing commands are sent through the normal message path.

Also found in 1 other location(s)

apps/web/src/components/chat/ChatComposer.tsx:5667

A pasted transcript added here is also ignored by the Codex feedback-command path: that path does not require composerTranscripts to be empty before parsing /feedback, submits only the command, and clears the draft. Consequently a transcript pasted together with a Codex feedback command is silently dropped rather than sent as transcript context.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatView.tsx around line 7539:

A Codex feedback command with a pasted transcript is submitted as feedback without the transcript context, and `submitCodexFeedback` then clears the draft, silently discarding the transcript. Require `composerTranscripts.length === 0` in this feedback-command guard so transcript-bearing commands are sent through the normal message path.

Also found in 1 other location(s):
- apps/web/src/components/chat/ChatComposer.tsx:5667 -- A pasted transcript added here is also ignored by the Codex feedback-command path: that path does not require `composerTranscripts` to be empty before parsing `/feedback`, submits only the command, and clears the draft. Consequently a transcript pasted together with a Codex feedback command is silently dropped rather than sent as transcript context.

files: [...composerFiles],
terminalContexts: [...composerTerminalContexts],
previewAnnotations: [...composerPreviewAnnotations],
transcripts: [...composerTranscripts],

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium components/ChatView.tsx:7723

A queued message containing only transcripts is silently discarded instead of starting a turn. sendQueuedMessage still calls deriveComposerSendState without message.transcripts, so the message is classified as empty, queue.finishSend runs, and the transcript is never appended to the outgoing prompt; include queued transcripts in both the sendability check and prompt construction.

Also found in 2 other location(s)

apps/web/src/components/chat/ChatComposer.tsx:2246

Counting composerTranscripts as sendable enables a transcript-only submission while a running turn is queued, but sendQueuedMessage still calls deriveComposerSendState without including message.transcripts. That queued sender treats the message as empty, calls queue.finishSend, and never appends the transcript to the outgoing prompt, so a transcript-only queued message is silently lost.

apps/web/src/components/chat/sendQueuedMessage.ts:99

A queued message containing only pasted transcript cards is silently discarded. hasSendableContent is calculated before the new append and elementContextCount excludes message.transcripts, so an empty prompt with no attachments/other contexts reaches the early finishSend return and never sends the transcript that this reduce would append. Direct composer submission includes transcript cards in that count.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/components/ChatView.tsx around line 7723:

A queued message containing only `transcripts` is silently discarded instead of starting a turn. `sendQueuedMessage` still calls `deriveComposerSendState` without `message.transcripts`, so the message is classified as empty, `queue.finishSend` runs, and the transcript is never appended to the outgoing prompt; include queued transcripts in both the sendability check and prompt construction.

Also found in 2 other location(s):
- apps/web/src/components/chat/ChatComposer.tsx:2246 -- Counting `composerTranscripts` as sendable enables a transcript-only submission while a running turn is queued, but `sendQueuedMessage` still calls `deriveComposerSendState` without including `message.transcripts`. That queued sender treats the message as empty, calls `queue.finishSend`, and never appends the transcript to the outgoing prompt, so a transcript-only queued message is silently lost.
- apps/web/src/components/chat/sendQueuedMessage.ts:99 -- A queued message containing only pasted transcript cards is silently discarded. `hasSendableContent` is calculated before the new append and `elementContextCount` excludes `message.transcripts`, so an empty prompt with no attachments/other contexts reaches the early `finishSend` return and never sends the transcript that this `reduce` would append. Direct composer submission includes transcript cards in that count.

) {
const gitCommonDir = yield* resolveGitCommonDir(cwd);
const excludePath = path.join(gitCommonDir, "info", "exclude");
const excludeEntry = `${WORKTREE_CONTEXT_DIRECTORY_NAME}/`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🟡 Medium vcs/GitVcsDriverCore.ts:3086

The info/exclude rule ${WORKTREE_CONTEXT_DIRECTORY_NAME}/ hides .context directories at every nesting level, so users cannot see or stage files in pre-existing paths such as packages/foo/.context/.... Prefix the pattern with / to restrict it to the repository-root scratch directory.

Suggested change
const excludeEntry = `${WORKTREE_CONTEXT_DIRECTORY_NAME}/`;
const excludeEntry = `/${WORKTREE_CONTEXT_DIRECTORY_NAME}/`;
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/vcs/GitVcsDriverCore.ts around line 3086:

The `info/exclude` rule `${WORKTREE_CONTEXT_DIRECTORY_NAME}/` hides `.context` directories at every nesting level, so users cannot see or stage files in pre-existing paths such as `packages/foo/.context/...`. Prefix the pattern with `/` to restrict it to the repository-root scratch directory.

@macroscopeapp

macroscopeapp Bot commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This is a broad 105-file feature bundle introducing new transcript, worktree-card, browser, diff, provider, and installation behavior, including a default change to sidebar behavior and new Git/worktree side effects. Multiple unresolved Medium and High findings also identify data-loss, performance, and interaction risks that require human attention.

Not approved because:

  • 15 blocking correctness issues found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

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

Labels

size:XXL 1,000+ changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant