Skip to content

feat(persona): guided persona builder over SOUL.md (#4253, PR1)#4412

Open
M3gA-Mind wants to merge 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/GH-4253-persona-builder
Open

feat(persona): guided persona builder over SOUL.md (#4253, PR1)#4412
M3gA-Mind wants to merge 2 commits into
tinyhumansai:mainfrom
M3gA-Mind:feat/GH-4253-persona-builder

Conversation

@M3gA-Mind

@M3gA-Mind M3gA-Mind commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds a guided persona builder so non-technical users can create/edit their assistant's persona through labeled fields instead of hand-writing SOUL.md.
  • Three friendly fields — Personality, Communication style, About you — map to named ## sections and are spliced in place, so SOUL.md stays the single source of truth the runtime injects. Every other byte (title, intro, hand-written sections) is preserved.
  • Guided is the default; the existing raw markdown editor stays behind an Advanced toggle (no capability regression).
  • Reuses the existing openhuman.workspace_file_read/write/reset RPC — no Rust/core changes.
  • First slice of the phased Guided persona builder and memory dashboard for non-technical users #4253 feature (persona builder + memory dashboard). Plan approved separately; later PRs cover role templates, the memory dashboard, state labels, project isolation, and conflict handling.

Problem

Feedback on #4253: the SOUL.md concept is strong, but users don't know how to write a good persona. The editor today is a raw markdown textarea. Non-technical users need a structured way to shape identity/behavior without understanding the file format — while keeping the assistant runtime's source of truth intact (not disconnected UI-only state).

Solution

  • personaSections.ts: a lossless, idempotent parser/serializer. parsePersonaFields reads the managed sections; applyPersonaField replaces only the target section's body (preserving surrounding whitespace), appends a ## About You block on demand, and returns the input unchanged when nothing changed. Deeper ### headings stay part of a section; matching is exact and case-insensitive.
  • PersonaGuidedFields.tsx: the structured form. Persona = identity/behavior prose only; it links out to Settings → Agent access for permissions/tools rather than duplicating that config.
  • PersonaPanel.tsx: Guided/Advanced mode toggle sharing one Save/Reset path; the raw text remains the single working value both modes edit.
  • i18n: 13 new settings.persona.builder.* keys added to en and all 13 locale files (parity 13/13 each).

Submission Checklist

  • Tests added or updated — personaSections.test.ts (parser: parse, idempotency, splice-preserves-other-bytes, append, clear, heading edge cases, round-trip) and extended PersonaPanel.test.tsx (guided default, guided-splice-save over RPC, Advanced raw-edit save, Agent-access link, reset/error paths).
  • Diff coverage ≥ 80% — new logic (personaSections.ts) and UI (PersonaGuidedFields/PersonaPanel) are exercised by the tests above. Please confirm in CI.
  • Coverage matrix updated — N/A: no new feature ID; enhances the existing Persona panel.
  • All affected feature IDs listed under Related — N/A: no matrix feature-behaviour change.
  • No new external network dependencies introduced.
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: additive Settings UI, no release-cut surface.
  • Linked issue closed via Closes #NNN — this is PR1 of several; intentionally references (not closes) Guided persona builder and memory dashboard for non-technical users #4253.

Impact

  • Desktop UI only (Settings → Personality). No core, schema, or migration changes. Persona persistence path is unchanged (same RPC + file); this only changes how the content is authored.

Related


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

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

Validation Run

  • pnpm --filter openhuman-app format:check — Prettier --check passes; also green in CI (Frontend Checks lane).
  • pnpm typechecktsc --noEmit passes (exit 0); also green in CI (Frontend Checks).
  • Focused tests — personaSections.test.ts + PersonaPanel.test.tsx run green (22 passed); pnpm i18n:check also passes (0 missing/extra across all locales). Confirmed by the passing Frontend Checks CI lane.
  • Rust fmt/check (if changed): N/A — no Rust changed
  • Tauri fmt/check (if changed): N/A — no Tauri changed

Validation Blocked

  • command: N/A
  • error: N/A
  • impact: N/A

Behavior Changes

  • Intended behavior change: Settings → Personality defaults to a structured persona editor; raw markdown moves under an Advanced toggle.
  • User-visible effect: Non-technical users can author a persona via fields; power users keep full raw editing.

Parity Contract

  • Legacy behavior preserved: Raw SOUL.md editing, Save, Reset, and the display-name/description store are all intact.
  • Guard/fallback/dispatch parity checks: Same workspace_file_* RPC and allowlist; guided edits produce plain SOUL.md markdown.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): None
  • Canonical PR: This PR
  • Resolution: N/A

Summary by CodeRabbit

  • New Features

    • Added a new persona editor with two modes: guided and advanced.
    • Guided editing now provides structured fields for personality, voice, and about details.
    • Added localized labels, help text, and prompts for the new editor across multiple languages.
  • Bug Fixes

    • Improved editor loading behavior so settings now wait for the interface to fully load before showing or editing content.
    • Updated save, reset, and navigation flows to behave more reliably in the persona settings panel.

Add a structured, non-technical persona editor that maps friendly fields
(Personality, Communication style, About you) to named SOUL.md sections and
splices them in place, keeping SOUL.md the runtime source of truth. Guided is
the default; the raw markdown editor stays behind an Advanced toggle. Reuses
the existing workspace_file_read/write/reset RPC — no core changes.

Part of the phased tinyhumansai#4253 work (PR1 of N).
@M3gA-Mind M3gA-Mind requested a review from a team July 2, 2026 11:44
@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a guided/advanced editing mode for SOUL.md persona files. Introduces a lossless managed-section parser/writer utility, a new PersonaGuidedFields component, wiring in PersonaPanel to toggle between modes, updated tests, and new settings.persona.builder.* translation keys across all locales.

Changes

Guided persona editor

Layer / File(s) Summary
Persona section parsing/editing utility
app/src/components/settings/panels/persona/personaSections.ts, .../personaSections.test.ts
New module parses and losslessly splices managed ## <heading> sections (personality, voice, about) within SOUL.md, with parsePersonaFields, applyPersonaField, applyPersonaFields, and matching tests.
PersonaGuidedFields component
app/src/components/settings/panels/persona/PersonaGuidedFields.tsx
New component renders personality/voice/about textareas backed by the section utility, splicing edits back into raw SOUL.md text, with i18n labels and a security note linking to agent access.
PersonaPanel mode toggle
app/src/components/settings/panels/PersonaPanel.tsx
Adds soulMode state and a guided/advanced toggle that conditionally renders PersonaGuidedFields or the existing raw textarea editor, reusing existing save/reset logic.
Test suite updates
app/src/components/settings/panels/PersonaPanel.test.tsx
Adds awaitLoaded/openAdvanced helpers and updates existing tests to gate on loaded state and mode switching, plus new default-mode and mode-switch coverage.
Persona builder translations
app/src/lib/i18n/ar.ts, bn.ts, de.ts, en.ts, es.ts, fr.ts, hi.ts, id.ts, it.ts, ko.ts, pl.ts, pt.ts, ru.ts, zh-CN.ts
Adds settings.persona.builder.* keys for mode labels, field placeholders, preserved-section note, and security link in all locales; removes obsolete SOUL error keys in zh-CN.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant PersonaPanel
  participant PersonaGuidedFields
  participant personaSections

  User->>PersonaPanel: toggle to guided mode
  PersonaPanel->>PersonaGuidedFields: render(soulDraft)
  PersonaGuidedFields->>personaSections: parsePersonaFields(soulDraft)
  personaSections-->>PersonaGuidedFields: PersonaFields
  User->>PersonaGuidedFields: edit personality/voice/about field
  PersonaGuidedFields->>personaSections: applyPersonaField(soul, key, value)
  personaSections-->>PersonaGuidedFields: updated soul text
  PersonaGuidedFields-->>PersonaPanel: onChange(updatedSoul)
  User->>PersonaPanel: click Save
  PersonaPanel->>PersonaPanel: writePersonaFile RPC
Loading

Poem

A rabbit toggles guide and code,
Splicing sections down the road. 🐇
Personality, voice, and "about you" too,
Preserved and safe, nothing askew.
Eighteen tongues now sing the same,
Hop, hop — SOUL.md's new frame!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: a guided persona builder for SOUL.md with an advanced/raw editing path.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 941d38a2fd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

*/
export function applyPersonaField(soul: string, key: PersonaFieldKey, value: string): string {
const heading = HEADING_FOR[key];
const nextBody = value.trim();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve live textarea newlines

In guided mode this helper runs on every textarea onChange, so trimming the draft before splicing immediately discards any trailing newline the user just typed. When a user presses Enter at the end of the Personality or Communication style field to add another paragraph/bullet, readSection(...) === nextBody can return the original SOUL.md and React re-renders without the newline, causing the next characters to be appended to the previous line. Since the default managed sections are multiline lists, preserve the live value here and only normalize for comparison/save if needed.

Useful? React with 👍 / 👎.

Prettier-only formatting of the persona builder components/tests and the
guided-persona i18n key additions across all locale files.
@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Fixed Frontend Checks. The lane failed at format:check — Prettier flagged 16 files (the persona-builder components/tests and the new guided-persona i18n key additions across all locales). Ran prettier --write on all 16.

Verified locally, all green: prettier --check ., tsc --noEmit, eslint (0 errors), pnpm i18n:check (0 missing / 0 extra across all locales — parity holds), and the persona specs (personaSections.test.ts, PersonaPanel.test.tsx) pass 22/22. Formatting only — no behavior change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
app/src/components/settings/panels/persona/personaSections.ts (1)

35-39: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

HEADING_FOR duplicates data already in PERSONA_SECTIONS.

Both structures map the same keys to the same heading strings. Consider deriving HEADING_FOR from PERSONA_SECTIONS to avoid the two falling out of sync if a heading is ever renamed.

♻️ Proposed refactor
-const HEADING_FOR: Record<PersonaFieldKey, string> = {
-  personality: 'Personality',
-  voice: 'Voice',
-  about: 'About You',
-};
+const HEADING_FOR: Record<PersonaFieldKey, string> = Object.fromEntries(
+  PERSONA_SECTIONS.map(({ key, heading }) => [key, heading])
+) as Record<PersonaFieldKey, string>;
🤖 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/components/settings/panels/persona/personaSections.ts` around lines
35 - 39, HEADING_FOR currently repeats the same heading strings already defined
in PERSONA_SECTIONS, so keep the mappings in sync by deriving HEADING_FOR from
PERSONA_SECTIONS instead of hardcoding a second source of truth. Update
personaSections.ts so the heading lookup is built from PERSONA_SECTIONS using
the existing PersonaFieldKey entries, and preserve the current values for
personality, voice, and about while reusing the same symbols.
app/src/components/settings/panels/persona/personaSections.test.ts (1)

67-72: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a regression test for the clear→refill whitespace-growth case.

The current "empties the body but keeps the heading" test doesn't cover refilling after clearing, which is where the lead/trail overlap bug in applyPersonaField (see personaSections.ts) manifests. A test like applyPersonaField(applyPersonaField(SOUL, 'voice', ''), 'voice', 'Be terse.') should assert the surrounding whitespace matches a direct apply.

🤖 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/components/settings/panels/persona/personaSections.test.ts` around
lines 67 - 72, Add a regression test in personaSections.test.ts for the
clear-then-refill case around applyPersonaField: after clearing the 'voice'
field from SOUL, apply a new non-empty value like 'Be terse.' and assert the
resulting text matches the same output as a direct apply to SOUL. Use
parsePersonaFields and applyPersonaField in the existing personaSections test
suite to verify the surrounding whitespace/section spacing stays identical and
does not grow after the clear→refill path.
app/src/components/settings/panels/PersonaPanel.tsx (1)

208-225: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a small data-driven render for the two mode buttons.

The guided/advanced buttons are nearly identical (only variant/aria-pressed/onClick/label differ). Could be collapsed into a .map() over a small config array for less duplication, but this is purely cosmetic given there are only two buttons.

🤖 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/components/settings/panels/PersonaPanel.tsx` around lines 208 - 225,
The guided and advanced mode buttons in PersonaPanel are duplicated aside from a
few props, so replace the repeated Button blocks with a small data-driven render
using a config array and .map(). Keep the existing behavior intact by preserving
the current soulMode checks, setSoulMode calls, test IDs, and translated labels
while moving the shared Button markup into one reusable path.
🤖 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/persona/personaSections.ts`:
- Around line 102-106: The live update path in applyPersonaField is trimming the
incoming textarea value on every keystroke, which breaks typing of trailing
spaces/newlines in PersonaGuidedFields. Update applyPersonaField so
readSection/HEADING_FOR is still used to compare normalized content for the
no-op check, but preserve the raw value when writing the updated section back
into the document; defer trimming to save/serialize time instead of applying it
to every onChange update.
- Around line 108-120: The whitespace-preservation logic in personaSections.ts
is double-counting newlines when a section body is only whitespace, because the
lead and trail matches overlap on the same raw string. Update the section
replacement path in the function that uses findSectionSpan and splices nextBody
so that leading/trailing newline capture is computed non-overlapping or
normalized when raw contains only newlines, ensuring repeated clear/refill
cycles stay idempotent and do not grow whitespace.

---

Nitpick comments:
In `@app/src/components/settings/panels/persona/personaSections.test.ts`:
- Around line 67-72: Add a regression test in personaSections.test.ts for the
clear-then-refill case around applyPersonaField: after clearing the 'voice'
field from SOUL, apply a new non-empty value like 'Be terse.' and assert the
resulting text matches the same output as a direct apply to SOUL. Use
parsePersonaFields and applyPersonaField in the existing personaSections test
suite to verify the surrounding whitespace/section spacing stays identical and
does not grow after the clear→refill path.

In `@app/src/components/settings/panels/persona/personaSections.ts`:
- Around line 35-39: HEADING_FOR currently repeats the same heading strings
already defined in PERSONA_SECTIONS, so keep the mappings in sync by deriving
HEADING_FOR from PERSONA_SECTIONS instead of hardcoding a second source of
truth. Update personaSections.ts so the heading lookup is built from
PERSONA_SECTIONS using the existing PersonaFieldKey entries, and preserve the
current values for personality, voice, and about while reusing the same symbols.

In `@app/src/components/settings/panels/PersonaPanel.tsx`:
- Around line 208-225: The guided and advanced mode buttons in PersonaPanel are
duplicated aside from a few props, so replace the repeated Button blocks with a
small data-driven render using a config array and .map(). Keep the existing
behavior intact by preserving the current soulMode checks, setSoulMode calls,
test IDs, and translated labels while moving the shared Button markup into one
reusable path.
🪄 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: f3e09482-8e59-4ab1-b56e-d4890f5793d1

📥 Commits

Reviewing files that changed from the base of the PR and between f979bfa and 7c750b4.

📒 Files selected for processing (19)
  • app/src/components/settings/panels/PersonaPanel.test.tsx
  • app/src/components/settings/panels/PersonaPanel.tsx
  • app/src/components/settings/panels/persona/PersonaGuidedFields.tsx
  • app/src/components/settings/panels/persona/personaSections.test.ts
  • app/src/components/settings/panels/persona/personaSections.ts
  • app/src/lib/i18n/ar.ts
  • app/src/lib/i18n/bn.ts
  • app/src/lib/i18n/de.ts
  • app/src/lib/i18n/en.ts
  • app/src/lib/i18n/es.ts
  • app/src/lib/i18n/fr.ts
  • app/src/lib/i18n/hi.ts
  • app/src/lib/i18n/id.ts
  • app/src/lib/i18n/it.ts
  • app/src/lib/i18n/ko.ts
  • app/src/lib/i18n/pl.ts
  • app/src/lib/i18n/pt.ts
  • app/src/lib/i18n/ru.ts
  • app/src/lib/i18n/zh-CN.ts

Comment on lines +102 to +106
export function applyPersonaField(soul: string, key: PersonaFieldKey, value: string): string {
const heading = HEADING_FOR[key];
const nextBody = value.trim();

if (readSection(soul, heading) === nextBody) return soul;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Trimming value on every call breaks live typing when this is wired to onChange per keystroke.

nextBody = value.trim() strips trailing whitespace/newlines unconditionally. PersonaGuidedFields calls onChange(applyPersonaField(value, field.key, e.target.value)) on every keystroke, and the textarea's displayed value comes back from parsePersonaFields(value) (which also trims via readSection). As a result, any trailing space or blank line the user types is immediately stripped on the very next render — the user can't type a trailing space/blank line, and the controlled textarea's committed value diverging from what was just typed can cause visible flicker or cursor-position glitches mid-typing.

Trimming is appropriate for save/serialize, but shouldn't run on every interim keystroke update. Consider trimming only when the value actually differs after normalization for comparison purposes, but preserving the untrimmed live value in the spliced document (only trim for the equality short-circuit check, not for what gets written back), or defer trimming to blur/save time in the consuming component.

🤖 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/components/settings/panels/persona/personaSections.ts` around lines
102 - 106, The live update path in applyPersonaField is trimming the incoming
textarea value on every keystroke, which breaks typing of trailing
spaces/newlines in PersonaGuidedFields. Update applyPersonaField so
readSection/HEADING_FOR is still used to compare normalized content for the
no-op check, but preserve the raw value when writing the updated section back
into the document; defer trimming to save/serialize time instead of applying it
to every onChange update.

Comment on lines +108 to +120
const span = findSectionSpan(soul, heading);
if (span) {
const raw = soul.slice(span.bodyStart, span.bodyEnd);
const lead = raw.match(/^\n*/)?.[0] ?? '';
const trail = raw.match(/\n*$/)?.[0] ?? '';
const spliced = nextBody ? `${lead}${nextBody}${trail || '\n'}` : `${lead}${trail}`;
return soul.slice(0, span.bodyStart) + spliced + soul.slice(span.bodyEnd);
}

if (!nextBody) return soul;
const base = soul.replace(/\n*$/, '\n');
return `${base}\n## ${heading}\n\n${nextBody}\n`;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Lead/trail newline capture overlaps when the section body is pure whitespace, causing whitespace to double on each clear→refill cycle.

When raw consists entirely of newlines (e.g., right after clearing a section with applyPersonaField(soul, key, '')), both raw.match(/^\n*/) and raw.match(/\n*$/) independently match the entire string (not disjoint halves), so lead and trail each duplicate the same newlines. Concrete trace using the section fixture in the test file:

  • Clear voice → body becomes "\n\n\n" (lead "\n" + trail "\n\n", concatenated, 3 chars total — correct so far).
  • Refill voice with 'Be terse.'raw is now the pure-whitespace "\n\n\n"; lead and trail each match all 3 chars independently, so the new body becomes "\n\n\n" + "Be terse." + "\n\n\n" (6 newlines) instead of the expected 3 that a direct apply would produce.

Each subsequent clear/refill cycle re-doubles the surrounding whitespace, silently growing the document. This breaks the "lossless and idempotent" guarantee described in the file's own docstring.

🐛 Proposed fix: prevent lead/trail overlap when raw is pure whitespace
   const span = findSectionSpan(soul, heading);
   if (span) {
     const raw = soul.slice(span.bodyStart, span.bodyEnd);
     const lead = raw.match(/^\n*/)?.[0] ?? '';
-    const trail = raw.match(/\n*$/)?.[0] ?? '';
+    const trail = lead.length >= raw.length ? '' : raw.match(/\n*$/)?.[0] ?? '';
     const spliced = nextBody ? `${lead}${nextBody}${trail || '\n'}` : `${lead}${trail}`;
     return soul.slice(0, span.bodyStart) + spliced + soul.slice(span.bodyEnd);
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const span = findSectionSpan(soul, heading);
if (span) {
const raw = soul.slice(span.bodyStart, span.bodyEnd);
const lead = raw.match(/^\n*/)?.[0] ?? '';
const trail = raw.match(/\n*$/)?.[0] ?? '';
const spliced = nextBody ? `${lead}${nextBody}${trail || '\n'}` : `${lead}${trail}`;
return soul.slice(0, span.bodyStart) + spliced + soul.slice(span.bodyEnd);
}
if (!nextBody) return soul;
const base = soul.replace(/\n*$/, '\n');
return `${base}\n## ${heading}\n\n${nextBody}\n`;
}
const span = findSectionSpan(soul, heading);
if (span) {
const raw = soul.slice(span.bodyStart, span.bodyEnd);
const lead = raw.match(/^\n*/)?.[0] ?? '';
const trail = lead.length >= raw.length ? '' : raw.match(/\n*$/)?.[0] ?? '';
const spliced = nextBody ? `${lead}${nextBody}${trail || '\n'}` : `${lead}${trail}`;
return soul.slice(0, span.bodyStart) + spliced + soul.slice(span.bodyEnd);
}
if (!nextBody) return soul;
const base = soul.replace(/\n*$/, '\n');
return `${base}\n## ${heading}\n\n${nextBody}\n`;
}
🤖 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/components/settings/panels/persona/personaSections.ts` around lines
108 - 120, The whitespace-preservation logic in personaSections.ts is
double-counting newlines when a section body is only whitespace, because the
lead and trail matches overlap on the same raw string. Update the section
replacement path in the function that uses findSectionSpan and splices nextBody
so that leading/trailing newline capture is computed non-overlapping or
normalized when raw contains only newlines, ensuring repeated clear/refill
cycles stay idempotent and do not grow whitespace.

@M3gA-Mind

Copy link
Copy Markdown
Collaborator Author

Also ticked the PR Submission Checklist honestly (it was the last soft-gate red). The 3 open items were all verified: prettier --check ✓, tsc --noEmit ✓ (exit 0), and the persona specs pass 22/22 — all also confirmed by the now-green Frontend Checks CI lane. No fabricated items.

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