[feat] Let each agent carry its own icon and colour - #6062
[feat] Let each agent carry its own icon and colour#6062ashrafchowdury wants to merge 8 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository YAML (base), Organization UI (inherited) Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
Included review availability: Your plan includes up to 8 reviews per rolling hour; 7 remain after this review. 📝 WalkthroughSummary by CodeRabbit
WalkthroughThe PR adds workflow-specific agent icons with persisted icon and color selections. It introduces a generated Phosphor catalog, shared rendering utilities, an icon picker, bounded local-storage persistence, and integrations across chat, playground, sidebar, commit notices, and agent cards. ChangesAgent icon customization
Estimated code review effort: 4 (Complex) | ~60 minutes Merge Risk: 🟡 Moderate · up to This PR adds persisted per-agent icons and colors across the product, but malformed stored icon data can reach SVG rendering and malformed storage can break agent surfaces during rendering; cancelled color drags may also leave page-level listeners attached. Merge should wait for sanitization/validation and drag-cleanup fixes. Sequence Diagram(s)sequenceDiagram
participant User
participant AgentIconTrigger
participant AgentIconPicker
participant agentIconAtomFamily
participant AgentSurface
User->>AgentIconTrigger: open workflow icon control
AgentIconTrigger->>AgentIconPicker: load picker for persisted workflow
AgentIconPicker->>agentIconAtomFamily: save icon, color, and SVG path
agentIconAtomFamily-->>AgentSurface: provide persisted icon record
AgentSurface->>AgentSurface: render workflow-specific icon chrome
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (3)
web/packages/agenta-ui/src/agent-icon/AgentIconPicker.tsx (1)
69-89: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winHandle
pointercanceland unmount so the drag listeners cannot outlive the picker.
stopis only bound topointerup. If the browser cancels the pointer, which happens on touch when a scroll or a system gesture takes over, nopointerupfires. Thepointermovelistener then stays attached towindowfor the rest of the page session and keeps callingonPreviewon a picker the user has already closed.Bind
pointercancelto the samestop, and clear the listeners if the picker unmounts first.♻️ Proposed fix: also stop on `pointercancel` and expose a cleanup
const trackDrag = ( event: PointerEvent<HTMLDivElement>, onMove: (x: number, y: number) => void, onCommit: () => void, -) => { +): (() => void) => { const rect = event.currentTarget.getBoundingClientRect() const track = (e: {clientX: number; clientY: number}) => onMove( clamp((e.clientX - rect.left) / rect.width, 0, 1), clamp((e.clientY - rect.top) / rect.height, 0, 1), ) track(event) const stop = () => { window.removeEventListener("pointermove", track) window.removeEventListener("pointerup", stop) + window.removeEventListener("pointercancel", stop) onCommit() } window.addEventListener("pointermove", track) window.addEventListener("pointerup", stop) + window.addEventListener("pointercancel", stop) + return stop }In
CustomColorArea, keep the returned stopper in a ref and call it from an unmount effect:const stopRef = useRef<(() => void) | null>(null) useEffect(() => () => stopRef.current?.(), []) // ... onPointerDown={(e) => { stopRef.current = trackDrag(e, (x, y) => onPreview(hsvToHex(hsv.h, x, 1 - y)), onCommit) }}web/oss/src/components/Sidebar/components/WorkflowIdentity.tsx (1)
96-106: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDo not use
chrome.styleas the "customised" discriminator.Line 105 infers "the user picked an icon" from the presence of
chrome.style. That reads an implementation detail ofagentIconChrome, which only attaches a style in the record branch.AgentIcon.tsxstates the intent is the opposite: the customised-or-not branch should live in the helper, not at the call site.The coupling is load-bearing here.
fallbackGlyphisnullon line 98, so ifagentIconChromeever attaches a style to the fallback branch, this row renders an empty glyph box and loses the prompt/agent icon class fromWORKFLOW_DISPLAY_META.Expose an explicit flag from the helper, for example
customised: boolean, and gate on that instead.Line 97 also restates the glyph size that
WorkflowIdentityViewalready computes on line 52. Two sources for one number can drift, and the glyph would then stop matching the box. Consider passing the resolved size down, or exporting it as a shared constant.web/packages/agenta-ui/src/agent-icon/index.ts (1)
1-9: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winSplit the picker into a separate package entry.
agentIcon.tsximportsagentIconChromefrom@agenta/ui/agent-icon, whileAgentIconChipdynamically importsAgentIconPickerfrom the same entry. The barrel synchronously re-exportsAgentIconPicker, so the picker and virtualizer enter the synchronous bundle. Expose the picker through a separate subpath and update the dynamic import.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 890b6b23-0911-46c4-b9c4-98b0615c4fad
⛔ Files ignored due to path filters (1)
web/pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (22)
web/.gitignoreweb/oss/src/components/AgentChatSlice/components/AgentChatEmptyState.tsxweb/oss/src/components/AgentIconChip/index.tsxweb/oss/src/components/Playground/Components/PlaygroundHeader/index.tsxweb/oss/src/components/Sidebar/components/WorkflowIdentity.tsxweb/oss/src/components/Sidebar/dynamic/registry.tsweb/package.jsonweb/packages/agenta-entities/src/workflow/index.tsweb/packages/agenta-entities/src/workflow/state/agentIcon.tsweb/packages/agenta-entities/src/workflow/state/boundedMap.tsweb/packages/agenta-entities/src/workflow/state/index.tsweb/packages/agenta-entities/src/workflow/state/persistedAgentType.tsweb/packages/agenta-entity-ui/src/agent/AgentCard.tsxweb/packages/agenta-entity-ui/src/agent/agentIcon.tsxweb/packages/agenta-entity-ui/src/agent/index.tsweb/packages/agenta-ui/package.jsonweb/packages/agenta-ui/scripts/generate-catalog.tsweb/packages/agenta-ui/src/agent-icon/AgentIcon.tsxweb/packages/agenta-ui/src/agent-icon/AgentIconPicker.tsxweb/packages/agenta-ui/src/agent-icon/colors.tsweb/packages/agenta-ui/src/agent-icon/index.tsweb/packages/agenta-ui/tests/unit/agentIconColors.test.ts
Included review availability: Your plan includes up to 8 reviews per rolling hour; 7 remain after this review.
Railway Preview Environment
|
b9db11e to
6b2d4ee
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: bb16fe66-e77a-4735-8e92-0ff5ce00e4c4
⛔ Files ignored due to path filters (1)
web/packages/agenta-ui/src/agent-icon/catalog.generated.tsis excluded by!**/*.generated.*
📒 Files selected for processing (3)
web/packages/agenta-ui/package.jsonweb/packages/agenta-ui/scripts/curated-icons.tsweb/packages/agenta-ui/scripts/generate-catalog.ts
💤 Files with no reviewable changes (1)
- web/packages/agenta-ui/package.json
🚧 Files skipped from review as they are similar to previous changes (1)
- web/packages/agenta-ui/scripts/generate-catalog.ts
Included review availability: Your plan includes up to 8 reviews per rolling hour; 7 remain after this review.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository YAML (base), Organization UI (inherited)
Review profile: CHILL
Plan: Pro Plus
Run ID: 207552cb-ec52-4926-ba71-5b75a29a2025
⛔ Files ignored due to path filters (1)
web/packages/agenta-ui/src/agent-icon/catalog.generated.tsis excluded by!**/*.generated.*
📒 Files selected for processing (9)
web/oss/src/components/Sidebar/components/WorkflowIdentity.tsxweb/packages/agenta-entities/src/workflow/state/agentIcon.tsweb/packages/agenta-entities/tests/unit/agent-icon-record.test.tsweb/packages/agenta-entities/tests/unit/bounded-map.test.tsweb/packages/agenta-entity-ui/src/agent/agentIcon.tsxweb/packages/agenta-ui/scripts/generate-catalog.tsweb/packages/agenta-ui/src/agent-icon/AgentIcon.tsxweb/packages/agenta-ui/src/agent-icon/AgentIconPicker.tsxweb/packages/agenta-ui/src/agent-icon/colors.ts
🚧 Files skipped from review as they are similar to previous changes (6)
- web/packages/agenta-ui/scripts/generate-catalog.ts
- web/packages/agenta-ui/src/agent-icon/AgentIconPicker.tsx
- web/packages/agenta-ui/src/agent-icon/AgentIcon.tsx
- web/packages/agenta-ui/src/agent-icon/colors.ts
- web/oss/src/components/Sidebar/components/WorkflowIdentity.tsx
- web/packages/agenta-entity-ui/src/agent/agentIcon.tsx
Included review availability: Your plan includes up to 8 reviews per rolling hour; 4 remain after this review.
|
@mmabrouk request you for a product review |
|
@ashrafchowdury The custom icon appears in these agent identity surfaces:
It does not appear in two other surfaces that represent the current agent:
I recommend that we use the custom icon in these two surfaces. The loading shell also shows the default robot. I think that is acceptable because the workflow data may not be available when the shell renders. The Agents navigation item, the empty Agents page, and app-type icons should keep the default robot. They represent the agent type, not one agent. |
|
Backend persistence is tracked in #6082. The issue moves the icon name and color to the workflow artifact under |
Every agent rendered the same fixed Robot glyph, so a workspace with many agents gave you nothing to tell them apart at a glance. Clicking the chip in the playground header now opens a picker — any Phosphor icon, a palette or custom colour, saved as you pick. The sidebar, the agent cards and the chat empty state display the choice; only the header edits it. Stored in localStorage for now, keyed by workflow id. The backend home is the workflow artifact's `meta`, which needs its meta-only update guard fixed first. Icons come from a generated catalog (`pnpm generate:phosphor-catalog`) that holds only the regular weight — that is what enforces outline-only. It loads as a lazy chunk when the picker opens: importing the Phosphor React barrel would put 4.8 MB in the entry chunk, and a per-icon dynamic import would emit ~1512 chunks. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
AgentGlyph returned a fragment whenever an agent had no icon of its own, and a fragment swallows the className antd clones onto a menu icon. Without `ant-menu-item-icon` the `.ant-menu-item-icon + .ant-menu-title-content` rule never matched, so every agent row lost its 10px gap — not only the ones with a custom icon. The fixed Robot glyph's own ~1.3px of ink padding was all that had been standing in for it, which is why it read as fine until the glyph changed. Always render one real element, and forward the className onto it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Searching down to two or three icons shrank the results area, which pulled the rest of the popover up under the pointer. The no-match case collapsed it further, to a single line of muted text that was easy to miss. The grid and the empty state now share one fixed height, so the panel is the same size from the moment it opens. The empty state also says what happened: a magnifier, "No icons found", and the query that missed, truncated so a long search cannot widen the panel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…king it The Phosphor catalog is 12k lines of generated path data, which made it 91% of this PR's diff and would do the same on every Phosphor bump. Nobody reviews it. @agenta/ui now owns the artifact: the generator, the @phosphor-icons/core dep, and a `prepare` script that regenerates it on every `pnpm install`. Same pattern @agentaai/api-client already uses to build its dist/. The root script delegates. The file is gitignored, so a fresh checkout gets it from install rather than from git. If it is somehow missing, the generator says to run `pnpm install` rather than failing on a missing module. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous commit moved the dep from the workspace root into @agenta/ui and added tsx there, but left pnpm-lock.yaml behind. CI installs with --frozen-lockfile, so every web job failed at the install step before its real work ran: "1 dependencies were removed: @phosphor-icons/core@2.1.1". No format or lint problem. Those steps were skipped, not failed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The full Phosphor set was 1512 icons, 12k generated lines and 222 KB gzipped, and the picker opened on a screen of aircraft and alignment glyphs. Curating to 160 icons someone would actually pick for an agent cuts the file to 1349 lines and 32 KB, and lets it be committed like any other source file. That removes the machinery the untracked version needed: no `prepare`, so nothing runs during install, so the Docker images need no early copy of the generator. Both CI failures we hit came from that machinery, and they are gone rather than patched. The list lives in scripts/curated-icons.ts, grouped by category; adding an icon is one line plus a regenerate. The generator now fails on a name Phosphor does not have instead of quietly shipping fewer icons, which caught `bracket` and `telescope` on the first run. Icons emit in curated order, so the picker opens on robot, brain, sparkle rather than alphabetically. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Correctness: - Do not cache a rejected catalog import. A chunk that 404s after a deploy left the picker spinning for the rest of the session with no retry; it now clears the cached promise and offers Try again. - Hold hue/saturation/value as state and derive the hex from it, not the reverse. Re-deriving HSV from the hex lost the hue at both achromatic edges, so any drag to white or black collapsed to red with no way back. - Give trackDrag a teardown and run it on unmount, so a drag interrupted by closing the popover no longer commits its colour. - Stop treating the default colour as selected. No swatch ring and no grid highlight until a record exists, so clicking the first swatch is a real change rather than a no-op that shifted the sidebar row in dark mode. Reachability: - Add a Reset control and widen onChange to accept null, making the atom's documented clear path reachable. Simplification: - Drop the useMemo in useAgentIconChrome; fallbackGlyph is a fresh element at most call sites, so the dependency array never compared equal. - Carry an explicit customised flag on AgentIconChrome instead of inferring it from the presence of style. - Decide the rail's 17/14 glyph size in one helper instead of two expressions. - Derive the loading and error heights from the grid height rather than hardcoding. Tests and docs: - Cover writeBounded's delete-before-insert ordering rule and the isAgentIconRecord guard on path, the two invariants with no coverage. - Point the generated catalog's banner at the real script path and command.
Review feedback: the 'Agent updated this configuration' notice still drew the shared robot for every agent. It now wears the agent's own mark, resolved from the signal's revision through the same workflowId selector the chat empty state uses, and guarded on isLocalDraftId. An uncustomised agent keeps its existing agent-tag chip untouched.
10dce76 to
7c48f5e
Compare
Context
Every agent renders the same Robot glyph. The sidebar, the playground header, the agent cards and the chat empty state all draw it, so once a workspace holds more than a handful of agents there is nothing to tell them apart at a glance. The only per-agent mark today is the initials tile on the cards, coloured by a hash of the id, which the other four surfaces do not use.
What this adds
Click the chip beside the agent name in the playground header and a picker opens. Pick an icon, pick a colour from the palette or mix a custom one, and it saves as you pick. There is no save button. The sidebar rows, the sidebar workflow switcher, the agent cards and the chat empty state then display the choice; the header is the only place that edits it.
An agent nobody has customised looks exactly as it does today, because each surface keeps its own existing fallback rather than being forced onto a shared default.
The choice lives in localStorage for now, keyed by workflow id:
The backend home is the workflow artifact's
meta. Moving it there needs the update guard inworkflow/api/api.tsfixed first, since it checksname || description || flags || tagsand ignores a meta-only change. Only the atom family behindagentIconAtomFamilyhas to change when that lands. No call site does.How the icons get here
Importing the Phosphor React barrel would put 4.8 MB in the entry chunk, and a variable dynamic import would emit roughly 1512 tiny chunks with the grid firing one request per visible icon. So
pnpm --filter @agenta/ui generate:iconsreads@phosphor-icons/coreand emits one module of raw SVG path data. The rootpnpm generate:phosphor-catalogstill delegates to it.It generates the
regularweight and nothing else, which is what enforces the outline-only rule. No other weight exists in the file to reach for.The set is curated, not complete: 160 icons listed in
scripts/curated-icons.tsand grouped by what someone naming an agent actually reaches for. The full 1512 are 12k lines of generated path data, and a picker whose first screen is aircraft and alignment glyphs helps nobody. Adding one is a line in that file plus a regenerate. The generator throws on a name@phosphor-icons/coredoes not have, so a Phosphor rename breaks the build instead of silently dropping an icon somebody already chose.The catalog is 105 KB raw, 32 KB gzipped, and it is a lazy chunk. It is referenced exactly twice: a type-only import that the compiler erases, and an
import()inside the picker. The picker itself sits behindnext/dynamic, so the sidebar, which draws agent icons on every route, never pays for the picker or the virtualizer. Nothing loads until the picker opens for the first time.The generated file is committed, which is why there is no
preparehook and nothing for the Docker images to copy early. Commit 4 tried generating it on install instead; commit 6 undoes that, so reviewers reading commit-by-commit will pass through apreparescript and a.gitignoreentry the tip no longer has.Tests / notes
agentIconColors.test.tscover the colour maths: hex parsing including junk input, HSV round-trip across every palette colour, palette-pair versus derived tint, the dark-mode derivations, and the palette invariants.@agenta/ui,@agenta/entities,@agenta/entity-uiand@agenta/oss. 361@agenta/entity-uitests pass.palette.tsalready uses for tag surfaces. Worth an eye from design before this ships.colors.tsrather than sourced frompalette.ts. They are user-chosen data rather than theme roles, but the default#113955does duplicate the agent tag token. Routing them through the token generator is a follow-up, not this PR.categories, so a category rail is an addition rather than a rewrite.AgentGlyphreturned a fragment when an agent had no icon of its own, and a fragment swallows the className antd clones onto a menu icon, so every agent row in the sidebar lost its 10px icon-to-label gap. It now always renders one real element and forwards the className.What to QA
robot,gitandchart. Matching works on tag names too, not just the icon name. A query with no matches shows the empty message.Preview
agent-icon.mp4