首页
/ Twenty Apps 开发指南:hello-world 示例中 LLM 协作开发的三条核心规则(UUID v4、视图导航关联、组件响应式)

Twenty Apps 开发指南:hello-world 示例中 LLM 协作开发的三条核心规则(UUID v4、视图导航关联、组件响应式)

2026-09-05 09:05:19作者:江焘钦

本文以 Twenty 官方 hello-world 应用示例中的 LLM 指令文档 LLMS.md 为主体,逐条拆解其中定义的"UUID 必须为 v4"硬性要求与两条常见陷阱,并对照该示例应用的全部源码实体(对象、视图、导航菜单项、前端组件、逻辑函数)说明每条规则背后的工程约束。读完后,你应当掌握用 LLM 辅助生成 Twenty App 代码时的三条自检规则,以及如何在仓库中找到对应的参照实现与更完整的 rich-app 样例。

一、LLMS.md 的定位:写给 LLM 的应用开发守则

hello-world 是 Twenty 仓库内置的"最小可用"应用示例,其 README 中明确写道:"Main docs and pitfalls are available in LLMS.md file."(见 README.md)。也就是说,LLMS.md 是该应用面向大语言模型(LLM)的官方提示文档,用于在 LLM 生成或修改应用代码时约束其行为边界。该文档由三个部分组成:

  1. Base documentation(基础文档入口):指向 Twenty 官方应用开发 Getting Started 文档,并指向一个内容更丰富的完整应用样例——在本仓库中对应 rich-app 示例,当 hello-world 不足以覆盖某种模式时,rich-app 是首选参照;
  2. UUID requirement(UUID 要求):"All generated UUIDs must be valid UUID v4."(所有生成的 UUID 必须是合法的 UUID v4);
  3. Common Pitfalls(常见陷阱):两条具体陷阱——
    • "Creating a view without a navigationMenuItem associated."(创建视图时未关联对应的 navigationMenuItem);
    • "Creating a front-end component that has a scroll instead of being responsive to its fixed widget height and width, unless it is specifically meant to be used in a canvas tab."(前端组件内置了滚动,而不是响应式地适应其固定的 widget 高度与宽度,除非该组件专门用于 canvas tab)。

这三条规则看似简短,却分别对应 Twenty 应用开发中三个最容易由 LLM"想当然"写错的层面:稳定标识符、界面导航注册、组件尺寸约束。下文逐条展开。

二、规则一:所有生成的 UUID 必须是合法的 UUID v4

2.1 为什么 universalIdentifier 是应用的核心

从 hello-world 示例的源码结构看,应用中的每一类实体都通过 universalIdentifier 字段建立唯一身份,且该值是硬编码在源码中的字符串常量,而不是运行时生成:

实体类型 定义文件 universalIdentifier 示例
应用 application-config.ts bb1decf6-dee5-43ef-b881-9799f97b02a8
对象(Object) example-object.ts 47fd9bd9-392b-4d9f-9091-9a91b1edf519
字段(Field) example-object.ts 2d9ff841-cf8e-44ec-ad8e-468455f7eebd
视图(View) example-view.ts 965e3776-b966-4be8-83f7-6cd3bce5e1bd
导航菜单项 example-navigation-menu-item.ts 9327db91-afa1-41b6-bd9d-2b51a26efb4c
前端组件 hello-world.tsx 7a758f23-5e7d-497d-98c9-7ca8d6c085b0
逻辑函数 hello-world.ts b05e4b30-72d4-4d7f-8091-32e037b601da
安装前钩子 pre-install.ts f8ad4b09-6a12-4b12-a52a-3472d3a78dc7
安装后钩子 post-install.ts 8c726dcc-1709-4eac-aa8b-f99960a9ec1b

这些标识符的作用可以从源码中相互引用的方式得到印证:example-object.ts 将对象标识符导出为 EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER、将 name 字段导出为 NAME_FIELD_UNIVERSAL_IDENTIFIER,随后 example-view.ts 通过 objectUniversalIdentifier 属性引用前者、通过 fieldMetadataUniversalIdentifier 引用后者,example-navigation-menu-item.ts 再引用视图的标识符。可见 universalIdentifier 是跨实体装配应用的稳定主键:安装、同步、升级都依赖它识别"这是同一个实体"。因此 LLM 新增任何实体时,必须生成一个全新的、合法的标识符并硬编码导出,而不能省略或随意填写。

