Skip to content

feat: configure AI chat system prompts#775

Merged
maccman merged 3 commits into
nextfrom
codex/ai-chat-system-prompt
Jul 15, 2026
Merged

feat: configure AI chat system prompts#775
maccman merged 3 commits into
nextfrom
codex/ai-chat-system-prompt

Conversation

@maccman

@maccman maccman commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Problem

AI chat always used Reflect's fixed system prompt. Users could choose providers and save editor-selection prompts, but they had no way to configure the chat assistant's tone, format, or working style.

A user-defined prompt also cannot safely replace Reflect's built-in prompt: the built-in instructions describe note retrieval, citations, tool usage, and private-note handling.

Before -> After

Before After
Chat behavior was fixed Users can add per-device instructions for every chat turn
No chat prompt control on desktop or iOS Desktop gets an AI chat settings section; mobile gets a System prompt drawer
Only Reflect's built-in prompt was sent Custom instructions are appended after the built-in grounding and privacy rules

Changes

  1. Persist a bounded chat prompt

    • Adds chatSystemPrompt to the shared settings schema with an empty default.
    • Trims and caps prompts at 20,000 characters so custom instructions cannot consume the model context window by themselves.
    • Uses the existing opaque settings store, so no Rust migration is required.
  2. Apply the prompt to every chat request

    • Appends nonblank user instructions to chatSystemPrompt while retaining Reflect's retrieval, citation, and private-note rules.
    • Includes the exact combined prompt in context-window accounting and in the provider request.
    • Reads the current setting at send time, so changing Settings affects the next message even in an existing conversation.
  3. Expose the setting across platforms

    • Adds a controlled desktop textarea with a hydration-safe local draft that persists on blur or Settings navigation and a one-click Use default reset.
    • Adds a mobile disclosure row and drawer with hydration-safe draft state, Save, and immediate reset-to-default behavior.
    • Uses the existing Settings, Textarea, Button, and Drawer primitives with labeled controls.
  4. Document the data boundary

    • Updates the privacy guide to state that the configured prompt is stored in the device settings file and sent to the selected provider on every chat turn.
    • Clarifies that private: true protects tool-based note reads, not content a user manually pastes into a message or prompt.

Tests

  • Settings schema: defaulting, invalid values, trimming, multiline preservation, and length capping.
  • System prompt and streaming engine: additive composition, blank omission, outbound delivery, and preservation of built-in rules.
  • Chat provider: the latest configured prompt reaches the next turn.
  • Desktop settings: hydration, blur/navigation persistence, normalization, and reset behavior.
  • Mobile settings: editing, pre-hydration updates, save, and reset behavior.
  • Existing settings and chat fixtures updated for the new required setting.

Verification

  • pnpm check — passed (typecheck, Oxlint, and React ESLint; two pre-existing max-lines warnings remain).
  • pnpm build — passed (expected local Sentry credential and bundle-size warnings only).
  • pnpm --filter @reflect/core exec vitest run src/settings/schema.test.ts src/ai/chat/system-prompt.test.ts src/ai/chat/stream-chat.test.ts — 38 tests passed.
  • pnpm --filter @reflect/desktop exec vitest run src/providers/chat-provider.test.tsx src/providers/settings-provider.test.tsx src/components/settings-screen.test.tsx src/components/settings/settings-navigator.test.tsx src/mobile/screens/settings.test.tsx src/components/chat/chat-screen.test.tsx src/mobile/screens/chat.test.tsx — 114 tests passed.

Risk / Rollout

  • No data migration or provider rollout is needed. Existing settings documents default to the built-in chat behavior.
  • Custom prompts are additive and cannot bypass the code-level private-note gates.
  • The prompt is device-local and is not stored in graph markdown or chat history.

Note

Medium Risk
Custom prompt text is sent to the user’s AI provider on every chat turn; built-in private-note tool gates remain, but pasted or prompt content is not protected.

Overview
Adds a device-local chatSystemPrompt setting so users can supply extra instructions for every AI chat turn without replacing Reflect’s built-in grounding, tool, citation, and private-note rules.

Core: New settings field (trimmed, capped at 20k chars, default empty). chatSystemPrompt is appended in chatSystemPrompt() after the built-in rules; streamChat passes it through context-window fitting and the provider request. ChatProvider reads the current value at send time via a ref so changes apply on the next message in an open thread.

UI: Desktop AI chat settings section (draft textarea, blur/unmount save, Use default). Mobile System prompt row and drawer (Save / Use default). Settings navigator registry updated.

Docs: Privacy guide notes the prompt is stored locally and sent to the user’s provider each turn, and that private-note protection does not cover pasted content or custom prompt text.

