Skip to content

feat(thread): support manual editing of conversation thread titles#2525

Merged
senamakel merged 10 commits into
tinyhumansai:mainfrom
neillee95:feat/thread-title-editing
May 25, 2026
Merged

feat(thread): support manual editing of conversation thread titles#2525
senamakel merged 10 commits into
tinyhumansai:mainfrom
neillee95:feat/thread-title-editing

Conversation

@neillee95
Copy link
Copy Markdown
Contributor

@neillee95 neillee95 commented May 23, 2026

Summary

  • Adds a openhuman.threads_update_title RPC method to the Rust core that persists a user-supplied title on a conversation thread, bypassing AI title generation.
  • Exposes the method through the controller registry (threads/schemas.rs) with request validation (empty-title guard).
  • Wires up a updateThreadTitle Redux thunk and updates the thread slice on success.
  • Renders a hover-reveal pencil icon next to the thread title in the conversation header; clicking it switches to an inline text input that commits on Enter/blur and
    cancels on Escape.

Problem

Thread titles were set exclusively by AI generation. Users had no way to rename a conversation, making it hard to find threads later or give them meaningful labels.

Solution

  • Rust core: UpdateConversationThreadTitleRequestops::thread_update_titleconversations::update_thread_title persists the title and returns the updated
    ConversationThreadSummary wrapped in the standard ApiEnvelope. Empty titles are rejected at the op layer before reaching storage.
  • Frontend: threadApi.updateTitle calls the new RPC method. The updateThreadTitle async thunk dispatches the call and the fulfilled reducer replaces the thread
    in-place in state.threads. The UI uses local editingTitle / editTitleValue state with a ref-focused <input> that auto-selects on open; on commit the thunk is
    dispatched and the display reverts to the resolved title.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy
  • Diff coverage ≥ 80% — changed lines (Vitest + cargo-llvm-cov merged via diff-cover) meet the gate enforced by
    .github/workflows/coverage.yml. Run pnpm test:coverage and pnpm test:rust locally; PRs below 80% on changed lines will not
    merge.
  • Coverage matrix updated — added/removed/renamed feature rows in docs/TEST-COVERAGE-MATRIX.md reflect this change (or N/A: behaviour-only change)
  • All affected feature IDs from the matrix are listed in the PR description under ## Related
  • No new external network dependencies introduced (mock backend used per Testing Strategy)
  • Manual smoke checklist updated if this touches release-cut surfaces (docs/RELEASE-MANUAL-SMOKE.md)
  • Linked issue closed via Closes #NNN in the ## Related section

Impact

  • Desktop only (macOS / Windows / Linux). No mobile/web/CLI surface.
  • No migration required — update_thread_title is additive; existing threads keep their AI-generated or default titles.
  • No new storage schema: the title field already exists on ConversationThread.

Related

  • Closes:
  • Follow-up PR(s)/TODOs:

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

Keep this section for AI-authored PRs. For human-only PRs, mark each field N/A.

Linear Issue

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

Commit & Branch

  • Branch: main
  • Commit SHA: 978c655ecba99d9fb0613db453b52741d741180f

Validation Run

  • pnpm --filter openhuman-app format:check
  • pnpm typecheck
  • Focused tests: pnpm debug unit src/pages/Conversations · pnpm debug rust thread_update_title
  • Rust fmt/check (if changed): cargo fmt --check && cargo check --manifest-path Cargo.toml
  • Tauri fmt/check (if changed): N/A

Validation Blocked

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

Behavior Changes

  • Intended behavior change: Users can now rename conversation threads via an inline edit in the conversation header.
  • User-visible effect: A pencil icon appears on hover beside the thread title; clicking it opens an inline input pre-filled with the current title. Enter or blur
    commits; Escape cancels.

Parity Contract

  • Legacy behavior preserved: AI-generated titles are still applied on new threads. The manual title write path is strictly additive and does not affect title generation
    logic.
  • Guard/fallback/dispatch parity checks: Empty titles are rejected at the Rust op layer; the Redux thunk propagates the error via rejectWithValue.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: N/A
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features

    • In-place thread title editing in the chat header — tap the pencil to edit, commit with Enter or blur, cancel with Escape.
  • Backend / Persistence

    • Server API to save user-updated thread titles so changes appear across listings and sessions.
  • Tests

    • New unit and end-to-end tests for title updates, validation (empty/whitespace rejected), and error handling.
  • Localization

    • Added "Edit thread title" translations for multiple languages.

Review Change Stack

@neillee95 neillee95 requested a review from a team May 23, 2026 09:24
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 23, 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

Adds in-place thread title editing end-to-end: frontend UI and handlers, threadApi.updateTitle, updateThreadTitle Redux thunk and reducer update, new JSON-RPC request model and server handler, RPC schema registration, translations, and unit/e2e tests.

Changes

In-place thread title editing

