Twenty Postcard 示例应用开发规范:UUID v4 校验机制、视图与侧边栏导航联动及前端组件尺寸适配
本文以 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.md、LLMS.md 内容一致)是一份面向 AI 编码助手的开发规范,浓缩了三条必须遵守的规则。本文将其逐条展开,并给出源码级依据。
规则一:所有生成的 UUID 必须是合法的 UUID v4
规范文档明确指出:All generated UUIDs must be valid UUID v4(所有生成的 UUID 必须是合法的 UUID v4)。
这不是一个"建议",而是 SDK 构建阶段的硬性校验。在 Twenty SDK 的 manifest 构建校验器中可以看到对应的实现:
- 校验入口 manifest-validate.ts 引入了
uuid包的validate与version两个函数,对每个实体的universalIdentifier执行两步检查:uuidValidate(identifier)不通过时,报错Universal identifier "..." is not a valid UUID.;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-042478e839ec、38d9c9d1-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/ 下的视图文件视为一个整体一起提交;二者的 position 与 universalIdentifier 都需遵循 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 })、MetadataApiClient与RestApiClient(GET /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
仓库为示例配了两层自动化验证:
- 单元/集成层:vitest.config.ts 与 schema.integration-test.ts,校验应用 schema 的合法性;
- E2E 层:playwright.config.ts 下的用例,其中 card-front-component.spec.ts 直接针对上文前端组件的
data-testid(定义于 card-test-ids.ts)断言渲染结果,shared-dependencies-bundle.spec.ts 验证共享依赖打包。yarn twenty build触发 manifest 校验时,UUID v4 规则即由 manifest-validate.ts 强制执行,是"规则一"的最终守门员。
小结
postcard 示例附带的规范文档虽短,但精准覆盖了 Twenty App 开发中最容易踩的三个坑:
- UUID 版本:所有
universalIdentifier必须是 v4 UUID,SDK 构建校验器会硬性拦截(最低版本常量MINIMUM_UNIVERSAL_IDENTIFIER_UUID_VERSION = 4); - 视图可达性:View 必须与 navigationMenuItem 成对出现,否则视图无法出现在左侧边栏;
- 组件尺寸:Front Component 默认要自适应固定 widget 容器、禁止内置滚动,canvas tab 组件除外。
结合 src/ 下各实体的真实代码与 E2E 测试,这份示例可以完整复制到自研 App 中:先 yarn twenty dev 热更新开发,构建时让 manifest 校验兜底,再借 E2E 用例验证前端组件行为。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00