2.2 如何验证一个 UUID 是合法的 v4

UUID v4 的格式为 8-4-4-4-12 共 32 位十六进制数,其中有两个"指纹位":

  • 第三组首位必须是版本位 4
  • 第四组首位必须是变体位,取值范围为 89ab

以本示例中的应用标识符 bb1decf6-dee5-43ef-b881-9799f97b02a8 为例:第三组 43ef4 开头(版本位正确),第四组 97999 开头(变体位合法),因此是一个标准的 UUID v4。示例中列出的其余标识符(如 47fd9bd9-392b-4d9f-9091-9a91b1edf519965e3776-b966-4be8-83f7-6cd3bce5e1bd)同样满足这两条指纹规则。

这对 LLM 生成代码有直接的实操含义:

  • 生成的每个 universalIdentifier 都应先通过上述两条指纹位校验;
  • 标识符必须在整个应用内唯一,新增实体时不能复制已有实体的值;
  • 标识符一经写入源码即成为稳定契约,后续迭代不应重新生成。

三、规则二:视图必须关联 navigationMenuItem,否则不会按预期出现在左侧边栏

LLMS.md 的第一条 Common Pitfall 指出:"Creating a view without a navigationMenuItem associated." 即只创建视图而不创建关联的导航菜单项,是 LLM 生成应用代码时最常见的错误之一。hello-world 示例给出了正确的完整配对方式。

3.1 视图侧:只负责声明"看什么"

example-view.ts 通过 defineView 声明了一个针对自定义对象 exampleItems 的列表视图:

export const EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER = '965e3776-b966-4be8-83f7-6cd3bce5e1bd';

export default defineView({
  universalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
  name: 'All example items',
  objectUniversalIdentifier: EXAMPLE_OBJECT_UNIVERSAL_IDENTIFIER,
  icon: 'IconList',
  position: 0,
  fields: [
    {
      universalIdentifier: 'f926bdb7-6af7-4683-9a09-adbca56c29f0',
      fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
      position: 0,
      isVisible: true,
      size: 200,
    },
  ],
});

注意视图中每一列同样拥有自己的 universalIdentifier(这里是 name 字段列),并引用对象字段定义中的 NAME_FIELD_UNIVERSAL_IDENTIFIER——"每列一个 v4 UUID"的规则在视图中再次得到贯彻。

3.2 菜单项侧:负责把视图"挂"到左侧导航

example-navigation-menu-item.ts 通过 defineNavigationMenuItem 声明导航入口,并以 type: NavigationMenuItemType.VIEW(类型来自 twenty-shared/types)加 viewUniversalIdentifier 指向该视图:

export default defineNavigationMenuItem({
  universalIdentifier: '9327db91-afa1-41b6-bd9d-2b51a26efb4c',
  name: 'example-navigation-menu-item',
  icon: 'IconList',
  color: 'blue',
  position: 0,
  type: NavigationMenuItemType.VIEW,
  viewUniversalIdentifier: EXAMPLE_VIEW_UNIVERSAL_IDENTIFIER,
});

从源码结构看,两者之间不互相导入组件实现,而是通过视图导出的标识符常量完成装配:菜单项决定"左侧边栏出现什么、排第几、什么颜色图标",视图决定"点开之后展示哪些字段"。因此 LLM 在生成代码时的检查项很明确:

  1. 每生成一个 defineView,必须同时生成一个 defineNavigationMenuItem
  2. 菜单项的 viewUniversalIdentifier 必须引用视图导出的标识符常量,而不是手敲一份副本;
  3. 菜单项自身同样需要独立的 UUID v4 标识符。

四、规则三:前端组件必须响应式适应 widget 尺寸,默认不内置滚动

