Repository navigation
Conversation
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)
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
|
Filed against the wrong repo by mistake; this belongs on the connorch fork. |
| // 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; |
There was a problem hiding this comment.
🟡 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
onPasteTranscriptscannot receive a large transcript paste because the editor'sonPasteCapturefirst runsonComposerPaste, which folds text at 32 KiB (or when the prompt limit would be exceeded), callspreventDefault()/stopPropagation(), and stages it aspasted-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); |
There was a problem hiding this comment.
🟡 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> |
There was a problem hiding this comment.
🟡 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.
| 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), | ||
| "", | ||
| ); |
There was a problem hiding this comment.
🟠 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; |
There was a problem hiding this comment.
🟡 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; |
There was a problem hiding this comment.
🟡 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
addTranscriptsremoves 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.
🤖 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") { |
There was a problem hiding this comment.
🟡 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.
There was a problem hiding this comment.
🟡 Medium
t3code/apps/web/src/components/ChatView.tsx
Line 7539 in ceb6b56
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
composerTranscriptsto 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], |
There was a problem hiding this comment.
🟡 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
composerTranscriptsas sendable enables a transcript-only submission while a running turn is queued, butsendQueuedMessagestill callsderiveComposerSendStatewithout includingmessage.transcripts. That queued sender treats the message as empty, callsqueue.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.
hasSendableContentis calculated before the new append andelementContextCountexcludesmessage.transcripts, so an empty prompt with no attachments/other contexts reaches the earlyfinishSendreturn and never sends the transcript that thisreducewould 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}/`; |
There was a problem hiding this comment.
🟡 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.
| 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.
ApprovabilityVerdict: 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:
Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more. |
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:
resolveChatListAnchoredEndSpacenow 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