Skip to content

feat: prepare 0.9.0-alpha.0 — avatar system, fleet improvements, footer, tests#31

Merged
simonCatBot merged 40 commits intomasterfrom
feat/avatar-customization
Apr 7, 2026
Merged

feat: prepare 0.9.0-alpha.0 — avatar system, fleet improvements, footer, tests#31
simonCatBot merged 40 commits intomasterfrom
feat/avatar-customization

Conversation

@simonCatBot
Copy link
Copy Markdown
Owner

rocCLAW v0.9.0-alpha.0 — Alpha Preview Release

What this PR does

Version bump:

  • package.json0.9.0-alpha.0 with alpha description and keywords

New CHANGELOG.md:

  • Full changelog documenting all changes from 0.1.0 through the current feat/avatar-customization branch

Avatar system (auto/default/custom):

  • AvatarModeContext — global state for footer + fleet avatar mode toggle
  • AvatarModeToggle — dropdown with Auto/Default/Custom modes, persists to localStorage
  • AgentAvatar — derives per-agent avatar from AvatarModeContext; supports auto (multiavatar lib), default (12 PNG profiles), and custom URL modes
  • AvatarSelector — in-agent settings panel with shuffle button and per-mode selection
  • deriveDefaultIndex — deterministic index from seed + explicitIndex for consistent defaults

Fleet sidebar improvements:

  • Soul names from IDENTITY.md per agent
  • AgentSettingsMutationController — batched optimistic settings updates
  • Fleet hydration with shared deriveDefaultIndex
  • Gateway version display in footer

Alpha disclaimers on all docs:

  • README.md (⚠️ banner + version badge)
  • ARCHITECTURE.md, CONTRIBUTING.md
  • docs/ui-guide.md, docs/permissions-sandboxing.md, docs/pi-chat-streaming.md

New tests (6 files, +839 lines):

File Tests
tests/unit/avatarModeContext.test.ts Provider init, localStorage, round-trip transitions
tests/unit/agentAvatar.test.ts deriveDefaultIndex, buildDefaultAvatarUrl
tests/unit/avatarSelector.test.ts Tab switching, shuffle, onChange calls
tests/unit/rocclawSettings.test.ts normalize, merge, resolve functions
tests/e2e/avatar-toggle.spec.ts Dropdown, mode switching, persistence
tests/e2e/agent-avatar-settings.spec.ts Fleet click, focused PUT, shuffle PUT

⚠️ Alpha Preview Notice

This software is early and unstable. Do not use in production. Expect breaking changes between releases.

Checklist

  • Version bump
  • CHANGELOG created
  • Alpha banners/disclaimers on all docs
  • Unit tests for new modules
  • E2E tests for avatar toggle and agent settings
  • Tests pass CI
  • At least one reviewer approved

This PR merges feat/avatar-customizationmaster to prepare the first alpha preview release.

Kapu and others added 30 commits April 3, 2026 12:44
…election

- Add AvatarSource type (auto | default | custom) and defaultAvatarIndex to AgentStoreSeed/AgentState
- Add AvatarSelector component with 3-tab UI (Auto/Default/Custom) for agent creation
- Update AgentAvatar to render all 3 sources with buildDefaultAvatarUrl helper
- Update AgentCreateModal to use AvatarSelector replacing single Shuffle button
- Add footer avatar mode toggle (auto/default) with localStorage persistence
- Extend ROCclawSettings schema with avatarSources for persistent per-agent config
- Wire persistAvatarConfig through creation flow and shuffle handler
- Update hydration to restore avatarSource/defaultAvatarIndex from settings
- Add 6 default profile images to public/avatars/
- Update all affected tests
…ion editing

- Add onAvatarChange prop to AgentBrainPanel (personality tab)
- Replace avatar text input with AvatarSelector component in identity section
- Add handleUpdateAgentAvatar to mutation controller (update-agent-avatar kind)
- Add gatewayUrl + schedulePatch to mutation controller params for local settings persistence
- Wire onAvatarChange → handleUpdateAgentAvatar in page.tsx
Replace the old toggle button with a proper dropdown menu styled
like ColorSchemeToggle. Shows current mode icon in the footer and
opens a picker with 3 options and descriptions.