第二条 Common Pitfall 要求:前端组件不应自带滚动条(scroll),而应响应式地适应其所在的 widget 的固定高度与宽度——唯一的例外是该组件明确用于 canvas tab 场景。

4.1 组件运行环境决定了这条规则

应用的前端组件最终由 Twenty 的前端组件渲染器加载执行,相关运行时实现位于 twenty-front-component-renderer 包中:从源码结构看,它包含 host/(宿主侧)与 remote/(远端沙箱侧)两部分及 polyfills/ 目录,负责把应用包中的 React 组件挂载到 Twenty 界面里的具体插槽中。组件在记录页、看板等位置呈现时,通常被放进尺寸固定的 widget 容器——如果组件内部再声明一套自己的滚动区域,就会出现"滚动套滚动"、内容被双重裁剪的观感问题。LLMS.md 因此把"响应容器而非自滚"定为默认规范,仅当组件是专为 canvas tab(大画布场景,用户预期内容可滚动扩展)设计时,才允许内部滚动。

4.2 hello-world 组件的示范写法

hello-world.tsx 展示了符合该规则的典型形态:

const client = new CoreApiClient();
// ...
return (
  <div style={{ padding: '20px', fontFamily: 'sans-serif' }}>
    <h1>Hello, World!</h1>
    <p>This is your first front component.</p>
    {data ? (
      <div>
        <p>Company name: {data.name}</p>
        <p>Company id: {data.id}</p>
      </div>
    ) : (
      <p>Company not found</p>
    )}
  </div>
);

export default defineFrontComponent({
  universalIdentifier: HELLO_WORLD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
  name: 'hello-world-front-component',
  description: 'A sample front component',
  component: HelloWorld,
});

要点有三:

  • 组件根节点是普通流式 div,不设固定高度、不设 overflow 滚动,内容自然撑开或由父容器约束;
  • 组件通过 CoreApiClient(来自 twenty-client-sdk/core)发起类型化的 GraphQL 查询(示例中按 position: 1 过滤取一条 company 记录),展示应用组件与宿主数据模型的交互方式;
  • 组件通过 defineFrontComponent 注册,并遵循规则一使用 UUID v4 的 universalIdentifier

对 LLM 的落地约束可以归纳为:生成组件时避免 height: 100vh、固定像素高容器、overflow: auto/scroll 这类写法;除非需求明确说明组件用于 canvas tab,否则一律按"内容自适应、滚动交给宿主"来写。

五、配套工作流与落地检查清单

规则生效的场景,正是 README 中描述的 LLM 参与开发的工作流(见 README.md):

# 认证到目标 workspace
yarn twenty remote:add --api-url http://localhost:2020 --as local
# 启动开发模式(watch + build + sync + 自动生成类型化 client)
yarn twenty dev
# 用脚手架生成新实体(object / field / function / front-component / role / view / navigation-menu-item)
yarn twenty dev:add

yarn twenty dev:add 支持脚手架化的实体类型中同时包含 viewnavigation-menu-item,这正对应本文规则二强调的"成对生成"。综合 LLMS.md 与示例源码,LLM 生成或修改 Twenty App 代码后,建议按下述清单自检:

  1. 标识符:所有新增/修改的 universalIdentifier(应用、对象、字段、视图、视图列、菜单项、组件、逻辑函数)均为合法 UUID v4,且全应用唯一;
  2. 视图-导航配对:每个新视图都有对应的 defineNavigationMenuItem,且通过导出的标识符常量引用该视图;
  3. 组件响应式:前端组件不内置滚动,适应 widget 固定尺寸;仅 canvas tab 专用组件例外;
  4. 引用方式:实体间引用一律使用导出的标识符常量,不复制粘贴字符串;
  5. 模式不足时:hello-world 覆盖不到的更复杂模式(角色权限、技能、字段类型等),参考本仓库中更完整的 rich-app 样例 与 LLMS.md 所指向的官方 Getting Started 文档。

以上五条规则在 hello-world 示例的每一个源码文件中都有对应实例可循,是 LLM 与开发者协作开发 Twenty 应用时最基础、也最值得固化的行为约束。

登录后查看全文
热门项目推荐
相关项目推荐