name: miniapp-dev description: Develops, maintains, and generates BitFun MiniApps (Zero-Dialect Runtime). Use when (1) working on miniapp framework code under src/crates/assembly/core/src/miniapp/ or src/web-ui/src/app/scenes/miniapps/; or (2) generating / creating / designing a NEW MiniApp for the user — including any request like "做一个小应用 / 生成 MiniApp / 写个 BitFun 小工具 / 创建 mini app". Also triggers on MiniApp, miniapps, bridge, zero-dialect, InitMiniApp, app.fs / app.shell / app.storage, or any work under MiniApp/Demo/ and MiniApp/Skills/.
BitFun MiniApp V2 指南
本 Skill 服务两类工作:
- 维护框架本身 → 阅读下方"代码架构 / Bridge / 权限模型 / window.app API"等章节。
- 生成一个新的 MiniApp → 必读
design-playbook.md,并遵循下方"生成新 MiniApp 必读(速查)"章节的硬约束。详细 API 见api-reference.md。
生成新 MiniApp 必读(速查)
完整指南见
design-playbook.md。这里是不可妥协的硬约束,AI 在用InitMiniApp工具创建骨架后必须遵守。
流程
- 先问,再做:用户的目标 / 受众 / 是否需要 node mode / 权限边界 / 是否需要 Tweaks 变体 / 是否多语言 / 是否有视觉参考——任何一项含糊就用
AskUserQuestion问。不要替用户决定。 - 找设计上下文:先读
MiniApp/Demo/与src/crates/contracts/product-domains/src/miniapp/builtin/assets/中最贴近形态的内置应用,复刻它的视觉语言(间距 / 圆角 / 卡片密度 / motif)。从零 mock 是最后选择。 - 声明设计系统:
style.css顶部用注释钉住 palette / typography / radius / motif(参见 playbook §1.3 模板),后续全应用复用。 - 占位先行 → 早预览:第一版用占位文本 / 占位图框 / fixture 数据,先在 Toolbox 里跑给用户看,再迭代。
- 验证:light/dark × zh/en 共 4 套截图都过;过 playbook §8 的 QA Checklist。
反 AI 味(默认禁用,除非用户明确要求)
- ❌ 蓝紫渐变 / "Aurora" 风背景
- ❌ Emoji 当主图标(用描边 SVG 或字母圆形容器)
- ❌ 左侧色条 + 圆角卡片组合
- ❌ 标题下加 1-2px 装饰横线
- ❌ 硬画复杂插画 SVG(用占位框,标注 "Image TBD")
- ❌ Inter / Roboto 兜底就完事(用
var(--bitfun-font-sans)优先) - ❌ 12px 以下文字 / hit target < 32px
- ❌ 圆角混用 4/8/12/16(钉 1-2 档全应用统一)
- ❌ 用装饰性 stats / icon / sparkline 填空白(空白是排版问题,不是内容问题)
颜色与字体
- 首选
var(--bitfun-*)系列 + fallback,与宿主主题协同(见下文"主题集成"章节的完整变量清单)。 - 一个颜色占视觉权重 60-70%(dominant),1-2 个 supporting,1 个 accent——禁止给所有色块同等权重。
- 字号:标题 18-22px / Section 14-15px / 正文 13-14px / Caption 11-12px。
Tweaks 变体(推荐做法)
对外观/密度/字号/布局的多种合理选择,做成运行时可切换、写入 app.storage('tweaks')、右下角浮动小面板"Tweaks"——一份代码服务多种偏好是 MiniApp 的天然优势。详细约定见 playbook §4。
占位优于劣质实现
没图标 / 没数据 / 没素材时,用明确的占位(标注尺寸或 "TBD"),并在 README 里登记待补清单——不要硬画一个糟糕的真实物。
工具型 vs 展示型
绝大多数 BitFun MiniApp 是工具型——信息密集、操作短、配色冷静,仿照 regex-playground / coding-selfie / git-graph 的克制感。只有用户明确要"对外展示 / 灵感型 / 作品集"时才放飞视觉。
内容守则
- 不为填空白而加内容——空白说明结构应被简化。
- 每个元素都要能回答"为什么在这里",回答不了就删掉。
- 加新 section / page / 功能前先问用户——你不比用户更懂他的目标。
核心哲学:Zero-Dialect Runtime
MiniApp 使用 标准 Web API + window.app:UI 侧为 ESM 模块(ui.js),后端逻辑在独立 JS Worker 进程(Bun 优先 / Node 回退)中执行。Rust 负责进程管理、权限策略和 Tauri 独占 API;Bridge 从旧的 require() shim + __BITFUN__ 替换为统一的 window.app Runtime Adapter。
代码架构
Rust 后端
src/crates/assembly/core/src/miniapp/
├── types.rs # MiniAppSource (ui_js/worker_js/esm_dependencies/npm_dependencies), NodePermissions
├── manager.rs # CRUD + recompile() + resolve_policy_for_app()
├── storage.rs # ui.js, worker.js, package.json, esm_dependencies.json
├── compiler.rs # Import Map + Runtime Adapter 注入 + ESM
├── bridge_builder.rs # window.app 生成 + build_import_map()
├── permission_policy.rs # resolve_policy() → JSON 策略供 Worker 启动 / host_dispatch 复用
├── host_dispatch.rs # 宿主直连分发 fs/shell/os/net(无需 Bun/Node Worker)
├── runtime_detect.rs # detect_runtime() Bun/Node
├── js_worker.rs # 单进程 stdin/stderr JSON-RPC
├── js_worker_pool.rs # 池管理 + install_deps
├── exporter.rs # 导出骨架
└── mod.rs
Tauri Commands
src/apps/desktop/src/api/miniapp_api.rs
- 应用管理:
list_miniapps,get_miniapp,create_miniapp,update_miniapp,delete_miniapp - 存储/授权:
get/set_miniapp_storage,grant_miniapp_workspace,grant_miniapp_path - 版本:
get_miniapp_versions,rollback_miniapp - Worker/Runtime:
miniapp_runtime_status,miniapp_worker_call,miniapp_host_call,miniapp_worker_stop,miniapp_install_deps,miniapp_recompile - 对话框由前端 Bridge 用 Tauri dialog 插件处理,无单独后端命令
Agent 工具
src/crates/assembly/core/src/agentic/tools/implementations/
└── miniapp_init_tool.rs # InitMiniApp — 唯一工具,创建骨架目录供 AI 用通用文件工具编辑
注册在 registry.rs 的 register_all_tools() 中。AI 后续用 Read/Edit/Write 等通用文件工具编辑 MiniApp 文件。
前端
src/web-ui/src/app/scenes/miniapps/
├── MiniAppGalleryScene.tsx / .scss
├── MiniAppScene.tsx / .scss
├── miniAppStore.ts
├── views/ MiniAppGalleryView
├── components/ MiniAppCard, MiniAppRunner (iframe 带 data-app-id)
├── hooks/
│ ├── useMiniAppBridge.ts # worker.call → workerCall() + dialog.open/save/message
│ └── useMiniAppCatalogSync.ts # 列表与运行态同步
└── utils/ miniAppIcons.tsx, buildMiniAppThemeVars.ts
src/web-ui/src/infrastructure/api/service-api/MiniAppAPI.ts # runtimeStatus, workerCall, workerStop, installDeps, recompile
src/web-ui/src/flow_chat/tool-cards/MiniAppToolDisplay.tsx # InitMiniAppDisplay
Worker 宿主
src/apps/desktop/resources/worker_host.js
Node/Bun 标准脚本:从 argv 读策略 JSON,stdin 收 RPC、stderr 回响应,内置 fs/shell/net/os/storage dispatch + 加载用户 source/worker.js 自定义方法。
MiniApp 数据模型 (V2)
// types.rs
MiniAppSource {
html, css,
ui_js, // 浏览器侧 ESM
esm_dependencies,
worker_js, // Worker 侧逻辑
npm_dependencies,
}
MiniAppPermissions { fs?, shell?, net?, node? } // node 替代 env/compute
权限模型
- permission_policy.rs:
resolve_policy(perms, app_id, app_data_dir, workspace_dir, granted_paths)生成 JSON 策略,传给 Worker 启动参数;Worker 内部按策略拦截越权。 - 路径变量同前:
{appdata},{workspace},{user-selected},{home}等。
Bridge 通信流程 (V2)
iframe 内 window.app.call(method, params)
→ postMessage({ method: 'worker.call', params: { method, params } })
→ useMiniAppBridge 监听
├─ 框架原语 (fs.* / shell.* / os.* / net.*):
│ ├─ node.enabled = false → miniAppAPI.hostCall → Tauri invoke('miniapp_host_call')
│ │ → bitfun_core::miniapp::host_dispatch(纯 Rust,无需 Bun/Node)
│ └─ node.enabled = true → miniAppAPI.workerCall → Tauri invoke('miniapp_worker_call')
│ → JsWorkerPool(保留旧路径,允许 worker.js 覆写 fs/shell 等)
├─ 自定义方法:始终走 worker.call → JsWorkerPool(要求 node.enabled = true 且 worker.js 导出)
└─ storage.* (node.enabled = false 时):直接走 get/set_miniapp_storage 命令
dialog.open / dialog.save / dialog.message
→ postMessage → useMiniAppBridge 直接调 @tauri-apps/plugin-dialog
何时使用「无 Node 模式」(推荐)
只要小应用的后端能力可以用 fs.* / shell.* / os.* / net.* 完成(例如调用 git 拉数据、读写工作区文件、抓取 HTTP API),就把 permissions.node.enabled 设为 false:
- 不依赖 Bun/Node 安装环境,bundle 后即点即用,避免 "JS Worker pool not initialized" 类问题;
- 安全与性能与 Worker 路径完全等价(同一份
permission_policy,Rust 直接执行); - 仍然可以使用
app.shell.exec / fs.* / net.fetch / os.info / storage.get|set全部框架原语。
什么时候需要 node.enabled = true:
- 需要写
worker.js自定义方法(CPU 密集 / 长流程 / 复杂解析等); - 需要
npm_dependencies安装第三方 npm 包; - 需要在 worker 内长期持有连接、缓存、状态。
走「无 Node 模式」时,禁止 调用
app.call('myCustomMethod', …),宿主会显式报错;只能调用框架原语和app.storage.*。
能力边界(重要)
MiniApp 框架只暴露下列能力,没有任何"通用 BitFun 后端通道"。设计 / 生成新小应用前请先比对,能力不在表内的需求请走相应替代方案,不要假设有 app.bitfun.* / app.workspace.* / app.git.* / app.session.* 之类的接口存在。
| 能力 | 入口 | 说明 |
|---|---|---|
| 文件系统 | app.fs.* |
受 permissions.fs.read/write 路径白名单限制 |
| 子进程 / 命令行 | app.shell.exec |
受 permissions.shell.allow 命令名白名单限制 |
| HTTP | app.net.fetch |
受 permissions.net.allow 域名白名单限制 |
| 系统信息 | app.os.info |
仅 platform / cpus / homedir / tmpdir 等只读字段 |
| KV 存储 | app.storage.get/set |
每个小应用独立的 storage.json,跨会话保留 |
| AI | app.ai.complete / chat / cancel / getModels |
复用宿主 AIClient,受 permissions.ai(含 allowed_models / 速率限制) |
| 对话框 | app.dialog.open/save/message |
Tauri dialog 插件 |
| 剪贴板 | app.clipboard.readText/writeText |
宿主 navigator.clipboard |
| 自定义后端 | app.call('xxx', …) + worker.js |
仅 node.enabled = true 时可用,自己实现业务逻辑 |
| 主题 / i18n | app.theme / app.locale / app.onThemeChange / app.onLocaleChange / app.t(...) |
见对应章节 |
框架不直接暴露的 BitFun 后端能力(截至本文档)
下面这些 BitFun 内部服务,目前没有给小应用开放调用通道:
- WorkspaceService(结构化工作区索引、统一搜索)
- GitService(结构化 status / diff / blame,区别于裸
git命令) - TerminalService(创建/读写交互式终端)
- Session / AgenticSystem(启动 Agent 会话、消费工具调用与流式事件)
- LSP / Snapshot / Mermaid / Skills / Browser API / Computer Use / Config 等
需要这类能力时的合规姿势:
- 能用裸命令行解决的(如 git)→ 在
permissions.shell.allow里加命令名,用app.shell.exec包一层(参考builtin-coding-selfie/ui.js的scanGitWorkspace); - 只是要读 BitFun 工作区内的文件(如某些项目元数据) → 把
{workspace}加到permissions.fs.read,自己用app.fs.*读 + 在前端解析; - 必须真调用某个内部服务 → 暂不支持,先记录到需求池。不要自己起一个 worker 去模拟服务行为,会和真正的 service 行为漂移。
维护者:以后若新增
app.bitfun.*/app.workspace.*这类宿主直通通道,请同步更新本节,避免"文档说没有、代码偷偷加了"的不一致。
window.app 运行时 API
MiniApp UI 内通过 window.app 访问:
| API | 说明 |
|---|---|
app.call(method, params) |
调用 Worker 方法(含 fs/shell/net/os/storage 及用户 worker.js 导出) |
app.fs.* |
封装为 worker.call('fs.*', …) |
app.shell.* |
同上 |
app.net.* |
同上 |
app.os.* |
同上 |
app.storage.* |
同上 |
app.dialog.open/save/message |
由 Bridge 转 Tauri dialog 插件 |
| 生命周期 / 事件 | 见 bridge_builder 生成的适配器 |
主题集成
MiniApp 在 iframe 中运行时自动与主应用主题同步,避免界面风格与主应用差距过大。
只读属性与事件
| 成员 | 说明 |
|---|---|
app.theme |
当前主题类型字符串:'dark' 或 'light'(随主应用切换更新) |
app.onThemeChange(fn) |
注册主题变更回调,参数为 payload:{ type, id, vars } |
data-theme-type 属性
编译后的 HTML 根元素 <html> 带有 data-theme-type="dark" 或 "light",便于用 CSS 按主题写样式,例如:
[data-theme-type="light"] .panel { background: #f5f5f5; }
[data-theme-type="dark"] .panel { background: #1a1a1a; }
--bitfun-* CSS 变量
宿主会将主应用主题映射为以下 CSS 变量并注入 iframe 的 :root。在 MiniApp 的 CSS 中建议用 var(--bitfun-*, <fallback>) 引用,以便在 BitFun 内与主应用一致,导出为独立应用时 fallback 生效。
背景
--bitfun-bg— 主背景--bitfun-bg-secondary— 次级背景(如工具栏、面板)--bitfun-bg-tertiary— 第三级背景--bitfun-bg-elevated— 浮层/卡片背景
文字
--bitfun-text— 主文字--bitfun-text-secondary— 次要文字--bitfun-text-muted— 弱化文字
强调与语义
--bitfun-accent、--bitfun-accent-hover— 强调色及悬停--bitfun-success、--bitfun-warning、--bitfun-error、--bitfun-info— 语义色
边框与元素
--bitfun-border、--bitfun-border-subtle— 边框--bitfun-element-bg、--bitfun-element-hover— 控件背景与悬停
圆角与字体
--bitfun-radius、--bitfun-radius-lg— 圆角--bitfun-font-sans、--bitfun-font-mono— 无衬线与等宽字体
滚动条
--bitfun-scrollbar-thumb、--bitfun-scrollbar-thumb-hover— 滚动条滑块
示例(在 style.css 中):
:root {
--bg: var(--bitfun-bg, #121214);
--text: var(--bitfun-text, #e8e8e8);
--accent: var(--bitfun-accent, #60a5fa);
}
body {
font-family: var(--bitfun-font-sans, system-ui, sans-serif);
color: var(--text);
background: var(--bg);
}
同步时机
- iframe 加载后 bridge 会向宿主发送
bitfun/request-theme,宿主回推当前主题变量,iframe 内_applyThemeVars写入:root。 - 主应用切换主题时,宿主会向 iframe 发送
themeChange事件,bridge 更新变量并触发onThemeChange回调。
国际化(i18n)
MiniApp 框架在 V2 之后内置 i18n 支持,开发者必须为多语言用户考虑两类文案:
- Gallery 元数据(
name/description/tags)—— 在meta.json顶层加i18n.locales块,宿主 Gallery / Card / Scene 标题自动按当前语言挑选。 - 应用内文案(HTML / JS 中的所有可见字符串)—— 通过
window.app.locale、window.app.onLocaleChange(fn)与window.app.t(table, fallback)实现。
meta.json 多语言示例
{
"id": "your-app",
"name": "默认名(兜底)",
"description": "默认描述",
"tags": ["默认标签"],
"i18n": {
"locales": {
"zh-CN": { "name": "中文名", "description": "中文描述", "tags": ["中文"] },
"en-US": { "name": "English Name", "description": "English desc", "tags": ["en"] }
}
}
}
回退顺序:current → en-US → zh-CN → 顶层默认值。
window.app i18n 运行时 API
| 成员 | 说明 |
|---|---|
app.locale |
当前语言 ID(如 'zh-CN' / 'en-US'),随宿主切换更新 |
app.onLocaleChange(fn) |
注册语言切换回调,参数为新 locale 字符串 |
app.t(table, fallback) |
从 { 'zh-CN': '...', 'en-US': '...' } 表挑选字符串;解析顺序:current → en-US → zh-CN → 表的第一项 → fallback |
HTML 静态文案:data-i18n 约定
宿主不强制要求该写法,但推荐 MiniApp 内部统一约定:
<span data-i18n="key">默认</span>—— 切换语言时applyStaticI18n()读取data-i18n并替换textContent<div data-i18n="ariaKey" data-i18n-attr="aria-label">...</div>—— 设置某个属性而非文本
参考 builtin/assets/gomoku/ui.js 等内置应用的 I18N 表 + applyStaticI18n() + app.onLocaleChange 三件套即可复用。
编写自检清单
-
meta.json已加i18n.locales(至少zh-CN/en-US) - HTML 中静态文案均带
data-i18n属性 - JS 内动态拼接的字符串使用
app.t()或自有I18N表 - 注册了
app.onLocaleChange,切换语言时重新渲染(包括动态列表、aria-label、title) - 持久化数据(
app.storage)保存语言无关的索引/键,而非已翻译的字符串
内置小应用(builtin/assets/*)维护规范
内置小应用通过 src/crates/contracts/product-domains/src/miniapp/builtin.rs 中的 BUILTIN_APPS 数组以 include_str! 方式打包进 Rust 二进制;首次启动 / 升级时由 seed_builtin_miniapps() 把资源写入用户的 miniapps_dir/<app_id>/,并在该目录下写入 .builtin-manifest.json 主标记文件,同时兼容写入 .builtin-version legacy 标记。
只有当 bundled version / asset hash 与 on-disk .builtin-manifest.json 不一致时才会重新 seed,否则启动时会跳过、用户看到的还是旧版本。
修改流程(强制)
凡是修改了 src/crates/contracts/product-domains/src/miniapp/builtin/assets/<app>/ 下任何文件(index.html / style.css / ui.js / worker.js / meta.json),都必须同步在 builtin.rs 的 BUILTIN_APPS 中把对应条目的 version: N → N + 1。
// src/crates/contracts/product-domains/src/miniapp/builtin.rs
BuiltinApp {
id: "builtin-daily-divination",
version: 14, // ← 改完资源就把这里 +1
...
}
未 bump 的后果:
- asset hash 变化仍会触发 reseed,但 bundle version 和
.builtin-versionlegacy 标记无法体现升级批次 - QA / Release 难以用版本号关联资源变更,容易误判资源是否已随包更新
自检清单
- 改完
assets/<app>/*任何文件 -
builtin.rs中对应BuiltinApp.version已 +1 - 本地清掉
~/.bitfun/miniapps/<app_id>/.builtin-manifest.json或直接整目录删,再启动验证 reseed 生效 - meta.json 中的
version字段(用户可见的元数据版本)按需同步(与 reseed 无关,但展示用)
提示
meta.json里的version(默认 1)是给用户看的版本号,不驱动 reseed- 真正驱动 reseed 的是
builtin.rs中的BuiltinApp.version字段(u32)和 bundled asset hash - 二者最好语义一致:资源有重大更新时同步 bump,便于排查
开发约定
新增 Agent 工具
当前仅 InitMiniApp。若扩展:
implementations/miniapp_xxx_tool.rs实现Toolmod.rs+registry.rs注册flow_chat/tool-cards/index.ts与MiniAppToolDisplay.tsx增加对应卡片
修改编译器
compiler.rs:注入 Import Map(build_import_map)、Runtime Adapter(build_bridge_script)、CSP;用户脚本以 <script type="module"> 注入 ui_js。
前端事件
后端 miniapp-created / miniapp-updated / miniapp-deleted / miniapp-worker-*,前端 useMiniAppCatalogSync 统一监听并刷新 store。
场景注册检查清单
同前:SceneBar/types.ts、scenes/registry.ts、SceneViewport.tsx、NavPanel/config.ts、app/types/index.ts、locales。
参考
- 重构计划:
.cursor/plans/miniapp_v2_full_refactor_*.plan.md - 架构说明见 plan 内「MiniApp V2 一步到位重构计划」