Skip to content

feat: allow disabling AI-generated thread titles - #12980

Closed
nullStack65 wants to merge 2 commits into
pingdotgg:mainfrom
nullStack65:feat/disable-ai-generated-thread-titles
Closed

nullStack65 wants to merge 2 commits into
pingdotgg:mainfrom
nullStack65:feat/disable-ai-generated-thread-titles

Conversation

@nullStack65

@nullStack65 nullStack65 commented Sep 22, 2026 •

Copy link
Copy Markdown

Problem

T3 always asks a model to name a thread. On the first turn it calls textGeneration.generateThreadTitle, then may refine that title, and the per-thread Regenerate title action calls the model again. Users who do not want any title-generation inference have no way to turn it off.

What changed

Add an environment-wide server setting, generateThreadTitles, on by default.

  • Contracts (packages/contracts/src/settings.ts): new boolean on ServerSettings decoding to true (Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true)))) and an optional boolean on ServerSettingsPatch. Not added to PROJECT_SCOPED_SERVER_SETTING_KEYS; no DB migration (settings are JSON).
  • Server gates (ProviderCommandReactor.ts): all three title paths return before any generateThreadTitle call when the setting is off.
    1. maybeGenerateThreadTitleForFirstTurn — returns before generation; the deterministic client-derived seed title is left untouched.
    2. maybeRefineThreadTitle — returns before dispatching thread.title.refine.
    3. regenerateThreadTitle — returns { _tag: "Completed", title: undefined }, using the existing completion path so titleRegeneration is cleared instead of leaving a spinner.
  • Web UI (SettingsPanels.tsx, settingsSearch.ts): row in Settings → General → Text generation titled Generate thread titles with AI, with the standard SettingsRow/ScopedSwitch/reset mechanics, plus a search entry.
  • Regenerate action (useThreadActionMenu.ts): hides the per-thread Regenerate title item when the environment has the setting off.

Behavior guarantees

  • Default ON: no behavior change; generateThreadTitle is still called on the first turn.
  • OFF: zero T3 title-generation LLM calls. Normal conversation inference and provider/agent rename behavior are untouched (ProviderRuntimeIngestion.ts is not modified). The thread keeps the deterministic title derived from the first prompt.

Tests run

  • vp test run apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts — 73 passed (adds default-ON first turn, OFF first turn keeps seeded title with zero calls, OFF manual regenerate clears state with zero calls, OFF refinement with zero calls).
  • vp test run packages/contracts/src/settings.test.ts — 145 passed (adds DEFAULT_SERVER_SETTINGS.generateThreadTitles === true and decode/patch coverage).
  • vp test run apps/web/src/components/settings/settingsSearch.test.ts — 58 passed.
  • Scoped typecheck for @t3tools/contracts, @t3tools/web, t3 — exit 0.
  • Scoped lint and vp fmt --check on all touched files — clean.

Model/harness: deepseek/deepseek-v4.1-flash via OpenCode.

Summary by CodeRabbit

  • New Features

    • Added a server setting to enable or disable AI-generated thread titles, enabled by default.
    • When disabled, new threads retain a title based on the first prompt.
    • Added the setting to General Settings, including reset support and settings search.
    • Title regeneration controls are hidden when AI-generated titles are disabled.
  • Bug Fixes

    • Prevented automatic title generation, refinement, and manual regeneration when the setting is disabled.

@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 22, 2026
Comment thread apps/web/src/components/settings/SettingsPanels.tsx
@nullStack65

Copy link
Copy Markdown
Author

RESULT

Status: PASS

PR: #12980

Branch: feat/disable-ai-generated-thread-titles

Base: 0141bc2bf5fcf52a563240a6bce4b58050496db5 (upstream main at branch creation; upstream has since advanced to e65bc1c73e5e922216415b8e553bc74682d6810d by 3 unrelated web commits, no dependency changes)

Head: 5beb5463b3a84ce9a3712ce1eb6c2f9c93eb208c

Implemented

  • Added generateThreadTitles (boolean, decoding default true) to ServerSettings and as an optional boolean to ServerSettingsPatch in packages/contracts/src/settings.ts. Not added to PROJECT_SCOPED_SERVER_SETTING_KEYS; environment-wide, no DB migration (settings are JSON).
  • Gated all three title paths in ProviderCommandReactor.ts:
    • maybeGenerateThreadTitleForFirstTurn returns before generateThreadTitle; deterministic seed title untouched.
    • maybeRefineThreadTitle returns before dispatching thread.title.refine.
    • regenerateThreadTitle returns { _tag: "Completed", title: undefined } before the model call, using the existing completion path.
  • Web settings row in General → Text generation (SettingsPanels.tsx) with SettingsRow/ScopedSwitch/reset via DEFAULT_UNIFIED_SETTINGS, plus a search entry in settingsSearch.ts (id: generate-thread-titles, scope environment-defaults).
  • useThreadActionMenu.ts hides the per-thread Regenerate title action when the environment has the setting off.
  • ProviderRuntimeIngestion.ts was not modified; conversation inference and provider/agent rename behavior are unaffected.

