6.2 KiB
Phase 7 — Composer Redesign (Hybrid)
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development. Steps use checkbox (
- [ ]) syntax.
Goal: Reduce composer toolbar from 9 cluttered icons to 5 hierarchically-organized buttons. Move "creative activities" (Whiteboard, Watch-Together, Mini-Games) into a + popover. Move View-Once from global composer toggle to per-attachment flag in the upload preview.
Rollback anchor: tag pre-phase7-composer (set in T1).
Task 1: Rollback anchor
- Run:
cd "D:\Programmieren\ChatApp-Electron\chat-app"
git tag -a pre-phase7-composer -m "Rollback anchor before Phase 7 composer redesign"
Task 2: <ComposerActionsMenu> popover component
Files: Create apps/desktop/src/components/ComposerActionsMenu.tsx
Shape:
interface Props {
anchorRef: React.RefObject<HTMLButtonElement | null>;
open: boolean;
onClose: () => void;
onAttachFile: () => void;
onCreatePoll: () => void;
onCreateWhiteboard: () => void;
onStartWatchTogether: () => void;
onStartGame: () => void;
canStartGame?: boolean;
}
Layout (floating panel anchored above anchorRef):
┌─────────────────────────────┐
│ 📎 Bild / Datei │
│ 📊 Umfrage │
├─────────────────────────────┤
│ AKTIVITÄTEN │
│ ✏ Whiteboard │
│ 📺 Watch Together │
│ 🎮 Spiel starten │
└─────────────────────────────┘
- Use existing icons from
apps/desktop/src/components/icons.tsx(grep forPaperclipIcon/PlusIcon,PollIcon,MonitorShareIcon,PlayBoxIcon,GameIcon). - Click outside or
Esc→onClose. - Disabled items:
opacity-50 cursor-not-allowed+titlehint (e.g. "Spiele nur in 1:1-Chats"). - Each row ≥ 44px tall,
role="menu"/role="menuitem", arrow-up/down keyboard nav.
Task 3: Refactor ConversationPage composer
Files: Modify apps/desktop/src/pages/ConversationPage.tsx
Target layout:
┌────────────────────────────────────────────────────────┐
│ [+] [😊] [GIF] [🎤] Nachricht schreiben… [→] │
└────────────────────────────────────────────────────────┘
Changes:
- Remove inline buttons for: file-attach, poll, whiteboard, watch-together, game-picker.
- Add a
+button at position 1 with auseRefanchor. - State:
const [menuOpen, setMenuOpen] = useState(false);+ render<ComposerActionsMenu>with the existing handlers wired (handleCreateWhiteboard,handleStartWatchTogether,handleStartGame,() => setPollDialogOpen(true),() => fileInputRef.current?.click()). - Remove the standalone View-Once toggle button (moves to T4 per-attachment).
- Keep inline: Emoji picker, GIF picker, voice mic, send arrow.
- Auto-close menu after any item action.
- Pass
canStartGame={conversation?.members?.length === 2}so the dropdown reflects the DM-only constraint.
Task 4: View-Once per-attachment in AttachmentPreview
Files:
- Modify
apps/desktop/src/pages/ConversationPage.tsx(AttachmentPreviewcomponent + theattachments[]state shape). - Modify
apps/desktop/src/hooks/useConversationMessages.ts(send()signature + per-attachment handling). - Possibly extend the per-attachment encrypt/upload helper if it still treats
viewOnceas a per-message flag.
Behavior:
Add a third hover-button on each image preview next to ✏ and ✕: a 👁 icon that toggles viewOnce per attachment.
- Active: icon switches (e.g. crossed-eye) + small
1×badge in lower-right corner of the thumb. - Image-only (
file.type.startsWith('image/')). Hidden on non-image previews.
State refactor:
Change attachments: File[] → attachments: Array<{ file: File; viewOnce: boolean }>. Every consumer site updated:
setAttachments((prev) => [...prev, ...newOnes.map((f) => ({ file: f, viewOnce: false }))])attachments.map((a, idx) => <AttachmentPreview file={a.file} ... onToggleViewOnce={() => setAttachments(prev => prev.map((x, i) => i === idx ? { ...x, viewOnce: !x.viewOnce } : x))} />)setAttachments((prev) => prev.filter((_, i) => i !== idx))— unchanged shape
Send path:
The send() currently accepts a viewOnce option that applies globally. Refactor so the per-attachment flag flows through:
- Either change
send(payload, attachments, replyTo, { viewOnce })→send(payload, attachmentsWithFlags, replyTo)where each entry carries its ownviewOnce - OR pass a parallel
viewOnceFlags: boolean[]array aligned with attachments
The encrypt/upload helper already supports per-attachment view_once (P2.T14 column message_attachments.view_once). The renderer just needs to pass the right flag per row.
Grep first to find the existing wiring: Grep -rn "view_once\|viewOnce" apps/desktop/src/ packages/shared/src/chat/ — adapt to what's actually there.
Task 5: Cleanup + Final gate
pnpm --filter @chat-app/desktop typecheck— greenpnpm --filter @chat-app/shared test -- --run— green (71 tests)pnpm --filter @chat-app/desktop test -- --run— greengit status— clean- Tag
phase7-done - Report smoke-test points:
- Composer shows 5 inline buttons (was 9)
- Click
+→ popover opens; click anywhere outside orEsccloses it - Attach image → preview shows ✏/👁/✕ on hover
- Toggle 👁 on attachment-1 only → recipient sees attachment-1 as view-once, attachment-2 normally
- Everything else unchanged (emoji, GIF, voice, send, edit-message, etc.)
Non-goals
- No emoji-as-icon (uses existing SVG icons).
- No slash-commands (deferred to potential Phase 7B).
- No reordering of inline buttons beyond the spec.
- No per-attachment poll-attach (polls remain message-level).