Skip to content

Commit 1d27311

Browse files
committed
Merge remote-tracking branch 'origin/main' into merge-theme
# Conflicts: # src/components/Context1MCheckbox.tsx # src/components/Messages/ChatBarrier/StreamingBarrier.tsx # src/components/ProjectSidebar.tsx # src/components/RightSidebar/CodeReview/FileTree.tsx # src/components/RightSidebar/CodeReview/ReviewControls.tsx # src/components/RightSidebar/CodeReview/ReviewPanel.tsx # src/components/RightSidebar/ConsumerBreakdown.tsx # src/components/RightSidebar/CostsTab.tsx # src/components/RightSidebar/VerticalTokenMeter.tsx # src/components/SecretsModal.tsx # src/components/Tooltip.tsx # src/styles/globals.css
2 parents abeb044 + 3445162 commit 1d27311

File tree

6 files changed

+68
-68
lines changed

6 files changed

+68
-68
lines changed

bun.lock

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
"lru-cache": "^11.2.2",
2929
"markdown-it": "^14.1.0",
3030
"minimist": "^1.2.8",
31+
"rehype-harden": "^1.1.5",
3132
"source-map-support": "^0.5.21",
33+
"streamdown": "^1.4.0",
3234
"undici": "^7.16.0",
3335
"write-file-atomic": "^6.0.0",
3436
"ws": "^8.18.3",
@@ -91,10 +93,8 @@
9193
"react-dnd": "^16.0.1",
9294
"react-dnd-html5-backend": "^16.0.1",
9395
"react-dom": "^18.2.0",
94-
"react-markdown": "^10.1.0",
9596
"rehype-katex": "^7.0.1",
9697
"rehype-raw": "^7.0.0",
97-
"rehype-sanitize": "^6.0.0",
9898
"remark-gfm": "^4.0.1",
9999
"remark-math": "^6.0.0",
100100
"shiki": "^3.13.0",
@@ -1718,8 +1718,6 @@
17181718

17191719
"hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
17201720

1721-
"hast-util-sanitize": ["hast-util-sanitize@5.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "unist-util-position": "^5.0.0" } }, "sha512-3yTWghByc50aGS7JlGhk61SPenfE/p1oaFeNwkOOyrscaOkMGrcW9+Cy/QAIOBpZxP1yqDIzFMR0+Np0i0+usg=="],
1722-
17231721
"hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
17241722

17251723
"hast-util-to-jsx-runtime": ["hast-util-to-jsx-runtime@2.3.6", "", { "dependencies": { "@types/estree": "^1.0.0", "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "hast-util-whitespace": "^3.0.0", "mdast-util-mdx-expression": "^2.0.0", "mdast-util-mdx-jsx": "^3.0.0", "mdast-util-mdxjs-esm": "^2.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "style-to-js": "^1.0.0", "unist-util-position": "^5.0.0", "vfile-message": "^4.0.0" } }, "sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg=="],
@@ -2090,6 +2088,8 @@
20902088

20912089
"lru-cache": ["lru-cache@11.2.2", "", {}, "sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg=="],
20922090

2091+
"lucide-react": ["lucide-react@0.542.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-w3hD8/SQB7+lzU2r4VdFyzzOzKnUjTZIF/MQJGSSvni7Llewni4vuViRppfRAa2guOsY5k4jZyxw/i9DQHv+dw=="],
2092+
20932093
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
20942094

20952095
"magic-string": ["magic-string@0.30.19", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw=="],
@@ -2492,12 +2492,12 @@
24922492

24932493
"regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
24942494

2495+
"rehype-harden": ["rehype-harden@1.1.5", "", {}, "sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A=="],
2496+
24952497
"rehype-katex": ["rehype-katex@7.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/katex": "^0.16.0", "hast-util-from-html-isomorphic": "^2.0.0", "hast-util-to-text": "^4.0.0", "katex": "^0.16.0", "unist-util-visit-parents": "^6.0.0", "vfile": "^6.0.0" } }, "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA=="],
24962498

24972499
"rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
24982500

2499-
"rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="],
2500-
25012501
"release-zalgo": ["release-zalgo@1.0.0", "", { "dependencies": { "es6-error": "^4.0.1" } }, "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA=="],
25022502

25032503
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
@@ -2646,6 +2646,8 @@
26462646

26472647
"storybook": ["storybook@8.6.14", "", { "dependencies": { "@storybook/core": "8.6.14" }, "peerDependencies": { "prettier": "^2 || ^3" }, "optionalPeers": ["prettier"], "bin": { "sb": "./bin/index.cjs", "storybook": "./bin/index.cjs", "getstorybook": "./bin/index.cjs" } }, "sha512-sVKbCj/OTx67jhmauhxc2dcr1P+yOgz/x3h0krwjyMgdc5Oubvxyg4NYDZmzAw+ym36g/lzH8N0Ccp4dwtdfxw=="],
26482648

2649+
"streamdown": ["streamdown@1.4.0", "", { "dependencies": { "clsx": "^2.1.1", "katex": "^0.16.22", "lucide-react": "^0.542.0", "marked": "^16.2.1", "mermaid": "^11.11.0", "react-markdown": "^10.1.0", "rehype-harden": "^1.1.5", "rehype-katex": "^7.0.1", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-math": "^6.0.0", "shiki": "^3.12.2", "tailwind-merge": "^3.3.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0" } }, "sha512-ylhDSQ4HpK5/nAH9v7OgIIdGJxlJB2HoYrYkJNGrO8lMpnWuKUcrz/A8xAMwA6eILA27469vIavcOTjmxctrKg=="],
2650+
26492651
"string-length": ["string-length@5.0.1", "", { "dependencies": { "char-regex": "^2.0.0", "strip-ansi": "^7.0.1" } }, "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow=="],
26502652

26512653
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],

docs/AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ This ensures transparency about AI-generated contributions.
2424

2525
## PR Management
2626

27-
**Prefer to reuse existing PRs** by force-pushing to the same branch, even if the branch name becomes irrelevant. Avoid closing and recreating PRs unnecessarily - PR spam clutters the repository history.
27+
**Prefer to reuse existing PRs** by force-pushing to the same branch, even if the branch name becomes irrelevant. Avoid closing and recreating PRs unnecessarily - PR spam clutters the repository history. **Never close PRs without explicit user instruction.** Always force-push to the existing branch instead of creating new PRs.
2828

2929
After submitting or updating PRs, **always check merge status**:
3030

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@
6868
"lru-cache": "^11.2.2",
6969
"markdown-it": "^14.1.0",
7070
"minimist": "^1.2.8",
71+
"rehype-harden": "^1.1.5",
7172
"source-map-support": "^0.5.21",
73+
"streamdown": "^1.4.0",
7274
"undici": "^7.16.0",
7375
"write-file-atomic": "^6.0.0",
7476
"ws": "^8.18.3",
@@ -131,10 +133,8 @@
131133
"react-dnd": "^16.0.1",
132134
"react-dnd-html5-backend": "^16.0.1",
133135
"react-dom": "^18.2.0",
134-
"react-markdown": "^10.1.0",
135136
"rehype-katex": "^7.0.1",
136137
"rehype-raw": "^7.0.0",
137-
"rehype-sanitize": "^6.0.0",
138138
"remark-gfm": "^4.0.1",
139139
"remark-math": "^6.0.0",
140140
"shiki": "^3.13.0",

src/components/ChatInput.tsx

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -748,35 +748,33 @@ export const ChatInput: React.FC<ChatInputProps> = ({
748748
)}
749749
<div className="flex items-center">
750750
<ChatToggles modelString={preferredModel}>
751-
<div className="mr-3 flex h-[11px] items-center gap-1 @[700px]:[&_.help-indicator-wrapper]:hidden">
751+
<div className="mr-3 flex items-center gap-1.5">
752752
<ModelSelector
753753
ref={modelSelectorRef}
754754
value={preferredModel}
755755
onChange={setPreferredModel}
756756
recentModels={recentModels}
757757
onComplete={() => inputRef.current?.focus()}
758758
/>
759-
<span className="help-indicator-wrapper">
760-
<TooltipWrapper inline>
761-
<HelpIndicator>?</HelpIndicator>
762-
<Tooltip className="tooltip" align="left" width="wide">
763-
<strong>Click to edit</strong> or use{" "}
764-
{formatKeybind(KEYBINDS.OPEN_MODEL_SELECTOR)}
765-
<br />
766-
<br />
767-
<strong>Abbreviations:</strong>
768-
<br /><code>/model opus</code> - Claude Opus 4.1
769-
<br /><code>/model sonnet</code> - Claude Sonnet 4.5
770-
<br />
771-
<br />
772-
<strong>Full format:</strong>
773-
<br />
774-
<code>/model provider:model-name</code>
775-
<br />
776-
(e.g., <code>/model anthropic:claude-sonnet-4-5</code>)
777-
</Tooltip>
778-
</TooltipWrapper>
779-
</span>
759+
<TooltipWrapper inline>
760+
<HelpIndicator>?</HelpIndicator>
761+
<Tooltip className="tooltip" align="left" width="wide">
762+
<strong>Click to edit</strong> or use{" "}
763+
{formatKeybind(KEYBINDS.OPEN_MODEL_SELECTOR)}
764+
<br />
765+
<br />
766+
<strong>Abbreviations:</strong>
767+
<br /><code>/model opus</code> - Claude Opus 4.1
768+
<br /><code>/model sonnet</code> - Claude Sonnet 4.5
769+
<br />
770+
<br />
771+
<strong>Full format:</strong>
772+
<br />
773+
<code>/model provider:model-name</code>
774+
<br />
775+
(e.g., <code>/model anthropic:claude-sonnet-4-5</code>)
776+
</Tooltip>
777+
</TooltipWrapper>
780778
</div>
781779
</ChatToggles>
782780
<div className="max-@[700px]:hidden ml-auto flex items-center gap-1.5">
@@ -799,20 +797,18 @@ export const ChatInput: React.FC<ChatInputProps> = ({
799797
onChange={setMode}
800798
/>
801799
</div>
802-
<span className="help-indicator-wrapper">
803-
<TooltipWrapper inline>
804-
<HelpIndicator>?</HelpIndicator>
805-
<Tooltip className="tooltip" align="center" width="wide">
806-
<strong>Exec Mode:</strong> AI edits files and execute commands
807-
<br />
808-
<br />
809-
<strong>Plan Mode:</strong> AI proposes plans but does not edit files
810-
<br />
811-
<br />
812-
Toggle with: {formatKeybind(KEYBINDS.TOGGLE_MODE)}
813-
</Tooltip>
814-
</TooltipWrapper>
815-
</span>
800+
<TooltipWrapper inline>
801+
<HelpIndicator>?</HelpIndicator>
802+
<Tooltip className="tooltip" align="center" width="wide">
803+
<strong>Exec Mode:</strong> AI edits files and execute commands
804+
<br />
805+
<br />
806+
<strong>Plan Mode:</strong> AI proposes plans but does not edit files
807+
<br />
808+
<br />
809+
Toggle with: {formatKeybind(KEYBINDS.TOGGLE_MODE)}
810+
</Tooltip>
811+
</TooltipWrapper>
816812
</div>
817813
</div>
818814
</div>

src/components/Messages/MarkdownCore.tsx

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import React, { useMemo } from "react";
2-
import ReactMarkdown from "react-markdown";
3-
import type { PluggableList } from "unified";
2+
import { Streamdown } from "streamdown";
3+
import type { Pluggable } from "unified";
44
import remarkGfm from "remark-gfm";
55
import remarkMath from "remark-math";
66
import rehypeKatex from "rehype-katex";
77
import rehypeRaw from "rehype-raw";
8-
import rehypeSanitize, { defaultSchema } from "rehype-sanitize";
8+
import { harden } from "rehype-harden";
99
import "katex/dist/katex.min.css";
1010
import { normalizeMarkdown } from "./MarkdownStyles";
1111
import { markdownComponents } from "./MarkdownComponents";
@@ -16,24 +16,24 @@ interface MarkdownCoreProps {
1616
}
1717

1818
// Plugin arrays are defined at module scope to maintain stable references.
19-
// ReactMarkdown treats new array references as changes requiring full re-parse.
20-
const REMARK_PLUGINS = [remarkGfm, remarkMath];
21-
22-
// Sanitization schema: whitelist only safe HTML elements
23-
// This prevents XSS attacks while allowing <details>/<summary> toggles
24-
const SANITIZE_SCHEMA = {
25-
...defaultSchema,
26-
tagNames: [...(defaultSchema.tagNames ?? []), "details", "summary"],
27-
attributes: {
28-
...defaultSchema.attributes,
29-
details: ["open"], // Allow 'open' attribute for default-expanded state
30-
},
31-
};
19+
// Streamdown treats new array references as changes requiring full re-parse.
20+
const REMARK_PLUGINS: Pluggable[] = [
21+
[remarkGfm, {}],
22+
[remarkMath, { singleDollarTextMath: false }],
23+
];
3224

33-
const REHYPE_PLUGINS: PluggableList = [
34-
rehypeRaw, // Parse HTML elements
35-
[rehypeSanitize, SANITIZE_SCHEMA], // Sanitize to whitelist only
36-
rehypeKatex, // Render math (must be after sanitization)
25+
const REHYPE_PLUGINS: Pluggable[] = [
26+
rehypeRaw, // Parse HTML elements first
27+
[
28+
harden, // Sanitize after parsing raw HTML to prevent XSS
29+
{
30+
allowedImagePrefixes: ["*"],
31+
allowedLinkPrefixes: ["*"],
32+
defaultOrigin: undefined,
33+
allowDataImages: true,
34+
},
35+
],
36+
[rehypeKatex, { errorColor: "var(--color-muted-foreground)" }], // Render math
3737
];
3838

3939
/**
@@ -48,13 +48,15 @@ export const MarkdownCore = React.memo<MarkdownCoreProps>(({ content, children }
4848

4949
return (
5050
<>
51-
<ReactMarkdown
51+
<Streamdown
5252
components={markdownComponents}
5353
remarkPlugins={REMARK_PLUGINS}
5454
rehypePlugins={REHYPE_PLUGINS}
55+
parseIncompleteMarkdown={true}
56+
className="space-y-2" // Reduce from default space-y-4 (16px) to space-y-2 (8px)
5557
>
5658
{normalizedContent}
57-
</ReactMarkdown>
59+
</Streamdown>
5860
{children}
5961
</>
6062
);

src/components/Messages/MessageWindow.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ export const MessageWindow: React.FC<MessageWindowProps> = ({
122122
)}
123123
</div>
124124
</div>
125-
<div className="relative z-10 p-3" data-message-content>
125+
<div className="relative z-10 m-3" data-message-content>
126126
{showJson ? (
127127
<pre className="m-0 overflow-x-auto rounded-sm bg-black/30 p-2 font-mono text-[11px] leading-snug whitespace-pre-wrap text-neutral-300">
128128
{JSON.stringify(message, null, 2)}

0 commit comments

Comments
 (0)