# 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: ```bash cd "D:\Programmieren\ChatApp-Electron\chat-app" git tag -a pre-phase7-composer -m "Rollback anchor before Phase 7 composer redesign" ``` --- ## Task 2: `` popover component **Files:** Create `apps/desktop/src/components/ComposerActionsMenu.tsx` **Shape:** ```tsx interface Props { anchorRef: React.RefObject; 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 for `PaperclipIcon`/`PlusIcon`, `PollIcon`, `MonitorShareIcon`, `PlayBoxIcon`, `GameIcon`). - Click outside or `Esc` → `onClose`. - Disabled items: `opacity-50 cursor-not-allowed` + `title` hint (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: 1. **Remove** inline buttons for: file-attach, poll, whiteboard, watch-together, game-picker. 2. **Add** a `+` button at position 1 with a `useRef` anchor. 3. **State:** `const [menuOpen, setMenuOpen] = useState(false);` + render `` with the existing handlers wired (`handleCreateWhiteboard`, `handleStartWatchTogether`, `handleStartGame`, `() => setPollDialogOpen(true)`, `() => fileInputRef.current?.click()`). 4. **Remove** the standalone View-Once toggle button (moves to T4 per-attachment). 5. **Keep inline:** Emoji picker, GIF picker, voice mic, send arrow. 6. **Auto-close menu** after any item action. 7. 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` (`AttachmentPreview` component + the `attachments[]` 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 `viewOnce` as 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) => 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 own `viewOnce` - 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` — green - [ ] `pnpm --filter @chat-app/shared test -- --run` — green (71 tests) - [ ] `pnpm --filter @chat-app/desktop test -- --run` — green - [ ] `git status` — clean - [ ] Tag `phase7-done` - [ ] Report smoke-test points: 1. Composer shows 5 inline buttons (was 9) 2. Click `+` → popover opens; click anywhere outside or `Esc` closes it 3. Attach image → preview shows ✏/👁/✕ on hover 4. Toggle 👁 on attachment-1 only → recipient sees attachment-1 as view-once, attachment-2 normally 5. 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).