Skip to content

Commit 8554d6b

Browse files
committed
Merge branch 'main' into shadcnify
2 parents a0573c2 + 3445162 commit 8554d6b

File tree

15 files changed

+94
-73
lines changed

15 files changed

+94
-73
lines changed

bun.lock

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
"@radix-ui/react-separator": "^1.1.7",
1414
"@radix-ui/react-slot": "^1.2.3",
1515
"@radix-ui/react-tabs": "^1.1.13",
16-
"@radix-ui/react-toggle": "^1.1.10",
1716
"@radix-ui/react-toggle-group": "^1.1.11",
1817
"@radix-ui/react-tooltip": "^1.2.8",
1918
"ai": "^5.0.72",
@@ -27,10 +26,11 @@
2726
"express": "^5.1.0",
2827
"jsonc-parser": "^3.3.1",
2928
"lru-cache": "^11.2.2",
30-
"lucide-react": "^0.546.0",
3129
"markdown-it": "^14.1.0",
3230
"minimist": "^1.2.8",
31+
"rehype-harden": "^1.1.5",
3332
"source-map-support": "^0.5.21",
33+
"streamdown": "^1.4.0",
3434
"undici": "^7.16.0",
3535
"write-file-atomic": "^6.0.0",
3636
"ws": "^8.18.3",
@@ -93,10 +93,8 @@
9393
"react-dnd": "^16.0.1",
9494
"react-dnd-html5-backend": "^16.0.1",
9595
"react-dom": "^18.2.0",
96-
"react-markdown": "^10.1.0",
9796
"rehype-katex": "^7.0.1",
9897
"rehype-raw": "^7.0.0",
99-
"rehype-sanitize": "^6.0.0",
10098
"remark-gfm": "^4.0.1",
10199
"remark-math": "^6.0.0",
102100
"shiki": "^3.13.0",
@@ -1720,8 +1718,6 @@
17201718

17211719
"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=="],
17221720

1723-
"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=="],
1724-
17251721
"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=="],
17261722

17271723
"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=="],
@@ -2092,7 +2088,7 @@
20922088

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

2095-
"lucide-react": ["lucide-react@0.546.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Z94u6fKT43lKeYHiVyvyR8fT7pwCzDu7RyMPpTvh054+xahSgj4HFQ+NmflvzdXsoAjYGdCguGaFKYuvq0ThCQ=="],
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=="],
20962092

20972093
"lz-string": ["lz-string@1.5.0", "", { "bin": { "lz-string": "bin/bin.js" } }, "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ=="],
20982094

@@ -2496,12 +2492,12 @@
24962492

24972493
"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=="],
24982494

2495+
"rehype-harden": ["rehype-harden@1.1.5", "", {}, "sha512-JrtBj5BVd/5vf3H3/blyJatXJbzQfRT9pJBmjafbTaPouQCAKxHwRyCc7dle9BXQKxv4z1OzZylz/tNamoiG3A=="],
2496+
24992497
"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=="],
25002498

25012499
"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=="],
25022500

2503-
"rehype-sanitize": ["rehype-sanitize@6.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-sanitize": "^5.0.0" } }, "sha512-CsnhKNsyI8Tub6L4sm5ZFsme4puGfc6pYylvXo1AeqaGbjOYyzNv3qZPwvs0oMJ39eryyeOdmxwUIo94IpEhqg=="],
2504-
25052501
"release-zalgo": ["release-zalgo@1.0.0", "", { "dependencies": { "es6-error": "^4.0.1" } }, "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA=="],
25062502

25072503
"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=="],
@@ -2650,6 +2646,8 @@
26502646

26512647
"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=="],
26522648

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+
26532651
"string-length": ["string-length@5.0.1", "", { "dependencies": { "char-regex": "^2.0.0", "strip-ansi": "^7.0.1" } }, "sha512-9Ep08KAMUn0OadnVaBuRdE2l615CQ508kr0XMadjClfYpdCyvrbFp6Taebo8yyxokQ4viUd/xPPUA4FGgUa0ow=="],
26542652

26552653
"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
@@ -70,7 +70,9 @@
7070
"lucide-react": "^0.546.0",
7171
"markdown-it": "^14.1.0",
7272
"minimist": "^1.2.8",
73+
"rehype-harden": "^1.1.5",
7374
"source-map-support": "^0.5.21",
75+
"streamdown": "^1.4.0",
7476
"undici": "^7.16.0",
7577
"write-file-atomic": "^6.0.0",
7678
"ws": "^8.18.3",
@@ -133,10 +135,8 @@
133135
"react-dnd": "^16.0.1",
134136
"react-dnd-html5-backend": "^16.0.1",
135137
"react-dom": "^18.2.0",
136-
"react-markdown": "^10.1.0",
137138
"rehype-katex": "^7.0.1",
138139
"rehype-raw": "^7.0.0",
139-
"rehype-sanitize": "^6.0.0",
140140
"remark-gfm": "^4.0.1",
141141
"remark-math": "^6.0.0",
142142
"shiki": "^3.13.0",

