ui-sdk-write-code

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Write ui-sdk code

编写 ui-sdk 代码

The bar: an expert in the framework reads the file and says "this is how I'd write it", and anyone else reads it top to bottom without stopping. Readable first, then fast, never clever.
标准:框架专家读完文件后会认为“这就是我会写的方式”,其他人也能从头到尾顺畅读完,无需中途停顿。先保证可读性,再考虑性能,最后才是巧妙。

1. Find the twin

1. 找到孪生实现

Every piece of code here has a twin that already does it well. Open it before you write a line, and copy its shape, names and order.
You're writingStart from
A core component
packages/core/src/components/tabs/tabs.ts
, or
menu/
once it passes about 400 lines (
menu.ts
,
menu-api.ts
,
menu-parts.ts
)
A popup
packages/core/src/components/popover/popover.ts
and its adapters, on
createPopup
in
foundation/popups/
A React, Vue or Solid partThe same component's file in
packages/<framework>/src/
, on
createParts
A Svelte part
packages/svelte/src/tabs/
A web component
packages/web/src/tabs.ts
, on
createParts
An exampleThe same example in another framework:
examples/components/<name>/<framework>/
A spec
tests/specs/tabs*.spec.ts
If two twins disagree, the shape is wrong in one of them. Say so instead of picking one.
Copy a twin's shape, not its comments. Older files carry more comments than the rules below allow.
这里的每一段代码都有一个已经做好的孪生实现。动笔前先打开它,并沿用它的结构、命名和顺序。
你要编写参考起点
core 组件
packages/core/src/components/tabs/tabs.ts
;当
menu/
超过约 400 行时,改用
menu/
(
menu.ts
、
menu-api.ts
、
menu-parts.ts
)
弹出层(popup)
packages/core/src/components/popover/popover.ts
及其适配器,以及
foundation/popups/
中的
createPopup
React、Vue 或 Solid 部件
packages/<framework>/src/
中同一组件的文件,基于
createParts
Svelte 部件
packages/svelte/src/tabs/
Web Component
packages/web/src/tabs.ts
,基于
createParts
示例其他框架中的同一示例:
examples/components/<name>/<framework>/
规格测试(spec)
tests/specs/tabs*.spec.ts
如果两个孪生实现不一致,说明其中一个的结构有问题。应明确指出这一点,而不是随意选择一个。
借鉴孪生实现的结构,而不是它的注释。旧文件中的注释数量超过了下列规则允许的范围。

2. Load the framework

2. 加载框架知识

Read the expert's knowledge file for every framework you touch:
.agents/experts/<framework>/knowledge.md
, part 1 and "Common mistakes to flag", and its
decisions.md
. It's pinned to the versions this repo installs, so trust it over memory. Write in the current idiom of that version:
  • React 19: pure render, no
    forwardRef
    ,
    ref
    as a prop, no effect where an event handler or a derived value belongs, code the React Compiler compiles without a bailout.
  • Vue 3.5:
    <script setup lang="ts">
    ,
    v-model
    on parts,
    defineModel
    when a component you write takes one, typed
    defineProps
    and
    defineSlots
    , no options API.
  • Svelte 5: runes only (
    $state
    ,
    $derived
    ,
    $props
    ,
    $bindable
    ), snippets,
    onclick
    attributes, attachments. No
    $:
    ,
    on:
    ,
    <slot>
    or
    createEventDispatcher
    . Props typed with an
    interface Props
    . Outside
    packages/svelte
    , declaration tags (
    {const x = $derived(...)}
    ) instead of
    {@const}
    .
  • Solid 2: props read where they're used, never destructured; derive with memos, not effects; Solid 2 APIs only.
  • Web components: platform first. Options reflect as attributes the way built-ins do, public events are
    composed
    and don't bubble (listen with
    { capture: true }
    ), and the user's markup stays in light DOM.
针对你接触的每个框架,读取专家知识文件:
.agents/experts/<framework>/knowledge.md
的第 1 部分和 “Common mistakes to flag” 章节,以及该框架的
decisions.md
。这些内容与本仓库安装的版本绑定,因此应以它们为准,而不是依赖记忆。按照当前版本的惯用写法编码:
  • React 19:采用纯渲染,不使用
    forwardRef
    ,将
    ref
    作为 prop;在应使用事件处理器或派生值的位置,不使用 effect;编写可由 React Compiler 编译且无需 bailout 的代码。
  • Vue 3.5:使用
    <script setup lang="ts">
    ,在 parts 上使用
    v-model
    ;当所编写的组件接受 model 时使用
    defineModel
    ;为
    defineProps
    和
    defineSlots
    添加类型;不使用 Options API。
  • Svelte 5:仅使用 runes(
    $state
    、
    $derived
    、
    $props
    、
    $bindable
    )、snippets、
    onclick
    属性和 attachments。禁止使用
    $:
    、
    on:
    、
    <slot>
    或
    createEventDispatcher
    。使用
    interface Props
    为 props 添加类型。在
    packages/svelte
    之外,使用声明标签(
    {const x = $derived(...)}
    ),而不是
    {@const}
    。
  • Solid 2:在 props 的使用位置读取它们,绝不解构;使用 memos 派生值,而不是 effects;仅使用 Solid 2 API。
  • Web Components:平台优先。像内置元素一样将 options 反映为 attributes;公共 events 应为
    composed
    且不冒泡(使用
    { capture: true }
    监听),用户的 markup 保持在 light DOM 中。

