首页
/ Twenty Postcard 示例应用开发规范:UUID v4 校验机制、视图与侧边栏导航联动及前端组件尺寸适配

Twenty Postcard 示例应用开发规范:UUID v4 校验机制、视图与侧边栏导航联动及前端组件尺寸适配

2026-09-05 22:16:00作者:郁楠烈Hubert

本文以 Twenty 仓库中 packages/twenty-apps/examples/postcard 示例应用附带的开发规范文档为骨架,系统讲解 Twenty App 开发中三条核心规则:所有 universalIdentifier 必须使用 UUID v4、View 必须与 navigationMenuItem 关联才能在左侧边栏展示、前端组件必须自适应固定尺寸的 widget 容器。读完后你既能掌握 postcard 示例的完整实体结构,也能理解 SDK 在构建 manifest 时对 UUID 的底层校验逻辑。

Postcard 示例应用:规范文档的落地载体

postcard 是 Twenty 仓库内一个功能完整的 Twenty App 示例,位于 postcard 应用目录,覆盖了 Twenty SDK 支持的全部实体类型。其 README 给出了实体清单:

实体类型 目录 说明
Application src/application.config.ts 应用元数据、应用变量、服务端变量
Objects src/objects/ 自定义对象、内联字段、关联表(junction table)
Fields src/fields/ 独立字段、关系字段(ONE_TO_MANY / MANY_TO_MANY 方向的关系)、扩展标准对象
Logic Functions src/logic-functions/ HTTP 路由、数据库事件触发、cron 定时任务、工具函数、安装钩子
Front Components src/components/ 渲染在 Twenty UI 内部的 React 组件
Roles src/roles/ 对象与字段级权限控制的角色
Views src/views/ 带列配置保存的表格视图
Navigation src/navigation-menu-items/ 指向视图的侧边栏链接
Skills src/skills/ 为 AI Agent 提供上下文的技能
Agents src/agents/ 带系统提示词的 AI 智能体
Page Layouts src/page-layouts/ 内嵌前端组件 widget 的自定义记录页

该应用目录下的 CLAUDE.md(与 AGENT.mdLLMS.md 内容一致)是一份面向 AI 编码助手的开发规范,浓缩了三条必须遵守的规则。本文将其逐条展开,并给出源码级依据。

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

规范文档明确指出:All generated UUIDs must be valid UUID v4(所有生成的 UUID 必须是合法的 UUID v4)。

这不是一个"建议",而是 SDK 构建阶段的硬性校验。在 Twenty SDK 的 manifest 构建校验器中可以看到对应的实现:

  • 校验入口 manifest-validate.ts 引入了 uuid 包的 validateversion 两个函数,对每个实体的 universalIdentifier 执行两步检查:
    1. uuidValidate(identifier) 不通过时,报错 Universal identifier "..." is not a valid UUID.
    2. uuidVersion(identifier) 低于最低版本时,报错 Universal identifier "..." is UUID version X. Only UUID version 4 or higher is allowed.
  • 最低版本阈值定义在 manifest-validation-helpers.ts
export const MINIMUM_UNIVERSAL_IDENTIFIER_UUID_VERSION = 4;

也就是说,yarn twenty build 生成 manifest 时,任何非 v4(或更低版本号)的 universalIdentifier 都会直接让构建失败。postcard 示例自身严格遵循了这一约束,例如 all-post-cards.view.ts 中的视图 ID b1a2b3c4-0001-4a7b-8c9d-0e1f2a3b4c5d、第三段以 4 开头的正是 v4 特征位,视图列字段 ID 如 501adcc2-6c2b-48bd-a042-042478e839ec38d9c9d1-a55c-4662-9727-e1e19bed6d82 同样符合 v4 格式。

实操要点:新增实体(对象、字段、视图、导航菜单项、前端组件等)时,每个实体的 universalIdentifier 都需要一个 v4 UUID,且整个应用内保持唯一。建议用 uuidgen 或语言库中的 v4 生成器(而非手写)生成,并作为命名常量导出复用——postcard 的做法就是 export const ALL_POST_CARDS_VIEW_ID = 'b1a2b3c4-0001-4a7b-8c9d-0e1f2a3b4c5d',再在 defineView 中引用。

规则二:创建 View 时必须关联 navigationMenuItem,否则视图不会进入左侧边栏

规范文档列出的第一个常见陷阱是:Creating a view without a navigationMenuItem associated. This will make the view available on the left sidebar.(创建了一个没有关联 navigationMenuItem 的视图。只有通过关联,该视图才能在左侧边栏中被访问到。)

postcard 示例完整演示了"View + 导航菜单项"这对组合的正确写法。

视图定义(all-post-cards.view.ts):

