Skip to content

fix(composer): compact context without clearing the draft - #363

Merged
incognitojam merged 2 commits into
mainfrom
styal/fix-compact-draft-composer
Sep 18, 2026
Merged

incognitojam merged 2 commits into
mainfrom
styal/fix-compact-draft-composer

Conversation

@incognitojam

@incognitojam incognitojam commented Sep 18, 2026 •

Copy link
Copy Markdown
Owner

Note

TL;DR: Compact context and the resume banner's Compact now work while a draft is in the composer, and the draft is left untouched. /compact is sent as its own turn instead of being typed into the draft. Upstream has an equivalent fix that depends on a larger compaction chain; this function gets replaced when that chain is taken in.

Problem

Compact context in the context meter, and Compact on the resume-compaction banner, were disabled whenever the composer held a draft, with the tooltip "Send or clear your draft before compacting". That was deliberate: compacting worked by overwriting the draft with /compact and submitting it through the normal send path, so any half-written message would have been destroyed.

Fix

ChatView now has a dedicated onCompactContext that sends /compact as its own turn. It never reads or clears the composer draft, and it omits composerDraftRevision, so the server does not clear its autosaved copy of the draft either. The draft check is removed from compactDisabled, and both entry points (context meter and resume banner) call the new function. ChatComposer no longer injects /compact into the draft, which also removes the compactContext handle method and the pasted-image-compression guard that only existed because compacting used to snapshot the draft.

docs/user/providers-claude.md gains one sentence saying compacting leaves a draft in place.

Surfaces: web and desktop share this code. Mobile has no Compact button. Only the Claude provider offers compaction. The server and wire contracts are unchanged.

Relationship to upstream

Upstream fixes the same bug with the same design in pingdotgg/t3code#11103. That change cannot be taken on its own: it edits code introduced by pingdotgg/t3code#9293, which moves compaction into a server-side command and adds it for Codex, OpenCode and mobile, and is followed by pingdotgg/t3code#9623, pingdotgg/t3code#10112 and pingdotgg/t3code#11107. This PR is a fork-written fix for the current client-side /compact path and is not a port of any of them, so it does not mark them as taken in. When that chain is taken in, replace onCompactContext in ChatView.tsx with the upstream version; the code comment says the same. For that reason there is no fork feature ledger entry.

Before / after

Same thread, same draft in the composer, context meter open.

Before After
Compact context disabled with the tooltip telling the user to send or clear their draft Compact context enabled while a draft is in the composer

After clicking Compact context with the draft present: /compact is sent as its own message and the draft is still in the composer. (Claude answered "Not enough messages to compact" here because this small thread had already been compacted a moment earlier.)

The /compact message in the timeline with the draft still in the composer

Verification

Exercised in a headless Chromium against an isolated dev server (worktree-local state seeded from a snapshot), in a fresh Claude Sonnet 5 thread in a scratch project:

  • With a draft typed in the composer, Compact context was enabled. Clicking it produced exactly one /compact user message and the compaction turn ran to completion ("Worked for 31s").
  • The draft text was intact immediately after the click, after compaction finished, and after a full page reload (so the server-side autosave was not cleared).
  • The preserved draft then sent normally and the composer cleared.
  • Compact context with an empty composer still works and leaves the composer empty.
  • No new browser console errors; the only ones seen were two HTTP 400 resource-load errors that appear on every page load, including before any interaction.

Focused checks: tsgo --noEmit for apps/web, vp lint on the changed files, and ContextWindowMeter.test.tsx / ComposerBannerStack.test.tsx pass.

Not verified: the desktop Electron shell was not run separately (it wraps the same web code), and the resume-compaction banner's Compact button was not clicked in the browser because the scratch thread was too small to trigger the banner; it calls the same function as the meter button.


Written by an agent (Claude Code, claude-fable-5-1).