Layer / File(s) Summary
RPC request contract and backend handler
src/openhuman/memory/rpc_models.rs, src/openhuman/threads/ops.rs
Adds UpdateConversationThreadTitleRequest and implements thread_update_title to trim/validate and persist the updated thread title, returning the updated thread summary.
RPC endpoint registration and schema
src/openhuman/threads/schemas.rs, src/openhuman/threads/schemas_tests.rs
Registers new update_title controller schema, defines required thread_id and title inputs and JSON result, wires handle_update_title to the handler, and updates schema tests fixture.
Frontend API client and tests
app/src/services/api/threadApi.ts, app/src/services/api/threadApi.test.ts
Adds threadApi.updateTitle that calls openhuman.threads_update_title RPC and returns the unwrapped Thread; includes a unit test verifying RPC params and returned thread.
Frontend Redux thunk and reducer
app/src/store/threadSlice.ts, app/src/store/__tests__/threadSlice.test.ts
Adds updateThreadTitle async thunk with consistent error handling; extraReducers updates or prepends the returned thread. Tests extend mocks and verify fulfilled and rejected behaviors.
Frontend UI with title editing
app/src/pages/Conversations.tsx
Adds local edit state, input ref, handlers to start/commit/cancel edits, and conditional header rendering that shows an input (commits on Enter/blur, cancels on Escape) or the title + edit button that dispatches the thunk.
Translations
app/src/lib/i18n/chunks/*, app/src/lib/i18n/en.ts
Adds chat.editThreadTitle translation key to multiple locale chunks and the main English map.
Server and integration tests
src/openhuman/threads/ops_tests.rs, tests/json_rpc_e2e.rs
Adds ops tests for empty/whitespace validation, persistence of trimmed titles, error on missing threads, and a JSON-RPC e2e test covering create/update/list and validation failure.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

rust-core

🐰 I nudged a title, small and bright,
From header field into the night.
Redux hummed, the RPC heard,
The server wrote the tiny word.
Now threads wear names — hop, what a sight!

🚥 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 and concisely summarizes the main feature: manual editing of conversation thread titles. It is specific, directly related to the changeset scope, and uses conventional commit format.
Docstring Coverage ✅ Passed Docstring coverage is 88.24% which is sufficient. The required threshold is 80.00%.
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.

✏️ 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.

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

🤖 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/pages/Conversations.tsx`:
- Around line 1299-1314: The inline title input (editTitleInputRef /
editTitleValue) and the pencil icon button that toggles editing (the control
that calls setEditingTitle) lack accessible names; add explicit accessible
labels: give the input either an aria-label (e.g., "Conversation title") or
aria-labelledby pointing to a visible label/heading id, and add an aria-label on
the pencil button (e.g., "Edit conversation title") so screen readers announce
the controls; ensure labels are unique, update any related JSX for the pencil
button (the toggle that uses setEditingTitle) to include the aria-label, and
keep existing handlers (handleCommitTitle, onBlur, onKeyDown) intact.
🪄 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: 40847230-b2a0-46ac-a04f-d87872234181

📥 Commits

Reviewing files that changed from the base of the PR and between d93f834 and d7b77e8.

📒 Files selected for processing (6)
  • app/src/pages/Conversations.tsx
  • app/src/services/api/threadApi.ts
  • app/src/store/threadSlice.ts
  • src/openhuman/memory/rpc_models.rs
  • src/openhuman/threads/ops.rs
  • src/openhuman/threads/schemas.rs

Comment thread app/src/pages/Conversations.tsx
@coderabbitai coderabbitai Bot added the working A PR that is being worked on by the team. label May 25, 2026
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.

🧹 Nitpick comments (1)
src/openhuman/threads/ops_tests.rs (1)

695-792: 🏗️ Heavy lift

Split ops_tests.rs; this addition pushes an already oversized module further beyond the repo limit.

This new test block is good coverage-wise, but src/openhuman/threads/ops_tests.rs is now ~793 lines. Please move this thread_update_title_* group into a dedicated sibling test module/file to keep responsibilities focused and stay within the size guideline.

As per coding guidelines **/*.{ts,tsx,rs}: "File size should not exceed approximately 500 lines. When a module grows beyond this threshold, split it into smaller, more focused modules with clear responsibilities."