Test fixtures and suites extended for schema, streaming, provider wiring, and both settings surfaces.

Reviewed by Cursor Bugbot for commit 0891e89. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Added customizable AI chat system prompts in desktop and mobile settings.
    • Prompts are normalized, length-limited, and can be restored to the default.
    • Configured prompts are applied to every AI chat turn.
  • Documentation

    • Clarified local prompt storage and private-note protections in AI chat privacy documentation.
  • Tests

    • Added coverage for prompt editing, persistence, normalization, and chat integration.

@coderabbitai

coderabbitai Bot commented Jul 13, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds a normalized, persisted custom AI chat system prompt across core streaming, desktop settings, mobile settings, provider wiring, tests, and privacy documentation.

Changes

Custom AI chat system prompt

Layer / File(s) Summary
Settings schema and exports
packages/core/src/settings/schema.ts, packages/core/src/settings/schema.test.ts, packages/core/src/exports/platform.ts
Adds the chatSystemPrompt setting with trimming, truncation, validation, an empty default, and public exports.
Prompt construction and streaming
packages/core/src/ai/chat/*, docs/privacy.md
Appends normalized custom instructions to generated prompts and forwards them through streaming calls; tests and privacy wording cover the behavior.
Desktop settings editor
apps/desktop/src/components/settings/*, apps/desktop/src/components/settings-screen.*
Adds an AI chat settings section with editable system prompt and “Use default” behavior.
Mobile settings drawer
apps/desktop/src/mobile/chat-system-prompt-drawer.tsx, apps/desktop/src/mobile/screens/settings.*
Adds a mobile system prompt row and drawer editor with draft, save, normalization, and default-reset behavior.
Provider integration and compatibility
apps/desktop/src/providers/*, apps/desktop/src/components/*.test.tsx, apps/desktop/src/mobile/*.test.tsx
Passes the latest prompt into chat streaming and updates settings mocks and persistence expectations for the new field.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant SettingsUI
  participant SettingsProvider
  participant ChatProvider
  participant streamChat
  participant Model
  SettingsUI->>SettingsProvider: save chatSystemPrompt
  ChatProvider->>SettingsProvider: read latest setting
  ChatProvider->>streamChat: pass customSystemPrompt
  streamChat->>Model: append normalized prompt
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Title check ✅ Passed The title clearly summarizes the main change: adding configurable AI chat system prompts.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/ai-chat-system-prompt

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

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/core/src/ai/chat/stream-chat.ts (1)

88-95: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Cover the streamChat forwarding path.

The custom-prompt assertion exercises streamChatTurn directly, so it would not catch a regression that omits the prompt from chatSystemPrompt during context-window fitting or from the subsequent streamChatTurn call. Add a focused streamChat-level test that captures the provider request and verifies the custom prompt is included.

This is based on the PR objective that the prompt participates in context accounting and provider requests, plus the guideline to test the changed logic specifically.

🤖 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 `@packages/core/src/ai/chat/stream-chat.ts` around lines 88 - 95, The existing
coverage only tests streamChatTurn directly; add a focused test for streamChat
that captures the provider request and verifies customSystemPrompt is forwarded
into both context-window accounting and the subsequent streamChatTurn request.
Use the streamChat entry point and existing test/provider mocking utilities,
preserving the current behavior for requests without a custom prompt.

Source: Coding guidelines

apps/desktop/src/components/settings/ai-chat-section.tsx (1)

19-30: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Every keystroke persists the setting; mobile intentionally avoids this.

value is bound directly to settings.chatSystemPrompt and onChange calls updateSettings on every keystroke (normalization only happens on blur). For a field allowing up to 20,000 characters, this fires a settings write on every character typed. Contrast with the mobile ChatSystemPromptDrawer, which buffers edits in local draft state and only persists on explicit Save/"Use default" — precisely to avoid this. Consider mirroring that pattern here: keep a local draft synced from settings.chatSystemPrompt when not dirty, and only call updateSettings on blur (or Save).

Beyond the write volume, if the underlying persistence is fire-and-forget without strict ordering, rapid typing could theoretically let an earlier in-flight write resolve after a later one and clobber the latest text. Worth confirming against the settings-provider implementation.

♻️ Suggested draft-buffering pattern (mirrors mobile drawer)
+import { useState } from 'react'
 import type { ReactElement } from 'react'
 import { CHAT_SYSTEM_PROMPT_MAX_LENGTH, normalizeChatSystemPrompt } from '`@reflect/core`'
 ...
 export function AiChatSection(): ReactElement {
   const { settings, updateSettings } = useSettings()
+  const [draft, setDraft] = useState(settings.chatSystemPrompt)
+  const [dirty, setDirty] = useState(false)
+  const currentDraft = dirty ? draft : settings.chatSystemPrompt

   return (
     <SettingsSection id="ai-chat">
       <SettingsField ...>
         <Textarea
           aria-label="System prompt"
-          value={settings.chatSystemPrompt}
-          onChange={(event) => updateSettings({ chatSystemPrompt: event.target.value })}
-          onBlur={(event) =>
-            updateSettings({ chatSystemPrompt: normalizeChatSystemPrompt(event.target.value) })
-          }
+          value={currentDraft}
+          onChange={(event) => {
+            setDirty(true)
+            setDraft(event.target.value)
+          }}
+          onBlur={() =>
+            updateSettings({ chatSystemPrompt: normalizeChatSystemPrompt(currentDraft) })
+          }
🤖 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 `@apps/desktop/src/components/settings/ai-chat-section.tsx` around lines 19 -
30, Update the settings chat prompt editor around the Textarea to use a local
draft instead of binding directly to settings.chatSystemPrompt. Keep the draft
synchronized with external settings changes while the field is not dirty, update
only the draft during typing, and persist the normalized draft on blur (or an
explicit save) via updateSettings; preserve the existing max length and default
behavior.
🤖 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 `@apps/desktop/src/providers/chat-provider.tsx`:
- Line 306: In the send flow, snapshot chatSystemPromptRef.current before the
first await, then pass that captured value as customSystemPrompt instead of
reading the ref after Promise.all. Keep the snapshot scoped to the submitted
turn so later setting changes do not affect the in-flight request.

In `@packages/core/src/settings/schema.test.ts`:
- Around line 123-126: Update the test around settingsSchema.parse and
chatSystemPrompt to assert that the truncated value equals the expected prefix
of the repeated input, while retaining the existing maximum-length assertion if
useful. Verify the result preserves the first CHAT_SYSTEM_PROMPT_MAX_LENGTH
characters rather than only checking its length.

---

Nitpick comments:
In `@apps/desktop/src/components/settings/ai-chat-section.tsx`:
- Around line 19-30: Update the settings chat prompt editor around the Textarea
to use a local draft instead of binding directly to settings.chatSystemPrompt.
Keep the draft synchronized with external settings changes while the field is
not dirty, update only the draft during typing, and persist the normalized draft
on blur (or an explicit save) via updateSettings; preserve the existing max
length and default behavior.

In `@packages/core/src/ai/chat/stream-chat.ts`:
- Around line 88-95: The existing coverage only tests streamChatTurn directly;
add a focused test for streamChat that captures the provider request and
verifies customSystemPrompt is forwarded into both context-window accounting and
the subsequent streamChatTurn request. Use the streamChat entry point and
existing test/provider mocking utilities, preserving the current behavior for
requests without a custom prompt.
🪄 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: a6b5013a-900c-42b0-b6ec-a9a47f45b689

📥 Commits

Reviewing files that changed from the base of the PR and between bf041b1 and 980f82d.

📒 Files selected for processing (24)
  • apps/desktop/src/components/chat/chat-screen.test.tsx
  • apps/desktop/src/components/daily-stream.test.tsx
  • apps/desktop/src/components/route-content.test.tsx
  • apps/desktop/src/components/settings-screen.test.tsx
  • apps/desktop/src/components/settings-screen.tsx
  • apps/desktop/src/components/settings/ai-chat-section.tsx
  • apps/desktop/src/components/settings/sections.ts
  • apps/desktop/src/mobile/chat-system-prompt-drawer.tsx
  • apps/desktop/src/mobile/mobile-screen.test.tsx
  • apps/desktop/src/mobile/note-conflict.test.tsx
  • apps/desktop/src/mobile/screens/chat.test.tsx
  • apps/desktop/src/mobile/screens/settings.test.tsx
  • apps/desktop/src/mobile/screens/settings.tsx
  • apps/desktop/src/providers/chat-provider.test.tsx
  • apps/desktop/src/providers/chat-provider.tsx
  • apps/desktop/src/providers/settings-provider.test.tsx
  • docs/privacy.md
  • packages/core/src/ai/chat/stream-chat.test.ts
  • packages/core/src/ai/chat/stream-chat.ts
  • packages/core/src/ai/chat/system-prompt.test.ts
  • packages/core/src/ai/chat/system-prompt.ts
  • packages/core/src/exports/platform.ts
  • packages/core/src/settings/schema.test.ts
  • packages/core/src/settings/schema.ts

Comment thread apps/desktop/src/providers/chat-provider.tsx Outdated
Comment thread packages/core/src/settings/schema.test.ts Outdated
@maccman
maccman merged commit 091519d into next Jul 15, 2026
10 checks passed
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