diff --git a/backend/internal/service/analytics.go b/backend/internal/service/analytics.go index 3f3dac780..6fff6f9a8 100644 --- a/backend/internal/service/analytics.go +++ b/backend/internal/service/analytics.go @@ -235,7 +235,7 @@ func (s *Service) decorateAPICallLogs(logs []model.ApiCallLog) error { taskIDs := make([]string, 0, len(logs)) seenTaskIDs := make(map[string]struct{}, len(logs)) for _, log := range logs { - if log.BillingOrderID != "" { + if log.Billable && log.BillingOrderID != "" { if _, exists := seenBillingOrderIDs[log.BillingOrderID]; !exists { seenBillingOrderIDs[log.BillingOrderID] = struct{}{} billingOrderIDs = append(billingOrderIDs, log.BillingOrderID) @@ -277,13 +277,15 @@ func (s *Service) decorateAPICallLogs(logs []model.ApiCallLog) error { logs[index].UserDisplayName = user.DisplayName logs[index].UserAccount = user.Username } - if order, exists := billingOrderByID[logs[index].BillingOrderID]; exists && order.UserID == logs[index].UserID { - logs[index].BillingAvailable = true - logs[index].BillingStatus = order.Status - if order.Status == model.BillingStatusSettled { - logs[index].BillingAmount = order.ActualAmountMicrocredits - } else if order.Status != model.BillingStatusRefunded { - logs[index].BillingAmount = order.ReservedAmountMicrocredits + if logs[index].Billable { + if order, exists := billingOrderByID[logs[index].BillingOrderID]; exists && order.UserID == logs[index].UserID { + logs[index].BillingAvailable = true + logs[index].BillingStatus = order.Status + if order.Status == model.BillingStatusSettled { + logs[index].BillingAmount = order.ActualAmountMicrocredits + } else if order.Status != model.BillingStatusRefunded { + logs[index].BillingAmount = order.ReservedAmountMicrocredits + } } } if task, exists := taskByID[logs[index].TaskID]; exists && task.UserID == logs[index].UserID { diff --git a/backend/internal/service/api_log_billing_test.go b/backend/internal/service/api_log_billing_test.go index 5a739d3cd..cc507a396 100644 --- a/backend/internal/service/api_log_billing_test.go +++ b/backend/internal/service/api_log_billing_test.go @@ -37,12 +37,13 @@ func TestDecorateAPICallLogsUsesBillingOrderSnapshot(t *testing.T) { } logs := []model.ApiCallLog{ - {ID: "log-settled", UserID: "user-1", BillingOrderID: "settled", Capability: "text"}, - {ID: "log-reserved", UserID: "user-1", BillingOrderID: "reserved", Capability: "text"}, - {ID: "log-uncertain", UserID: "user-1", BillingOrderID: "uncertain", Capability: "text"}, - {ID: "log-refunded", UserID: "user-1", BillingOrderID: "refunded", Capability: "text"}, - {ID: "log-missing", UserID: "user-1", BillingOrderID: "missing", Capability: "text"}, - {ID: "log-other-user", UserID: "user-1", BillingOrderID: "other-user", Capability: "text"}, + {ID: "log-settled", UserID: "user-1", BillingOrderID: "settled", Capability: "text", Billable: true}, + {ID: "log-reserved", UserID: "user-1", BillingOrderID: "reserved", Capability: "text", Billable: true}, + {ID: "log-uncertain", UserID: "user-1", BillingOrderID: "uncertain", Capability: "text", Billable: true}, + {ID: "log-refunded", UserID: "user-1", BillingOrderID: "refunded", Capability: "text", Billable: true}, + {ID: "log-missing", UserID: "user-1", BillingOrderID: "missing", Capability: "text", Billable: true}, + {ID: "log-other-user", UserID: "user-1", BillingOrderID: "other-user", Capability: "text", Billable: true}, + {ID: "log-download", UserID: "user-1", BillingOrderID: "settled", Capability: "image", RequestKind: "download", Billable: false}, } svc := &Service{repo: repository.New(db)} @@ -56,6 +57,7 @@ func TestDecorateAPICallLogsUsesBillingOrderSnapshot(t *testing.T) { assertBillingSnapshot(t, logs[3], true, model.BillingStatusRefunded, 0) assertBillingSnapshot(t, logs[4], false, "", 0) assertBillingSnapshot(t, logs[5], false, "", 0) + assertBillingSnapshot(t, logs[6], false, "", 0) } func assertBillingSnapshot(t *testing.T, log model.ApiCallLog, available bool, status model.BillingStatus, amount int64) { diff --git a/web/src/components/canvas/canvas-appearance-controls.tsx b/web/src/components/canvas/canvas-appearance-controls.tsx index c4faa28ad..51bd42e6e 100644 --- a/web/src/components/canvas/canvas-appearance-controls.tsx +++ b/web/src/components/canvas/canvas-appearance-controls.tsx @@ -12,8 +12,8 @@ import { } from "@/lib/canvas/canvas-appearance"; import type { CanvasBackgroundMode, CanvasColorTheme, CanvasTheme } from "@/lib/canvas-theme"; -const LIGHT_PRESETS = ["#EDEEEE", "#F3DCE5", "#EEE5D8", "#DFE9E6"]; -const DARK_PRESETS = ["#262626", "#30272B", "#302D26", "#25302E"]; +const LIGHT_PRESETS = ["#F0F0F0", "#F3DCE5", "#EEE5D8", "#DFE9E6"]; +const DARK_PRESETS = ["#000000", "#30272B", "#302D26", "#25302E"]; export function CanvasAppearanceControls({ appearance, diff --git a/web/src/lib/canvas-theme.ts b/web/src/lib/canvas-theme.ts index f8446ce98..09a8a01db 100644 --- a/web/src/lib/canvas-theme.ts +++ b/web/src/lib/canvas-theme.ts @@ -4,9 +4,9 @@ export type CanvasBackgroundMode = "dots" | "lines" | "blank"; export const canvasThemes = { light: { canvas: { - background: "#edeeee", - dot: "rgba(15,23,42,.20)", - line: "rgba(15,23,42,.15)", + background: "#f0f0f0", + dot: "rgba(0,0,0,.80)", + line: "rgba(0,0,0,.80)", selectionFill: "rgba(17,17,17,.10)", }, node: { @@ -70,9 +70,9 @@ export const canvasThemes = { }, dark: { canvas: { - background: "#262626", - dot: "rgba(205,205,205,.28)", - line: "rgba(205,205,205,.28)", + background: "#000000", + dot: "rgba(175,175,175,.80)", + line: "rgba(175,175,175,.80)", selectionFill: "rgba(255,255,255,.12)", }, node: { diff --git a/web/src/lib/canvas/canvas-appearance.ts b/web/src/lib/canvas/canvas-appearance.ts index 9936e563f..374ea7d16 100644 --- a/web/src/lib/canvas/canvas-appearance.ts +++ b/web/src/lib/canvas/canvas-appearance.ts @@ -27,10 +27,12 @@ export type ResolvedCanvasAppearance = { grid: string; }; +export const DEFAULT_CANVAS_BACKGROUND_MODE: CanvasBackgroundMode = "dots"; + const CANVAS_APPEARANCE_DEFAULT_KEY = "infinite-canvas:canvas-appearance-default"; const HEX_COLOR_PATTERN = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i; -const CUSTOM_GRID_COLOR = "#B6B3B3"; -const CUSTOM_GRID_OPACITY = 70; +const CUSTOM_GRID_COLOR: Record = { light: "#000000", dark: "#AFAFAF" }; +const CUSTOM_GRID_OPACITY = 80; export function canvasAppearanceForTheme(theme: CanvasColorTheme, previous?: CanvasAppearance): CanvasAppearance { return previous?.custom?.baseTheme === theme ? { mode: theme, custom: previous.custom } : { mode: theme }; @@ -43,7 +45,7 @@ export function customCanvasAppearanceFromTheme(theme: CanvasColorTheme): Canvas baseTheme: theme, backgroundColor: canvasThemes[theme].canvas.background.toUpperCase(), backgroundBrightness: 0, - gridColor: CUSTOM_GRID_COLOR, + gridColor: CUSTOM_GRID_COLOR[theme], gridOpacity: CUSTOM_GRID_OPACITY, }, }; diff --git a/web/src/pages/admin/components/api-log-detail-drawer.tsx b/web/src/pages/admin/components/api-log-detail-drawer.tsx index ebc848d21..bb50dfbb4 100644 --- a/web/src/pages/admin/components/api-log-detail-drawer.tsx +++ b/web/src/pages/admin/components/api-log-detail-drawer.tsx @@ -70,6 +70,8 @@ function LogDetail({ log, querying, onQueryProviderTask }: { log: ApiCallLog; qu ], ["渠道 / 模型", `${log.channelName || "未记录渠道"} / ${log.model || "未识别模型"}`], ["能力", capabilityText(log.capability)], + ["请求阶段", requestKindText(log.requestKind)], + ["计费属性", log.billable ? "计费调用" : "不计费"], ["总耗时", formatDuration(log.durationMs)], ["视频轮询", log.capability === "video" ? `${log.pollCount || 0} 次` : "--"], ["Token", log.usageAvailable ? `${log.inputTokens} 输入 / ${log.outputTokens} 输出 / ${log.cachedTokens} 缓存` : "未返回"], @@ -110,12 +112,18 @@ function LogDetail({ log, querying, onQueryProviderTask }: { log: ApiCallLog; qu } function billingText(log: ApiCallLog) { + if (!log.billable) return "不计费"; if (!log.billingAvailable) return "未扣积分"; const status = log.billingStatus || "reserved"; const statusLabel = ({ settled: "已结算", refunded: "已退回", uncertain: "待核对", running: "运行中", reserved: "已预授权" } as const)[status]; return `${formatCredits(log.billingAmountMicrocredits)} 积分 · ${statusLabel}`; } +function requestKindText(value: ApiCallLog["requestKind"]) { + const labels: Partial> = { create: "模型生成", poll: "状态查询", download: "结果下载", repair: "结果修复" }; + return labels[value] || "上游请求"; +} + function PayloadPanel({ value, empty }: { value?: string; empty: string }) { if (!value) return ; return ( diff --git a/web/src/pages/admin/logs/logs-page.tsx b/web/src/pages/admin/logs/logs-page.tsx index f4b5d9526..97e783a3e 100644 --- a/web/src/pages/admin/logs/logs-page.tsx +++ b/web/src/pages/admin/logs/logs-page.tsx @@ -82,7 +82,7 @@ export default function LogsPage() { }, { title: "能力", dataIndex: "capability", width: 88, render: capabilityText }, { title: "结果", width: 118, render: (_, log) => setMediaPreview({ url, kind, title: `${capabilityText(log.capability)}结果` })} /> }, - { title: "调用状态", width: 160, render: (_, log) => }, + { title: "请求阶段 / 状态", width: 160, render: (_, log) => }, { title: "错误信息", width: 260, @@ -125,7 +125,7 @@ export default function LogsPage() { return ( exportAdminApiLogs({ keyword: debouncedKeyword || undefined, status: status === "all" ? undefined : status })} @@ -258,6 +258,7 @@ function formatDuration(value: number) { } function BillingSummary({ log }: { log: ApiCallLog }) { + if (!log.billable) return 不计费; if (!log.billingAvailable) return 未扣积分; const status = log.billingStatus || "reserved"; const statusLabel = ({ settled: "已结算", refunded: "已退回", uncertain: "待核对", running: "运行中", reserved: "已预授权" } as const)[status]; @@ -308,8 +309,14 @@ function CallStatus({ log }: { log: ApiCallLog }) { const failed = log.status === "failed" || ["failed", "cancelled", "expired"].includes(providerStatus || ""); return (
+
{requestKindText(log.requestKind)}
{log.capability === "video" ?
已轮询 {log.pollCount || 0} 次
: null}
); } + +function requestKindText(value: ApiCallLog["requestKind"]) { + const labels: Partial> = { create: "模型生成", poll: "状态查询", download: "结果下载", repair: "结果修复" }; + return labels[value] || "上游请求"; +} diff --git a/web/src/pages/canvas/project.tsx b/web/src/pages/canvas/project.tsx index 4e475ff48..46fd7a2b2 100644 --- a/web/src/pages/canvas/project.tsx +++ b/web/src/pages/canvas/project.tsx @@ -12,7 +12,7 @@ import { uploadImage } from "@/services/image-storage"; import { imageMetadata } from "@/lib/canvas/canvas-generation-task-sync"; import copyToClipboard from "copy-to-clipboard"; import { nanoid } from "nanoid"; -import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, normalizeCanvasAppearance, resolveCanvasAppearance, writeCanvasAppearanceDefault, type CanvasAppearance } from "@/lib/canvas/canvas-appearance"; +import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, DEFAULT_CANVAS_BACKGROUND_MODE, normalizeCanvasAppearance, resolveCanvasAppearance, writeCanvasAppearanceDefault, type CanvasAppearance } from "@/lib/canvas/canvas-appearance"; import { canvasThemes, type CanvasBackgroundMode } from "@/lib/canvas-theme"; import { persistCanvasMediaPerformanceMode, readCanvasMediaPerformanceMode } from "@/lib/canvas/canvas-performance-mode"; import { summarizeCanvasContext } from "@/lib/canvas/canvas-context-summary"; @@ -256,7 +256,7 @@ function InfiniteCanvasPage() { const [contextMenu, setContextMenu] = useState(null); const [isMiniMapOpen, setIsMiniMapOpen] = useState(false); const [canvasAppearance, setCanvasAppearance] = useState(() => canvasAppearanceForTheme(colorTheme)); - const [backgroundMode, setBackgroundMode] = useState("lines"); + const [backgroundMode, setBackgroundMode] = useState(DEFAULT_CANVAS_BACKGROUND_MODE); const [showImageInfo, setShowImageInfo] = useState(false); const [canvasTool, setCanvasTool] = useState("move"); const [mediaPerformanceMode, setMediaPerformanceMode] = useState(readCanvasMediaPerformanceMode); diff --git a/web/src/pages/canvas/shared.tsx b/web/src/pages/canvas/shared.tsx index 5c18375a7..a2769b5c9 100644 --- a/web/src/pages/canvas/shared.tsx +++ b/web/src/pages/canvas/shared.tsx @@ -13,7 +13,7 @@ import { InfiniteCanvas } from "@/components/canvas/infinite-canvas"; import { FullScreenLoader } from "@/components/ui/aceternity/full-screen-loader"; import { WorkspaceState } from "@/components/layout/workspace-state"; import { NODE_DEFAULT_SIZE } from "@/constant/canvas"; -import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, normalizeCanvasAppearance, resolveCanvasAppearance, type CanvasAppearance } from "@/lib/canvas/canvas-appearance"; +import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, DEFAULT_CANVAS_BACKGROUND_MODE, normalizeCanvasAppearance, resolveCanvasAppearance, type CanvasAppearance } from "@/lib/canvas/canvas-appearance"; import { canvasThemes } from "@/lib/canvas-theme"; import { FOLDER_COLLAPSED_HEIGHT, FOLDER_COLLAPSED_WIDTH, isCanvasFolderNode, isFrameNode, isNodeHiddenByCollapsedFrame, resolveFrameConnection } from "@/lib/canvas/canvas-frame"; import { ensureMediaNodeMinimumSize } from "@/lib/canvas/canvas-node-size"; @@ -93,7 +93,7 @@ export default function SharedCanvasPage() { setAppearance(nextAppearance); appliedShareTheme = canvasAppearanceBaseTheme(nextAppearance, themeBeforeShare); useThemeStore.getState().setTheme(appliedShareTheme); - setBackgroundMode(project.backgroundMode || "lines"); + setBackgroundMode(project.backgroundMode || DEFAULT_CANVAS_BACKGROUND_MODE); const initial = project.viewport || { x: 0, y: 0, k: 1 }; viewportRef.current = initial; setViewport(initial); diff --git a/web/src/pages/canvas/use-canvas-project-lifecycle.ts b/web/src/pages/canvas/use-canvas-project-lifecycle.ts index 4908e802c..e1fcf0a7f 100644 --- a/web/src/pages/canvas/use-canvas-project-lifecycle.ts +++ b/web/src/pages/canvas/use-canvas-project-lifecycle.ts @@ -2,7 +2,7 @@ import { startTransition, useCallback, useEffect, useRef, useState, type Dispatc import { App } from "antd"; import { useNavigate } from "react-router"; -import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, normalizeCanvasAppearance, type CanvasAppearance } from "@/lib/canvas/canvas-appearance"; +import { canvasAppearanceBaseTheme, canvasAppearanceForTheme, DEFAULT_CANVAS_BACKGROUND_MODE, normalizeCanvasAppearance, type CanvasAppearance } from "@/lib/canvas/canvas-appearance"; import type { CanvasBackgroundMode } from "@/lib/canvas-theme"; import { removeCanvasDrawing } from "@/lib/canvas/canvas-drawing-storage"; import { normalizeCanvasNodeTimestamps } from "@/lib/canvas/canvas-node-timestamps"; @@ -103,7 +103,7 @@ export function useCanvasProjectLifecycle({ chatSessions: restoredSessions, activeChatId: project.activeChatId || null, canvasAppearance: restoredAppearance, - backgroundMode: project.backgroundMode || "dots", + backgroundMode: project.backgroundMode || DEFAULT_CANVAS_BACKGROUND_MODE, showImageInfo: project.showImageInfo || false, }; nodesRef.current = snapshot.nodes; diff --git a/web/src/stores/canvas/use-canvas-store.ts b/web/src/stores/canvas/use-canvas-store.ts index bc755c6ef..efd57ff34 100644 --- a/web/src/stores/canvas/use-canvas-store.ts +++ b/web/src/stores/canvas/use-canvas-store.ts @@ -2,7 +2,7 @@ import { create } from "zustand"; import { persist, type PersistStorage, type StorageValue } from "zustand/middleware"; import { nanoid } from "nanoid"; -import { normalizeCanvasAppearance, readCanvasAppearanceDefault, type CanvasAppearance } from "@/lib/canvas/canvas-appearance"; +import { DEFAULT_CANVAS_BACKGROUND_MODE, normalizeCanvasAppearance, readCanvasAppearanceDefault, type CanvasAppearance } from "@/lib/canvas/canvas-appearance"; import { parseCanvasStorageDocument, rebaseCanvasProjects, serializeCanvasStorageDocument, type CanvasStorageDocument } from "@/lib/canvas/canvas-storage-revision"; import { localForageStorageForScope } from "@/lib/localforage-storage"; import { getActiveUserScope } from "@/lib/user-scope"; @@ -448,7 +448,7 @@ export const useCanvasStore = create()( chatSessions: [], activeChatId: null, appearance: appearanceDefault?.appearance, - backgroundMode: appearanceDefault?.backgroundMode || "lines", + backgroundMode: appearanceDefault?.backgroundMode || DEFAULT_CANVAS_BACKGROUND_MODE, showImageInfo: false, viewport: initialViewport, directorScenes: [], @@ -470,7 +470,7 @@ export const useCanvasStore = create()( activeChatId: source.activeChatId || null, starterMode: source.starterMode, appearance: source.appearance ? normalizeCanvasAppearance(source.appearance, "dark") : undefined, - backgroundMode: source.backgroundMode || "lines", + backgroundMode: source.backgroundMode || DEFAULT_CANVAS_BACKGROUND_MODE, showImageInfo: source.showImageInfo || false, viewport: source.viewport || initialViewport, directorScenes: source.directorScenes || [], diff --git a/web/test/admin-ui-regressions.test.ts b/web/test/admin-ui-regressions.test.ts index cf7d38e2b..34885cfd9 100644 --- a/web/test/admin-ui-regressions.test.ts +++ b/web/test/admin-ui-regressions.test.ts @@ -192,9 +192,15 @@ test("request logs display user credit billing independently from upstream cost" const billingSummary = sourceSection(listSource, "function BillingSummary", "function MediaResult"); expect(listSource).toContain('title: "积分计费"'); + expect(listSource).toContain('title: "请求阶段 / 状态"'); + expect(listSource).toContain('description="模型生成、状态查询与结果下载;仅计费调用扣除积分"'); expect(billingSummary).toContain("billingAmountMicrocredits"); expect(billingSummary).toContain("billingAvailable"); + expect(billingSummary).toContain("!log.billable"); + expect(billingSummary).toContain("不计费"); expect(billingSummary).not.toContain("costAvailable"); + expect(detailSource).toContain('["请求阶段", requestKindText(log.requestKind)]'); + expect(detailSource).toContain('["计费属性", log.billable ? "计费调用" : "不计费"]'); expect(detailSource).toContain('["积分计费", billingText(log)]'); expect(detailSource).toContain('["上游成本", log.costAvailable'); expect(apiSource).toContain("billingAmountMicrocredits: number"); diff --git a/web/test/canvas-appearance.test.ts b/web/test/canvas-appearance.test.ts index fc6a27baa..d70e80484 100644 --- a/web/test/canvas-appearance.test.ts +++ b/web/test/canvas-appearance.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, test } from "bun:test"; import { + DEFAULT_CANVAS_BACKGROUND_MODE, canvasAppearanceForTheme, customCanvasAppearanceFromTheme, enterCustomCanvasAppearance, @@ -29,21 +30,25 @@ beforeEach(() => { }); describe("canvas custom appearance", () => { + test("uses point grid as the default for new canvases", () => { + expect(DEFAULT_CANVAS_BACKGROUND_MODE).toBe("dots"); + }); + test("inherits the active fixed theme the first time custom mode is selected", () => { const light = enterCustomCanvasAppearance(canvasAppearanceForTheme("light"), "light"); expect(light).toEqual({ mode: "custom", custom: { baseTheme: "light", - backgroundColor: "#EDEEEE", + backgroundColor: "#F0F0F0", backgroundBrightness: 0, - gridColor: "#B6B3B3", - gridOpacity: 70, + gridColor: "#000000", + gridOpacity: 80, }, }); const dark = enterCustomCanvasAppearance(canvasAppearanceForTheme("dark"), "dark"); - expect(dark.custom).toMatchObject({ baseTheme: "dark", backgroundColor: "#262626", backgroundBrightness: 0, gridColor: "#B6B3B3", gridOpacity: 70 }); + expect(dark.custom).toMatchObject({ baseTheme: "dark", backgroundColor: "#000000", backgroundBrightness: 0, gridColor: "#AFAFAF", gridOpacity: 80 }); }); test("restores a previous custom profile only under the same base theme", () => { @@ -56,7 +61,7 @@ describe("canvas custom appearance", () => { expect(fixedDark.custom).toBeUndefined(); expect(enterCustomCanvasAppearance(fixedDark, "dark").custom).toMatchObject({ baseTheme: "dark", - backgroundColor: "#262626", + backgroundColor: "#000000", }); }); @@ -136,6 +141,8 @@ describe("canvas custom appearance", () => { test("does not expose background opacity after legacy values are ignored", async () => { const controlsSource = await Bun.file(new URL("../src/components/canvas/canvas-appearance-controls.tsx", import.meta.url)).text(); + expect(controlsSource).toContain('const LIGHT_PRESETS = ["#F0F0F0"'); + expect(controlsSource).toContain('const DARK_PRESETS = ["#000000"'); expect(controlsSource).not.toContain('label="背景透明度"'); expect(controlsSource).toContain('aria-label="界面样式"'); expect(controlsSource).toContain('label="网格强度"'); diff --git a/web/test/canvas-visual-contrast.test.ts b/web/test/canvas-visual-contrast.test.ts index 7d3f11ecb..567a38795 100644 --- a/web/test/canvas-visual-contrast.test.ts +++ b/web/test/canvas-visual-contrast.test.ts @@ -11,7 +11,7 @@ describe("canvas visual contrast", () => { }); test("changes only the canvas substrate while retaining original surfaces", () => { - expect(canvasThemes.light.canvas.background).toBe("#edeeee"); + expect(canvasThemes.light.canvas.background).toBe("#f0f0f0"); expect(canvasThemes.light.node.fill).toBe("#ffffff"); expect(canvasThemes.light.node.edge).toBe("rgba(15,23,42,.16)"); expect(canvasThemes.light.node.shadow).toBe("0 6px 18px rgba(15,23,42,.08)"); @@ -19,7 +19,7 @@ describe("canvas visual contrast", () => { expect(canvasThemes.light.toolbar.panel).toBe("rgba(255,255,255,.94)"); expect(canvasThemes.light.spatial.elevated).toBe("rgba(255,255,255,.94)"); - expect(canvasThemes.dark.canvas.background).toBe("#262626"); + expect(canvasThemes.dark.canvas.background).toBe("#000000"); expect(canvasThemes.dark.node.fill).toBe("#181818"); expect(canvasThemes.dark.node.edge).toBe("rgba(255,255,255,.18)"); expect(canvasThemes.dark.node.shadow).toBe("0 8px 24px rgba(0,0,0,.34)"); @@ -30,10 +30,10 @@ describe("canvas visual contrast", () => { }); test("pins intentional grid tokens while retaining canvas grid opacity", async () => { - expect(canvasThemes.light.canvas.dot).toBe("rgba(15,23,42,.20)"); - expect(canvasThemes.light.canvas.line).toBe("rgba(15,23,42,.15)"); - expect(canvasThemes.dark.canvas.dot).toBe("rgba(205,205,205,.28)"); - expect(canvasThemes.dark.canvas.line).toBe("rgba(205,205,205,.28)"); + expect(canvasThemes.light.canvas.dot).toBe("rgba(0,0,0,.80)"); + expect(canvasThemes.light.canvas.line).toBe("rgba(0,0,0,.80)"); + expect(canvasThemes.dark.canvas.dot).toBe("rgba(175,175,175,.80)"); + expect(canvasThemes.dark.canvas.line).toBe("rgba(175,175,175,.80)"); const source = await Bun.file(new URL("../src/components/canvas/infinite-canvas.tsx", import.meta.url)).text(); expect(source).toContain('opacity: mode === "dots" ? 0.34 : 0.46');