
Rivet Actors 前端工程规范React 交互原语、多 Flavor 特性开关与测试策略实践【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors导读Rivet Actors 的 Web Dashboardfrontend/即rivetkit/engine-frontend是一个面向有状态 Actor 工作负载的控制台同一套前端构建需同时服务云平台cloud、自托管 OSS 与私有化 Enterprise 三种部署形态。本文以仓库内 frontend/CLAUDE.md 为核心结合 features.ts、agent-mocks.ts 等源码系统讲解该前端的编码约定键盘快捷键与异步 Mutation 的统一抽象、渲染期派生状态、嵌入式构建路径、跨 Flavor 特性开关、双形态布局同步以及基于 Ladle 与 MSW 的组件测试与端到端 Mock 方案。读完本文你将掌握在 Rivet 仓库内开发 Dashboard 功能时的完整落地姿势与可验证的源码依据。一、交互原语统一使用 TanStack 抽象禁止手写 DOM 监听Dashboard 大量承载快捷键 异步提交类交互如 Actor 详情页的 REPL、状态编辑、构建操作。仓库规范明确要求不要自己手写window.addEventListener(keydown, ...)也不要手写useState加载标志 try/catch/setState。1.1 键盘快捷键useHotkey/useHotkeySequence快捷键统一通过tanstack/react-hotkeys的useHotkey/useHotkeySequence注册该依赖已声明于 frontend/package.json 的 dependencies 中。其关键用法之一是ignoreInputs选项当焦点位于 input、textarea、contenteditable 等可输入元素内时应通过ignoreInputs让快捷键自动失效禁止手动检查e.target.tagName来做等价判断。原因很直接手写监听不仅要处理注册/注销的生命周期还要自行维护输入态判断与快捷键冲突而 TanStack 抽象把这些都收敛到选项里语义更清晰、更不容易漏掉边界。// 推荐声明式注册 ignoreInputs useHotkey(modk, () openCommandPalette(), { ignoreInputs: true });1.2 异步用户操作一律走useMutation任何带有 pending / loading / error 状态的用户触发操作创建 Actor、删除构建、更新配置、提交表单等都必须用tanstack/react-query的useMutation封装UI 通过mutation.isPending/mutation.mutate()驱动const mutation useMutation({ mutationFn: (id: string) deleteActor(id), onSuccess: () queryClient.invalidateQueries({ queryKey: [actors] }), }); return ( Button isLoading{mutation.isPending} onClick{() mutation.mutate(actor.id)} Delete /Button );这样做的收益是pending 状态、错误对象、重试与失效缓存invalidateQueries都交给 React Query 统一管理UI 层不再散落手工状态机。仓库中大量页面如 actor-stop-button.tsx、各*-form.tsx都遵循这一模式。二、状态管理渲染期派生禁用 useEffect 作为逃生通道规范的核心约束是状态应该在渲染期间派生而不是用useEffect去同步。即如果一个值可以由 props 或现有 state 计算得到就在组件体内直接计算不要先渲染旧值、再用 effect 补一刀不要把useEffect当作通用逃生通道escape hatch使用如果确实找不到其他方案、认为某个 effect 不可避免先停下来向用户/维护者确认后再添加。这条规则直接决定了 Dashboard 中大量列表、过滤、派生统计例如 namespace 下的 runner 数量、builds 排序的写法先计算再渲染而不是渲染后同步避免闪烁、重复渲染与 effect 竞态。相关范式可参考 engine-namespace-landing.tsx 中渲染期对 builds 排序、对 runner 配置计数的实现。三、嵌入式构建BASE_URL/ui/ 是唯一正确的路径策略Rivet Engine 会把 Dashboard 前端嵌入到rivet-engine产物中对外服务这要求构建时指定资源基路径。规范规定嵌入式构建必须使用pnpm build:engine等价于npx turbo build:engine -F rivetkit/engine-frontend该命令会设置BASE_URL/ui/见 frontend/package.json 的build:enginescript严禁通过给 engine 添加根路径/assets/*路由来补偿错误的构建基路径——发现问题时应修正嵌入式构建的 base而不是在 engine 侧打补丁。这条约束保证了同一份源码既能以根路径构建出独立 Dashboardpnpm build也能以/ui/基路径嵌入 engine 自托管控制台两套产物互不干扰。四、一份构建服务三种形态Feature Flags 架构Dashboard 用一套前端构建同时服务 cloud / OSS / enterprise 三种部署 flavor其机制是features.*特性开关。所有开关集中定义在 frontend/src/lib/features.ts 一处调用点只读取布尔值import { features } from /lib/features; if (features.platform) { // cloud-platform-only UI }从 features.ts 源码可以看到完整的开关解析链运行时事实来源是VITE_FEATURE_FLAGS环境变量逗号分隔的开关名列表开发环境import.meta.env.DEV下localStorage的FEATURE_FLAGS键优先于环境变量用于本地模拟任意 flavor未设置undefined表示所有开关全开即完整云构建显式给出空字符串或列表则只启用所列开关PostHog 标志仅云是纯增量合并只能把某个开关打开永远不能关闭环境变量/localStorage 已启用的开关见 lib/posthog.ts 的getPosthogEnabledFeatureFlags开关之间存在隐含依赖在 features.ts 内编码而非在每个调用点处理。例如platform隐含auth、acl隐含于platform、compute要求platform、captcha要求auth。当前仓库中定义的开关及其语义依据 features.ts 与 .claude/reference/feature-flags.md开关语义authDashboard 提供登录/注册流程不代表engine 需要凭据platform云平台栈publishable-token 端点、billing、projects、多租户隐含authaclengine 在公网端点强制 token 鉴权platform隐含企业版单独开启billing计费 UIcaptcha认证表单上的 Turnstile 验证码要求authcomputeRivet Compute托管池UInamespace 部署、日志入口、Rivet provider 选项要求platformbyocBring Your Own Cloud创建项目流程中的 BYOC 选项、集群列表与集群页要求platformservices托管服务Durable Streams产品选择器 Services 分区、onboarding 路径、namespace settings 的 Services 页签不依赖platformsupport/branding/datacenter/danger-zone帮助入口、品牌装饰、数据中心相关 UI、危险操作features.dangerZone三种 flavor 的开关组合大致为cloud 全开OSS 关闭auth/platform/aclenterprise 开启acl、关闭auth/platformengine 强制鉴权但没有登录 UI。注意compute在云上也是按环境显式 opt-in的各 Railway 服务在VITE_FEATURE_FLAGS中自行添加并非继承云默认全开集合。4.1 何时新增开关能力命名 防蔓延按 feature-flags.md 的约定只有满足以下条件才新增开关功能在至少一种 flavor 上不可用/受限/行为不同或引入了某个 flavor 可能整体关闭的较大 UI 表面整页、面板、设置区、子系统。小范围的通用改动bug 修复、文案、布局打磨不加开关命名以能力为准如billing、support而不是以部署形态命名如enterprise-only——因为 flavor 是由开关组合出来的反过来不行。不确定是否需要开关时先与用户确认因为一旦各 flavor 依赖上开关就难以移除。4.2 跨 Flavor 测试OSS 是必测项规范强调任何前端改动完成前至少要在 OSS 与 cloud 两种 flavor 上验证。原因是 OSS 关掉了最多的能力auth/platform/acl任何假设云上下文org/project 参数、登录会话、云数据提供器的代码都会在 OSS 下静默损坏而 OSS 的 namespace 下拉、侧边栏、context switcher、onboarding 与云走的是完全不同的代码路径。无需重启 dev server在浏览器控制台切换 flavor 即可// OSS 自托管全部关闭 localStorage.setItem(FEATURE_FLAGS, ); location.reload(); // 完整云全部开启参考 frontend/.env.local 中的注释清单 localStorage.setItem( FEATURE_FLAGS, compute,platform,acl,auth,captcha,branding,support,billing,datacenter,danger-zone,multitenancy,byoc,services, ); location.reload(); // Enterprise开启 acl无登录 UI localStorage.setItem(FEATURE_FLAGS, acl,branding,support,datacenter,danger-zone); location.reload();测试完毕后执行localStorage.removeItem(FEATURE_FLAGS)恢复环境变量默认。也可以用JSON.stringify(features)确认当前激活的开关集合。原理见 features.ts 第 5-7 行dev 构建中localStorage优先于VITE_FEATURE_FLAGS。五、双形态布局必须同步OSS 与 Platform 不允许视觉分叉由于 OSS 与 cloud 在同一套代码里走不同路径engine 路由 vs 云路由、不同数据提供器很容易出现同一屏幕两个样子。规范明确OSS 与 platform 的布局不得视觉分叉。仓库中有两条已知的平行配对改动时必须成对维护namespace 落地页engine 端 engine-namespace-landing.tsx 与云端 actors-grid.tsx两者共享ActorBuildCard/ActorGridCardSkeleton呈现组件源码注释明确要求保持视觉同步并指出云端的 Deployments 区块与日志链接是 OSS 有意省略的部分namespace 设置抽屉settings-drawer.tsx 是一个 flavor 感知组件engine 形态只展示 Namespace 分区Account/Project/Billing/Organization 等云专属导航项被隐藏。维护守则改动配对中的一侧必须镜像另一侧优先抽取共享的呈现组件而不是复制 markup。5.1 路由行为无 Actor 选中时不自动跳转Engine 与 cloud 的 namespace index 路由在没有选择 Actor 名n参数时都渲染 Actor-grid 落地页规范禁止自动重定向到第一个 build/actor。只有当用户选中某个 build设置n后才显示 Actor 列表/详情。这保证了跨 flavor 一致的信息架构也避免强制改变用户心智模型。六、Ladle Story 规范为真实状态写故事而非为属性组合仓库用 Ladle。三条硬性准则故事化集成单元而不是薄包装。如果组件只是某个原语如三种 label 变体的Badge的薄封装不为它写 story——应为把它与其他状态组合起来的父组件写。有趣的状态都出现在数据形态交互处行 单元格 tooltip、表单 校验 提交。三个几乎相同的渲染是噪声不是覆盖率。用贴近真实 API 的 fixtures 驱动而不是属性排列。fixtures 要镜像真实后端响应空结果、单条、多区域、部分失败、混合类型。每个 story 回答的问题是后端返回 X 时长什么样。逐一列举属性组合会让 story 通过设计评审却漏掉真实 bug——比如空 endpoint 集合落入 Multiple endpoints 分支。示例 stories 文件中对 HTTP 500 / 502、连接错误、非法 SSE payload 等错误类型的真实 JSON fixture 就是典型做法。覆盖这个组件真实出过 bug 的状态。修复视觉 bug 时回归用例就固化成 story如果一个状态无法改变行为那就不需要再写 story。此外如果写 story 需要 mock 路由 loader、auth 或整套数据提供器栈就跳过 story。优先重构组件把输入收成 propsstory 自然免费获得或者通过运行中的 dashboard 的父路由来测试严禁在 story 内 stubuseLoaderData/useRouteContext该路径腐化极快。七、Dev 端到端 HTTP MockMSW ?mock1驱动错误 UIDashboard 提供了仅限开发环境的端到端 HTTP Mock 能力用于在不搭建真实 engine 状态的情况下演练错误 UI。实现位于 frontend/src/lib/agent-mocks.ts并受import.meta.env.DEV门控见源码第 54 行生产包完全不受影响——import(msw/browser)是 dev 门后的动态导入。7.1 启动与 API给任意 dashboard URL 追加?mock1应用启动时即初始化 MSW workermaybeStartAgentMocks然后在 DevTools / agent-browser 控制台使用两个全局 API// 注册一条 mockpattern 是 MSW 路径匹配器 window.__rivetMock(*/actors/:id/kv/keys/*, { status: 503, body: { group: guard, code: service_unavailable, message: ... }, }); // 清空所有 mock window.__rivetClearMocks();7.2 关键行为源码级细节支持 method 与延时MockSpec 可带methodget/post/put/delete/patch默认 get与delayMsdelayMs会先delay()再返回响应从而让触发它的 query 长时间停留在 pending 状态便于检查 loading 骨架屏见 agent-mocks.ts。持久化到 sessionStoragemock 会写入__rivetAgentMocks键并在刷新后恢复worker.start时重建 handlers契合设置 mock → 刷新重放 query的常见 agent 测试工作流__rivetClearMocks同时清空存储并resetHandlers()。未处理的请求一律 bypassonUnhandledRequest: bypass不会误伤正常请求。加载骨架示例用delayMs挂住响应即可window.__rivetMock(*/actors/names, { status: 200, body: { names: {} }, delayMs: 10000 });7.3 生产安全由于 mock 激活被import.meta.env.DEV硬门控、MSW 模块在 dev gate 之后才动态导入生产 bundle 中不存在 mock 代码与 worker无需担心?mock1泄露到线上环境。八、落地清单一份改动从编码到验收的完整路径结合以上规范一次典型的 Dashboard 前端改动应经过编码快捷键用useHotkey/useHotkeySequence配ignoreInputs异步操作包useMutation派生状态在渲染期计算非必要不写useEffect确实不可避免时先征询确认。构建若改动最终要嵌入 engine用pnpm build:engine验证/ui/基路径产物不在 engine 侧加根路径路由。Flavor 验证在浏览器控制台用localStorage.setItem(FEATURE_FLAGS, )切到 OSS、用移除键恢复云默认OSS 与 cloud 至少各验证一遍若涉及features.*条件渲染的表面sidebar、context switcher、onboarding、settings、auth、billing逐个 flavor 在浏览器里实际操作。布局同步命中 namespace 落地页或设置抽屉等平行配对时镜像另一侧实现优先抽共享呈现组件。Story新组件有值得教的状态时补 Ladle story真实 fixtures 回归 bug 状态薄包装或需 stub 路由/auth 的跳过。Mock 演练需要展示错误/空/加载态时用?mock1__rivetMock端到端演练后再收尾。这套约定把多形态单构建的复杂度收敛为少数几条可复用的抽象features 开关、Mutation、MSW 门控、平行配对也是仓库内所有前端贡献者共同遵循的工程基线相关完整说明可继续阅读 .claude/reference/feature-flags.md 与 frontend/CLAUDE.md。【免费下载链接】actorsRivet Actors are the primitive for stateful workloads. Built for AI agents, collaborative apps, and durable execution.项目地址: https://gitcode.com/GitHub_Trending/riv/actors创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考