New files:
- AvatarModeToggle.tsx — dropdown with auto/default/custom modes

Updated:
- FooterBar.tsx — uses AvatarModeToggle, reads mode from localStorage directly
- Remove unused AvatarSelectorHandle import and avatarRef

- Add AgentInspectPanels.tsx to eslint exception for setState in effect

- Switch useEffect to useLayoutEffect for avatar value sync
Moved AvatarModeToggle out of conditional blocks so it appears
regardless of agent state, placed directly beside ColorSchemeToggle
in the right-most section of the footer bar.
…n sync

AvatarModeToggle was managing state in isolation, so FooterBar was reading
stale localStorage values and never re-rendering on mode change.

Solution: lift state into AvatarModeContext (AvatarModeProvider) so both
AvatarModeToggle and FooterBar read from the same React state. Added
Providers wrapper in layout.tsx for the provider. FooterBar now calls
useAvatarMode() reactively.

New files:
- AvatarModeContext.tsx — shared state + provider
- Providers.tsx — client-side provider wrapper

Updated:
- AvatarModeToggle.tsx — reads/writes via context
- FooterBar.tsx — uses useAvatarMode() hook
- layout.tsx — wraps app in AvatarModeProvider
AgentAvatar (used in fleet tiles, agent header, etc.) now calls useAvatarMode()
internally. The global footer toggle now propagates to every AgentAvatar instance
throughout the app, not just FooterBar.
… different images

When in 'default' mode, deriveDefaultIndex uses a hash of the agent's seed
string to pick an avatar — so every agent gets a distinct image even if they
all have defaultAvatarIndex=0. Explicitly set indices (via Cycle or grid
click) still override.

Also added:
- UNSET_AVATAR_INDEX sentinel (-1) to distinguish 'never explicitly set'
  from 'user chose index 0'
- Cycle button in AvatarSelector default tab (replaces index badge)
- buildDefaultAvatarSelectorValue seeds with UNSET_INDEX
…ique default images on reload

deriveDefaultIndex now always combines seed hash + explicitIndex so different agents
always get different images even if all have defaultAvatarIndex=0.
…torage read

Avoid calling setState directly inside useEffect when the initial value
can be computed synchronously at render time.
TasksDashboard was always calling buildAvatarDataUrl directly for agent avatars,
completely bypassing AgentAvatar and the AvatarModeContext. Now:
- agentAvatarSrc() accepts footerMode and derives index from agentId
- useAvatarMode() called in TasksDashboard and AgentFilterChips
- footerMode threaded through CronJobTile, RunTile, SortableCronJobTile,
  TaskDetailPanel, and all JSX usages
- default mode shows default avatars in task/agent filter chips too
AgentAvatar now supports size="fill" which uses Next.js fill + object-cover
to scale to the parent container. FleetSidebar uses flex-1 on the avatar
container so it expands to fill all available space above the text.

Also added object-cover class to AgentAvatar Image for better aspect handling.
AgentAvatar with size=fill now uses relative positioning and object-cover
so the image properly fills and centers within its container.
…an template

- Avatar container now uses flex items-center justify-center for true centering
- Removed redundant soul name conditional; identityName ?? name is always used
- Clean badge row: model + status only, no double agent name
- Removed unused getSoulName helper
identityName now checks agent.identity?.name first (from API) before
agent.identityName (fallback). This correctly shows the soul name set
in Agent Settings → Behavior → Identity → Name.