src/components/Context1MCheckbox.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,7 @@ export const Context1MCheckbox: React.FC<Context1MCheckboxProps> = ({ modelStrin
1818
return (
1919
<div className="ml-2 flex items-center gap-1.5">
2020
<label className="text-foreground flex cursor-pointer items-center gap-1 truncate text-[10px] select-none hover:text-white">
21-
<input
22-
type="checkbox"
23-
checked={use1M}
24-
onChange={(e) => setUse1M(e.target.checked)}
25-
className="border-border-light bg-dark hover:border-accent checked:bg-accent checked:border-accent relative m-0 h-[11px] w-3 cursor-pointer appearance-none rounded-sm border checked:after:absolute checked:after:top-0 checked:after:left-[3px] checked:after:h-[6px] checked:after:w-1 checked:after:rotate-45 checked:after:border-r-[1.5px] checked:after:border-b-[1.5px] checked:after:border-solid checked:after:border-white checked:after:content-['']"
26-
/>
21+
<input type="checkbox" checked={use1M} onChange={(e) => setUse1M(e.target.checked)} />
2722
1M Context
2823
</label>
2924
<Tooltip>

src/components/Messages/ChatBarrier/StreamingBarrier.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ export const StreamingBarrier: React.FC<StreamingBarrierProps> = ({
2323
{tokenCount !== undefined && (
2424
<span className="text-assistant-border font-mono text-[11px] whitespace-nowrap select-none">
2525
~{tokenCount.toLocaleString()} tokens
26-
{tps !== undefined && tps > 0 && (
27-
<span className="text-text-dim ml-1">@ {tps} t/s</span>
28-
)}
26+
{tps !== undefined && tps > 0 && <span className="text-dim ml-1">@ {tps} t/s</span>}
2927
</span>
3028
)}
3129
</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
@@ -124,7 +124,7 @@ export const MessageWindow: React.FC<MessageWindowProps> = ({
124124
)}
125125
</div>
126126
</div>
127-
<div className="relative z-10 p-3" data-message-content>
127+
<div className="relative z-10 m-3" data-message-content>
128128
{showJson ? (
129129
<pre className="text-light m-0 overflow-x-auto rounded-sm bg-black/30 p-2 font-mono text-[11px] leading-snug whitespace-pre-wrap">
130130
{JSON.stringify(message, null, 2)}

src/components/ProjectSidebar.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ const ProjectDragLayer: React.FC = () => {
137137
<div className="pointer-events-none fixed inset-0 z-[9999] cursor-grabbing">
138138
<div style={{ transform: `translate(${currentOffset.x + 10}px, ${currentOffset.y + 10}px)` }}>
139139
<div className="bg-hover/95 text-foreground border-l-accent flex w-fit max-w-72 min-w-44 items-center rounded border-l-[3px] px-3 py-1.5 shadow-[0_6px_24px_rgba(0,0,0,0.4)]">
140-
<span className="text-text-dim mr-1.5 text-xs"></span>
140+
<span className="text-dim mr-1.5 text-xs"></span>
141141
<span className="text-muted mr-2 text-[10px]"></span>
142142
<div className="min-w-0 flex-1">
143143
<div className="text-foreground truncate text-sm font-medium tracking-[0.2px]">
@@ -482,7 +482,7 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
482482
<span
483483
data-drag-handle
484484
aria-hidden
485-
className="text-text-dim mr-1.5 cursor-grab text-xs opacity-0 transition-opacity duration-150 select-none"
485+
className="text-dim mr-1.5 cursor-grab text-xs opacity-0 transition-opacity duration-150 select-none"
486486
>
487487
488488
</span>
@@ -596,12 +596,12 @@ const ProjectSidebarInner: React.FC<ProjectSidebarProps> = ({
596596
>
597597
<div className="flex items-center gap-1.5">
598598
<span>Older than {formatOldWorkspaceThreshold()}</span>
599-
<span className="text-text-dim font-normal">
599+
<span className="text-dim font-normal">
600600
({old.length})
601601
</span>
602602
</div>
603603
<span
604-
className="arrow text-text-dim text-[11px] transition-transform duration-200 ease-in-out"
604+
className="arrow text-dim text-[11px] transition-transform duration-200 ease-in-out"
605605
style={{
606606
transform: showOldWorkspaces
607607
? "rotate(90deg)"

src/components/RightSidebar/CodeReview/FileTree.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,8 @@ const TreeNodeContent: React.FC<{
146146
className={cn(
147147
"flex-1",
148148
isFullyRead &&
149-
"text-text-dim line-through [text-decoration-color:var(--color-read)] [text-decoration-thickness:2px]",
150-
isUnknownState && !isFullyRead && "text-text-dim",
149+
"text-dim line-through [text-decoration-color:var(--color-read)] [text-decoration-thickness:2px]",
150+
isUnknownState && !isFullyRead && "text-dim",
151151
!isFullyRead && !isUnknownState && "text-muted"
152152
)}
153153
>
@@ -181,8 +181,8 @@ const TreeNodeContent: React.FC<{
181181
className={cn(
182182
"flex-1",
183183
isFullyRead &&
184-
"text-text-dim line-through [text-decoration-color:var(--color-read)] [text-decoration-thickness:2px]",
185-
isUnknownState && !isFullyRead && "text-text-dim",
184+
"text-dim line-through [text-decoration-color:var(--color-read)] [text-decoration-thickness:2px]",
185+
isUnknownState && !isFullyRead && "text-dim",
186186
!isFullyRead && !isUnknownState && "text-foreground"
187187
)}
188188
>

src/components/RightSidebar/CodeReview/ReviewControls.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ export const ReviewControls: React.FC<ReviewControlsProps> = ({
9393
onBlur={handleBaseBlur}
9494
onKeyDown={handleBaseKeyDown}
9595
placeholder="HEAD, main, etc."
96-
className="bg-dark text-foreground border-border-medium hover:border-accent focus:border-accent placeholder:text-text-dim w-36 rounded border px-2 py-1 font-mono text-[11px] transition-[border-color] duration-200 focus:outline-none"
96+
className="bg-dark text-foreground border-border-medium hover:border-accent focus:border-accent placeholder:text-dim w-36 rounded border px-2 py-1 font-mono text-[11px] transition-[border-color] duration-200 focus:outline-none"
9797
/>
9898
<datalist id="base-suggestions">
9999
<option value="HEAD" />

0 commit comments

Comments
 (0)