3. Write it

3. 编写代码

The rules in
contributing/code.md
hold everywhere. The ones that make code look generated when missed:
  • One shape.
    <Name>
    root,
    <Name><Part>
    per slot,
    render
    to change the element, never
    as
    . Same names and the same props in every framework.
  • Names in full words that say what a thing is. Booleans read as questions.
  • Exports listed once at the end. Inline types. No return type annotations.
  • No comment by default. Write one only when a reader would get the code wrong without it: a browser bug, an APG rule, a trade-off. One to three lines. Never a comment per declaration, and never one that explains the design or the data flow. Examples have none.
  • No defensive code for cases that can't happen. Types already rule them out.
  • No helper for one caller, and no wrapper that only renames.
  • Behavior lives in core. An adapter that grows a listener, a focus call or an
    aria-*
    attribute is in the wrong layer.
contributing/code.md
中的规则适用于所有代码。遗漏以下规则会让代码看起来像是机器生成的:
  • 统一结构。 以
    <Name>
    为根,每个 slot 对应
    <Name><Part>
    ,通过
    render
    更换元素,绝不使用
    as
    。所有框架采用相同的名称和 props。
  • **名称使用完整词语,**明确表达事物是什么。布尔值读起来应像一个问题。
  • Exports 只在末尾列出一次。 使用内联类型。不添加返回类型注解。
  • 默认不写注释。 仅当缺少注释会让读者错误理解代码时才添加,例如浏览器 bug、APG 规则或某种权衡。注释控制在 1 到 3 行。绝不为每个声明添加注释,也不要添加解释设计或数据流的注释。示例中不添加任何注释。
  • 不为不可能发生的情况编写防御性代码。 类型已经排除了这些情况。
  • **不为只有一个调用方的逻辑创建 helper,**也不要添加只做重命名的 wrapper。
  • 行为放在 core 中。 如果 adapter 增加了事件监听器、focus 调用或
    aria-*
    属性,就说明它放在了错误的层级。

4. Check it

4. 检查

Run each and check the exit code, not the output:
sh
pnpm lint
pnpm format
pnpm typecheck
pnpm --filter <package> test
pnpm --filter examples build   # when you touched examples
pnpm test:pr                   # the specs your change reaches
Then run each framework's own lint rules on the files you touched:
npx eslint <files>
(
pnpm lint:idioms
runs them on the whole repo, as CI does). Fix what each one reports and run it again until all pass. Older errors are listed in
eslint-suppressions.json
; when you fix one, run
pnpm lint:idioms --prune-suppressions
.
运行下面每条命令并检查退出码,而不只是查看输出:
sh
pnpm lint
pnpm format
pnpm typecheck
pnpm --filter <package> test
pnpm --filter examples build   # 修改了示例时运行
pnpm test:pr                   # 运行本次修改涉及的规格测试
然后,对你修改过的文件运行每个框架自身的 lint 规则:
npx eslint <files>
(
pnpm lint:idioms
会像 CI 一样对整个仓库运行这些规则)。修复每一条报告的问题,然后重新运行,直到全部通过。旧错误列在
eslint-suppressions.json
中;每修复一个错误,就运行
pnpm lint:idioms --prune-suppressions
。

5. Read it cold

5. 冷静通读

Read the whole diff as the framework's expert would, using the "Review checklist" in each
knowledge.md
you loaded, and as a teammate who has never seen it:
  • Would an expert in this framework write it this way? If you can name a more idiomatic version, write that one.
  • Is every name clear without its comment?
  • Is anything duplicated that exists elsewhere?
  • Does any function do two things, or any file hold two ideas?
  • Any allocation on a hot path, or a layout read after a write?
以框架专家的视角,以及一位从未见过这段代码的团队成员的视角,完整通读整个 diff,并使用你之前加载的每个
knowledge.md
中的 “Review checklist”:
  • 框架专家会这样写吗?如果你能说出更符合该框架惯用法的版本,就改用那个版本。
  • 不看注释,每个名称是否都清晰明了?
  • 是否有任何内容与别处已有的实现重复?
  • 是否有函数同时做了两件事,或文件同时承载了两个想法?
  • 热路径中是否存在内存分配,或在写入之后读取布局?

6. Unslop

6. Unslop

Run
ui-sdk-unslop
on your diff. The change isn't done until it has. Before merging a change to an adapter, core, the specs, examples or framework docs, run
ui-sdk-expert-review
.
对 diff 运行
ui-sdk-unslop
。在完成该步骤前,修改不算完成。将 adapter、core、specs、examples 或框架文档的修改合并之前,运行
ui-sdk-expert-review
。