Compact worked by overwriting the composer draft with "/compact" and
submitting it, so the button was disabled whenever the composer had
content. Send "/compact" as its own turn from ChatView instead: it never
reads or clears the draft and omits composerDraftRevision so the server
keeps its autosaved copy. The context meter and the resume-compaction
banner both use the new path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Upstream fixes the same bug in `pingdotgg#11103`, which depends on
its server-side compaction command in `pingdotgg#9293`. Record that
in the comment so the intake of that chain replaces this function.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@incognitojam
incognitojam merged commit 81dbe8b into main Sep 18, 2026
13 checks passed
@incognitojam
incognitojam deleted the styal/fix-compact-draft-composer branch September 18, 2026 15:14
incognitojam added a commit that referenced this pull request Sep 25, 2026
Some intake commits carry `Intake-Note:` trailers, but no script,
report, or runbook reads them. I reviewed all 50. Most record how a
merge conflict was resolved and need no follow-up. Two are waiting on
upstream, and several show fork behavior that the fork ledger doesn't
cover. This adds both to the places intake already reports on.

**Tracked upstream PRs** (`.github/upstream-tracked-prs.json`)

- `11103` (merged upstream, not yet taken) replaces the fork's
draft-preserving compact action from fork #363. The intake note on the
`9293` import kept the fork version "until upstream `11103` lands".
- `10411` (still open upstream) was imported early from its unmerged
commits and renamed for styal in fork #425. When upstream merges it, the
queue takes it and has to reconcile it with that import.

**Fork ledger** (`data-import-from-t3-code`)

Fork #306 derives `thread.turn-prompt-linked` events so imported user
prompts stay attached to their turns. Four intake commits (`8368`,
`8600`, `9715`, `9726`) preserved that behavior while resolving
conflicts in the live coalescing, replay, and snapshot paths, but the
ledger didn't mention it. The entry now lists #306, states the
invariant, and adds the projection pipeline, snapshot query, and live
event coalescer to `upstream_paths`, so upstream changes to those files
point reviewers to this feature.

The other behavior those notes preserved, excluding imported messages
from a thread's latest user message time, is now in upstream too (`NOT
GLOB 'import:*'`), so it stays out of the ledger.

**Validation**

- `node scripts/upstream-tracked-prs-report.ts` lists `11103` as pending
(merged 3.1 days ahead of the fork's last reconciled upstream
integration) and `10411` as open, with each reason shown.
- `vp run --filter @t3tools/scripts ledger:check` validates all 64
ledger entries, and the three new `upstream_paths` exist on upstream
main.
- `vp test run scripts/upstream-tracked-prs.test.ts
scripts/fork-feature-ledger.test.ts` passes (12 tests).

---
Written by an agent (Claude Code, claude-opus-5-5).
incognitojam pushed a commit that referenced this pull request Sep 29, 2026
Fork adaptation: Upstream's onCompactContext replaces the fork's draft-preserving version from fork #363. It stays a memoized callback declared before the fork's resume-compaction banner, which also calls it, and a successful compaction still records the thread visit baseline and acknowledges a woken thread, as the fork's send path does.

(cherry picked from commit ef6fa11)
Upstream-PR: 11103
incognitojam pushed a commit that referenced this pull request Oct 2, 2026
…pingdotgg#11673)

(cherry picked from commit cc839c4)

Fork adaptation: Queued messages carry the fork's GitHub issue contexts: queuing takes them from the composer, sending a queued message appends them like a direct send, and Stop or cancel returns them to the composer; the queued row counts them as context items. Queuing and sending clear the composer with the fork's composer draft sync mark, so retained model and mode preferences do not resurrect the cleared draft. The send button keeps the fork's disabled-reason tooltip while it queues beside Stop, and the fork's compact action, which no longer lives in ChatView (fork #363), is not reintroduced. The timeline keeps the fork's review findings and standalone activity rows beside the queued message row. The ledger records the issue context feature from fork #31.

Upstream-PR: 11673
Fork-Feature: github-issue-thread-context, composer-draft-sync
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant