diff --git a/.commandcode/taste/frontend/taste.md b/.commandcode/taste/frontend/taste.md new file mode 100644 index 00000000..b414b0ab --- /dev/null +++ b/.commandcode/taste/frontend/taste.md @@ -0,0 +1,13 @@ +# Frontend / Styling + +- Wants CSS refactored away from raw files joined by `@import` cascade order into a managed, tooling-based architecture (Sass/Less, or a Tailwind-plugin-style abstraction like `@tailwindcss/typography`). Confidence: 0.85 +- Strongly prefers semantic design tokens: every fontsize, padding, margin, and background should carry meaning (e.g. `--surface-raised`, `--text-body`) rather than hardcoded pixel values. Confidence: 0.9 +- Wants responsive typography/readability controlled via CSS variables + classNames across breakpoints (mobile / pc / lg screens), instead of scattered per-file media queries. Confidence: 0.85 +- Wants tokens and colors managed through a config layer that supports override and extension (theme config / Tailwind theme tokens). Confidence: 0.85 +- When defining conventions (token taxonomies, naming grammars, patterns), wants them grounded in and traceable to established industry design systems (e.g. Material 3, IBM Carbon, GitHub Primer, Atlassian, Apple HIG, Polaris, Ant Design) rather than invented ad hoc — asked to extend the semantic categories "from big-company styleguides you know". Confidence: 0.8 +- Inviolable rule (user-declared "不可打破的原则"): px values must be integers, preferably even — fractional px like `font-size: 12.5px` are rejected and rounded to nearest even (12.5→12, 13.5→14); values that genuinely can't be even (sub-pixel `letter-spacing: 0.5px`, `1.5px` borders, negative hot-zone margins) stay but must carry an inline flag for later re-inspection (e.g. `/* tc-review: 非偶数值,保留原因 */`). Confidence: 0.95 +- In styling refactors, tokenize ALL colors first as the initial round — including collapsing every `color-mix()` into a derived variable/token — before migrating other value categories (spacing, sizing, etc.). Confidence: 0.9 +- Color organization must be deliberately re-planned by proximity: similar colors grouped adjacent (e.g. paper family → ink family → depth family → functional colors → whites/shadows) so palette files read as clusters. Confidence: 0.9 +- z-index must always be controlled through semantic variables (named layer roles, e.g. column-local layers vs overlay layers with stacking-context audit notes), never bare numeric literals. Confidence: 0.95 +- Cares that finished styling modules (e.g. the `.tc-prose` typography module) are reusable in other Tailwind projects — asked about cross-project portability before confirming: values self-contained, zero-runtime, purely CSS-variable-driven forms (vendor copy → npm package → Tailwind `@plugin`) over build-tool-coupled ones (Sass mixins would become dead code once copied out). Confidence: 0.65 +- Styling-refactor acceptance runs on two tracks that must never be mixed: value-preserving steps (token renames, same-value re-pointing like `#b07d2e` → `--tc-depth-2`, deleting dead fallbacks) must prove computed-style equivalence (grep zero-hit lists + build-output comparison), while any value-altering fix (e.g. re-targeting the unsaved-state gold to depth-1) is logged as a registered visual change and moved to the screenshot-baseline track — ambiguous historical intent gets logged as a decision, never silently fixed. Confidence: 0.7 diff --git a/.commandcode/taste/taste.md b/.commandcode/taste/taste.md new file mode 100644 index 00000000..57d8fa07 --- /dev/null +++ b/.commandcode/taste/taste.md @@ -0,0 +1,6 @@ +# Taste + +## Communication +- Communicates in Simplified Chinese; respond in Chinese. Confidence: 0.7 +- When asked to react to external reviews (e.g. a GPT critique of its own spec artifacts), first fact-checks the review's claims against the codebase, then delivers a structured verdict — verified-valid points, points held with reservations, plus findings the review missed — and proposes concrete revisions instead of blindly accepting or dismissing. Confidence: 0.8 +- Also commissions adversarial re-audits of the assistant's own recent replies, via a second model ("@codex 你来阅读下这最近2条回复看下是否有不对的地方") — so every factual/numeric claim written into replies or artifacts may be re-checked later; on such an audit pass, re-verify each claim against the code (grep counts, file:line evidence, exact numbers not approximations), report findings classified by severity (substantive error vs inconsistency/omission), own mistakes plainly, propose concrete fixes to the artifacts, and apply corrections only after confirmation. Confidence: 0.75 diff --git a/.commandcode/taste/workflow/taste.md b/.commandcode/taste/workflow/taste.md new file mode 100644 index 00000000..6b79e112 --- /dev/null +++ b/.commandcode/taste/workflow/taste.md @@ -0,0 +1,7 @@ +# Workflow / Process + +- Uses OpenSpec for change management: formalize a design as a change (`pnpm exec openspec new change ""`), write artifacts in the tool's prescribed build order (proposal → specs delta → design → tasks), fetching per-artifact instructions via `openspec instructions --change ...`, and finish with `openspec validate --all --strict`. Confidence: 0.85 +- Wants change artifacts (design/tasks) grounded in verified code reality before being written — e.g. confirmed `theme.ts` dynamically builds `var(--d${N})`, enumerated every `.md-body` selector dependency in TS/CSS — rather than resting on discussion-level assumptions. Confidence: 0.75 +- Operates as a principles-then-review loop: states a few inviolable principles up front, delegates the remaining design judgment ("其他的你再看看"), and then wants the written/revised artifacts summarized and presented for his explicit confirmation before implementation/apply begins ("给我再看一眼变更,我来确认") — end the authoring phase by waiting, don't proceed into apply unbidden. Confidence: 0.85 +- When confirming a design before apply, wants concrete end-state previews — the finished module's form factor, the planned directory/file structure (e.g. the `tokens/` tree), and sample code — not prose summaries alone ("可以先把 tokens 的结构给我看吗"). Confidence: 0.7 +- When a batch of edits lands on the same artifact in parallel, don't trust the per-edit result snapshots (they can show stale/inconsistent state) — re-verify the file's final on-disk state (grep for each inserted marker/phrase, spot-read the long lines) before running validation or reporting completion; caught this twice (design.md, tasks.md) and corrected course. Confidence: 0.65 diff --git a/CLAUDE.md b/CLAUDE.md index 23f16edb..802e7080 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,7 +54,7 @@ Next.js **16** App Router project (React 19, TypeScript, Tailwind CSS **v4**), s - **shadcn/ui on Base UI, not Radix.** `components.json` uses the `base-rhea` style; primitives in `components/ui/` import from `@base-ui/react` (e.g. `@base-ui/react/button`). Don't reach for `@radix-ui/*` when editing or adding components. - **The full component kit is already vendored** in `components/ui/` (~60 components), including chat-oriented primitives: `message.tsx`, `message-scroller.tsx`, `bubble.tsx`, `attachment.tsx`, `marker.tsx`. Check for an existing component before adding or writing a new one. - **Tailwind v4, CSS-first config.** There is no tailwind.config file; theme tokens live as CSS variables in `app/globals.css`. Class merging goes through `cn()` in `lib/utils.ts`. -- **thread-chat 的手写样式** 是独立于 Tailwind 的一层:全部收敛在 `.tc` 作用域(手工命名空间的手稿风设计系统,语义类名、非原子类)。实体规则按功能区块拆在 `app/thread-chat/styles/*.css`,`app/thread-chat/thread-chat.css` 只是按**源码顺序** `@import` 它们的桶文件——改这里务必保持 `@import` 顺序(级联依赖它),且非相邻功能刻意拆成 `*-collapse/-stream/-extras` 等后缀文件以保序(如流式的 `.send.stop` 覆盖必须在 `composer.css` 之后)。设计 token(`--paper/--ink/--d1..d5/字体/尺寸`)的**单一来源**是 `styles/tokens.css`,`theme.ts` 的深度→变量映射依赖此处变量名。 +- **thread-chat 的手写样式** 是独立于 Tailwind 的一层:全部收敛在 `.tc` 作用域(手工命名空间的手稿风设计系统,语义类名、非原子类)。实体规则按功能区块拆在 `app/thread-chat/styles/*.css`,`app/thread-chat/thread-chat.css` 只是按**源码顺序** `@import` 它们的桶文件——改这里务必保持 `@import` 顺序(级联依赖它),且非相邻功能刻意拆成 `*-collapse/-stream/-extras` 等后缀文件以保序(如流式的 `.send.stop` 覆盖必须在 `composer.css` 之后)。设计 token 的单一入口是 `styles/tokens.css`,内部按序导入 `styles/tokens/`:primitive(`palette.css`)→ semantic(surface/content/border/depth/typography/prose/z-index)→ 派生色(`color-derived.css`);区块文件只能引用 semantic/contextual token,不能直引 primitive、写裸颜色或 `color-mix()`。`theme.ts` 的深度映射指向 `--tc-depth-*`。新增或迁移的 px 值必须为整数且尽量为偶数;确需保留亚像素时必须就近添加 `/* tc-review: 非偶数值,保留原因 */`。`.md-body` 只保留文本锚点 DOM 契约,正文排版由 `.tc-prose` 负责。 - Path aliases: `@/components`, `@/components/ui`, `@/lib`, `@/hooks` (see `components.json` and tsconfig.json). - Theming via `next-themes` through `components/theme-provider.tsx`, wired up in `app/layout.tsx` (dark mode toggles with the `d` key on the starter page). diff --git a/app/thread-chat/branching/assistant/anchored-assistant-body.tsx b/app/thread-chat/branching/assistant/anchored-assistant-body.tsx index 8532a382..ae68b2ad 100644 --- a/app/thread-chat/branching/assistant/anchored-assistant-body.tsx +++ b/app/thread-chat/branching/assistant/anchored-assistant-body.tsx @@ -1,5 +1,6 @@ "use client" +import type { MarkdownDensity } from "../../chat/message/markdown-body" import type { ConversationViewMessage, ThreadTreeState } from "../../core/types" import { WebResearchPanel } from "../../orchestration/overlays/web-research-panel" import { AnchoredMarkdown } from "./anchored-markdown" @@ -9,10 +10,12 @@ export function AnchoredAssistantBody({ state, message, onOpenThread, + density = "default", }: { state: ThreadTreeState message: ConversationViewMessage onOpenThread: (targetId: string, opts?: { keepSource?: boolean }) => void + density?: MarkdownDensity }) { const renderPlan = assistantPartRenderPlan(message) @@ -27,6 +30,7 @@ export function AnchoredAssistantBody({ msg={message} source={part.text} onOpenThread={onOpenThread} + density={density} /> ) } diff --git a/app/thread-chat/branching/assistant/anchored-markdown.tsx b/app/thread-chat/branching/assistant/anchored-markdown.tsx index 25242416..29dc793e 100644 --- a/app/thread-chat/branching/assistant/anchored-markdown.tsx +++ b/app/thread-chat/branching/assistant/anchored-markdown.tsx @@ -3,8 +3,11 @@ import React, { useCallback, useEffect, useReducer, useRef } from "react" import type { Message, ThreadTreeState } from "../../core/types" import { threadTitle } from "../../core/selectors" -import { dc } from "../../theme" -import { MarkdownBody } from "../../chat/message/markdown-body" +import { dc, dvar } from "../../theme" +import { + MarkdownBody, + type MarkdownDensity, +} from "../../chat/message/markdown-body" import { useSmoothText } from "../../chat/message/smooth-text" import { clearHighlights, @@ -25,6 +28,7 @@ export function AnchoredMarkdown({ source, insertAt, insert, + density = "default", }: { state: ThreadTreeState msg: Message @@ -34,6 +38,7 @@ export function AnchoredMarkdown({ /** 在流事件记录的正文字符偏移处插入工具活动;缺省时渲染普通单段 Markdown。 */ insertAt?: number insert?: React.ReactNode + density?: MarkdownDensity }) { const hostRef = useRef(null) // forksKey 只随 fork 的增删与编号变化——source 未变、仅新增 fork 时也能触发重绘 @@ -89,22 +94,29 @@ export function AnchoredMarkdown({ } } if (!markdownBody || !located) continue - const color = `color-mix(in srgb, var(--d${dc(fork.depth)}) 20%, transparent)` - paintRange(located.range, fork.threadId, color) + // 高亮底色统一走 CSS 派生 token(--tc-fc-mark 内做 20% 混色), + // 这里只注入 fork 深度的 contextual 变量(与 marks 的 fc-N 类同源) + paintRange(located.range, fork.threadId, "var(--tc-fc-mark)", { + "--fc": dvar(fork.depth), + }) const marks = markdownBody.querySelectorAll( `[data-text-anchor-mark="${cssEscape(fork.threadId)}"]` ) marks.forEach((mark) => { mark.setAttribute("data-fork-id", fork.threadId) - mark.classList.add("anchored-mark", `fc-${dc(fork.depth)}`) + mark.classList.add( + "anchored-mark", + "tc-fork-context", + `fc-${dc(fork.depth)}` + ) mark.title = `分支「${threadTitle(state, fork.threadId)}」· 点击打开 · ⌘点击保留本列在右侧打开` }) const last = marks[marks.length - 1] if (last) { const footnote = document.createElement("sup") - footnote.className = `fn-mark fc-${dc(fork.depth)}` + footnote.className = `fn-mark tc-fork-context fc-${dc(fork.depth)}` footnote.setAttribute("data-fork-id", fork.threadId) footnote.textContent = String(fork.num) last.after(footnote) @@ -147,6 +159,7 @@ export function AnchoredMarkdown({ ) : null} @@ -155,6 +168,7 @@ export function AnchoredMarkdown({ ) : null} diff --git a/app/thread-chat/branching/selection/bubble-shape.tsx b/app/thread-chat/branching/selection/bubble-shape.tsx index 61391f66..e026bc18 100644 --- a/app/thread-chat/branching/selection/bubble-shape.tsx +++ b/app/thread-chat/branching/selection/bubble-shape.tsx @@ -101,7 +101,7 @@ export function buildBubblePath({ * 纯形状(svg 背景层)。面板宽 W、高 H,尾巴另占 ah 高。 * dir="down":面板在上、尾巴朝下(顶点 y=H+ah); * dir="up" :整条 path 竖直翻转,面板落到下半、尾巴翻到上半(顶点 y=0)。 - * fill 默认 var(--ink),与气泡深底一致;shadow 用 feDropShadow 复刻原气泡阴影。 + * fill 默认 var(--tc-surface-ink),与气泡深底一致;shadow 用 feDropShadow 复刻原气泡阴影。 */ export function BubbleShape({ W, @@ -109,7 +109,7 @@ export function BubbleShape({ cx, geo, dir, - fill = "var(--ink)", + fill = "var(--tc-surface-ink)", shadow = true, }: { W: number diff --git a/app/thread-chat/branching/selection/selection-draft-guard.css b/app/thread-chat/branching/selection/selection-draft-guard.css index ceb6a340..f8aae4fa 100644 --- a/app/thread-chat/branching/selection/selection-draft-guard.css +++ b/app/thread-chat/branching/selection/selection-draft-guard.css @@ -23,13 +23,13 @@ position: absolute; top: calc(100% + 7px); left: 0; - font-family: var(--font-mono); + font-family: var(--tc-typography-family-code); font-size: 10px; line-height: 1; - color: #e8e2d4; + color: var(--tc-onink-secondary); text-shadow: - 0 1px 2px rgba(30, 26, 20, 0.7), - 0 0 6px rgba(30, 26, 20, 0.5); + 0 1px 2px var(--tc-shadow-hint-strong), + 0 0 6px var(--tc-shadow-hint-soft); white-space: nowrap; pointer-events: none; animation: tc-fade-in 0.12s ease; @@ -43,8 +43,8 @@ .tc .sb-confirm-mask { position: fixed; inset: 0; - z-index: 70; - background: rgba(30, 26, 20, 0.32); + z-index: var(--tc-z-confirm); + background: var(--tc-shadow-confirm-mask); display: flex; align-items: center; justify-content: center; @@ -52,25 +52,25 @@ } .tc .sb-confirm { width: min(340px, calc(100vw - 48px)); - background: var(--paper); - color: var(--ink); - border: 1px solid color-mix(in srgb, var(--ink) 18%, transparent); + background: var(--tc-surface-base); + color: var(--tc-content-primary); + border: 1px solid var(--tc-ink-confirm-border); border-radius: 12px; padding: 18px 18px 16px; - box-shadow: 0 12px 32px rgba(30, 26, 20, 0.28); + box-shadow: 0 12px 32px var(--tc-shadow-confirm); animation: tc-pop 0.14s ease; } .tc .sb-confirm-title { - font-family: var(--font-ui); - font-size: 15px; + font-family: var(--tc-typography-family-ui); + font-size: 16px; font-weight: 700; margin-bottom: 8px; } .tc .sb-confirm-body { - font-family: var(--font-read); - font-size: 13px; + font-family: var(--tc-typography-family-read); + font-size: 14px; line-height: 1.55; - color: var(--ink-soft); + color: var(--tc-content-secondary); margin-bottom: 16px; } .tc .sb-confirm-actions { @@ -83,26 +83,26 @@ cursor: pointer; border-radius: 8px; padding: 7px 14px; - font-size: 13px; + font-size: 14px; font-weight: 600; - font-family: var(--font-ui); + font-family: var(--tc-typography-family-ui); } .tc .sb-confirm-actions button:focus-visible { - outline: 2px solid var(--ink); + outline: 2px solid var(--tc-content-primary); outline-offset: 1px; } .tc .sb-confirm-actions button.ghost { background: transparent; - color: var(--ink-soft); - border: 1px solid color-mix(in srgb, var(--ink) 25%, transparent); + color: var(--tc-content-secondary); + border: 1px solid var(--tc-ink-confirm-action-border); } .tc .sb-confirm-actions button.ghost:hover { - background: var(--paper-2); - color: var(--ink); + background: var(--tc-surface-raised); + color: var(--tc-content-primary); } .tc .sb-confirm-actions button.danger { - background: var(--d3); - color: #fff; + background: var(--tc-depth-3); + color: var(--tc-content-on-accent); } .tc .sb-confirm-actions button.danger:hover { filter: brightness(1.08); diff --git a/app/thread-chat/branching/selection/text-anchor.ts b/app/thread-chat/branching/selection/text-anchor.ts index 53e668cd..a41b3c80 100644 --- a/app/thread-chat/branching/selection/text-anchor.ts +++ b/app/thread-chat/branching/selection/text-anchor.ts @@ -441,7 +441,10 @@ function isHighlightableTextNode(node: Text): boolean { export function paintRange( range: Range, id: string, - color = "rgba(255, 214, 0, 0.4)" + /** 高亮底色:CSS 值或变量引用(如 "var(--tc-fc-mark)"),不内置颜色字面量 */ + color: string, + /** 需要一并写到高亮 span 上的 CSS 变量(contextual 注入,如 { "--fc": dvar(depth) }) */ + vars?: Record ): boolean { if (range.collapsed) return false const nodes = collectTextNodes(range) @@ -467,6 +470,12 @@ export function paintRange( const span = target.ownerDocument.createElement("span") span.setAttribute(MARK_ATTR, id) + if (vars?.["--fc"]) span.classList.add("tc-fork-context") + if (vars) { + for (const [name, value] of Object.entries(vars)) { + span.style.setProperty(name, value) + } + } span.style.background = color span.style.borderRadius = "2px" span.style.color = "inherit" @@ -513,11 +522,11 @@ function cssEscape(value: string): string { * const anchor = describeRange(root, range) // { quote, position } * if (anchor) save(id, anchor) // JSON.stringify 存起来 * - * // 2) 恢复高亮:页面(可能已漂移)加载后,把锚点找回来重新绘制 + * 2) 恢复高亮:页面(可能已漂移)加载后,把锚点找回来重新绘制 * for (const { id, anchor } of load()) { * const hit = locateAnchor(root, anchor) // 三层降级 position/exact/fuzzy * if (hit) { - * paintRange(hit.range, id) + * paintRange(hit.range, id, "var(--tc-fc-mark)", { "--fc": dvar(depth) }) * console.log(id, hit.strategy, hit.score) // 'fuzzy' 时 score < 1 * } else { * console.warn(id, '内容改动过大,判定丢失') diff --git a/app/thread-chat/chat/message/markdown-body.tsx b/app/thread-chat/chat/message/markdown-body.tsx index 7a623714..11e9733c 100644 --- a/app/thread-chat/chat/message/markdown-body.tsx +++ b/app/thread-chat/chat/message/markdown-body.tsx @@ -6,7 +6,8 @@ * 关键约束:整个组件按 source 用 memo 记忆——source 不变时绝不重渲染。 * 这样 anchored-markdown 在渲染后「手绘」到 .md-body 里的锚点高亮 / 脚注上标, * 不会被 React 的 reconcile 抹掉(详见 anchored-markdown 的绘制 effect 注释)。 - * 渲染进一个稳定容器 `
`,即锚点定位的坐标系容器。 + * 渲染进稳定容器 `
`:md-body 是锚点坐标契约, + * tc-prose 承担正文排版。 */ import React, { @@ -27,6 +28,7 @@ import remarkGfm from "remark-gfm" import { Check, Copy } from "lucide-react" import { ShikiCode } from "@/components/markdown/shiki-code" +import { cn } from "@/lib/utils" import { createMarkdownSettlementBatch, markdownSettlementRevision, @@ -92,7 +94,7 @@ function CodeBlock({ ) return ( -
+
{lang ? (
{lang} @@ -150,13 +152,17 @@ const components: Components = { code: MarkdownCode, } +export type MarkdownDensity = "default" | "compact" + export const MarkdownBody = memo(function MarkdownBody({ source, streaming = false, + density = "default", onContentSettled, }: { source: string streaming?: boolean + density?: MarkdownDensity onContentSettled?: (revision: number) => void }) { const batch = useMemo( @@ -189,7 +195,10 @@ export const MarkdownBody = memo(function MarkdownBody({ return (
diff --git a/app/thread-chat/orchestration/artifacts/markdown-artifact-card.tsx b/app/thread-chat/orchestration/artifacts/markdown-artifact-card.tsx index 09dd51e8..9d88dcce 100644 --- a/app/thread-chat/orchestration/artifacts/markdown-artifact-card.tsx +++ b/app/thread-chat/orchestration/artifacts/markdown-artifact-card.tsx @@ -35,7 +35,7 @@ export function MarkdownArtifactProgressCard({ return (
onOpen(artifact.id)} > diff --git a/app/thread-chat/orchestration/artifacts/project-panel.tsx b/app/thread-chat/orchestration/artifacts/project-panel.tsx index 43b692a1..3971ad28 100644 --- a/app/thread-chat/orchestration/artifacts/project-panel.tsx +++ b/app/thread-chat/orchestration/artifacts/project-panel.tsx @@ -160,7 +160,11 @@ export function ProjectPanel({ .filter((artifact) => { const query = artifactQuery.trim().toLowerCase() if (!query) return true - return [artifact.title, artifact.kind, artifact.sourceThreadTitle ?? ""] + return [ + artifact.title, + artifact.kind, + artifact.sourceThreadTitle ?? "", + ] .join(" ") .toLowerCase() .includes(query) @@ -168,7 +172,10 @@ export function ProjectPanel({ [artifactQuery, artifacts] ) const sortedFiles = useMemo( - () => [...files].sort((left, right) => right.addedAt.localeCompare(left.addedAt)), + () => + [...files].sort((left, right) => + right.addedAt.localeCompare(left.addedAt) + ), [files] ) @@ -277,7 +284,11 @@ export function ProjectPanel({
-
+
{selectedArtifact.kind === "markdown" && ( - + )} {selectedArtifact.kind === "code" && (
{selectedArtifact.content}
@@ -552,7 +576,8 @@ export function ProjectPanel({
PROJECT ARTIFACTS

整个 Project 的持久化成果

- 包含根 Thread 和所有 Fork 产生的 Artifact;仅发现与查看,不会自动注入无关 Thread。 + 包含根 Thread 和所有 Fork 产生的 + Artifact;仅发现与查看,不会自动注入无关 Thread。

@@ -569,7 +594,8 @@ export function ProjectPanel({ 还没有 Artifact - 在任意 Thread 中生成 Markdown、Code 或 Note 后会出现在这里。 + 在任意 Thread 中生成 Markdown、Code 或 Note + 后会出现在这里。
) : ( diff --git a/app/thread-chat/orchestration/artifacts/store-bound-project-panel.tsx b/app/thread-chat/orchestration/artifacts/store-bound-project-panel.tsx index bcd3d291..c3b88b2a 100644 --- a/app/thread-chat/orchestration/artifacts/store-bound-project-panel.tsx +++ b/app/thread-chat/orchestration/artifacts/store-bound-project-panel.tsx @@ -8,22 +8,28 @@ import type { ConversationCommands } from "../../net/commands/conversation-comma import { ProjectPanel } from "./project-panel" function findMessageElement(messageId: string): HTMLElement | null { - return [...document.querySelectorAll("[data-thread-chat-message-id]")].find( - (element) => element.dataset.threadChatMessageId === messageId - ) ?? null + return ( + [ + ...document.querySelectorAll( + "[data-thread-chat-message-id]" + ), + ].find((element) => element.dataset.threadChatMessageId === messageId) ?? + null + ) } function revealMessage(messageId: string, attempt = 0) { const element = findMessageElement(messageId) if (!element) { - if (attempt < 8) window.setTimeout(() => revealMessage(messageId, attempt + 1), 60) + if (attempt < 8) + window.setTimeout(() => revealMessage(messageId, attempt + 1), 60) return } element.scrollIntoView({ behavior: "smooth", block: "center" }) element.animate( [ { backgroundColor: "transparent" }, - { backgroundColor: "color-mix(in srgb, currentColor 8%, transparent)" }, + { backgroundColor: "var(--tc-ink-hover)" }, { backgroundColor: "transparent" }, ], { duration: 1600, easing: "ease-out" } diff --git a/app/thread-chat/orchestration/canvas/canvas-expand.tsx b/app/thread-chat/orchestration/canvas/canvas-expand.tsx index 97a97a9d..70dd97fd 100644 --- a/app/thread-chat/orchestration/canvas/canvas-expand.tsx +++ b/app/thread-chat/orchestration/canvas/canvas-expand.tsx @@ -71,6 +71,7 @@ export function CanvasExpand({ state={state} message={assistantMessage} onOpenThread={(id) => actions.focusThread(id)} + density="compact" /> ) : null } diff --git a/app/thread-chat/orchestration/canvas/canvas-node.tsx b/app/thread-chat/orchestration/canvas/canvas-node.tsx index 4a8d406a..5b20ba4c 100644 --- a/app/thread-chat/orchestration/canvas/canvas-node.tsx +++ b/app/thread-chat/orchestration/canvas/canvas-node.tsx @@ -57,10 +57,10 @@ export const CanvasCard = memo(function CanvasCard({ }: NodeProps) { return (
- n.data.depth > 0 ? `fc-${dc(n.data.depth)}` : "" + n.data.depth > 0 ? `tc-fork-context fc-${dc(n.data.depth)}` : "" export interface ThreadCanvasProps { store: ThreadTreeReadableStore @@ -193,7 +193,7 @@ function CanvasFlow({ pannable zoomable nodeClassName={minimapNodeClass} - maskColor="rgba(245, 242, 234, 0.75)" + maskColor="var(--tc-canvas-minimap-mask)" />