Files
ChatApp/docs/superpowers/plans/2026-05-17-phase7-composer-redesign.md
T

6.2 KiB
Raw Blame History

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 for PaperclipIcon/PlusIcon, PollIcon, MonitorShareIcon, PlayBoxIcon, GameIcon).
  • Click outside or EsconClose.
  • 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 <ComposerActionsMenu> 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) => <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 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).