Skip to content

feat(space): replace ThreadedChatComposer with ChatComposer in task view#1563

Merged
lsm merged 3 commits intodevfrom
space/replace-threadedchatcomposer-with-chatcomposer-in-space
Apr 21, 2026
Merged

feat(space): replace ThreadedChatComposer with ChatComposer in task view#1563
lsm merged 3 commits intodevfrom
space/replace-threadedchatcomposer-with-chatcomposer-in-space

Conversation

@lsm
Copy link
Copy Markdown
Owner

@lsm lsm commented Apr 21, 2026

Replaces the minimal ThreadedChatComposer in the Space task pane with the rich ChatComposer experience, which includes the SessionStatusBar (model switcher, context usage) and full MessageInput (attachments, command autocomplete, interrupt, rewind mode).

Changes

  • MessageInput: new agentMentionCandidates and placeholder props; @-query detection wired into content change handler with keyboard navigation (↑↓/Enter/Esc) before reference and command autocomplete.
  • InputTextarea: new agent mention autocomplete props; MentionAutocomplete renders with highest priority over reference and command autocomplete.
  • ChatComposer: threads agentMentionCandidates and inputPlaceholder down to MessageInput.
  • TaskSessionChatComposer (new): wraps ChatComposer with all non-applicable features disabled, useModelSwitcher for the task session, placeholder driven by hasTaskAgentSession, and an onSend adapter mapping the boolean-return contract to ChatComposer's async void signature.
  • SpaceTaskPane: swaps ThreadedChatComposer for TaskSessionChatComposer; removes the now-redundant absolute wrapper div since ChatComposer owns its own positioning.
  • TaskSessionChatComposer.test.tsx (new): 11 unit tests covering rendering, prop threading, error banner, disabled state, and placeholder selection.

All pre-existing tests pass; no regressions.

Copy link
Copy Markdown
Owner Author

@lsm lsm left a comment

Choose a reason for hiding this comment

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

Review: Replace ThreadedChatComposer with ChatComposer in Space task view

Overall the approach is sound — creating a TaskSessionChatComposer wrapper that delegates to ChatComposer with feature flags disabled is the right pattern. The new test suite covers the prop threading well. A few issues need attention before merging:

Must fix

  1. Delete dead code: ThreadedChatComposer.tsx and ThreadedChatComposer.test.tsx are no longer imported anywhere. They should be deleted in this PR rather than left as dead code.

  2. Draft loss on send failure (behavioral regression): The old ThreadedChatComposer only cleared the draft on successful send (if (sent) { setDraft('') }). The new flow via MessageInput.handleSubmit calls clearDraft() before await onSend(...), so if sendThreadMessage returns false (failure), the draft is already gone. The user sees the error banner but loses their message. This should be addressed — either by not clearing the draft pre-send for task sessions, or by restoring it on failure.

  3. isProcessing hardcoded to false: TaskSessionChatComposer passes isProcessing={false} to ChatComposer. This means the SessionStatusBar will never show a processing indicator even when the task agent is actively working. The parent SpaceTaskPane has access to agent working state — consider wiring it through. At minimum, isAgentWorking from the old component was used for the interrupt button, and it should be connected.

Nice to have

  1. @ts-nocheck in test file: The test file uses // @ts-nocheck at the top. The mocked modules should type-check fine on their own (the mocks match the return signatures). Consider removing the directive — if it fails, the specific type issues should be fixed rather than suppressed.

  2. Dual useModelSwitcher instantiation: Both TaskSessionChatComposer and MessageInput call useModelSwitcher(sessionId) for the same session. This is wasteful (two subscriptions/RPC calls) but not functionally broken since they operate independently. The model data from the outer hook flows to SessionStatusBar, while MessageInput needs its own instance for the InputActionsMenu. Consider whether ChatComposer could pass model data down to MessageInput to eliminate the duplication.

  3. Reference autocomplete suppression: When workflow agents exist, typing @ now triggers the agent mention autocomplete and completely suppresses the reference autocomplete (@ref{...}). This is likely intentional but worth documenting as a known trade-off.

import { PendingCompletionActionBanner } from './PendingCompletionActionBanner';
import { PendingTaskCompletionBanner } from './PendingTaskCompletionBanner';
import { ThreadedChatComposer } from './ThreadedChatComposer';
import { TaskSessionChatComposer } from './TaskSessionChatComposer';
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Dead code: ThreadedChatComposer.tsx is no longer imported anywhere (the only consumer SpaceTaskPane.tsx now imports TaskSessionChatComposer). Delete this file and its test ThreadedChatComposer.test.tsx in this PR.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed — deleted both ThreadedChatComposer.tsx and ThreadedChatComposer.test.tsx in the follow-up commit.

