# 诊断码与修法 诊断同一条结构供三处使用:`canvas_check` 的输出、tab 里的错误卡、host 日志。**先跑 `canvas_check`**——它只编译不渲染,能在你还没打开页面时就把问题说清楚。 ~~~ts type CanvasDiagnostic = { code: string; severity: "error" | "warning"; message: string; line?: number; col?: number; hint?: string; }; ~~~ ## 速查 | 码 | 级别 | 产出方 | 一句话 | 修法要点 | |---|---|---|---|---| | `E_PARSE` | error | sucrase | 语法 / TS 解析失败 | 看行列号,通常是 JSX 标签不配对 | | `E_PARSE_IMPORT` | error | 扫描 | import 了非 `dsh/canvas` 的模块 | 只用 `dsh/canvas`(`import React` 也归这条) | | `E_NO_DEFAULT` | error | 编译管线 | 缺 `export default` | 导出一个无参组件 | | `E_SIDE_EFFECT` | error | 扫描 | 顶层副作用 | I/O 与定时器移出模块顶层 | | `E_DYNAMIC` | error | 扫描 | `eval` / `new Function` / 动态 `import()` | 改成普通分支 | | `E_EXTERNAL` | error | 扫描 | 远程资源 / iframe | 去掉;用内联 SVG | | `E_DATA_NOT_LITERAL` | error | 抽取 | `DATA` 不是纯字面量 | 把值写成字面量,计算放渲染期 | | `E_MERGE` | error | `canvas_state_merge` | 读不出 `DATA` | `DATA` 必须是对象字面量,且每行有 `id` | | `E_TOO_LARGE` | error | 编译管线 | 超硬阈值,拒绝编译 | 拆画布 / 外置历史 | | `W_NO_DATA` | warning | 抽取 | 没有 `export const DATA` | 把数据搬进 `DATA` | | `W_LARGE_FILE` | warning | 编译管线 | 超软阈值 | 拆画布 / 用引用替代长字段 | | `W_REMOTE_URL` | warning | 扫描 | 源码里出现 `http(s)://` | 画布不能拉远程内容;内联数据 | | `W_MANY_ROWS` | warning | **渲染期文案(不是诊断)** | 行数被截断 | 加筛选器 | --- ## E_PARSE **含义**:sucrase 无法解析源码(语法错误,或 TS 类型剥离失败)。行列号直接来自编译器。 **典型写法** ~~~tsx hi {/* 打开的是 Stack,关的却是 Card */} ~~~ **修法**:按错误卡给的行列号检查那一处的 JSX 配对与括号。JSX 要求关闭标签与最近的打开标签同名——把上面的 `` 改成 `` 即可。 > 真实案例:设计文档附录 A 的示例画布就有一处把 `` 写成了 ``,属于这个码。 --- ## E_PARSE_IMPORT **含义**:出现了非 `"dsh/canvas"` 的模块说明符。 **典型写法** ~~~tsx import _ from "lodash"; // 第三方 import { helper } from "./helper"; // 相对路径 import React from "react"; // 同样归 E_PARSE_IMPORT(§21 D2 已删掉专用码) import { LineChart } from "recharts"; // 图表库 ~~~ **修法**:画布只允许 `dsh/canvas`。需要的工具函数就地写;需要的图表用套件里的 `BarChart` 或自己用内联 SVG 画。**画布之间也不能互相 import**——需要复用就复制那几行,或把逻辑提成 agent 侧的工具。 --- ## E_NO_DEFAULT **含义**:文件里没有 `export default`,或默认导出的不是组件。 **典型写法** ~~~tsx export function Board() { return null; } // 只有具名导出 const Board = () => null; // 定义了但没导出 ~~~ **修法** ~~~tsx export default function Board() { return ...; } ~~~ 具名导出(`export const DATA`、辅助函数)可以同时存在,宿主只取 default。 --- ## E_SIDE_EFFECT **含义**:模块顶层执行了有副作用或依赖运行环境的调用。模块在动态 import 时求值一次,顶层副作用会让"编译检查"和"渲染"都不再是纯函数。 **典型写法** ~~~tsx console.log("canvas loaded"); // 顶层日志 const timer = setTimeout(() => {}, 1000); // 定时器 const base = window.location.pathname; // 环境依赖 localStorage.setItem("k", "v"); // 存储写入 const data = fetchRows(); // I/O ~~~ **修法** - 数据 → 写进 `DATA`(字面量)。 - 需要随交互发生的动作 → 放进事件处理器,或经 `useCanvasAction`。 - 顶层**纯计算**(`const TASKS = DATA.tasks.filter(...)`、`DATA.tasks.map(...)`)是允许的:它不读环境、不写外部。 - 定时器与网络在画布里**本来也不可用**;不要试图绕过。 --- ## E_DYNAMIC **含义**:用了动态代码执行。 **典型写法** ~~~tsx const f = new Function("return 1"); const v = eval("1 + 1"); const mod = await import(someUrl); ~~~ **修法**:改成显式分支或查表。需要"按类型渲染不同内容"就写 `switch` / 映射对象。 --- ## E_EXTERNAL **含义**:引用了画布之外的资源。 **典型写法** ~~~tsx