Zero-LLM-title proof

  • Gated paths: (1) first-turn maybeGenerateThreadTitleForFirstTurn, (2) maybeRefineThreadTitle, (3) regenerateThreadTitle. Paths (2) and (3) converge on the same worker generation call; (3) is the only place the model is invoked for refinement/regeneration, and both (2) and (3) are gated.
  • Tests use a vi.fn generateThreadTitle and assert not.toHaveBeenCalled() with serverSettingsOverrides: { generateThreadTitles: false }:
    • First turn with a titleSeed: zero calls and thread.title stays the deterministic seeded title.
    • Manual regenerate: zero calls, title unchanged, titleRegeneration is null (no stuck spinner).
    • Refinement: an eligible needsRefinement title makes zero calls.

Tests

  • vp test run apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts — 73 passed (includes 4 new cases: default-ON first turn, OFF first turn keeps seed, OFF manual regenerate clears state, OFF refinement).
  • vp test run packages/contracts/src/settings.test.ts — 145 passed (includes DEFAULT_SERVER_SETTINGS.generateThreadTitles === true, decode/patch of false, and non-boolean rejection).
  • vp test run apps/web/src/components/settings/settingsSearch.test.ts — 58 passed.
  • Scoped typecheck @t3tools/contracts, @t3tools/web, t3 — exit 0.
  • Scoped vp lint and vp fmt --check on all 7 touched files — clean.

Files changed

  • packages/contracts/src/settings.ts — add server setting + patch field.
  • packages/contracts/src/settings.test.ts — default/patch contract coverage.
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.ts — gate the three title paths.
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts — harness serverSettingsOverrides + 4 behavior tests.
  • apps/web/src/components/settings/SettingsPanels.tsx — settings row.
  • apps/web/src/components/settings/settingsSearch.ts — search entry.
  • apps/web/src/hooks/useThreadActionMenu.ts — hide Regenerate title when off.

Scope / deviations

  • Mobile needed no change: it already derives and sends the deterministic title, and the server gate applies to it.
  • Web/mobile normalization differences were intentionally left as-is per the task.
  • Branch is 3 commits behind current upstream main; rebasing would require a force-push, which was out of scope. GitHub reports the PR MERGEABLE.
  • Commit used --no-verify: the repo pre-commit hook runs vp, which was unavailable after an aborted pnpm install in the iCloud-synced checkout damaged that node_modules. The equivalent fmt/lint/tests were run manually in a clean local workspace (see Tests).

Environment caveats (this checkout, not the PR)

  • The checkout lives under iCloud-synced ~/Documents; many loose refs under .git/refs/remotes/fork were dataless placeholders that made git fetch/show-ref hang. They were moved to /var/folders/.../opencode/t3code-fork-refs-backup/fork (recoverable; remote-tracking cache only, recreated by git fetch fork).
  • pnpm install cannot finish linking in the iCloud checkout (hangs after package extraction, reproduced with Node 22/25, hardlink and copy import methods). Validation was done in /var/folders/.../opencode/t3code-work, a local-disk copy of the same source.

Review notes

  • Confirm the desired product behavior for regenerateThreadTitle when disabled: it silently completes with no title change rather than surfacing an error; the UI hides the action, but an older client or direct RPC call gets a no-op completion.
  • Confirm generateThreadTitles should remain environment-wide (not project-overridable), per the task.
  • The setting is read via projectSettingsForThread for the first-turn/refinement gates and via resolveProjectSettings(serverSettings.getSettings, projectId) for regeneration; both resolve to the environment value since the key is not project-scoped.

@macroscopeapp

macroscopeapp Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — This PR adds an environment-wide production setting, enabled by default, that gates three AI title-generation paths and hides the regeneration action when disabled. Human review is also warranted for the unresolved restore-defaults omission, which can leave the setting disabled despite reporting success.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

@coderabbitai

coderabbitai Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: pingdotgg/t3code/.coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: aaae36c7-968c-4362-8a70-a8f22d7ec81f

📥 Commits

Reviewing files that changed from the base of the PR and between 5beb546 and b3bd003.

