docs: add app icons/emoji feature plan#2722
Conversation
Comprehensive plan for adding visual identity (emoji + generated avatars) to Dyad apps, covering chat tabs, app list, app details, backfill strategy, and icon picker modal. Generated via swarm planning session with PM, UX, and Eng. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
@BugBot run |
Summary of ChangesHello @wwwillchen, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a comprehensive feature plan for integrating app icons and emoji into the Dyad application. The plan aims to enhance user experience by providing a clear visual identity for each app, making it easier to distinguish between multiple open chat tabs and apps in the sidebar. It details the user flows for icon customization, the technical architecture including data model changes and new components, and a phased implementation strategy. The document also covers critical aspects such as accessibility, performance, and a robust testing strategy to ensure a smooth rollout of this new visual system. Highlights
Changelog
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with π and π on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This is an exceptionally detailed and well-structured feature plan. It covers all the necessary aspects from user stories and UX design to technical implementation, testing, and risk mitigation. The phased approach is logical and helps de-risk the project. I have a few suggestions on the plan to further improve the user experience and robustness of the implementation, particularly around icon customization and data validation. Overall, this is an excellent document that sets a clear path for development.
|
|
||
| ### 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) |
There was a problem hiding this comment.
The plan for generated avatars mentions approximately 256 unique combinations. While users can regenerate icons if they get a duplicate, having a larger pool of unique combinations for the initial auto-generation would reduce the likelihood of collisions, especially for users with many apps. Consider increasing the number of patterns or colors to expand the combination space. For instance, increasing the number of patterns from 8 to 16 would double the combinations to 512, improving the initial uniqueness of generated icons.
| **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 |
There was a problem hiding this comment.
The user flow for setting an icon is well-defined, but it appears to be missing a "Reset to default" option. Once a user customizes an icon (either by picking an emoji or regenerating an avatar), there's no clear way to revert to the original, deterministically generated avatar based on the app's ID and name. This could be frustrating for users who want to undo their customization. I suggest adding a "Reset to default" button in the icon picker modal to improve the user experience.
|
|
||
| 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 |
There was a problem hiding this comment.
The backfill migration plan specifies updating apps in batches of 10. This batch size seems quite small and might lead to a large number of database transactions and IPC calls if there are many existing apps, potentially slowing down the overall migration process. I suggest considering a larger batch size, for example 50 or 100, to improve efficiency. This can be tuned during implementation, but a larger default in the plan seems more appropriate.
| input: z.object({ | ||
| appId: z.number(), | ||
| iconType: z.enum(["emoji", "generated"]), | ||
| iconData: z.string(), | ||
| }), |
There was a problem hiding this comment.
The updateAppIcon IPC handler's input schema defines iconData as a generic z.string(). When iconType is "emoji", this could allow arbitrarily long strings to be saved as an icon, potentially breaking UI layouts where the icon is displayed. I recommend adding validation to ensure that when iconType is "emoji", the iconData string is a single emoji character or has a very short maximum length. This could be handled with a .refine() on the Zod schema.
For example:
z.object({
appId: z.number(),
iconType: z.enum(["emoji", "generated"]),
iconData: z.string(),
}).refine(data => {
if (data.iconType === 'emoji') {
// A simple length check is a good starting point.
// A more robust check could validate it's a single grapheme cluster.
return data.iconData.length > 0 && data.iconData.length <= 10;
}
return true;
}, {
message: "For 'emoji' type, iconData must be a single emoji character.",
path: ["iconData"],
})
Greptile OverviewGreptile SummaryAdds a comprehensive feature plan document (
Confidence Score: 5/5
Important Files Changed
Flowchartflowchart TD
A[App Created / Copied] --> B{Auto-generate avatar}
B --> C[Store icon_type + icon_data in apps table]
C --> D[Icon renders across all surfaces]
D --> E[Chat Tabs - 16px icon + title]
D --> F[Sidebar - 20px icon + name]
D --> G[App Details - Large icon header]
G --> H{User clicks icon}
H --> I[Icon Picker Modal]
I --> J[Emoji Tab]
I --> K[Avatar Tab]
J --> L[Select emoji β quick-apply]
K --> M[Regenerate β Apply]
L --> N[updateAppIcon IPC]
M --> N
N --> C
subgraph Backfill
O[First launch after deploy] --> P{Apps with NULL icon_type?}
P -->|Yes| Q[Batch generate avatars]
Q --> R[Persist completion flag]
P -->|No| S[Skip]
end
Last reviewed commit: 92d2d73 |
π Dyadbot Code Review SummaryVerdict: β YES - Ready to merge Reviewed by 3 independent agents: Correctness Expert, Code Health Expert, UX Wizard. This is a well-structured plan document with thorough coverage of data model, UX flows, accessibility, risks, and phased implementation. No blocking issues found β the confirmed findings below are design clarification suggestions to consider during implementation. Issues Summary
π’ Low Priority Notes (7 items)
π« Dropped False Positives (7 items)
Generated by Dyadbot multi-agent code review |
| - [ ] 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 |
There was a problem hiding this comment.
π‘ MEDIUM | design-contradiction
Avatar seed uses app.name which contradicts icon persistence guarantee
The plan states "Icons persist independently of app name" (Decision Log) and "Renaming an app does not change its icon." However, this line specifies the avatar generation algorithm is "seeded by app ID + name." While the stored seed prevents changes after initial generation, the repeated mention of hash(app.id + app.name) as the seed formula (here, line 63, and line 155) could mislead implementers into re-deriving the seed from the current name rather than using the stored value.
π‘ Suggestion: Clarify that app.name is only used at initial generation time and the resulting seed is stored permanently. Consider using only app.id (or app.id + creation_timestamp) to avoid any confusion. Update all mentions to be consistent.
| - **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 |
There was a problem hiding this comment.
π‘ MEDIUM | interaction-design
Inconsistent quick-apply pattern between emoji and avatar tabs
The emoji tab uses quick-apply (click = apply + close modal) while the avatar tab requires clicking "Apply" after "Regenerate." This creates two different mental models within the same modal. Users who learn the emoji tab's instant-apply pattern may be surprised when regenerating an avatar doesn't immediately save.
π‘ Suggestion: Document the rationale for the difference (emoji selection is a single action, avatar regeneration is exploratory). Consider adding a brief callout in the plan explaining this intentional asymmetry so implementers understand the design choice.
| - 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:** |
There was a problem hiding this comment.
π‘ MEDIUM | interaction-design
Cancel/ESC behavior undefined when avatar previews are pending
The avatar tab allows clicking "Regenerate" multiple times to preview options before clicking "Apply." If the user presses ESC or Cancel after regenerating but before applying, it's unclear whether the preview reverts to the original avatar. This ambiguity could lead to inconsistent implementation.
π‘ Suggestion: Add a line clarifying: "Cancel/ESC always reverts to the icon state from when the modal was opened, discarding any unsaved regenerations."
|
|
||
| ### Key States | ||
|
|
||
| - **Default**: Generated geometric avatar (deterministic from app ID + name) |
There was a problem hiding this comment.
π‘ MEDIUM | error-states
Empty state falls back to generic icon instead of first-letter fallback
The Error/Fallback state uses a first-letter colored circle (which is unique per app), but the Empty state uses a generic Lucide icon (which would make all affected apps look identical). Since the Empty state "should never occur," it's likely a bug scenario β and showing identical generic icons for multiple apps makes it harder to distinguish them, which is the exact problem this feature solves.
π‘ Suggestion: Use the same first-letter fallback for both Error and Empty states. Reserve the generic Lucide icon only for when the app name is also unavailable.
π Playwright Test Resultsβ Some tests failed
Summary: 707 passed, 3 failed, 11 flaky, 224 skipped Failed Testsπ macOS
πͺ Windows
π Re-run Failing Tests (macOS)Copy and paste to re-run all failing spec files locally: npm run e2e \
e2e-tests/concurrent_chat.spec.ts \
e2e-tests/context_manage.spec.ts
|
Summary
plans/app-icons.md)Test plan
π€ Generated with Claude Code