Unverified 提交 e5ec62c9 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

docs: add app icons/emoji feature plan (#2722)

## Summary - Adds comprehensive feature plan for app icons/emoji system (`plans/app-icons.md`) - Every app gets a visual identity (emoji or GitHub-style generated avatar) shown in chat tabs, sidebar, and app details - Chat tabs become condensed single-line layouts (icon + chat title) for better density - Plan covers data model, UX flows, accessibility, backfill strategy, and phased implementation ## Test plan - [x] Markdown renders correctly - [x] All lint/type checks pass - [x] No code changes, plan document only 🤖 Generated with [Claude Code](https://claude.com/claude-code) <!-- devin-review-badge-begin --> --- <a href="https://app.devin.ai/review/dyad-sh/dyad/pull/2722" target="_blank"> <picture> <source media="(prefers-color-scheme: dark)" srcset="https://static.devin.ai/assets/gh-open-in-devin-review-dark.svg?v=1"> <img src="https://static.devin.ai/assets/gh-open-in-devin-review-light.svg?v=1" alt="Open with Devin"> </picture> </a> <!-- devin-review-badge-end --> Co-authored-by: 's avatarClaude Opus 4.6 <noreply@anthropic.com>
上级 4a0e3571
# App Icons & Emoji for Dyad Apps
> Generated by swarm planning session on 2026-02-13
## Summary
Add a visual identity system to Dyad apps — every app gets an icon (emoji or GitHub-style generated avatar) that appears in chat tabs, the app list sidebar, and the app details page. Chat tabs become condensed single-line layouts (icon + chat title) for better density. Icons are auto-generated for all apps (including existing ones via backfill) and customizable by the user through a modal picker on the app details page.
## Problem Statement
When users have multiple apps with similar names or many open chat tabs, it's difficult to quickly distinguish between them at a glance. The current two-line tab layout (app name + chat title) consumes significant horizontal space, limiting how many tabs are visible simultaneously. Users lack a fast visual anchor to identify apps — they must read text labels every time.
## Scope
### In Scope (MVP)
- **Generated avatars**: GitHub-style geometric avatars (deterministic from app ID + name, pure CSS/SVG, ~256 unique combinations from 16 colors x 8 patterns x 2 foreground options)
- **Emoji picker**: Full emoji support via emoji-mart library (lazy-loaded), with search, categories, and recently-used section
- **Icon picker modal**: Two-tab modal (Emoji | Avatar) accessible by clicking the icon on the app details page
- **Condensed chat tabs**: Always single-line layout — icon (16px) + chat title, with hover tooltip showing app name
- **App list icons**: Icon displayed next to app name in sidebar (20x20px)
- **App details header icon**: Large icon display with click-to-edit
- **Auto-generation**: New apps get a generated avatar automatically on creation
- **Copy differentiation**: Copied apps always get a different generated avatar than the original
- **Backfill**: All existing apps receive auto-generated avatars via one-time background migration
- **Fallback**: First letter of app name in a deterministic colored circle when icon data is missing/corrupt
- **Accessibility**: ARIA labels on all icons, keyboard navigation in picker, screen reader support, colorblind-safe patterns (shape/pattern variance, not just color)
### Out of Scope (Follow-up)
- Custom image uploads (storage, security, optimization complexity)
- Per-chat icon overrides (app-level only)
- Icon themes or premium icon packs
- AI-generated contextual icons
- Pattern/color customization for avatars (just "Regenerate" button for MVP)
- Adaptive tab layout (show app name when few tabs) — revisit post-launch if needed
- Icons in chat message content
- Window title bar / OS task switcher icons
## User Stories
- **US1**: As a user creating a new app, I want it to automatically have a unique visual identity so I can recognize it immediately without configuration
- **US2**: As a user with many similar apps, I want to customize each app's icon (emoji or avatar) so I can tell them apart at a glance
- **US3**: As a user copying an app, I want the copy to have a different icon so I don't confuse it with the original
- **US4**: As a user with existing apps, I want them to automatically get icons so I don't have to manually configure dozens of apps
- **US5**: As a power user with 15+ tabs open, I want compact single-line tabs so I can see more tabs without scrolling
- **US6**: As a user hovering over a condensed tab, I want to see the full app name in a tooltip so I can confirm which app it belongs to
## UX Design
### User Flow
**Setting an icon (primary flow):**
1. User navigates to app details page
2. Sees current icon (generated avatar by default) prominently displayed in header
3. Hovers icon — sees edit overlay (pencil icon + "Change icon" tooltip)
4. Clicks icon — modal opens with two tabs: "Emoji" and "Avatar"
5. **Emoji tab**: User searches or browses emoji categories, clicks one — modal closes, icon updates immediately
6. **Avatar tab**: User sees large preview, clicks "Regenerate" to cycle through options, clicks "Apply" to save
7. Icon updates across all surfaces (tabs, sidebar, header) via optimistic UI
**New app creation:**
1. User creates app — system auto-generates avatar from `hash(app.id + app.name)`
2. Icon appears immediately in all surfaces, no user action needed
**Copying an app:**
1. User copies app — system generates NEW avatar (different seed from original)
2. Toast: "App copied! Customize its icon in app settings."
**Backfill (one-time, on feature launch):**
1. On first app startup after feature ships, background migration generates avatars for all existing apps
2. If >500ms, show subtle progress: "Setting up app icons..."
3. Apps show first-letter fallback until their avatar is generated
4. Migration persists completion flag — never runs again
### Key States
- **Default**: Generated geometric avatar (deterministic from app ID + name)
- **Customized (emoji)**: User-selected emoji character
- **Customized (avatar)**: User-regenerated avatar (different seed stored)
- **Loading**: Skeleton placeholder in icon picker; fade-in animation for tab icons
- **Error/Fallback**: First letter of app name in deterministic colored circle (color from app.id hash using same 16-color palette)
- **Empty**: Should never occur due to backfill — if it does, show generic app icon (Lucide)
### Interaction Details
**Icon picker modal:**
- **Emoji tab**: Search bar at top, category tabs, grid of emoji (40px cells), recently-used section. Clicking emoji immediately applies and closes modal (quick-apply).
- **Avatar tab**: Large centered preview (128px), "Regenerate" button, "Apply" button. Must preview in both light and dark mode side-by-side.
- **Footer**: Cancel (ESC key) closes without changes
- **Tab persistence**: Remember last-used tab in localStorage
**Chat tabs:**
- Layout: `[Icon 16px] [8px gap] Chat Title [Close button]` (single line)
- Hover: Tooltip appears within 300ms showing `**App Name** - Chat Title` (full text, no truncation)
- Tooltip must be keyboard-accessible (focus on tab shows tooltip after 1 second)
- Icon has subtle fade-in animation (150ms ease) on render
**Overflow menu:**
- Show icons alongside text: `[Icon 14px] App Name - Chat Title`
- Keep app name text in overflow menu for clarity (more horizontal space available)
### Accessibility
- **Screen readers**: Emoji wrapped in `<span aria-hidden="true">`, with `<span class="sr-only">[App Name]</span>` for screen reader text. Tabs have `aria-label="App Name: Chat Title"`
- **Keyboard navigation**: Icon picker fully keyboard-navigable (Tab between sections, Arrow keys in emoji grid, Enter to select). Emoji grid supports arrow key navigation like Windows emoji picker.
- **Colorblind safety**: Generated avatars must vary by SHAPE and PATTERN, not just color. Test in grayscale to verify distinctness.
- **Color contrast**: WCAG AA minimum (4.5:1) for icon elements against both light and dark theme backgrounds
- **Motion sensitivity**: Respect `prefers-reduced-motion` — disable scale/fade animations, use instant transitions
- **Touch targets**: Icon in app details minimum 44x44px tap area; emoji grid cells minimum 40x40px; tab icons minimum 32x32px tap area
## Technical Design
### Architecture
Client-side SVG avatar generation using a deterministic algorithm seeded by `hash(app.id + app.name)`. Emoji rendering uses native OS fonts (test cross-platform; if issues found, add Twemoji fallback). Emoji picker (emoji-mart) is lazy-loaded to avoid impacting bundle size. Backfill runs as a one-time async background task using batched DB updates.
### Components Affected
- `src/db/schema.ts` — Add `iconType` and `iconData` columns to `apps` table
- `src/ipc/types/app.ts` — Update `AppBaseSchema` with new icon fields
- `src/ipc/handlers/app_handlers.ts` — Modify `createApp` (auto-generate icon), `copyApp` (generate different icon), add `updateAppIcon` handler
- `src/components/chat/ChatTabs.tsx` — Refactor to single-line layout with icon, reduce `MIN_VISIBLE_TAB_WIDTH_PX`, add hover tooltip
- `src/pages/app-details.tsx` — Add icon display in header with click-to-edit, icon picker modal
- `src/components/AppList.tsx` / `src/components/appItem.tsx` — Add icon rendering next to app name
- **New**: `src/components/ui/AppIcon.tsx` — Shared icon rendering component (handles emoji, avatar, and fallback modes)
- **New**: `src/components/ui/IconPickerModal.tsx` — Modal with emoji and avatar tabs
### Data Model Changes
Add two nullable text columns to the `apps` table:
```sql
ALTER TABLE apps ADD COLUMN icon_type TEXT;
ALTER TABLE apps ADD COLUMN icon_data TEXT;
```
- `icon_type`: `"emoji"` | `"generated"` | `null`
- `icon_data`:
- For emoji: single UTF-8 emoji character (e.g., `"🚀"`)
- For generated: JSON string with avatar seed/config (e.g., `{"seed": "a1b2c3", "version": 1}`)
- `null`: triggers fallback (first-letter colored circle)
**Zod schema update** in `src/ipc/types/app.ts`:
```typescript
iconType: z.enum(["emoji", "generated"]).nullable(),
iconData: z.string().nullable(),
```
**Backfill migration**: One-time background script that:
1. Queries all apps where `icon_type IS NULL`
2. Generates avatar seed from `hash(app.id + app.name)` for each
3. Updates in batches of 10 with yielding to main thread
4. Stores completion flag in app settings/DB to prevent re-running
### API Changes
**New IPC handler — `updateAppIcon`:**
```typescript
{
channel: "update-app-icon",
input: z.object({
appId: z.number(),
iconType: z.enum(["emoji", "generated"]),
iconData: z.string(),
}),
output: z.void(),
}
```
**Modified handlers:**
- `createApp`: Generate default avatar seed, set `iconType = "generated"` and `iconData = JSON seed`
- `copyApp`: Generate NEW avatar seed (different from original), never copy icon from source app
## Implementation Plan
### Phase 1: Foundation (Backend + Avatar Generation)
- [ ] Add `icon_type` and `icon_data` columns to `apps` table schema
- [ ] Update `AppBaseSchema` Zod type with new icon fields
- [ ] Implement deterministic avatar generation algorithm (pure CSS/SVG, seeded by app ID + name, 16-color palette, 8 geometric patterns)
- [ ] Create shared `AppIcon.tsx` component that renders: emoji (if iconType=emoji), generated avatar (if iconType=generated), or first-letter fallback (if null)
- [ ] Add `updateAppIcon` IPC handler
- [ ] Modify `createApp` handler to auto-generate avatar on app creation
- [ ] Modify `copyApp` handler to generate different avatar for copied apps
- [ ] Implement one-time background backfill migration (batched, async, with progress indicator if >500ms)
### Phase 2: Icon Display Surfaces
- [ ] Add icon to app details page header (large display, clickable with hover edit overlay)
- [ ] Add icon to app list sidebar items (20x20px, left of app name)
- [ ] Refactor chat tabs to single-line layout: icon (16px) + chat title
- [ ] Reduce `MIN_VISIBLE_TAB_WIDTH_PX` (start at 140px, test and adjust)
- [ ] Add hover tooltip on tabs showing `App Name - Chat Title`
- [ ] Add icons to tab overflow menu (14px icon + app name + chat title)
### Phase 3: Icon Picker Modal + Emoji
- [ ] Install and configure emoji-mart (`@emoji-mart/react`, `@emoji-mart/data`) with lazy loading via dynamic import
- [ ] Build `IconPickerModal.tsx` with two tabs (Emoji | Avatar)
- [ ] Emoji tab: search, categories, recently-used, quick-apply on click
- [ ] Avatar tab: large preview (128px) with light/dark mode side-by-side, "Regenerate" button, "Apply" button
- [ ] Persist last-used tab in localStorage
- [ ] Wire modal to `updateAppIcon` handler with optimistic UI updates
### Phase 4: Polish & Testing
- [ ] Dark mode testing for all generated avatar colors (WCAG AA contrast in both themes)
- [ ] Cross-platform emoji rendering verification (macOS, Windows, Linux)
- [ ] Accessibility audit: ARIA labels, keyboard navigation, screen reader testing
- [ ] Performance testing: backfill with 50, 100, 200 apps (must be <3s for 100 apps)
- [ ] Bundle size verification (emoji-mart lazy-loaded, total increase <300KB)
- [ ] Update E2E test snapshots for new tab layout
- [ ] Write new E2E tests (see Testing Strategy)
## Testing Strategy
### Unit Tests
- [ ] Avatar generation determinism: same seed always produces same output
- [ ] Avatar generation uniqueness: sequential app IDs produce visually distinct avatars
- [ ] Copy app produces different icon than original
- [ ] Icon data validation (valid emoji characters, valid generated JSON config)
- [ ] Fallback logic: null iconType renders first-letter circle
### E2E Tests
- [ ] Icon persistence: set emoji, restart app, icon unchanged
- [ ] Copy distinctness: copy app, verify new app has different icon
- [ ] Tab condensation: open 8+ tabs, all show icons + titles in single line
- [ ] Tooltip accuracy: hover tab, tooltip shows correct app name
- [ ] Fallback rendering: corrupt icon data shows first-letter fallback
- [ ] Overflow menu: open 12+ tabs, overflow menu shows icons for hidden tabs
- [ ] Modal keyboard navigation: open modal, Tab/Arrow keys work, Enter applies
- [ ] Dark mode: switch theme, all icons remain visible and readable
- [ ] Screen reader: navigate tabs with VoiceOver, announces app name + chat title (not emoji unicode)
- [ ] Backfill performance: create 100 apps, restart, measure startup time (<3s)
## Risks & Mitigations
| Risk | Likelihood | Impact | Mitigation |
| ------------------------------------------------------------------- | ---------- | ------ | -------------------------------------------------------------------------------------------------------------- |
| Startup performance regression from backfilling 100+ apps | HIGH | HIGH | Run migration in background with batching + yielding; show progress indicator; persist completion flag |
| Chat tab layout regression (drag/drop, overflow, context menu) | MEDIUM | HIGH | Implement tabs last; comprehensive E2E test suite covering all existing tab behaviors before refactoring |
| Emoji rendering inconsistency across OS (macOS vs Windows vs Linux) | MEDIUM | MEDIUM | Test on all 3 platforms before launch; if issues found, add Twemoji/emoji image fallback |
| emoji-mart bundle size impact (~200KB) | MEDIUM | MEDIUM | Lazy-load via dynamic import; only load when modal opens; monitor bundle size in CI |
| Icon similarity causing app misidentification | LOW | HIGH | Use shape+pattern variance (not just color); seed includes app name for entropy; exact-match duplicate warning |
| Always-condensed tabs reduce scannability for users with few tabs | MEDIUM | MEDIUM | Tooltip on hover (critical path); if >10% user complaints, ship adaptive layout patch |
| Generated avatars poor contrast in dark mode | MEDIUM | MEDIUM | Test all 16 palette colors against both theme backgrounds; show dual preview in picker |
## Open Questions
- **Tab minimum width**: Start at 140px, but measure with real chat titles. May need adjustment to 120-130px based on truncation data. Acceptance: <30% of tabs truncated beyond first 15 characters.
- **Emoji rendering quality**: If native OS emoji looks inconsistent across platforms, do we switch to Twemoji image sprites (adds ~500KB)? Decision deferred to cross-platform testing results.
- **Overflow menu design**: Icons confirmed in overflow menu, but exact layout (icon size, spacing, whether to show both app name and chat title) needs visual design pass during implementation.
## Decision Log
| Decision | Reasoning |
| -------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Both emoji + avatars in MVP | User decision. Emoji adds expressiveness and delight (Notion-like). Avatar provides automatic uniqueness. Use emoji-mart library, lazy-loaded. |
| Auto-backfill all existing apps | User decision. Ensures consistent visual experience from day one. Requires background migration with performance safeguards. |
| Always condensed tabs (no adaptive layout) | User decision. Simpler implementation, consistent UX. Tooltip on hover mitigates discoverability concern. Adaptive layout available as fallback if user feedback demands it. |
| Click icon → modal for editing | User decision. Standard interaction pattern, gives enough space for emoji grid + avatar preview. Quick-apply for emoji (click = apply + close), explicit Apply for avatar regeneration. |
| Icons persist independently of app name | Renaming an app does not change its icon. Icons are identity, not derived from name. Avoids surprising users. |
| App-level icons only (no per-chat) | Simpler mental model. Icon = app identity. Per-chat overrides deferred to v2 if requested. |
| No custom image uploads in v1 | Avoids storage, security (SVG XSS), and content moderation complexity. Emoji + avatars provide sufficient customization. |
| Client-side SVG avatar generation | Faster rendering, no IPC overhead, deterministic from seed. No external dependencies needed. Can refactor to shared utility later if backend rendering needed. |
| Phased implementation (foundation → display → picker → polish) | Chat tabs are highest-risk surface, done last. Avatar system can be validated independently before touching critical navigation. |
---
_Generated by dyad:swarm-to-plan_
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论