Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
21 changes: 14 additions & 7 deletions apps/web/src/components/ChatMarkdown.tsx

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.

toggleExpanded pins header min-width from measured collapsed column widths before expanding, so the table keeps its layout. Now that expanded comes from shared settings, any other writer (another table, a code block's wrap chip, the Settings toggle) flips this table to data-expanded="true" without that measurement, so its columns reflow instead of holding their widths.

Smallest fix: keep the table's expand state per-instance (its own useState seeded from the preference), or pin widths in a layout effect that measures before data-expanded changes so externally driven expansion is handled too.

Posted via Macroscope — UI Consistency

Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ import { LRUCache } from "../lib/lruCache";
import { getSyntaxHighlighterPromise } from "../lib/syntaxHighlighting";
import { RenderErrorBoundary } from "./RenderErrorBoundary";
import { useTheme } from "../hooks/useTheme";
import { getClientSettings } from "../hooks/useSettings";
import { getClientSettings, updateClientSettings, useClientSettings } from "../hooks/useSettings";

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.

getClientSettings no longer has a call site in this file after readInitialWordWrapSetting was removed; consider dropping it from the import.

Suggested change
import { getClientSettings, updateClientSettings, useClientSettings } from "../hooks/useSettings";
import { updateClientSettings, useClientSettings } from "../hooks/useSettings";

Posted via Macroscope — UI Consistency

import {
chatMarkdownClipboardPayload,
serializeTableElementToCsv,
Expand Down Expand Up @@ -427,14 +427,21 @@ function estimateHighlightedSize(html: string, code: string): number {
return Math.max(html.length * 2, code.length * 3);
}

function readInitialWordWrapSetting(): boolean {
return getClientSettings().wordWrap;
// Live word-wrap preference: survives virtualizer remounts, unlike component state.
function useWordWrapPreference(): [boolean, (value: boolean) => void] {
const wordWrap = useClientSettings((settings) => settings.wordWrap);
return [
wordWrap,
useCallback((value: boolean) => {
updateClientSettings({ wordWrap: value });
}, []),
];
}
Comment on lines +430 to 439

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.

Each markdown code block's wrap chip and each table's expand chip now write the user's persisted global wordWrap setting, so one click changes every mounted code block and table, flips Settings → Word wrap (including its "modified from default" reset affordance), and immediately re-lays-out FilePreviewPanel, which reads useClientSettings((s) => s.wordWrap) live. Previously these were per-instance chrome toggles seeded from the preference.

If the goal is only to survive virtualizer remounts, consider keeping the toggles ephemeral: a module-local useSyncExternalStore store in this file, seeded once from getClientSettings().wordWrap, survives remounts without overwriting a user-facing setting or driving unrelated surfaces. If writing through to settings is intended, that should be a deliberate product decision documented here, since the chip is not presented as a global preference control.

Posted via Macroscope — UI Consistency


function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) {
const containerRef = useRef<HTMLDivElement | null>(null);
const tableRef = useRef<HTMLTableElement | null>(null);
const [expanded, setExpanded] = useState(readInitialWordWrapSetting);
const [expanded, setExpanded] = useWordWrapPreference();
const [copied, setCopied] = useState(false);
const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const expandLabel = expanded ? "Collapse table cells" : "Expand table cells";
Expand All @@ -461,7 +468,7 @@ function MarkdownTable({ children, ...props }: React.ComponentProps<"table">) {
});
}

setExpanded((value) => !value);
setExpanded(!expanded);

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.

Table expand skips width lock

Medium Severity

Column minWidth locking still runs only inside toggleExpanded when this table is clicked. expanded now tracks shared wordWrap, so another table’s expand control or a code-block wrap toggle can set data-expanded without that measurement. Collapsed tables that expand through the shared preference can reflow differently than a directly expanded table.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit fcd943a. Configure here.

}

const handleCopy = useCallback((format: "markdown" | "csv") => {
Expand Down Expand Up @@ -664,7 +671,7 @@ function MarkdownCodeBlock({
children: ReactNode;
}) {
const [copied, setCopied] = useState(false);
const [wrapped, setWrapped] = useState(readInitialWordWrapSetting);
const [wrapped, setWrapped] = useWordWrapPreference();
const copiedTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const wrapLabel = wrapped ? "Disable line wrap" : "Wrap lines";
const copyLabel = copied ? "Copied" : "Copy code";
Expand Down Expand Up @@ -731,7 +738,7 @@ function MarkdownCodeBlock({
size="icon-xs"
className="chat-markdown-chrome-action"
aria-pressed={wrapped}
onClick={() => setWrapped((value) => !value)}
onClick={() => setWrapped(!wrapped)}
aria-label={wrapLabel}
/>
}
Expand Down
5 changes: 5 additions & 0 deletions apps/web/src/hooks/useSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ export function getClientSettings(): ClientSettings {
return getClientSettingsSnapshot();
}

/** Imperative word-wrap preference update that any component (hook or not) can call. */

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.

🟡 Medium hooks/useSettings.ts:188

updateClientSettings can overwrite a user's new wordWrap value with the older persisted value, causing the toggle to visibly revert. It reads the default snapshot before hydrateClientSettings() completes, while the in-flight hydration later replaces that snapshot; await hydration before applying the patch.

🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/web/src/hooks/useSettings.ts around line 188:

`updateClientSettings` can overwrite a user's new `wordWrap` value with the older persisted value, causing the toggle to visibly revert. It reads the default snapshot before `hydrateClientSettings()` completes, while the in-flight hydration later replaces that snapshot; await hydration before applying the patch.

export function updateClientSettings(patch: ClientSettingsPatch): void {
persistClientSettings({ ...getClientSettingsSnapshot(), ...patch });
}

/**
* Resolves once client settings have been read from disk.
*
Expand Down
Loading