Skip to content

feat(settings): add Persona Pack v1 (closes #2345)#2558

Merged
senamakel merged 13 commits into
tinyhumansai:mainfrom
hemanth1999k:feat/persona-pack-v1-2345
May 25, 2026
Merged

feat(settings): add Persona Pack v1 (closes #2345)#2558
senamakel merged 13 commits into
tinyhumansai:mainfrom
hemanth1999k:feat/persona-pack-v1-2345

Conversation

@hemanth1999k
Copy link
Copy Markdown
Contributor

@hemanth1999k hemanth1999k commented May 24, 2026

Summary

  • Add a single Settings → Persona surface that brings the assistant's identity together in one place (issue Add Persona Pack v1 for SOUL, voice, and custom GIF mascot #2345).
  • New persona display name + description, persisted per-user via a persona redux-persist slice (same pattern as mascot prefs).
  • Edit / reset SOUL.md (the personality prompt) from the app, backed by three new core RPCs.
  • Avatar (custom GIF override) and reply voice already exist in Mascot settings — the panel links out to them rather than duplicating that UI.
  • Adds Vitest + Rust unit tests, an about_app capability entry, and a coverage-matrix section.

Problem

OpenHuman already has the ingredients of a persona scattered across the app — SOUL.md for personality/tone, an ElevenLabs voice id, and mascot rendering with a custom-GIF override. What was missing is a single user-facing bundle that makes them feel like one identity, plus there was no in-app way to view or edit SOUL.md (it was only written once at workspace init and read for prompt injection) and no persona name/description concept at all.

Solution

Core (Rust) — new src/openhuman/workspace/rpc.rs exposes three controller-registered methods:

  • openhuman.workspace_file_read — returns the file contents, falling back to the bundled default (with is_default: true) when the workspace copy is missing.
  • openhuman.workspace_file_write — overwrites the file (size-capped at 256 KiB).
  • openhuman.workspace_file_reset — restores the bundled default.

The editable set is allowlisted to the bundled prompt files (SOUL.md, IDENTITY.md) via a single source of truth (ops::bundled_default_contents), so a caller can never read or clobber an arbitrary path under the workspace. Non-allowlisted names (including traversal attempts like ../escape.md) are rejected before any disk access.

App (React) — new PersonaPanel with three sections: Identity (name + description → persona slice), Personality (SOUL.md editor with Save / Reset over RPC, loading + error states, "using default" badge), and a link to Mascot settings for avatar & voice. Wired into the settings menu, router, and navigation hook. All copy goes through useT() with keys added to en.ts.

Design decisions:

  • Name/description are cosmetic display fields, so they live in redux-persist like other UI prefs; SOUL.md is the real personality lever and lives on disk via RPC. The slice intentionally does not hold SOUL.md.
  • The panel reuses the existing mascot avatar/voice UI instead of duplicating it, keeping the diff focused.

Submission Checklist

  • Tests added or updated (happy path + failure/edge cases) — Vitest: personaSlice.test.ts (11), PersonaPanel.test.tsx (10, incl. load-error path); Rust: workspace/rpc.rs unit tests (allowlist rejection, size-cap, read-default, round-trip, reset) + workspace/schemas.rs controller tests.
  • Diff coverage ≥ 80% — Frontend changed lines covered by the two Vitest suites (21 passing). Rust coverage could not be measured locally — see Validation Blocked below; tests are written for every new Rust function/branch.
  • Coverage matrix updated — new ### 5.4 Persona section (IDs 5.4.1–5.4.3) in docs/TEST-COVERAGE-MATRIX.md.
  • All affected feature IDs listed in ## Related.
  • No new external network dependencies introduced — N/A: no new deps; RPC goes through the existing in-process core client.
  • Manual smoke checklist — N/A: not a release-cut surface.
  • Linked issue closed via Closes #2345 (see ## Related).

Impact

  • Platform: desktop UI + Rust core. No mobile/web/CLI-specific branches.
  • Security: file RPCs are allowlisted to bundled prompt files with a size cap and no path-traversal surface; no secrets logged.
  • Compatibility: additive only. The persona persist slice defaults to empty and scrubs corrupt/oversize values on rehydrate; existing flows are unchanged.

Validation Run (local)

  • pnpm typecheck — clean for all changed files (4 pre-existing errors are unrelated missing-module issues in iOS/tunnel/devices code, none in this PR).
  • Prettier — applied; changed files formatted.
  • eslint (changed files) — 0 errors, 0 warnings.
  • Focused Vitest — personaSlice.test.ts + PersonaPanel.test.tsx: 21 passing.
  • cargo fmt --check (changed Rust) — clean.

Validation Blocked

  • command: GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml / cargo test --lib workspace:: / pnpm test:rust (and the pnpm rust:check pre-push hook, hence --no-verify on push)
  • error: No space left on device (os error 28) while building C dependencies (aws-lc-sys, whisper.cpp) — the dev machine's disk is full (~1 GB free).
  • impact: The Rust core could not be compiled or cargo-tested locally, so Rust changed-line coverage was not measured here. The Rust changes are small, isolated to src/openhuman/workspace/ + one about_app catalog row, follow the existing controller/RpcOutcome patterns, and ship with unit tests. CI will compile + run them.

Related

  • Closes: Add Persona Pack v1 for SOUL, voice, and custom GIF mascot #2345
  • Coverage matrix feature IDs: 5.4.1 (Persona Name & Description), 5.4.2 (SOUL.md Edit & Reset), 5.4.3 (Persona Settings Surface)
  • Follow-up PR(s)/TODOs: optional state-specific avatar assets, persona import/export manifest, and voice-clone flow remain out of scope for v1 (per the issue).

AI Authored PR Metadata (required for Codex/Linear PRs)

  • N/A — human-authored PR (not a Codex/Linear-launched run).

Summary by CodeRabbit

  • New Features

    • Persona settings panel: configurable display name and description with persisted storage
    • Personality editor (SOUL.md): load, edit, save, reset, bundled-default handling and error/loading states
    • Settings menu navigation to Persona and link to Mascot settings
    • UI text added for Persona in 12 languages
  • Documentation

    • Test coverage matrix updated with Persona entries
  • Tests

    • New unit and UI tests for Persona flows and APIs

Review Change Stack

Bundle the assistant's identity into one Settings > Persona surface:

- Persona display name + description, persisted per-user via a new
  redux-persist `persona` slice (mirrors the mascot preference pattern).
- Edit / reset the SOUL.md personality prompt from the app, backed by new
  core RPCs `openhuman.workspace_file_read` / `_file_write` / `_file_reset`.
  The editable set is allowlisted to the bundled prompt files (SOUL.md,
  IDENTITY.md) with a server-side size cap, so a caller can never read or
  clobber an arbitrary path under the workspace.
- Link out to Mascot settings for the avatar (custom GIF override) and reply
  voice, which already exist there.

Adds Vitest coverage for the slice + panel, Rust unit tests for the workspace
file RPCs, an about_app capability entry, and a coverage-matrix section.
@hemanth1999k hemanth1999k requested a review from a team May 24, 2026 02:30
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR implements Persona Pack v1—a unified surface for users to configure persona identity (display name and description), edit or reset a personality prompt (SOUL.md), and access related avatar and voice settings. The implementation spans TypeScript Redux state with local persistence, a React PersonaPanel UI component, TypeScript RPC wrappers for workspace file operations, comprehensive i18n translation keys across 12 locales, and a Rust backend with workspace file RPC operations, controller schemas, unit tests, and a capability catalog entry.

Changes

Persona Pack v1 Feature

Layer / File(s) Summary
Redux persona state, persistence, and slice tests
app/src/store/personaSlice.ts, app/src/store/index.ts, app/src/store/personaSlice.test.ts, app/src/test/test-utils.tsx
Redux slice defines bounded displayName and description fields with normalization and max-length enforcement. Slice includes REHYDRATE scrubbing and reset handlers. Persisted via personaPersistConfig into userScopedStorage. Tests verify normalization, length validation, reset behavior, and rehydration. Test utilities wired with persona reducer.
Frontend RPC facade and internationalization
app/src/services/api/personaFilesApi.ts, app/src/services/api/personaFilesApi.test.ts, app/src/lib/i18n/en.ts, app/src/lib/i18n/chunks/*-5.ts
TypeScript exports readPersonaFile, writePersonaFile, resetPersonaFile that call corresponding openhuman.workspace_file_* RPC methods. Tests verify correct method calls and error propagation. Translation keys settings.persona.* added across multiple language chunks and the main English map, covering UI labels, identity fields, SOUL.md editor, avatar & voice guidance, and mascot navigation link.
PersonaPanel UI, settings menu, and routing
app/src/components/settings/panels/PersonaPanel.tsx, app/src/components/settings/panels/PersonaPanel.test.tsx, app/src/components/settings/SettingsHome.tsx, app/src/components/settings/__tests__/SettingsHome.test.tsx, app/src/components/settings/hooks/useSettingsNavigation.ts, app/src/components/settings/hooks/__tests__/useSettingsNavigation.test.tsx, app/src/pages/Settings.tsx
PersonaPanel manages identity drafts (display name/description) with dirty tracking and Redux dispatch, and an editable SOUL.md textarea with async load/save/reset. Renders loading state, error messages, and conditional UI based on file is_default and busy state. SettingsHome adds Persona menu item navigating to persona route. useSettingsNavigation extends SettingsRoute union, maps /settings/persona URL to route, and adds breadcrumb case. Settings.tsx imports and routes to PersonaPanel. Component tests verify SOUL.md load/save/reset flows, identity persistence, error handling, default-state UI, and mascot navigation.
Rust workspace RPC operations with validation
src/openhuman/workspace/rpc.rs
Module implements read_workspace_file, write_workspace_file, reset_workspace_file with MAX_WORKSPACE_FILE_BYTES limit. Operations validate filenames against allowlist, read falls back to bundled defaults when missing (is_default=true), oversized reads/writes are rejected, and UTF-8 is validated. Write/reset create workspace directory as needed. Extensive tests cover edge cases, path-traversal rejection, and round-trips.
Rust workspace controller schemas and integration
src/openhuman/workspace/schemas.rs, src/openhuman/workspace/ops.rs, src/openhuman/workspace/mod.rs, src/openhuman/about_app/catalog.rs
schemas.rs implements controller schemas and async handlers for workspace namespace functions (file_read, file_write, file_reset) that parse params and call RPC operations; ops.rs exports bundled_default_contents; mod.rs exposes rpc submodule; catalog.rs adds settings.persona_pack capability entry. Schema and integration tests added.
Test coverage matrix documentation
docs/TEST-COVERAGE-MATRIX.md
Adds "5.4 Persona" section with three rows covering persona identity, SOUL.md edit/reset via RPC, and settings panel surface; updates aggregate coverage counts and totals.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • senamakel
  • M3gA-Mind
  • graycyrus

"🐰
A tiny rabbit hops with glee,
SOUL and persona, one UI tree,
Save your name, reset your tune,
Mascot waves beneath the moon —
Hooray for features, soft and free!"

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding a Persona Pack v1 feature with settings integration. It is concise, specific, and directly related to the changeset.
Linked Issues check ✅ Passed The PR implements all primary coding objectives from issue #2345: persona display name/description persistence via Redux, SOUL.md editing/reset via RPC, file-size validation, allowlist enforcement, and comprehensive tests.
Out of Scope Changes check ✅ Passed All changes are scoped to Persona Pack v1 objectives. Frontend adds PersonaPanel with identity/personality sections; core adds workspace file RPC with validation; i18n and tests support the feature. No unrelated refactoring or scope creep detected.
Docstring Coverage ✅ Passed Docstring coverage is 97.30% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link
Copy Markdown

Review Summary by Qodo

Add Persona Pack v1: unified assistant identity settings surface with SOUL.md editor

✨ Enhancement

Grey Divider

Walkthroughs

Description
• Add Persona Pack settings surface bundling assistant identity (name, description, personality
  prompt)
• Implement three new core RPCs for reading, writing, and resetting editable workspace files
  (SOUL.md, IDENTITY.md)
• Create Redux slice for persisting persona display name and description with validation and size
  limits
• Add comprehensive Vitest and Rust unit tests covering happy path, edge cases, and error scenarios
Diagram
flowchart LR
  User["User"] -->|"Edit name/description"| Panel["PersonaPanel"]
  User -->|"Edit SOUL.md"| Panel
  Panel -->|"Dispatch actions"| Slice["personaSlice<br/>Redux"]
  Panel -->|"Call RPC"| API["personaFilesApi"]
  API -->|"openhuman.workspace_file_*"| Core["Workspace RPC<br/>Rust Core"]
  Core -->|"Read/Write/Reset"| Disk["Workspace Files<br/>SOUL.md, IDENTITY.md"]
  Slice -->|"Persist"| Storage["localStorage<br/>redux-persist"]
  Panel -->|"Link to"| Mascot["Mascot Settings<br/>Avatar & Voice"]

Loading

File Changes

1. src/openhuman/about_app/catalog.rs ✨ Enhancement +10/-0

Add Persona Pack capability entry to catalog

src/openhuman/about_app/catalog.rs


2. src/openhuman/workspace/mod.rs ✨ Enhancement +1/-0

Export new workspace RPC module

src/openhuman/workspace/mod.rs


3. src/openhuman/workspace/ops.rs ✨ Enhancement +15/-0

Add bundled_default_contents allowlist function

src/openhuman/workspace/ops.rs


View more (13)
4. src/openhuman/workspace/rpc.rs ✨ Enhancement +244/-0

Implement read/write/reset workspace file RPCs

src/openhuman/workspace/rpc.rs


5. src/openhuman/workspace/schemas.rs ✨ Enhancement +202/-4

Register workspace file RPC controllers and schemas

src/openhuman/workspace/schemas.rs


6. app/src/components/settings/hooks/useSettingsNavigation.ts ✨ Enhancement +6/-0

Add persona route to settings navigation

app/src/components/settings/hooks/useSettingsNavigation.ts


7. app/src/lib/i18n/en.ts 📝 Documentation +24/-0

Add i18n keys for Persona panel UI

app/src/lib/i18n/en.ts


8. app/src/services/api/personaFilesApi.ts ✨ Enhancement +42/-0

Create persona file API façade for RPC calls

app/src/services/api/personaFilesApi.ts


9. app/src/store/__tests__/personaSlice.test.ts 🧪 Tests +114/-0

Add comprehensive tests for persona Redux slice

app/src/store/tests/personaSlice.test.ts


10. app/src/store/index.ts ✨ Enhancement +8/-0

Register persisted persona reducer in store

app/src/store/index.ts


11. app/src/store/personaSlice.ts ✨ Enhancement +92/-0

Create persona Redux slice with validation

app/src/store/personaSlice.ts


12. app/src/components/settings/SettingsHome.tsx ✨ Enhancement +16/-0

Add Persona menu item to settings home

app/src/components/settings/SettingsHome.tsx


13. app/src/components/settings/panels/PersonaPanel.tsx ✨ Enhancement +262/-0

Implement Persona settings panel with three sections

app/src/components/settings/panels/PersonaPanel.tsx


14. app/src/components/settings/panels/__tests__/PersonaPanel.test.tsx 🧪 Tests +152/-0

Add comprehensive tests for PersonaPanel component

app/src/components/settings/panels/tests/PersonaPanel.test.tsx


15. app/src/pages/Settings.tsx ✨ Enhancement +2/-0

Wire PersonaPanel into settings router

app/src/pages/Settings.tsx


16. docs/TEST-COVERAGE-MATRIX.md 📝 Documentation +8/-0

Add Persona Pack coverage matrix section

docs/TEST-COVERAGE-MATRIX.md


Grey Divider

Qodo Logo

@qodo-code-review
Copy link
Copy Markdown

qodo-code-review Bot commented May 24, 2026

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Remediation recommended

1. Locale reload overwrites edits 🐞 Bug ≡ Correctness
Description
PersonaPanel’s SOUL.md load effect depends on t, so changing locale re-runs the effect and
unconditionally overwrites soulDraft/soulSaved, potentially discarding unsaved edits. Because
soulLoading isn’t set back to true on re-run, the overwrite can happen without a visible loading
transition.
Code

app/src/components/settings/panels/PersonaPanel.tsx[R45-68]

Evidence
PersonaPanel re-runs its SOUL.md load effect whenever t changes and overwrites
soulDraft/soulSaved. I18nProvider recreates t whenever locale changes, so a locale switch
can trigger a reload and overwrite unsaved edits.

app/src/components/settings/panels/PersonaPanel.tsx[22-68]
app/src/lib/i18n/I18nContext.tsx[72-93]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`PersonaPanel` reloads SOUL.md whenever the i18n `t` callback changes (locale switch) and overwrites editor state. This can silently drop unsaved edits and also re-fetch without showing a loading state.
## Issue Context
- `t` changes identity when `locale` changes.
- The current effect always calls `setSoulDraft/setSoulSaved` with the RPC response.
## Fix Focus Areas
- app/src/components/settings/panels/PersonaPanel.tsx[45-68]
- app/src/lib/i18n/I18nContext.tsx[72-93]
## Suggested fix
- Make the SOUL.md load effect run only once on mount (empty deps) **or** keep locale as a dep but guard against clobbering when the editor is dirty (e.g., if `soulDraft !== soulSaved`, skip applying the loaded contents).
- If the effect can re-run, set `soulLoading` back to `true` at the start of the async read so the UI reflects the reload.
- If you need translated fallback error text without re-running the load, store an error *key* (or a boolean) and translate during render, or intentionally accept that the fallback message language is “as of mount” for this screen.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Identity drafts can desync 🐞 Bug ≡ Correctness
Description
PersonaPanel initializes nameDraft/descriptionDraft from the store once and never re-syncs them
when the persisted persona state changes. If the store is reset/changed while the panel is mounted
(e.g., resetUserScopedState during identity flip), clicking Save can write stale draft values back
into the newly-reset store.
Code

app/src/components/settings/panels/PersonaPanel.tsx[R27-33]

Evidence
PersonaPanel seeds drafts from selectors only at mount via useState(stored…). Separately, the app
has a documented identity flip path that dispatches resetUserScopedState, and personaSlice
explicitly resets to initialState on that action—meaning store values can change under a mounted
panel.

app/src/components/settings/panels/PersonaPanel.tsx[27-33]
app/src/providers/CoreStateProvider.tsx[151-183]
app/src/store/personaSlice.ts[62-64]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`nameDraft` and `descriptionDraft` are seeded from selectors via `useState(...)` and never updated if the store values change later. This can leave stale UI and allow saving old drafts into a reset persona slice.
## Issue Context
The app can dispatch `resetUserScopedState()` as part of identity flip handling, and `personaSlice` resets to `initialState` on that action.
## Fix Focus Areas
- app/src/components/settings/panels/PersonaPanel.tsx[27-77]
- app/src/store/personaSlice.ts[62-64]
- app/src/providers/CoreStateProvider.tsx[151-183]
## Suggested fix
- Add a `useEffect` to sync drafts when `storedDisplayName` / `storedDescription` change.
- Simple approach: always `setNameDraft(storedDisplayName)` and `setDescriptionDraft(storedDescription)` when those store values change.
- Safer approach: only sync when the user hasn’t modified the drafts (e.g., keep a `hasUserEditedIdentity` ref or check current dirty flags) to avoid overwriting in-progress edits.
- Consider clearing identity draft state as part of navigation back/unmount already happens, but this fix covers in-place store resets (identity flip / logout flows).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Read path lacks size cap ✓ Resolved 🐞 Bug ☼ Reliability
Description
read_workspace_file reads the entire on-disk file via read_to_string without enforcing
MAX_WORKSPACE_FILE_BYTES, even though writes are capped. If SOUL.md/IDENTITY.md is already
oversized on disk, this RPC can trigger large allocations and return huge payloads to the UI.
Code

src/openhuman/workspace/rpc.rs[R48-58]

Evidence
The read RPC directly uses std::fs::read_to_string with no size check, while the write RPC
explicitly rejects contents larger than MAX_WORKSPACE_FILE_BYTES. This creates an unbounded read
path for already-large files on disk.

src/openhuman/workspace/rpc.rs[45-58]
src/openhuman/workspace/rpc.rs[74-87]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The new workspace file read RPC (`read_workspace_file`) has no size guard, so it will try to load any size file into memory and return it over RPC. This is inconsistent with the write cap and can degrade stability if the file is manually edited/corrupted to be very large.
## Issue Context
`write_workspace_file` enforces `MAX_WORKSPACE_FILE_BYTES`, but `read_workspace_file` does not.
## Fix Focus Areas
- src/openhuman/workspace/rpc.rs[48-58]
- src/openhuman/workspace/rpc.rs[82-87]
## Suggested fix
- Add a size check before reading:
- `let meta = std::fs::metadata(&path)` and if `meta.len() > MAX_WORKSPACE_FILE_BYTES as u64`, return an error like `"{filename} exceeds ..."`.
- Alternatively, stream-read up to the cap (e.g., `File` + `take(MAX_WORKSPACE_FILE_BYTES as u64)`) and error if more bytes remain.
- Consider aligning the meaning of the cap with the UI: either reject oversize reads (preferred for editor) or return a truncated payload with an explicit flag (requires schema/UI changes).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels May 24, 2026
Comment thread src/openhuman/workspace/rpc.rs Outdated
Comment thread src/openhuman/workspace/rpc.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/store/__tests__/personaSlice.test.ts (1)

1-115: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Move this test to co-locate with personaSlice.ts.

The suite is currently under app/src/store/__tests__/, but this repo’s Vitest convention requires *.test.ts next to the source module.

As per coding guidelines: “Co-locate Vitest unit tests as *.test.ts / *.test.tsx under app/src/** alongside the code they test.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/src/store/__tests__/personaSlice.test.ts` around lines 1 - 115, Move the
vitest test file so it sits alongside the personaSlice implementation (co-locate
with personaSlice.ts); update any imports if needed so they still import
reducer, MAX_PERSONA_DESCRIPTION_LEN, MAX_PERSONA_DISPLAY_NAME_LEN,
resetPersona, selectPersonaDescription, selectPersonaDisplayName,
setPersonaDescription, setPersonaDisplayName and resetUserScopedState from the
local personaSlice module; ensure the REHYDRATE import and test suite name
remain unchanged and run the tests to confirm paths/resolution are correct.
🧹 Nitpick comments (1)
docs/TEST-COVERAGE-MATRIX.md (1)

502-512: ⚡ Quick win

Update the summary counts to reflect the 3 new Persona features.

The summary section should be updated to account for the three new covered features added in section 5.4:

  • Line 505: Update ✅ Covered from 66 to 69
  • Line 509: Update **Total leaves** from **131 explicit + nested = 202 product features** to **134 explicit + nested = 205 product features**
📊 Proposed fix for summary counts
 | Status           | Count                                            |
 | ---------------- | ------------------------------------------------ |
-| ✅ Covered       | 66                                               |
+| ✅ Covered       | 69                                               |
 | 🟡 Partial       | 27                                               |
 | ❌ Missing       | 26                                               |
 | 🚫 Manual smoke  | 11                                               |
-| **Total leaves** | **131 explicit + nested = 202 product features** |
+| **Total leaves** | **134 explicit + nested = 205 product features** |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/TEST-COVERAGE-MATRIX.md` around lines 502 - 512, Update the summary
counts in TEST-COVERAGE-MATRIX.md to reflect three new Persona features: change
the "✅ Covered" count from 66 to 69 (the table cell labeled "✅ Covered") and
update the "**Total leaves**" line from "**131 explicit + nested = 202 product
features**" to "**134 explicit + nested = 205 product features**" so the summary
matches section 5.4 additions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/src/components/settings/panels/__tests__/PersonaPanel.test.tsx`:
- Around line 1-54: Move the test file to sit next to PersonaPanel.tsx as
PersonaPanel.test.tsx and remove the bespoke render/store harness (functions
buildStore and renderPanel) in favor of the shared helpers from app/src/test/
(use the project's test render and store helpers to create a redux provider and
MemoryRouter). Replace the manual vi.mock of hooks and api only if the shared
helpers don't already provide facilities for mocking; otherwise call the shared
mock utilities for personaFilesApi and useSettingsNavigation and keep references
to the existing mocks (readPersonaFileMock, writePersonaFileMock,
resetPersonaFileMock, mockNavigateBack, mockNavigateToSettings) so the test
continues to control those behaviors.

In `@app/src/components/settings/panels/PersonaPanel.tsx`:
- Around line 45-109: Add namespaced debug logging around the SOUL
load/save/reset flows (useEffect readPersonaFile, onSaveSoul, onResetSoul) by
calling debug('settings:persona:soul') at entry/exit and on branches and errors;
log when each RPC is invoked and its returned file (include file.contents,
file.is_default), and log state changes to soulDraft, soulSaved, soulIsDefault
as well as errors (include err.message or stringified error). Ensure logs are
added before/after the await calls in readPersonaFile, writePersonaFile,
resetPersonaFile and inside the .catch/.finally blocks so every entry/exit/error
path is covered and namespaced consistently.

In `@app/src/services/api/personaFilesApi.ts`:
- Around line 23-42: Add namespaced debug() instrumentation around the RPC calls
in readPersonaFile, writePersonaFile, and resetPersonaFile: log an entry message
(including the RPC method string and filename), log success on return (including
method and filename but never the file contents), and log failures with the
error details (method + filename) before rethrowing; use the same debug()
namespace as other services and wrap the callCoreRpc invocation in try/catch for
each function to ensure entry/success/failure are emitted.

In `@src/openhuman/workspace/rpc.rs`:
- Around line 54-58: The file-read match using std::fs::read_to_string (the let
(contents, is_default) = match ...) currently only handles success logging; add
debug/trace logs for the NotFound fallback and the Err branch that include a
stable prefix/identifier and the path.display() (but not file contents), and
likewise add diagnostic logging for the write/create-dir failure paths and the
reset failure paths referenced around lines 83-97 and 119-127; ensure logs
include the operation (e.g., "workspace:read:fail", "workspace:read:default",
"workspace:write:fail", "workspace:reset:fail"), the file path identifier, and
the error description (e.to_string()) while avoiding sensitive content.

In `@src/openhuman/workspace/schemas.rs`:
- Around line 124-157: The three controller handlers handle_file_read,
handle_file_write, and handle_file_reset lack controller-level diagnostics; add
entry/exit and error branch logs that include a stable prefix, the handler name,
the filename, and the outcome (success or error) so RPC troubleshooting is
actionable. In each function, log at entry (e.g., "workspace:controller:enter
<handler> filename=<...>"), on successful return ("workspace:controller:exit
<handler> filename=<...> outcome=ok"), and on any early/errored return include
the error details with the same stable prefix and handler+filename context (use
the project's standard logger/tracing facility to emit debug/info for entry/exit
and error for failures). Ensure logs are emitted before/after calling
workspace_rpc::read_workspace_file, write_workspace_file, and
reset_workspace_file and include trimmed filename values.

---

Outside diff comments:
In `@app/src/store/__tests__/personaSlice.test.ts`:
- Around line 1-115: Move the vitest test file so it sits alongside the
personaSlice implementation (co-locate with personaSlice.ts); update any imports
if needed so they still import reducer, MAX_PERSONA_DESCRIPTION_LEN,
MAX_PERSONA_DISPLAY_NAME_LEN, resetPersona, selectPersonaDescription,
selectPersonaDisplayName, setPersonaDescription, setPersonaDisplayName and
resetUserScopedState from the local personaSlice module; ensure the REHYDRATE
import and test suite name remain unchanged and run the tests to confirm
paths/resolution are correct.

---

Nitpick comments:
In `@docs/TEST-COVERAGE-MATRIX.md`:
- Around line 502-512: Update the summary counts in TEST-COVERAGE-MATRIX.md to
reflect three new Persona features: change the "✅ Covered" count from 66 to 69
(the table cell labeled "✅ Covered") and update the "**Total leaves**" line from
"**131 explicit + nested = 202 product features**" to "**134 explicit + nested =
205 product features**" so the summary matches section 5.4 additions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4fcbcf07-4451-480f-9f4c-8a92a9dc0c63

📥 Commits

Reviewing files that changed from the base of the PR and between cf600a9 and 2810714.

📒 Files selected for processing (16)
  • app/src/components/settings/SettingsHome.tsx
  • app/src/components/settings/hooks/useSettingsNavigation.ts
  • app/src/components/settings/panels/PersonaPanel.tsx
  • app/src/components/settings/panels/__tests__/PersonaPanel.test.tsx
  • app/src/lib/i18n/en.ts
  • app/src/pages/Settings.tsx
  • app/src/services/api/personaFilesApi.ts
  • app/src/store/__tests__/personaSlice.test.ts
  • app/src/store/index.ts
  • app/src/store/personaSlice.ts
  • docs/TEST-COVERAGE-MATRIX.md
  • src/openhuman/about_app/catalog.rs
  • src/openhuman/workspace/mod.rs
  • src/openhuman/workspace/ops.rs
  • src/openhuman/workspace/rpc.rs
  • src/openhuman/workspace/schemas.rs

Comment thread app/src/components/settings/panels/__tests__/PersonaPanel.test.tsx Outdated
Comment thread app/src/components/settings/panels/PersonaPanel.tsx
Comment thread app/src/services/api/personaFilesApi.ts
Comment thread src/openhuman/workspace/rpc.rs Outdated
Comment thread src/openhuman/workspace/schemas.rs
)

The i18n coverage tests require every key in the en.ts aggregate to also
exist in the en-N.ts chunks and in every locale's matching chunk. The
Persona Pack keys were only added to the en.ts aggregate, so the
chunk-parity, per-locale "defines every English key", and en.ts↔chunks
checks failed (Frontend Unit Tests, Frontend Coverage, i18n Coverage).

Add the 20 settings.persona.* keys to chunk 5 of all 12 locales (same
chunk index as the neighbouring mascot keys). Non-English locales use the
English value as a fallback placeholder, matching existing repo practice;
translators fill these in via the i18n-coverage worklist flow.
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 24, 2026
…ging

Review feedback from Almanax + CodeRabbit on PR tinyhumansai#2558:

- Cap `read_workspace_file` at MAX_WORKSPACE_FILE_BYTES via a metadata stat
  before reading, so a huge SOUL.md dropped on disk can't be slurped into
  memory and shipped over RPC (Medium: unbounded-read DoS).
- Drop the absolute `path` from the WorkspaceFile RPC payload and reference
  only the filename in user-facing errors; full paths stay in debug logs
  (Low: filesystem-layout disclosure).
- Add namespaced debug diagnostics on entry/branch/error paths — Rust
  `log::debug!` in rpc.rs + schemas.rs handlers, `debug('persona:*')` in the
  panel and files API (never logging file contents).
…1-2345

# Conflicts:
#	app/src/lib/i18n/chunks/ar-5.ts
#	app/src/lib/i18n/chunks/bn-5.ts
#	app/src/lib/i18n/chunks/de-5.ts
#	app/src/lib/i18n/chunks/es-5.ts
#	app/src/lib/i18n/chunks/fr-5.ts
#	app/src/lib/i18n/chunks/hi-5.ts
#	app/src/lib/i18n/chunks/id-5.ts
#	app/src/lib/i18n/chunks/it-5.ts
#	app/src/lib/i18n/chunks/pt-5.ts
#	app/src/lib/i18n/chunks/ru-5.ts
#	app/src/lib/i18n/chunks/zh-CN-5.ts
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
src/openhuman/workspace/schemas.rs (1)

118-129: 🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Add controller exit logs and cover the early failure paths.

These handlers now log the outbound workspace RPC call and post-call errors, but controller-level diagnostics are still missing for load_config_with_timeout() failures, read_required() failures, and successful exits. That leaves the new RPC surface only partially traceable from the controller boundary.

As per coding guidelines: “Debug logging must follow these rules: default to verbose diagnostics on new/changed flows; log entry/exit, branches, external calls, retries/timeouts, state transitions, and errors; use stable grep-friendly prefixes ([domain], [rpc], [ui-flow]) and correlation fields (request IDs, method names, entity IDs); never log secrets or full PII. All changes lacking diagnosis logging are incomplete.”

Also applies to: 132-148, 151-162

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/workspace/schemas.rs` around lines 118 - 129, Add
controller-level entry/exit and early-failure debug logs around the async
controller functions (e.g., handle_file_read) so failures in
load_config_with_timeout(), read_required(), and the overall success path are
observable; specifically, log at function entry (include method name and
filename/request id), log and return when config_rpc::load_config_with_timeout()
returns Err (include the error), log and return when read_required::<String>()
returns Err (include the error and param name), and log a success/exit message
just before returning to_json(result?) — reference symbols: handle_file_read,
config_rpc::load_config_with_timeout, read_required,
workspace_rpc::read_workspace_file, to_json, ControllerFuture.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/openhuman/workspace/rpc.rs`:
- Around line 60-101: The metadata() size check is TOCTOU-prone—replace the
separate metadata() + read_to_string() flow by opening the file
(std::fs::File::open(&path)), read via a bounded reader (e.g.
std::io::Read::take with MAX_WORKSPACE_FILE_BYTES + 1) and collect into a
buffer, then check the actual number of bytes read and return the same error
when it exceeds MAX_WORKSPACE_FILE_BYTES; keep the existing branches for
NotFound (returning RpcOutcome with default_contents/WorkspaceFile) and other
stat/open errors, and update the log messages that currently reference
metadata/read_to_string to reflect open/bounded-read failures while still using
filename/path in the logs.

---

Duplicate comments:
In `@src/openhuman/workspace/schemas.rs`:
- Around line 118-129: Add controller-level entry/exit and early-failure debug
logs around the async controller functions (e.g., handle_file_read) so failures
in load_config_with_timeout(), read_required(), and the overall success path are
observable; specifically, log at function entry (include method name and
filename/request id), log and return when config_rpc::load_config_with_timeout()
returns Err (include the error), log and return when read_required::<String>()
returns Err (include the error and param name), and log a success/exit message
just before returning to_json(result?) — reference symbols: handle_file_read,
config_rpc::load_config_with_timeout, read_required,
workspace_rpc::read_workspace_file, to_json, ControllerFuture.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9aac821d-28dd-469d-ac31-54881062f4e5

📥 Commits

Reviewing files that changed from the base of the PR and between b301a97 and eae8359.

📒 Files selected for processing (19)
  • app/src/components/settings/panels/PersonaPanel.tsx
  • app/src/lib/i18n/chunks/ar-5.ts
  • app/src/lib/i18n/chunks/bn-5.ts
  • app/src/lib/i18n/chunks/de-5.ts
  • app/src/lib/i18n/chunks/en-5.ts
  • app/src/lib/i18n/chunks/es-5.ts
  • app/src/lib/i18n/chunks/fr-5.ts
  • app/src/lib/i18n/chunks/hi-5.ts
  • app/src/lib/i18n/chunks/id-5.ts
  • app/src/lib/i18n/chunks/it-5.ts
  • app/src/lib/i18n/chunks/pt-5.ts
  • app/src/lib/i18n/chunks/ru-5.ts
  • app/src/lib/i18n/chunks/zh-CN-5.ts
  • app/src/lib/i18n/en.ts
  • app/src/pages/Settings.tsx
  • app/src/services/api/personaFilesApi.ts
  • app/src/store/index.ts
  • src/openhuman/workspace/rpc.rs
  • src/openhuman/workspace/schemas.rs
✅ Files skipped from review due to trivial changes (6)
  • app/src/lib/i18n/chunks/hi-5.ts
  • app/src/lib/i18n/chunks/bn-5.ts
  • app/src/lib/i18n/chunks/pt-5.ts
  • app/src/lib/i18n/chunks/ar-5.ts
  • app/src/lib/i18n/chunks/en-5.ts
  • app/src/lib/i18n/en.ts

Comment thread src/openhuman/workspace/rpc.rs Outdated
…ver gate

The diff-cover gate failed at 76% (need 80%): personaFilesApi.ts was 0%
because PersonaPanel.test mocks the whole module, and the SOUL save/reset
error branches, persona menu click, and persona breadcrumb were unexercised.

- Add personaFilesApi.test.ts (mocks callCoreRpc): read/write/reset success,
  error propagation, and a non-Error rejection.
- Cover the panel save-error and reset-error paths, and persist the
  description field on identity save.
- Add the persona menu-click case to SettingsHome and the persona breadcrumb
  case to useSettingsNavigation.
…ests

- rpc.rs: enforce MAX_WORKSPACE_FILE_BYTES via a bounded reader on the
  opened handle (File::open + take(cap + 1)) instead of a TOCTOU-prone
  metadata() stat, so an oversized file can't be slurped into memory after
  a racing grow/swap. Also reject non-UTF-8 contents. Adds at-limit and
  non-UTF-8 read tests.
- Co-locate personaSlice.test.ts and PersonaPanel.test.tsx next to their
  source modules (repo Vitest convention). PersonaPanel test now uses the
  shared renderWithProviders helper — persona slice wired into the test
  store — instead of a bespoke render/store harness.
- docs(TEST-COVERAGE-MATRIX): bump summary counts for the 3 persona
  features (66->69 covered, 202->205 total) and update the moved test paths.
@hemanth1999k
Copy link
Copy Markdown
Contributor Author

The requested changes are addressed in f85cedac:

  • workspace/rpc.rs — replaced the TOCTOU-prone metadata() size check with a bounded reader (File::open + take(MAX_WORKSPACE_FILE_BYTES + 1)) so the cap is enforced on the opened handle; also reject non-UTF-8 contents. Added at-limit and non-UTF-8 read tests.
  • Tests — co-located personaSlice.test.ts and PersonaPanel.test.tsx next to their sources (repo Vitest convention) and switched the panel test to the shared renderWithProviders helper instead of a bespoke harness.
  • Docs — updated TEST-COVERAGE-MATRIX.md summary counts and the moved test paths.

All CI checks are green on this commit. Could you take another look and clear the earlier change request?

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 24, 2026

@hemanth1999k Thanks for the detailed summary and for addressing all the earlier concerns! Here's a quick acknowledgment of what was fixed before I kick off the re-review:

  • TOCTOU fix — Using File::open + take(MAX_WORKSPACE_FILE_BYTES + 1) enforces the cap atomically on the open file handle, which is the right approach. Good that at-limit and non-UTF-8 tests were added alongside.
  • Test co-location — Moving personaSlice.test.ts and PersonaPanel.test.tsx next to their sources and adopting the shared renderWithProviders harness aligns with the repo convention — cleaner.
  • Docs — Coverage matrix counts and paths updated to reflect the moves.

I'll do a full re-review now.

(≧◡≦) 🐇

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@senamakel
Copy link
Copy Markdown
Member

WOW what a banger feature. merging this shortly.

@senamakel senamakel self-assigned this May 25, 2026
senamakel added 5 commits May 24, 2026 21:59
# Conflicts:
#	app/src/lib/i18n/chunks/ar-5.ts
#	app/src/lib/i18n/chunks/bn-5.ts
#	app/src/lib/i18n/chunks/en-5.ts
#	app/src/lib/i18n/chunks/es-5.ts
#	app/src/lib/i18n/chunks/fr-5.ts
#	app/src/lib/i18n/chunks/hi-5.ts
#	app/src/lib/i18n/chunks/it-5.ts
#	app/src/lib/i18n/chunks/pt-5.ts
#	app/src/lib/i18n/chunks/ru-5.ts
…dits

Remove `t` from the useEffect dependency array so the SOUL.md load runs
only on mount. Also re-sync identity drafts when the store is reset
externally (e.g. resetUserScopedState) to prevent writing stale values.
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 25, 2026
senamakel added 2 commits May 24, 2026 22:38
The Korean locale chunk was missing all persona i18n keys, causing the
i18n coverage test to fail in CI.
@senamakel senamakel merged commit b399eab into tinyhumansai:main May 25, 2026
28 of 30 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. working A PR that is being worked on by the team.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add Persona Pack v1 for SOUL, voice, and custom GIF mascot

2 participants