diff --git a/docs/superpowers/plans/2026-05-17-phase7-composer-redesign.md b/docs/superpowers/plans/2026-05-17-phase7-composer-redesign.md new file mode 100644 index 0000000..e850e1e --- /dev/null +++ b/docs/superpowers/plans/2026-05-17-phase7-composer-redesign.md @@ -0,0 +1,138 @@ +# 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).