Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/desktop/src/settings/DesktopClientSettings.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as DesktopClientSettings from "./DesktopClientSettings.ts";

const clientSettings: ClientSettings = {
autoOpenPlanSidebar: false,
chatWordWrap: true,
confirmThreadArchive: true,
confirmThreadDelete: false,
dismissedProviderUpdateNotificationKeys: [],
Expand Down
9 changes: 7 additions & 2 deletions apps/web/src/components/ChatMarkdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ import { resolveDiffThemeName, type DiffThemeName } from "../lib/diffRendering";
import { fnv1a32 } from "../lib/diffRendering";
import { LRUCache } from "../lib/lruCache";
import { useTheme } from "../hooks/useTheme";
import { useClientSettings } from "../hooks/useSettings";
import {
chatMarkdownClipboardPayload,
serializeTableElementToCsv,
Expand Down Expand Up @@ -295,7 +296,9 @@ function getHighlighterPromise(language: string): Promise<DiffsHighlighter> {
function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) {
const containerRef = useRef<HTMLDivElement | null>(null);
const tableRef = useRef<HTMLTableElement | null>(null);
const [expanded, setExpanded] = useState(false);
const [expanded, setExpanded] = useState(
useClientSettings((settings) => settings.chatWordWrap)
);

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.

Chat wrap ignores updated settings

Medium Severity

The MarkdownTable and MarkdownCodeBlock components initialize their expanded and wrapped states from the chatWordWrap setting using useState. This causes them to only read the setting once on mount. As a result, changes to the chatWordWrap setting (e.g., after async hydration or user toggling) don't update the component's wrap/expand state, leading to an incorrect display until the component remounts.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 6684c33. Configure here.

const [copied, setCopied] = useState(false);
const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const expandLabel = expanded ? "Collapse table cells" : "Expand table cells";
Expand Down Expand Up @@ -525,7 +528,9 @@ function MarkdownCodeBlock({
children: ReactNode;
}) {
const [copied, setCopied] = useState(false);
const [wrapped, setWrapped] = useState(false);
const [wrapped, setWrapped] = useState(
useClientSettings((settings) => settings.chatWordWrap)
);
const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const wrapLabel = wrapped ? "Disable line wrap" : "Wrap lines";
const copyLabel = copied ? "Copied" : "Copy code";
Expand Down
29 changes: 29 additions & 0 deletions apps/web/src/components/settings/SettingsPanels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,9 @@ export function useSettingsRestore(onRestored?: () => void) {
...(settings.sidebarThreadPreviewCount !== DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount
? ["Visible threads"]
: []),
...(settings.chatWordWrap !== DEFAULT_UNIFIED_SETTINGS.chatWordWrap
? ["Wrap code blocks and tables"]
: []),
...(settings.diffWordWrap !== DEFAULT_UNIFIED_SETTINGS.diffWordWrap
? ["Diff line wrapping"]
: []),
Expand Down Expand Up @@ -428,6 +431,7 @@ export function useSettingsRestore(onRestored?: () => void) {
[
isGitWritingModelDirty,
settings.autoOpenPlanSidebar,
settings.chatWordWrap,
settings.confirmThreadArchive,
settings.confirmThreadDelete,
settings.addProjectBaseDirectory,
Expand Down Expand Up @@ -456,6 +460,7 @@ export function useSettingsRestore(onRestored?: () => void) {
setTheme("system");
updateSettings({
timestampFormat: DEFAULT_UNIFIED_SETTINGS.timestampFormat,
chatWordWrap: DEFAULT_UNIFIED_SETTINGS.chatWordWrap,
diffWordWrap: DEFAULT_UNIFIED_SETTINGS.diffWordWrap,
diffIgnoreWhitespace: DEFAULT_UNIFIED_SETTINGS.diffIgnoreWhitespace,
sidebarThreadPreviewCount: DEFAULT_UNIFIED_SETTINGS.sidebarThreadPreviewCount,
Expand Down Expand Up @@ -593,6 +598,30 @@ export function GeneralSettingsPanel() {
}
/>

<SettingsRow
title="Wrap code blocks and tables"
description="Wrap long lines in chat code blocks and table cells by default."
resetAction={
settings.chatWordWrap !== DEFAULT_UNIFIED_SETTINGS.chatWordWrap ? (
<SettingResetButton
label="code block and table wrapping"
onClick={() =>
updateSettings({
chatWordWrap: DEFAULT_UNIFIED_SETTINGS.chatWordWrap,
})
}
/>
) : null
}
control={
<Switch
checked={settings.chatWordWrap}
onCheckedChange={(checked) => updateSettings({ chatWordWrap: Boolean(checked) })}
aria-label="Wrap code blocks and tables by default"
/>
}
/>

<SettingsRow
title="Diff line wrapping"
description="Set the default wrap state when the diff panel opens."
Expand Down
2 changes: 2 additions & 0 deletions packages/contracts/src/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const DEFAULT_SIDEBAR_THREAD_PREVIEW_COUNT: SidebarThreadPreviewCount = 6

export const ClientSettingsSchema = Schema.Struct({
autoOpenPlanSidebar: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
chatWordWrap: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

actually this should probalby just be wordWrap and be applied as default to diff and code views in general?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

makes sense. let me look into it

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@juliusmarminge this is done, please check :)

image

confirmThreadArchive: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(false))),
confirmThreadDelete: Schema.Boolean.pipe(Schema.withDecodingDefault(Effect.succeed(true))),
dismissedProviderUpdateNotificationKeys: Schema.Array(TrimmedNonEmptyString).pipe(
Expand Down Expand Up @@ -535,6 +536,7 @@ export type ServerSettingsPatch = typeof ServerSettingsPatch.Type;

export const ClientSettingsPatch = Schema.Struct({
autoOpenPlanSidebar: Schema.optionalKey(Schema.Boolean),
chatWordWrap: Schema.optionalKey(Schema.Boolean),
confirmThreadArchive: Schema.optionalKey(Schema.Boolean),
confirmThreadDelete: Schema.optionalKey(Schema.Boolean),
diffIgnoreWhitespace: Schema.optionalKey(Schema.Boolean),
Expand Down
Loading