Also increased card min-width from 200px to 240px so model names
fit on one line.
The AgentsListResult type in agentFleetHydration.ts was missing
identityName and identityEmoji fields that are present in the
derivation file. This ensures soul names from Agent Settings
properly flow through the agents.list response.
Gateway agents.list returns identity: { name } not flat identityName.
Removing flat fields from AgentsListResult type — derivation already
correctly uses agent.identity?.name as primary source.
Clean up all flat identityName/identityEmoji fields — gateway only
returns nested identity: { name }. Now consistently reads from
agent.identity?.name across both hydration files.
Gateway may return flat identityName field alongside nested identity.name.
Priority: identity?.name (nested) || identityName (flat) || null.
This restores the original logic that checked both sources.
After agents.list, fetch IDENTITY.md for each agent in parallel batches
of 6. parsePersonalityFiles extracts identity.name/emoji which now flows
through deriveHydrateAgentFleetResult into AgentStoreSeed.identityName.

FleetSidebar now correctly shows soul names (Kapu, Simon, etc.) instead
of repeating the agent ID.
Kapu added 4 commits April 3, 2026 18:53
1. Soul names in fleet cards: fetch IDENTITY.md via HTTP API for each
   agent during hydration. Parse identity.name directly since the file
   API only returns one file at a time. This correctly shows Kapu,
   Frank, Peter etc. in fleet cards.

2. TasksDashboard avatar divisor: was hardcoded to 6, changed to
   DEFAULT_AVATAR_COUNT (12) so correct profile images show.

3. Footer AvatarModeToggle icon: replaced gradient dot with Smile
   icon from lucide-react for auto/default modes.
TasksDashboard was using a different hash formula (| 0) than AgentAvatar
(& 0xffffffff), causing developer agent to show profile-3.png in task
tiles but profile-11.png in fleet cards. Export deriveDefaultIndex from
AgentAvatar and reuse it in TasksDashboard so both compute the same
index for all agents.
FooterBar was passing defaultAvatarIndex directly to buildDefaultAvatarUrl,
skipping the seed-hash derivation. This made footer avatars differ from
fleet/card avatars in default mode. Now threads avatarSeed through
deriveDefaultIndex like AgentAvatar and TasksDashboard do.
Unit tests (4 new files):
- avatarModeContext: AvatarModeProvider, useAvatarMode, useSetAvatarMode,
  localStorage persistence, round-trip state transitions
- agentAvatar: deriveDefaultIndex (determinism, range, edge cases),
  buildDefaultAvatarUrl (wrapping, profile paths)
- avatarSelector: renders tabs/shuffle, calls onChange for all three
  modes (auto/default/custom), calls shuffle with new seed
- rocclawSettings: normalizeGatewayKey, normalizeROCclawSettings,
  mergeROCclawSettings, resolveFocusedPreference,
  resolveAgentAvatarSeed, resolveAgentAvatarConfig

E2E tests (2 new files, 8 tests):
- avatar-toggle: open dropdown, switch modes, persistence across reload,
  toggle present with empty fleet
- agent-avatar-settings: fleet row click → selected class, triggers focused
  PUT with selectedAgentId, shuffle triggers avatar PUT

Bug fix: pre-existing studioSettingsRoute test had insufficient timeout
(5000ms) causing WebSocket ENOTFOUND to time out; raised to 30000ms.
Kapu added 6 commits April 6, 2026 15:41
- avatarModeContext: add @ts-expect-error for null localStorage mock
- avatarSelector: import AvatarSelectorValue type, remove duplicate test,
  use screen.getAllByRole('img') instead of data-avatar-index attribute
- studioSettingsRoute: remove broken testTimeout reference that CI doesn't
  support (pre-existing flaky test — unrelated to these changes)
Clicking the Default tab fires onChange (tab switch), so filter
calls by avatar index rather than asserting exact call count.
Use container.querySelector for grid buttons since they aren't
accessible via getByRole in jsdom.
Merge with origin/master clobbered:
- package.json version back to 0.1.0 (restored to 0.9.0-alpha.0)
- CHANGELOG.md (created fresh with all changes since 0.1.0)
- README.md alpha banner and version badge
@simonCatBot simonCatBot merged commit 32764a6 into master Apr 7, 2026
4 checks passed
@simonCatBot simonCatBot deleted the feat/avatar-customization branch April 7, 2026 04:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant