From 8ec48efcd680197e3ba3f2c74b2caff5fa727e54 Mon Sep 17 00:00:00 2001 From: cruldra Date: Fri, 16 Oct 2026 20:21:20 +1100 Subject: [PATCH] =?UTF-8?q?=F0=9F=93=9D=20docs(code-reader):=20=E8=A7=84?= =?UTF-8?q?=E6=A0=BC=E9=87=8C=E7=9A=84=20React=20=E9=92=A9=E5=AD=90?= =?UTF-8?q?=E6=8D=A2=E6=88=90=E7=BB=9F=E4=B8=80=E7=9A=84=E9=92=A9=E5=AD=90?= =?UTF-8?q?=E5=8D=A1=E7=89=87=E5=92=8C=E8=87=AA=E5=AE=9A=E4=B9=89=E9=92=A9?= =?UTF-8?q?=E5=AD=90=E5=8D=A1=E7=89=87=EF=BC=8C=E8=B0=83=E7=94=A8=E6=A0=91?= =?UTF-8?q?=E8=AE=A4=E4=B8=8D=E5=87=BA=E7=9A=84=E5=90=8D=E5=AD=97=E6=94=B9?= =?UTF-8?q?=E6=9F=A5=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/superpowers/specs/code-reader/spec.md | 91 +++++++++++++--------- 1 file changed, 56 insertions(+), 35 deletions(-) diff --git a/docs/superpowers/specs/code-reader/spec.md b/docs/superpowers/specs/code-reader/spec.md index caa3da6..314d6b7 100644 --- a/docs/superpowers/specs/code-reader/spec.md +++ b/docs/superpowers/specs/code-reader/spec.md @@ -182,9 +182,9 @@ | health 健康检查 | `activity` | start 启动 | `play` | trait 特质 | `network` | | impl 实现 | `square-function` | module 模块 | `layers` | attribute 编译属性 | `sliders-horizontal` | - 库规则的种类用规则给的图标(`KindBadge.icon`),跟在语言的种类图标后面:SQLAlchemy 数据表 `database`、数据类 `package`、Pydantic 数据模型 `braces`(和类型块同一个图标,颜色不同)、Celery 应用 `server-cog`、定时任务 `calendar-clock`、FastAPI 接口 `globe`、FastAPI 路由器 `network`、React 组件 `atom`、状态 `toggle-right`、引用 `anchor`、数据源 `download`、派生值 `sigma`、副作用 `zap`、事件处理函数 `mouse-pointer-click`、渲染输出 `layout-template`、serde 可序列化 `braces`、thiserror 错误 `triangle-alert`、axum 接口 `globe`、sqlx 查询 `database`、tauri 桌面命令 `square-function`。数据类写了 `frozen=True` 再加一把锁(`lock`,悬停「不可修改」)。实现块、特质块再由规则 `rust.impl`、`rust.trait` 换成卡片,左边因此有两个「实现」「特质」图标。 + 库规则的种类用规则给的图标(`KindBadge.icon`),跟在语言的种类图标后面:SQLAlchemy 数据表 `database`、数据类 `package`、Pydantic 数据模型 `braces`(和类型块同一个图标,颜色不同)、Celery 应用 `server-cog`、定时任务 `calendar-clock`、FastAPI 接口 `globe`、FastAPI 路由器 `network`、React 组件 `atom`、钩子和自定义钩子 `fishing-hook`、事件处理函数 `mouse-pointer-click`、渲染输出 `layout-template`、serde 可序列化 `braces`、thiserror 错误 `triangle-alert`、axum 接口 `globe`、sqlx 查询 `database`、tauri 桌面命令 `square-function`。数据类写了 `frozen=True` 再加一把锁(`lock`,悬停「不可修改」);React 自带的钩子再加一个作用图标(状态 `toggle-right`、副作用 `zap`、引用 `anchor`、性能 `sigma`、上下文 `share-2`、并发 `hourglass`、外部数据源 `database`)。钩子卡片、自定义钩子卡片不带语言的种类图标,左边第一个就是钩子图标(`RuleCard.languageConcepts: false`)。实现块、特质块再由规则 `rust.impl`、`rust.trait` 换成卡片,左边因此有两个「实现」「特质」图标。 - 这组图标由插件自己定义,是一个封闭的集合:契约里的 `KIND_ICON_NAMES`(上表 26 个——命令、管道、执行共用一个,解释器、解析指令共用一个,选项、设置、编译属性共用一个,字段、标签共用一个,类型、接口共用一个——加库规则用的 14 个(React 的组件、状态、引用、数据源、派生值、副作用、事件处理函数、渲染输出的 8 个也在里面),lucide 的 kebab-case 名字),类型 `KindIconName` 是它们的联合,概念的 `icon` 只能写其中一个,写别的是类型错误。内置种类 → 图标名在 `contract.ts` 的 `KIND_ICONS`(宿主写进概念列表)。webview 给每个名字配一个逐个 import 的 lucide 图标组件(`kindIcons.tsx` 的 `KIND_ICON_COMPONENTS`,打包只带这些,预览脚本约 157 kB);webview 的 `KindIconName` 照契约手抄,`kindIconNames.test.ts` 核对两边一样。规则要新图标就把名字加进这组、在 webview 配上图标,没有按任意名字找图标的路。 + 这组图标由插件自己定义,是一个封闭的集合:契约里的 `KIND_ICON_NAMES`(上表 26 个——命令、管道、执行共用一个,解释器、解析指令共用一个,选项、设置、编译属性共用一个,字段、标签共用一个,类型、接口共用一个——加库规则用的 16 个(React 的组件、钩子、钩子的七种作用、事件处理函数、渲染输出用到其中 11 个),lucide 的 kebab-case 名字),类型 `KindIconName` 是它们的联合,概念的 `icon` 只能写其中一个,写别的是类型错误。内置种类 → 图标名在 `contract.ts` 的 `KIND_ICONS`(宿主写进概念列表)。webview 给每个名字配一个逐个 import 的 lucide 图标组件(`kindIcons.tsx` 的 `KIND_ICON_COMPONENTS`,打包只带这些,预览脚本约 157 kB);webview 的 `KindIconName` 照契约手抄,`kindIconNames.test.ts` 核对两边一样。规则要新图标就把名字加进这组、在 webview 配上图标,没有按任意名字找图标的路。 - 右上角的图标按钮(原型组件 `Reader / BlockTools`,普通块、属性表块、库规则的卡片共用):只有图标、不带字,名字写在悬停提示和 `aria-label` 里;整排是 `role="group"`(`aria-label="块操作"`)。最前面是「按注释规范重写」(注释图标,块还没有注释时叫「按注释规范写注释」,重写过、记录收着时叫「展开重写记录」;不能写注释的块没有,见「重写注释」),然后是「问 AI」(sparkles 图标,点开 / 收起块下面的问答区,展开时带按下的底色、`aria-expanded`;文件不在工作区文件夹里时没有,见「问 AI」),再是「编辑 / 预览」切换:预览时铅笔「编辑」,编辑时眼睛「预览」、带按下的底色(`--vscode-toolbar-activeBackground`)。按钮宽 24px、高 20px(和种类图标同高),图标 14px、`descriptionForeground` 色;悬停 `--vscode-toolbar-hoverBackground`,键盘聚焦描 1px `--vscode-focusBorder`;Tab 能走到,回车 / 空格切换。 - 悬停提示(整个插件的网页只走这一条路,预览和看板共用同一个组件):带提示的元素只写 `data-tooltip="提示"`(名字照旧写在 `aria-label` 里,只有图标的按钮必须有 `aria-label`),页面根上挂一个 `TooltipHost`(`webview-ui/src/components/ui/tooltip.tsx`)统一弹,不用原生 `title`;提示里的换行照样换行(看板摘修复弹框里「迁移」列出的几个文件一行一个);注释、回答里 Markdown 链接的标题(`[文字](网址 "标题")`)也写成 `data-tooltip`。网页的 TSX 里写原生 `title` 属性是 lint 错误(`vscode/eslint.config.mjs`),只有 `iframe` 这种必须带标题的元素例外,组件自己叫 `title` 的属性不算。原因:macOS 上 VS Code 带的 Electron(1.139 是 Electron 43)只在鼠标压着带 `title` 的元素自身边缘时才弹原生提示,停在它里面的图标、文字上不弹(electron/electron#49843,Electron 38 起),种类图标 20px 的框几乎被 16px 的图形占满,基本弹不出来;样子也是系统的。样子照 VS Code 的悬停框:`--vscode-editorHoverWidget-background` 底、`--vscode-editorHoverWidget-border` 边框、`--vscode-editorHoverWidget-foreground` 字、`0 2px 8px --vscode-widget-shadow` 阴影,11px 字,圆角 4px,最宽 22rem;优先在元素上方,上方放不下翻到下方,水平对着元素中间,贴边留 8px,不接鼠标。鼠标停 500ms 才弹,在同一个元素里移动不重新计时,已经弹着时移到另一个带提示的元素上立刻换;移开、移出页面、按下鼠标、按任意键(含 Esc)、滚动就收,收了之后鼠标不离开这个元素不再弹。键盘把焦点移到带提示的元素上(`:focus-visible`)立刻弹,焦点离开就收;提示不是元素自己的名字(`aria-label` 或它的字)时用 `aria-describedby` 挂到元素上,读屏当描述念。 - 种类图标和底色按种类区分(颜色见「颜色」);文件开头的说明(种类「注释」)虚线框。 @@ -1256,7 +1256,7 @@ export interface Translator { export interface ThemeColor { dark: string, light: string } /** 种类图标:插件自己定义的一组 lucide 图标(kebab-case 名字);前 26 个给内置种类,后面的给库规则。规则要新图标就加进这组,没有按任意名字找图标的路。 */ -export const KIND_ICON_NAMES = ['import', 'square-function', /* …26 个内置种类的… */ 'database', 'package', 'server-cog', 'calendar-clock', 'globe', 'network', 'atom', 'zap', 'mouse-pointer-click', 'toggle-right', 'anchor', 'download', 'sigma', 'layout-template'] as const +export const KIND_ICON_NAMES = ['import', 'square-function', /* …26 个内置种类的… */ 'database', 'package', 'server-cog', 'calendar-clock', 'globe', 'network', 'atom', 'zap', 'mouse-pointer-click', 'toggle-right', 'anchor', 'sigma', 'layout-template', 'fishing-hook', 'share-2', 'hourglass'] as const export type KindIconName = (typeof KIND_ICON_NAMES)[number] /** 内置种类 → 图标名。宿主写进概念列表,webview 不自己查。 */ @@ -1279,6 +1279,9 @@ export interface RuleCard { kind: string // 种类 id,「规则 id.名字」(sqlalchemy.table):筛选的 kind 值,要稳定 badge: KindBadge // 种类的名字、颜色、图标;筛选栏里这个种类也叫这个名字,卡片边框用这个颜色 concepts?: BlockConcept[] // 额外认出的概念,排在种类后面;语言的概念仍在更前面,同一个 id 只留第一个 + languageConcepts?: false // 填 false 时左边不带语言的概念,从种类画起(React 钩子卡片左边第一个是钩子图标) + tint?: ThemeColor // 卡片淡底的颜色,不填用种类色;边框仍是种类色(React 钩子卡片描边都是钩子紫,淡底按来源分) + calls?: SourceRange // 能看调用树,查这个名字被谁调用(钩子卡片查返回的 setOpen,自定义钩子卡片查钩子自己);BlockView.callTree.name 就是它 rows: PropertyRow[] defines?: SourceRange // 块自己的名字(类名):跳到的定义是它时闪整块 attributes?: BlockAttributes // 规则自己的筛选属性,叠在宿主从原块推出的上面;kind 填了不算;名字由 LibraryRule.attributeNames 给 @@ -1838,34 +1841,32 @@ export interface CommandRule { ### 内置:React 组件(TypeScript / JavaScript) -`vscode/src/codeReader/rules/react.ts`(读语法树的办法在 `rules/reactTree.ts`,都是纯函数),`languageIds: ['typescriptreact', 'javascriptreact']`,`libraries: []`(新的 JSX 写法不用导入 react,TSX / JSX 文件都启用)。原型:`docs/prototype/code-reader/openpencil/react.js` 的三张画板「编辑器全貌」「卡片种类」「对照」(导出图 `exports/react-main.png`、`react-kinds.png`、`react-compare.png`);样例是 luffy 的 `chat/code/bubbles/QueuedBubble.tsx`(`fixtures/queuedBubble.tsx.txt`)和 `chat/code/PlanChecklist.tsx` 第 91–226 行(`fixtures/planChecklist.tsx.txt`,前面带上文件开头的导入),以前的 `DesktopTitlebar`、`LegalDocPage`、`DeleteAppDialog`、`DesktopSettingsDialog`、`AppVaultSummary`、`ShareButton`、`PosterDialog`、`OverviewSection` 也在 `fixtures/`。 +`vscode/src/codeReader/rules/react.ts`(读语法树的办法在 `rules/reactTree.ts`,都是纯函数),`languageIds: ['typescriptreact', 'javascriptreact']`,`libraries: []`(新的 JSX 写法不用导入 react,TSX / JSX 文件都启用)。原型:`docs/prototype/code-reader/openpencil/react.js` 的三张画板「编辑器全貌」「卡片种类」「对照」(导出图 `exports/react-main.png`、`react-kinds.png`、`react-compare.png`),钩子卡片照 `react-hooks.js` 的两张画板(导出图 `exports/react-hooks-panel.png`、`react-hooks-custom.png`,样例源码同 `fixtures/hooksPanel.tsx.txt`);样例是 luffy 的 `chat/code/bubbles/QueuedBubble.tsx`(`fixtures/queuedBubble.tsx.txt`)和 `chat/code/PlanChecklist.tsx` 第 91–226 行(`fixtures/planChecklist.tsx.txt`,前面带上文件开头的导入),以前的 `DesktopTitlebar`、`LegalDocPage`、`DeleteAppDialog`、`DesktopSettingsDialog`、`AppVaultSummary`、`ShareButton`、`PosterDialog`、`OverviewSection` 也在 `fixtures/`。 -TSX 和 Python 一样,从上往下一块语法一张卡片,嵌套的块排在父卡片下面,缩进、竖线、折叠同函数体。规则只换掉 React 的几种写法,别的块(导入、接口、类型、常量、普通变量、if、尝试……)照语言默认的卡片,注释照常挂在它讲的卡片上。组件卡片不把组件体并进来:组件体的语句照源码顺序是它的子块,照常交给各规则看。表格的格子里只写名字和位置,函数体只出现在子卡片里。 +TSX 和 Python 一样,从上往下一块语法一张卡片,嵌套的块排在父卡片下面,缩进、竖线、折叠同函数体。规则只换掉 React 的几种写法,别的块(导入、接口、类型、常量、普通变量、if、尝试……)照语言默认的卡片,注释照常挂在它讲的卡片上。组件卡片、自定义钩子卡片不把函数体并进来:语句照源码顺序是它的子块,照常交给各规则看。表格的格子里只写名字和位置,函数体只出现在子卡片里。 卡片种类(颜色写暗 / 亮): | 种类 id | 名字 | 图标 | 颜色 | 认哪一块 | 行 | | --- | --- | --- | --- | --- | --- | | `react.component` | React 组件 | `atom` | #61dafb / #0a7ea4 | 组件的声明 | 名称 / 说明 / 构造参数 | -| `react.state` | 状态 | `toggle-right` | #9cdcfe / #001080 | `const [v, setV] = useState(…)`、`useReducer(…)` | 名称 / 初始值 / 更新位置 | -| `react.ref` | 引用 | `anchor` | #9cdcfe / #001080 | `useRef(…)` | 名称 / 初始值 / 使用位置 | -| `react.source` | 数据源 | `download` | #9cdcfe / #001080 | 别的钩子赋给变量(`useQuery`、`usePathname`、`useRouter`、自定义 `useXxx`) | 变量 / 钩子 / 说明 | -| `react.derived` | 派生值 | `sigma` | #9cdcfe / #001080 | `useMemo`、`useCallback` | 名称 / 计算表达式 / 依赖项 | -| `react.effect` | 副作用 | `zap` | #e5c07b / #9a6a00 | `useEffect`、`useLayoutEffect`、`useInsertionEffect` | 触发时机 / 清理函数 | +| `react.hook` | 钩子 | `fishing-hook` | #b180d7 / #7f3fa6 | 直接写在组件体、自定义钩子体里的钩子调用:`const x = useX(…)`、`const [a, b] = useX(…)`、整句的 `useEffect(…)` | 钩子 / 类型参数 / 参数 / 返回 | +| `react.customHook` | 自定义钩子 | `fishing-hook` | #b180d7 / #7f3fa6 | 自定义钩子的声明(`function useXxx`、`const useXxx = () => …`) | 名称 / 说明 / 参数 / 返回 | | `react.handler` | 事件处理函数 | `mouse-pointer-click` | #dcdcaa / #795e26 | 组件体里定义的函数(函数声明、值是函数的常量) | 名称 / 说明 / 参数 / 绑定事件(说明里的块标签另有几行,见下) | | `react.render` | 渲染输出 | `layout-template` | #89d185 / #388a34 | 组件函数自己返回 JSX 的 return | JSX 结构大纲 | -组件色同时用在渲染大纲里的组件、「客户端组件」标签上;状态、引用、数据源、派生值都是数据,共用变量块的浅蓝,靠图标区分;副作用同「选项」块的黄,事件处理函数同「函数」块,渲染输出同「返回」块的绿。筛选栏的「种类」按这几个 id 筛,叫上表的名字。 +组件色同时用在渲染大纲里的组件、「客户端组件」标签、React 自带钩子的淡底上;钩子卡片、自定义钩子卡片描边都是钩子紫,淡底按来源分(见下);事件处理函数同「函数」块,渲染输出同「返回」块的绿。筛选栏的「种类」按这几个 id 筛,叫上表的名字。 认哪些是组件:名字大写开头;是函数声明、值是函数的变量(箭头函数、函数表达式,直接返回 JSX 的也算),或被 `memo(…)` / `forwardRef(…)`(`React.memo` 也算,可以套几层)包着的函数;函数自己的 return(嵌套函数里的不算)里有一处返回 JSX(元素、片段,三元、`&&` 的某一支是 JSX 也算;和 `null` 混着返回也算)。小写开头的工具函数、不返回 JSX 的大写函数照常是函数块。组件里又定义的组件自己也是一张组件卡片,不算事件处理函数。 认领(`LibraryRule.claim` 一块一块看,组件卡片的 `absorbs` 是空的): - 组件块(函数块、只声明一个变量的常量 / 变量块)是组件时换成组件卡片。文件开头的 `"use client"`(文件里有组件时)略去(`RuleOmit`),讲成卡片上的「客户端组件」标签;它挂着注释时不略去(注释没地方放),照常是一张语句卡片。组件上面的 JSDoc 是组件块自己的说明(TypeScript 的 JSDoc 能当常量、变量块的说明,`memo(…)`、直接返回 JSX 的常量块也是),讲成卡片的「说明」。 -- 组件体里的块:语法树往上找,块的语句直接是某个组件函数主体(语句块)的子语句才认(`enclosingComponent`:所在的函数经 `componentOfFunction` 找回它的声明,再用认组件的 `componentAt` 核对),按组件体的归类(`bodyOf`)认成状态、引用、数据源、派生值、副作用、事件处理函数。 +- 自定义钩子的声明(函数块、只声明一个变量的常量 / 变量块;名字是 `use` 后面接大写字母或数字,只叫 `use` 的不算,`customHookAt`)换成自定义钩子卡片。 +- 组件体、钩子体里的块:语法树往上找,块的语句直接是某个组件函数或自定义钩子主体(语句块)的子语句才认(`enclosingComponent` / `enclosingCustomHook`:所在的函数找回声明它的语句,再用 `componentAt` / `customHookAt` 核对)。只声明一个变量、值是钩子调用(`React.useX` 也算),或整句就是一次钩子调用的(`hookCallAt`),认成钩子卡片;组件体里定义的函数认成事件处理函数(`bodyOf`)。值是钩子调用再取属性的(`useX()?.a ?? b`)照常是变量卡片。 - `return` 块:所在的最内层函数(`enclosingFunction`)是组件函数、返回的是 JSX 时认成渲染输出;写在 `if` 里的提前返回 JSX 也算,它是如果卡片下面的子块。返回 `null` 这类的照常是返回块。 - 别的照语言默认:组件体里普通的 `const x = 表达式` 是变量卡片,`if (…) return null` 是如果卡片、下面是返回卡片,注释是它讲的那张卡片的「注释」一行。 -- 回调、函数里的语句来自 TypeScript 语言插件本来给的子块:调用的实参是带语句块的函数时(任何调用都一样),那句语句、那个变量的子块就是函数里的语句,所以 `useEffect(() => {…})`、`useCallback(() => {…})`、`useMemo(() => {…})` 的语句排在副作用、派生值卡片下面;事件处理函数是函数块,函数体照常是子块。 +- 回调、函数里的语句来自 TypeScript 语言插件本来给的子块:调用的实参是带语句块的函数时(任何调用都一样),那句语句、那个变量的子块就是函数里的语句,所以 `useEffect(() => {…})`、`useCallback(() => {…})`、`useMemo(() => {…})` 的语句(连返回清理函数的那句 return)排在钩子卡片下面;事件处理函数、自定义钩子是函数块,函数体照常是子块。 组件卡片: @@ -1885,16 +1886,26 @@ TSX 和 Python 一样,从上往下一块语法一张卡片,嵌套的块排 构造参数:属性类型的字段照类型里的顺序一行一个(类型字面量、同文件的接口 / 类型别名,交叉类型各部分接起来,`Readonly<…>`、`PropsWithChildren<…>` 看里面;没标注参数类型时 `const X: FC` 的泛型实参、`forwardRef` 的第二个泛型实参),解构里有、类型里没有的接在后面,`...rest` 最后(类型写「其余的属性」)。名称是属性名(定义处带下标:解构出来的是解构那处,没解构的是字段;跳到定义只闪这一行),`children` 后面标「子元素」、函数类型的属性(`(x) => R`、方法签名)标「回调」(组件色的小标签),解构时改了名写「(组件内名为 x)」;类型照翻译插件翻,方法签名写成函数签名;默认值取解构的默认值,没有写「—」;必填看类型的 `?`(是 / 否,storage 色),没类型可看时写了默认值的「否」、别的「—」;说明是字段上面的 JSDoc / 注释、这一行行尾的注释(comment 色,几行接成一行)。类型是导入的名字时经工作区索引查它的字段(索引 `react.propTypes`:看工作区里所有 `**/*.{ts,tsx}`,预筛有 `interface X` / `type X` 才解析,按 typescriptreact 解析;从文件顶层认出接口、类型别名,键是名字、值是各字段的名字、类型写法、可省略、回调、说明的 JSON),查法同「规则认领时查别的文件里的定义」:先照解构的出行、类型「未标注」,查到了按字段重排、重发;别的文件里的类型照写文字(type 色)。 -状态、引用、数据源、派生值(名称、变量带下标,解构模式照常翻;跳到定义闪这张卡片): +钩子卡片(所有钩子共用一种,不分析钩子里写了什么;行照钩子签名排,没有的行不显示): -- 状态的初始值:useState 的实参、useReducer 的第二个实参(后面括号里写 reducer)。更新位置:调用改状态函数的各处,只写名字和位置,「、」隔开,写出来一样的只留一处,没有时「—」:「副作用」(几个时「副作用 2」,副作用色)、事件处理函数名 / 派生值名 / 变量名 / 数据源的变量名(带下标,⌘ + 点击跳到那张卡片)、「