_images?: MessageImage[],
_deliveryMode?: MessageDeliveryMode
): Promise<void> => {
await onSend(content);
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Behavioral regression: The old ThreadedChatComposer only cleared the draft on successful send (if (sent) { setDraft('') }). MessageInput.handleSubmit (line 340 in the parent) calls clearDraft() before await onSend(...), so if sendThreadMessage returns false (failure), the user's message is already gone. Consider either: (a) passing the boolean return through and only clearing on success, or (b) restoring the draft on failure.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed. Changed MessageInput.onSend return type to Promise<void | boolean>. handleSubmit now saves the content before clearing optimistically, then restores it via setContent(savedContent) when the result is false. TaskSessionChatComposer.handleSend now returns the boolean from sendThreadMessage so the signal propagates correctly.

sessionId={sessionId}
readonly={false}
isProcessing={false}
features={{
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

isProcessing hardcoded: The old ThreadedChatComposer used isAgentWorking.value for the interrupt button. Here isProcessing={false} means SessionStatusBar will never show a processing indicator. Consider wiring the actual agent working state through (the parent SpaceTaskPane has access to it via isAgentWorking or the task status).

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Fixed. Added isProcessing: boolean to TaskSessionChatComposerProps and passed isAgentActive from SpaceTaskPane (which already computes it from activity member states). ChatComposer now receives the live processing state and SessionStatusBar will correctly show the processing indicator.

@@ -0,0 +1,123 @@
// @ts-nocheck
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Consider removing @ts-nocheck. The mocks match the return signatures and should type-check. If there are specific type errors, fix them individually rather than suppressing the whole file.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Removed. Exported ChatComposerProps from ChatComposer.tsx, typed lastChatComposerProps as ChatComposerProps | null, and updated all assertions to use optional chaining. Also added an isProcessing forwarding test.

@lsm lsm force-pushed the space/replace-threadedchatcomposer-with-chatcomposer-in-space branch from 33a4b1b to c03102b Compare April 21, 2026 03:14
lsm added 3 commits April 20, 2026 23:22
Introduces TaskSessionChatComposer — a thin wrapper around the rich
ChatComposer — for the Space task pane, replacing the minimal
ThreadedChatComposer. Adds agent @mention autocomplete support to
MessageInput/InputTextarea/ChatComposer so workflow agents can be
mentioned directly in the full composer experience.

- Add agentMentionCandidates prop + state to MessageInput with @-query
  detection, keyboard navigation (↑↓/Enter/Esc), and handleAgentMentionSelect
- Add showAgentMentionAutocomplete/agentMentionCandidates/etc. props to
  InputTextarea; render MentionAutocomplete with highest priority over
  reference and command autocomplete
- Add agentMentionCandidates and inputPlaceholder props to ChatComposer
  and thread them through to MessageInput
- Create TaskSessionChatComposer with features disabled, model switcher,
  placeholder derived from hasTaskAgentSession, and onSend adapter
- Update SpaceTaskPane to use TaskSessionChatComposer, removing the
  wrapping absolute div (ChatComposer owns its own positioning)
- Add TaskSessionChatComposer unit tests (11 cases)
…atComposer

ChatComposer renders absolute-positioned, so an external error banner in
TaskSessionChatComposer would be hidden behind the footer. Add errorMessage
prop to ChatComposer to render the banner inside the footer div, and
thread it through TaskSessionChatComposer.
- Delete ThreadedChatComposer.tsx and its test (dead code, no longer imported)
- Fix draft-loss regression: MessageInput.onSend now accepts boolean|void return;
  handleSubmit saves content before clearing and restores on false return so
  task send failures preserve the user's message
- Wire isProcessing from SpaceTaskPane.isAgentActive instead of hardcoding false;
  SessionStatusBar now shows correct processing indicator for task sessions
- Remove @ts-nocheck from test file; use typed ChatComposerProps capture and
  add isProcessing forwarding test
@lsm lsm force-pushed the space/replace-threadedchatcomposer-with-chatcomposer-in-space branch from c03102b to 7c21d1d Compare April 21, 2026 03:22
Copy link
Copy Markdown
Owner Author

@lsm lsm left a comment

Choose a reason for hiding this comment

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

Re-review: LGTM ✅

All 4 items from the previous review are addressed:

  1. Dead code deletedThreadedChatComposer.tsx and its test removed.
  2. Draft loss fixedMessageInput.handleSubmit saves content before clearing, restores via setContent when onSend returns false. Return type widened to Promise<void | boolean> across ChatComposerMessageInputTaskSessionChatComposer.
  3. isProcessing wired — now passed from SpaceTaskPane as isAgentActive (derived from activity member states).
  4. @ts-nocheck removedChatComposerProps exported, mock properly typed, new forwarding test added (12 tests, up from 11).

All checks pass: tests ✅, lint ✅, typecheck ✅, PR mergeable ✅.

@lsm lsm merged commit 5f6833f into dev Apr 21, 2026
44 checks passed
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