🤖 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/threads/ops_tests.rs` around lines 695 - 792, The tests for
thread title updates are pushing ops_tests.rs over the size limit; extract the
four tests thread_update_title_rejects_empty_title,
thread_update_title_rejects_whitespace_only_title,
thread_update_title_persists_new_title, and
thread_update_title_returns_error_for_missing_thread into a new sibling test
module file and import the same helpers/fixtures they rely on (EnvVarGuard,
create_thread_with_title, thread_update_title, config::TEST_ENV_LOCK, tempfile).
Ensure the new file declares the same async #[tokio::test] functions with
identical names and that it has the necessary use statements so it compiles
standalone; finally remove those tests from ops_tests.rs so the original file
goes back under the size guideline.
🤖 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.

Nitpick comments:
In `@src/openhuman/threads/ops_tests.rs`:
- Around line 695-792: The tests for thread title updates are pushing
ops_tests.rs over the size limit; extract the four tests
thread_update_title_rejects_empty_title,
thread_update_title_rejects_whitespace_only_title,
thread_update_title_persists_new_title, and
thread_update_title_returns_error_for_missing_thread into a new sibling test
module file and import the same helpers/fixtures they rely on (EnvVarGuard,
create_thread_with_title, thread_update_title, config::TEST_ENV_LOCK, tempfile).
Ensure the new file declares the same async #[tokio::test] functions with
identical names and that it has the necessary use statements so it compiles
standalone; finally remove those tests from ops_tests.rs so the original file
goes back under the size guideline.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f7e7fc88-537b-4df1-9d3e-8cd72b136e31

📥 Commits

Reviewing files that changed from the base of the PR and between d7b77e8 and 3f52220.

📒 Files selected for processing (4)
  • app/src/services/api/threadApi.test.ts
  • app/src/store/__tests__/threadSlice.test.ts
  • src/openhuman/threads/ops_tests.rs
  • tests/json_rpc_e2e.rs

@senamakel senamakel self-assigned this May 25, 2026
senamakel added 2 commits May 24, 2026 21:54
The tests assert that all_controller_schemas() and
all_registered_controllers() match a hardcoded list. The new
update_title controller was registered but not added to ALL_FUNCTIONS,
causing CI to fail (16 actual vs 15 expected).
…coderabbitai)

Add aria-label to the inline title input and pencil edit button for
screen reader support. Adds 'chat.editThreadTitle' i18n key to en.ts
and all locale chunk-2 files.
@coderabbitai coderabbitai Bot added the feature Net-new user-facing capability or product behavior. label May 25, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes May 25, 2026
Exercises handleStartEditTitle, handleCommitTitle, and the inline
edit JSX branches (Enter commit, Escape cancel, empty-title guard).
Addresses diff-cover failure on Conversations.tsx changed lines.
@coderabbitai coderabbitai Bot added the rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. label May 25, 2026
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

🧹 Nitpick comments (1)
app/src/pages/__tests__/Conversations.render.test.tsx (1)

1346-1377: ⚡ Quick win

Assert the user-visible commit result, not just the API call.

This test currently validates an implementation detail (threadApi.updateTitle invocation). Add assertions that edit mode exits and the updated title appears.

Suggested refactor
-    await waitFor(() => {
-      expect(threadApi.updateTitle).toHaveBeenCalledWith('commit-title-thread', 'New Title');
-    });
+    await waitFor(() => {
+      expect(threadApi.updateTitle).toHaveBeenCalledWith('commit-title-thread', 'New Title');
+      expect(screen.queryByLabelText('Edit thread title')).not.toBeInTheDocument();
+      expect(screen.getByText('New Title')).toBeInTheDocument();
+    });

As per coding guidelines: "Prefer testing behavior over implementation details."

🤖 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/pages/__tests__/Conversations.render.test.tsx` around lines 1346 -
1377, The test currently only asserts threadApi.updateTitle was called; update
it to assert the user-visible outcome: after firing Enter, wait for edit mode to
exit (e.g., the 'Edit thread title' input is no longer in the DOM or its label
is gone) and assert the updated title text "New Title" is rendered in the
document (e.g., screen.getByText('New Title')) and that normal title UI (edit
button via getByRole('button', { name: 'Edit thread title' })) is present again;
keep existing calls to renderConversations, selectedThreadState, socketState and
the mock for threadApi.updateTitle but add these DOM assertions instead of
relying solely on the implementation detail.
🤖 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/pages/__tests__/Conversations.render.test.tsx`:
- Around line 1316-1319: The suite currently calls vi.clearAllMocks() which
preserves mock implementations and can leak prior overrides of
mockUseUsageState; update the beforeEach to reset the usage-state mock (e.g.,
call mockUseUsageState.mockReset() or vi.resetAllMocks()) before setting
mockGetThreadMessages so this suite starts with a clean mockUseUsageState
implementation and avoids order-dependent flakes.

---

Nitpick comments:
In `@app/src/pages/__tests__/Conversations.render.test.tsx`:
- Around line 1346-1377: The test currently only asserts threadApi.updateTitle
was called; update it to assert the user-visible outcome: after firing Enter,
wait for edit mode to exit (e.g., the 'Edit thread title' input is no longer in
the DOM or its label is gone) and assert the updated title text "New Title" is
rendered in the document (e.g., screen.getByText('New Title')) and that normal
title UI (edit button via getByRole('button', { name: 'Edit thread title' })) is
present again; keep existing calls to renderConversations, selectedThreadState,
socketState and the mock for threadApi.updateTitle but add these DOM assertions
instead of relying solely on the implementation detail.
🪄 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: 536f6cd6-169c-4ff3-bf89-3d1db52771ec

📥 Commits

Reviewing files that changed from the base of the PR and between b4591b0 and 4792b34.

📒 Files selected for processing (1)
  • app/src/pages/__tests__/Conversations.render.test.tsx

Comment thread app/src/pages/__tests__/Conversations.render.test.tsx
Addresses CodeRabbit review: vi.clearAllMocks() keeps existing mock
implementations, so explicitly reset mockUseUsageState to prevent
order-dependent flakiness.
@senamakel senamakel merged commit b7b8ba6 into tinyhumansai:main May 25, 2026
29 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.

2 participants