Skip to content

Extract shared composer hook (useComposerArea)#283

Open
brsbl wants to merge 1 commit into
mainfrom
bb/extract-shared-composer-hook-usecomposerarea-thr_xuvnw67au8
Open

Extract shared composer hook (useComposerArea)#283
brsbl wants to merge 1 commit into
mainfrom
bb/extract-shared-composer-hook-usecomposerarea-thr_xuvnw67au8

Conversation

@brsbl

@brsbl brsbl commented Jun 20, 2026

Copy link
Copy Markdown
Collaborator

What

Extract the duplicated prompt-composer wiring into one shared hook,
apps/app/src/components/promptbox/useComposerArea.ts, and migrate the three
existing call sites onto it. Refactor only — no new feature, no behavior
change to any surface.

The composer assembly (useThreadCreationOptions + usePromptDraftStorage +
usePromptMentions + useCommandSuggestions + useUploadPromptAttachment +
buildProviderPromptActionProps, built into the execution / permission /
typeahead / attachments / composer configs) was hand-rolled in three places. We
refused to add a 4th copy for the upcoming Loops/Skills inline composer.

Parity-first migration

Each step was verified (typecheck + tests) before the next:

  1. Build the hook by lifting SideChatTabContent's assembly (the most
    self-contained), parameterized.
  2. SideChatTabContent (FollowUpPromptBox; submit creates the child
    thread; read-only footer).
  3. ThreadDetailPromptArea (FollowUpPromptBox; submit steers / follows
    up; editable footer).
  4. RootComposeView (NewThreadPromptBox; submit creates a new thread) —
    the most coupled. Its project picker, branch/worktree selection,
    queued-message stack, and the box itself stay per-site; only the config
    assembly comes from the hook. Command discovery follows the resolved
    environment selection — which the hook itself produces — so commands.environmentId
    accepts a resolver over the live selection value to break the cycle.

What stays per-site

The box choice (NewThreadPromptBox vs FollowUpPromptBox), the submit
handler, and site chrome (project picker, branch/worktree pickers, readOnly,
trigger message, queued-message stack). Sites differ on a small set of explicit,
typed hook parameters: execution interactivity (read-only / provider-switchable),
permission shaping (editable / editable-gated / read-only), the attachment
upload-error strategy (collect failed names / first error), and the
mention/command scoping.

Net: the three sites shed ~756 lines of duplicated assembly; the shared hook is
the single home for it.

Follow-up (not in this PR)

The Loops/Skills inline composer that motivated the extraction is a separate
follow-up that will consume useComposerArea as its 4th call site. It is
intentionally not built here.

Validation

  • pnpm exec turbo run typecheck --filter=@bb/app — green.
  • pnpm exec turbo run test --filter=@bb/app — 137 files / 871 tests pass,
    including SideChatTabContent.test.tsx, which renders the real component
    through the new hook (submit-creates-thread, mention/command typeahead,
    attachments, steer).
  • eslint clean (0 errors) on the changed files.
  • Live app smoke (new thread / steer / side chat) was not run: the worktree's
    better-sqlite3 is built for Node 22 (ABI 127) while the runtime is Node 23
    (ABI 131), so the dev host daemon won't boot — an environment/native-module
    gap unrelated to this frontend-only change. Worth a manual pass after a native
    rebuild before merge.

🤖 Generated with Claude Code

The prompt-composer wiring — useThreadCreationOptions + usePromptDraftStorage
+ usePromptMentions + useCommandSuggestions + useUploadPromptAttachment +
buildProviderPromptActionProps, assembled into the execution / permission /
typeahead / attachments / composer configs — was hand-rolled in three places.
Extract it into one shared hook, apps/app/src/components/promptbox/
useComposerArea.ts, and migrate the three call sites onto it.

Parity-first migration (no behavior change to any surface), in order of
coupling:

1. SideChatTabContent (FollowUpPromptBox, read-only footer, submit creates the
   child thread) — lifted first as the most self-contained.
2. ThreadDetailPromptArea (FollowUpPromptBox, editable footer, submit steers /
   follows up).
3. RootComposeView (NewThreadPromptBox, submit creates a new thread) — most
   coupled; its project picker, branch/worktree selection, queued-message
   stack, and the box itself stay per-site. Command discovery follows the
   resolved environment selection, which the hook itself produces, so it is
   supplied as a resolver over the live selection value.

The hook owns only the shared assembly. Each site keeps its box choice, submit
handler, and chrome. Sites differ on a few explicit, typed parameters:
execution interactivity (read-only / provider-switchable), permission shaping
(editable / editable-gated / read-only), the attachment upload-error strategy
(collect failed names / first error), and the mention/command scoping.

The Loops/Skills inline composer that motivated this is a follow-up that will
consume the hook; it is intentionally not built here.

Validation: typecheck and the full @bb/app test suite pass (871 tests,
including the SideChatTabContent render test that exercises the real component
through the new hook); lint clean on the changed files.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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