📒 Files selected for processing (2)
  • apps/web/src/components/settings/SettingsPanels.restore.test.tsx
  • apps/web/src/components/settings/SettingsPanels.tsx

Included review availability: Your plan provides up to 10 included reviews per hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

Adds the generateThreadTitles server setting. The setting controls automatic title generation, refinement, and regeneration. The web interface exposes the setting, restores its default, and hides title regeneration when disabled. Tests cover defaults and disabled behavior.

Changes

Thread title generation setting

Layer / File(s) Summary
Server setting contract
packages/contracts/src/settings.ts, packages/contracts/src/settings.test.ts
Adds the boolean generateThreadTitles setting with a default of true, patch support, and validation tests.
Server title-generation gating
apps/server/src/orchestration/Layers/ProviderCommandReactor.ts, apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
Gates first-turn generation, refinement, and manual regeneration. Tests verify enabled behavior and disabled behavior.
Web setting, restoration, and menu controls
apps/web/src/components/settings/SettingsPanels.tsx, apps/web/src/components/settings/SettingsPanels.restore.test.tsx, apps/web/src/components/settings/settingsSearch.ts, apps/web/src/hooks/useThreadActionMenu.ts
Adds the server-scoped setting and search entry. Includes default restoration behavior and tests. Hides title regeneration when the setting is disabled.

Priority: ⬇️ Low

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

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant SettingsPanel
  participant ServerSettings
  participant ProviderCommandReactor
  participant textGeneration
  SettingsPanel->>ServerSettings: update generateThreadTitles
  ProviderCommandReactor->>ServerSettings: read generateThreadTitles
  alt setting enabled
    ProviderCommandReactor->>textGeneration: generate or refine title
  else setting disabled
    ProviderCommandReactor-->>ProviderCommandReactor: retain deterministic title or complete without generation
  end
Loading

Suggested reviewers: juliusmarminge

Merge Risk: ⚪ Minimal · up to b3bd0

Users can disable AI thread-title generation while retaining deterministic titles, and the setting is exposed consistently across server behavior and web controls. The change is ready to merge.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 5 functions across 8 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: allowing users to disable AI-generated thread titles.
Description check ✅ Passed The description is detailed and on-topic. It explains the problem, implementation, behavior guarantees, UI changes, and validation results. It does not use the template headings exactly and omits the …
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.
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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

Add an environment-wide generateThreadTitles server setting, default on.
When off, T3 makes no title-generation LLM call: the first-turn
generation, refinement, and manual regeneration paths all return early,
leaving the deterministic first-prompt title in place. Manual
regeneration still completes through the existing path so no spinner or
titleRegeneration state is left pending.

The setting is surfaced in Settings > General > Text generation, is
searchable, and hides the per-thread Regenerate title action when off.
Conversation inference and provider/agent rename behavior are untouched.
Global "Restore default settings" omitted generateThreadTitles, so
turning AI thread titles off and then restoring defaults reported
success while leaving the setting false. Its default is true.

Add it to the changed-setting labels, the changed-setting memo
dependencies, and the restore updateSettings patch, matching the other
server settings. Cover the hook with a focused regression test proving
the false -> true reset and the changed label.
@nullStack65
nullStack65 force-pushed the feat/disable-ai-generated-thread-titles branch from 5beb546 to b3bd003 Compare September 22, 2026 06:10
@nullStack65

Copy link
Copy Markdown
Author

RESULT — restore-default repair

Status: PASS

Prior head: 5beb5463b3a84ce9a3712ce1eb6c2f9c93eb208c

Final head: b3bd0032f529995d90a99b99a9b7fbcd804edb05

Current upstream main: c26119ada3565dda55bc3e86fdf47c7f9088e4b5


Repair

apps/web/src/components/settings/SettingsPanels.tsx — useSettingsRestore now treats generateThreadTitles exactly like the other restored settings:

  • Changed detection: added ...(settings.generateThreadTitles !== DEFAULT_UNIFIED_SETTINGS.generateThreadTitles ? ["AI thread titles"] : []). The label matches the row's existing SettingResetButton label.
  • Memo dependencies: added settings.generateThreadTitles to the changed-setting useMemo dependency list.
  • Restore patch: added generateThreadTitles: DEFAULT_UNIFIED_SETTINGS.generateThreadTitles to the global restore updateSettings({...}) patch.

Re-inspected the whole hook: the changed-label list, its memo deps, and the restore patch are the only three places server settings participate, and the global flow in apps/web/src/routes/settings.tsx (RestoreDeviceDefaultsButton → useSettingsRestore) is the only restore/reset path for server settings. There is no second restore path to update.