export default defineView({
  universalIdentifier: ALL_POST_CARDS_VIEW_ID,
  name: 'All Post Cards',
  objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
  type: ViewType.TABLE,
  icon: 'IconMail',
  position: 0,
  fields: [
    {
      universalIdentifier: '501adcc2-6c2b-48bd-a042-042478e839ec',
      fieldMetadataUniversalIdentifier: NAME_FIELD_UNIVERSAL_IDENTIFIER,
      position: 0,
      isVisible: true,
      size: 200,
    },
    // ... 其余列:recipient、content 等,每列均为 { universalIdentifier, fieldMetadataUniversalIdentifier, position, isVisible, size }
  ],
});

配套的导航菜单项(post-cards.navigation-menu-item.ts):

export default defineNavigationMenuItem({
  universalIdentifier: 'c1a2b3c4-0001-4a7b-8c9d-0e1f2a3b4c5d',
  position: 0,
  type: NavigationMenuItemType.OBJECT,
  targetObjectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER,
});

从源码结构看,导航菜单项通过 targetObjectUniversalIdentifier 指向自定义对象 PostCard,从而把该对象名下配置好的 Table 视图挂载到 Twenty 的左侧对象导航中;position 字段同时控制侧边栏中的排序。因此,只写 View 而不写 navigationMenuItem,视图在数据模型上是存在的,但用户无法从侧边栏入口触达——这正是规范文档要规避的"隐形视图"问题。

实操要点:为自定义对象创建视图时,把 src/navigation-menu-items/ 下的菜单项文件和 src/views/ 下的视图文件视为一个整体一起提交;二者的 positionuniversalIdentifier 都需遵循 v4 UUID 规则。

规则三:前端组件必须自适应固定的 widget 尺寸,不要内置滚动(画布页签除外)

第二个常见陷阱是: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.(除非组件明确用于 canvas tab(画布页签),否则前端组件不应内置滚动条,而应响应其固定的 widget 高度与宽度。)

Twenty 的 Front Component 是嵌入主应用 UI 的 React 组件,被宿主以固定尺寸的 widget 容器渲染。若组件内部使用 overflow: auto 之类的滚动布局,会在固定容器内出现"双层滚动",破坏 Twenty 原生的界面一致性;只有明确以 canvas tab 形式全屏展示的组件才适合自带滚动。

postcard 的核心前端组件 card.front-component.tsx 给出了正例写法:

  • 根节点 CardDisplay 使用流式布局(padding + flex),不设置任何 overflow / 固定像素高度,让内容随容器高度自然伸缩,长正文通过 whiteSpace: 'pre-line' 换行而非内部滚动;
  • 通过 defineFrontComponent 注册,并引用 useRecordId(来自 twenty-sdk/front-component)获取当前记录的 ID:
export default defineFrontComponent({
  universalIdentifier: CARD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
  name: 'card-component',
  description: 'A component using an external component file',
  component: PostCardPreview,
});
  • 组件内通过 three 个客户端访问数据:CoreApiClient(GraphQL 查询记录,如 postCard: { __args: { filter: { id: { eq: recordId } } }, name: true, content: true, status: true })、MetadataApiClientRestApiClientGET /rest/postCards),并用 setInterval 每 3 秒轮询刷新卡片状态(DRAFT / SENT / DELIVERED / RETURNED)。组件对异常状态(无 recordId、token 缺失)也做了降级展示,而非抛出滚动容器。

实操要点:写 Front Component 时默认假设宿主容器高度固定且不可控,采用"内容截断/自适应"策略;仅当组件专门用于 canvas tab 时,才考虑启用内部滚动。

运行与验证:dev、单元测试与 E2E

规范文档还指向了两个基础参考:Twenty 官方 App 开发文档(Getting Started),以及仓库内的完整示例应用 rich-app fixture——后者展示了更复杂的实体组合,可作为 postcard 的进阶参照。

本地运行方式(见 postcard README):

# 进入示例目录后
yarn install
yarn twenty dev

仓库为示例配了两层自动化验证:

小结

postcard 示例附带的规范文档虽短,但精准覆盖了 Twenty App 开发中最容易踩的三个坑:

  1. UUID 版本:所有 universalIdentifier 必须是 v4 UUID,SDK 构建校验器会硬性拦截(最低版本常量 MINIMUM_UNIVERSAL_IDENTIFIER_UUID_VERSION = 4);
  2. 视图可达性:View 必须与 navigationMenuItem 成对出现,否则视图无法出现在左侧边栏;
  3. 组件尺寸:Front Component 默认要自适应固定 widget 容器、禁止内置滚动,canvas tab 组件除外。

结合 src/ 下各实体的真实代码与 E2E 测试,这份示例可以完整复制到自研 App 中:先 yarn twenty dev 热更新开发,构建时让 manifest 校验兜底,再借 E2E 用例验证前端组件行为。

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