Files
kandev/docs/plans/slash-command-composer/plan.md
T
2026-08-24 11:09:03 +00:00

141 lines
6.2 KiB
Markdown

---
spec: docs/specs/ui/requirements/slash-command-composer.md
created: 2026-07-02
status: implemented
---
# Implementation Plan: Slash Command Composer Selection
## Overview
The backend already delivers ACP slash commands through `session.available_commands`, and the frontend already renders them in the shared TipTap composer. The fix is frontend-only: replace the current selection callback that calls `onSubmit('/cmd')` with draft insertion inside the TipTap suggestion command, remove the now-dangerous submit callback plumbing, then add focused unit and E2E coverage for task chat, quick chat, and mobile touch selection.
---
## Backend
No backend changes are planned. The existing ACP conversion and WebSocket path stays unchanged:
- `apps/backend/internal/agent/agentctl/transport/acp/adapter_updates.go`
- `apps/backend/internal/agent/lifecycle/events.go`
- `apps/backend/internal/backendapp/helpers.go`
- `apps/web/lib/ws/handlers/available-commands.ts`
Reason:
- The bug happens after commands are already in the client store. Selection currently submits from the frontend composer callback.
---
## Frontend
### Slash Command Types and Insertion Text
Files:
- `apps/web/components/task/chat/slash-command-types.ts` (new)
- `apps/web/components/task/chat/tiptap-suggestion.tsx`
- `apps/web/components/task/chat/slash-command-menu.tsx`
- `apps/web/components/task/chat/tiptap-input.tsx`
Changes:
- Move `SlashCommand` and `SlashCommandAction` out of the legacy `apps/web/hooks/use-inline-slash.ts` file into a chat-local type module.
- Add a small pure helper, for example `formatSlashCommandInsertion(command: SlashCommand): string`, that returns a slash-prefixed command plus trailing space for continued typing.
- In `createSlashSuggestion().command`, replace the active TipTap suggestion `range` with `formatSlashCommandInsertion(cmd)` and keep focus in the editor.
- Remove `SlashSuggestionCallbacks.onAgentCommand`; selection must not call an external submit callback.
- Keep the current menu filtering, keyboard navigation, Escape close behavior, and portal-rendered `SlashCommandMenu`.
Reason:
- The selection command is the only place that should mutate the draft. Sending is owned by the normal composer submit path.
### Remove Auto-Submit Plumbing
Files:
- `apps/web/components/task/chat/tiptap-input.tsx`
- `apps/web/components/task/chat/chat-input-body.tsx`
- `apps/web/components/task/chat/chat-input-container.tsx`
- `apps/web/components/task/chat/use-chat-input-container.ts`
- `apps/web/components/task/chat/chat-input-body.test.tsx`
- `apps/web/hooks/use-inline-slash.ts`
Changes:
- Remove the `onAgentCommand` prop from `TipTapInput`.
- Remove `handleAgentCommand` from `ChatInputEditorAreaProps`, `buildEditorAreaProps`, and `useChatInputContainer`.
- Delete `apps/web/hooks/use-inline-slash.ts` after moving the shared types if `rg "useInlineSlash"` confirms no runtime consumers. If deletion is not clean, update the hook to use the same draft insertion helper and remove its `onAgentCommand` callback.
- Update affected mocks and prop builders in tests.
Reason:
- Leaving a `handleAgentCommand -> onSubmit('/cmd')` path in the composer keeps the original bug one import away from returning.
---
## Tests
- **What:** slash command insertion text is normalized and leaves room for arguments.
**File:** `apps/web/components/task/chat/slash-command-types.test.ts`
**How:** unit test the exported helper with `agentCommandName: "slow"`, labels with and without a leading slash, and command names that contain punctuation such as `tool:read`.
- **What:** Enter in a suggestion menu still defers to the suggestion plugin instead of submit, including plain-Enter submit mode.
**File:** `apps/web/components/task/chat/use-tiptap-editor.test.ts`
**How:** keep the existing `decideSubmitShortcut` regression tests and add a case name that ties it to slash command selection if useful.
- **What:** removing auto-submit plumbing does not break the chat input container's disabled and submit state derivation.
**File:** `apps/web/components/task/chat/use-chat-input-container.test.ts`
**How:** update existing tests after `handleAgentCommand` is removed; no new container behavior is expected.
- **What:** TypeScript catches stale prop usage after the prop cleanup.
**File:** no dedicated file.
**How:** run:
```bash
cd apps
pnpm --filter @kandev/web typecheck
```
---
## E2E Tests
- **Scenario:** GIVEN a task chat session with advertised commands, WHEN the user types `/s` and presses Enter to choose `/slow`, THEN the composer contains `/slow`, focus remains in the composer, and no user message appears in `.chat-message-list`.
**File:** `apps/web/e2e/tests/chat/slash-command-composer.spec.ts`
**What to verify:** selection by Enter inserts a draft and does not auto-send.
- **Scenario:** GIVEN the selected `/slow` draft remains in task chat, WHEN the user types `1s` and clicks Send, THEN the user message `/slow 1s` appears and the mock agent responds through the normal command path.
**File:** `apps/web/e2e/tests/chat/slash-command-composer.spec.ts`
**What to verify:** explicit send still sends the edited draft.
- **Scenario:** GIVEN quick chat is open with an initialized mock agent, WHEN the user selects `/slow` from the slash menu, THEN the quick chat editor keeps `/slow` as a draft and no message is sent until explicit send.
**File:** `apps/web/e2e/tests/chat/slash-command-composer.spec.ts`
**What to verify:** the shared composer behavior works in quick chat.
- **Scenario:** GIVEN mobile task chat has advertised commands, WHEN the user types `/s` and taps `/slow`, THEN the mobile composer keeps `/slow` as draft text and the Send button remains the only send action.
**File:** `apps/web/e2e/tests/chat/mobile-slash-command-composer.spec.ts`
**What to verify:** touch selection has parity with desktop and no workflow depends on hover or a wide viewport.
Recommended E2E command:
```bash
cd apps/web
pnpm e2e:run tests/chat/slash-command-composer.spec.ts tests/chat/mobile-slash-command-composer.spec.ts
```
---
## Implementation Waves
Wave 1:
- [x] [task-01-frontend-selection](task-01-frontend-selection.md)
Wave 2:
- [x] [task-02-unit-tests](task-02-unit-tests.md)
Wave 3:
- [x] [task-03-e2e-verification](task-03-e2e-verification.md)