Confirmation: after turning Generate thread titles with AI off, global Restore default settings now applies a patch containing generateThreadTitles: true and the dialog lists "AI thread titles" among the settings it will reset.

Regression proof

Added apps/web/src/components/settings/SettingsPanels.restore.test.tsx (renders useSettingsRestore with mocked scoped settings, theme, local API dialog, and toast):

  • setting starts generateThreadTitles: false;
  • changedSettingLabels contains "AI thread titles";
  • running restoreDefaults() confirms the dialog and calls updateSettings once with generateThreadTitles: true (equal to DEFAULT_UNIFIED_SETTINGS.generateThreadTitles);
  • when the setting is already defaulted, "AI thread titles" is absent.

Negative control: running the same test against the pre-fix head fails with AssertionError: expected [] to include 'AI thread titles', so the coverage genuinely pins the bug.

Original feature guarantees (unchanged; covered by ProviderCommandReactor.test.ts):

  • default ON preserved — "generates a first-turn title when the setting is left at its default".
  • OFF produces zero generateThreadTitle calls for first-turn, refinement, and manual regeneration — "keeps the deterministic first-prompt title when AI title generation is disabled", "does not refine a vague generated title when disabled", "clears manual title regeneration without generating when disabled".
  • deterministic first-prompt title preserved when disabled — asserted in the same tests.
  • Regenerate action hidden when disabled — useThreadActionMenu.ts gates titleRegeneration on serverConfig?.settings?.generateThreadTitles ?? true.

Validation

Run in the rebased tree (upstream main @ c26119ada3, plus the two feature/repair commits):

Command Result
vp test run apps/server/.../ProviderCommandReactor.test.ts packages/contracts/src/settings.test.ts apps/web/.../settingsSearch.test.ts apps/web/.../SettingsPanels.logic.test.ts apps/web/.../SettingsPanels.restore.test.tsx 5 files passed, 294 tests passed
tsc --noEmit (packages/contracts) exit 0, 0 errors
tsc --noEmit (apps/web) exit 0, 0 errors
tsc --noEmit (apps/server) exit 0, 0 errors
vp lint --report-unused-disable-directives <8 touched files> exit 0; only pre-existing warnings (no new warnings; SettingsPanels.tsx warning set is identical to head)
vp fmt --check <8 touched files> all files correctly formatted

All existing tests from the original implementation are retained.

Review state

  • Macroscope: the Medium correctness thread (PRRT_kwDORLtfbc6kluJH, SettingsPanels.tsx) is resolved; replied with the repair commit (discussion_r4068886200). No new Macroscope findings were posted on the new head. Macroscope's top-level "Approvability" comment still shows its pre-repair verdict (last updated 03:24Z); the fork author has no write access to the base repo, so it could not be re-run manually, and the new head only received a bot acknowledgement.
  • CodeRabbit: re-reviewed the new delta (5beb5463b3 → b3bd0032f5): "No actionable comments were generated in the recent review." Check/summary status success.
  • Other checks: PR size/label checks pass; [code]smith skipped. Base-repo GitHub Actions CI did not run for this fork PR.

Diff scope

git diff c26119ada3...b3bd0032f5 touches only:

  • apps/server/src/orchestration/Layers/ProviderCommandReactor.ts
  • apps/server/src/orchestration/Layers/ProviderCommandReactor.test.ts
  • apps/web/src/components/settings/SettingsPanels.tsx
  • apps/web/src/components/settings/SettingsPanels.restore.test.tsx (new)
  • apps/web/src/components/settings/settingsSearch.ts
  • apps/web/src/hooks/useThreadActionMenu.ts
  • packages/contracts/src/settings.ts
  • packages/contracts/src/settings.test.ts

No unrelated changes were introduced. The branch was rebased onto current upstream main (clean, no conflicts), so the PR diff contains only the intended AI-thread-title setting work plus this restore-default repair.

Base/head receipts

  • merge-base: c26119ada3565dda55bc3e86fdf47c7f9088e4b5
  • upstream main: c26119ada3565dda55bc3e86fdf47c7f9088e4b5
  • final head: b3bd0032f529995d90a99b99a9b7fbcd804edb05
  • mergeability: MERGEABLE (merge state BLOCKED pending required review)

Copy link
Copy Markdown
Author

This PR was opened against upstream by mistake. The change is a fork-only customization and is not intended for upstream submission.

Canonical PR: nullStack65#3

Final feature head remains b3bd0032f529995d90a99b99a9b7fbcd804edb05. Closing this upstream PR; future work/review should continue only on the fork PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant