Multica 移动端 RNR 迁移实践:shadcn 式组件体系、CSS 变量暗色主题与三阶段落地方案
本篇基于 Multica 移动端的迁移设计文档 rnr-migration.md,完整拆解一次"手写 React Native 组件 → react-native-reusables(RNR)+ 类级暗色模式"的迁移工程:为什么选 RNR 而不是 Tamagui/Gluestack,如何用"iOS 原生 > RNR > 讨论"瀑布收敛 18 个手写 Sheet,如何用 CSS 变量 + darkMode: 'class' 搭出 Light / Dark / System 三档主题切换,以及三阶段(Phase 0/1/2/3)的推进节奏与已验证的源码实现。读完你可以复用到自己的 NativeWind 4 + Expo SDK 55 项目中,并了解每个设计决策背后的源码依据。
1. 背景:迁移要解决的三个问题
文档开篇给出了迁移前的基线状态(注意:这描述的是 Phase 1 之前的状态,主题部分如今已修复,组件部分仍是待办):
apps/mobile/components/ui/下有 21 个手写组件,约 1,379 行,全部基于裸<View/Text/Pressable/Modal>构建;apps/mobile/components/下有 18 个手写 sheet/modal 文件,全部复制同一种形态(Modal transparent fade+ 手绘背景遮罩)。移动端基线文档 CLAUDE.md 的 Lesson 6 早已记录这种模式对多数内容形态是错的,并由此衍生出一串 bug:键盘挤压、maxHeight截断 FlatList、Modal内部useSafeAreaInsets返回 0;- 没有任何暗色/亮色主题基础设施:
tailwind.config.js用硬编码 hex 值,global.css只有三条@tailwind指令,没有 CSS 变量、没有darkMode、没有主题切换器。
移动端基线(CLAUDE.md)自 SDK 55 bootstrap 起就把 RNR 定位为 shadcn 的移动端等价物,但 RNR 从未真正安装。本文档记录了"为什么迁移到 RNR、评估过哪些替代方案、迁移如何分阶段推进"三件事,面向所有会动到 apps/mobile/components/ui/ 或给移动端加新 UI 的开发者。
2. 备选方案评估
评估维度有四项:(a) 与现有技术栈的契合度(NativeWind 4、Tailwind 3.4、Expo SDK 55、React 19);(b) 所有权模型(锁定 vs 复制粘贴);(c) 打包/编译开销;(d) 无障碍基线。
| 库 | 栈契合度 | 所有权 | 构建成本 | 无障碍 | 结论 |
|---|---|---|---|---|---|
| react-native-reusables (RNR) | NativeWind 4 + RN-Primitives + CVA —— 与本项目完全一致 | 复制粘贴,代码归你 | 零(只是 CSS + JSX) | RN-Primitives(焦点管理、ARIA 等价物) | 入选 |
| Tamagui | 自带编译器 + 自带 styled API,非 NativeWind | 库锁定 | Babel/bundler 配置 + 学习成本 | 内置 | 否决 —— 核心价值是 web+native 同代码库(移动端独立,用不上) |
| Gluestack UI v2 | 自带 styled API;可以配合 NativeWind 用但非原生路径 | 可复制粘贴 | 低 | @react-native-aria(强) |
否决 —— 已有 NativeWind 在跑,换样式体系没有收益 |
| NativeBase / RN Paper / RN UI Lib | 成熟但库绑定 | 锁定 | 低 | 视情况 | 否决 —— 长生命周期安装型 App 需要不依赖上游发版就能打组件补丁的能力,复制粘贴哲学很关键 |
RNR 的"组件归你所有"模型还与桌面/Web 端的模式(packages/ui/components/ui/ 也是 shadcn 复制粘贴)保持一致——整个代码库一种心智模型。
3. 决策:RNR 及其治理承诺
采用 RNR 时,文档立下了若干承诺。前两条是原则,约束迁移期每个 PR 以及之后的每个 UI 决策:
3.1 默认值优先(Defaults first)
使用任何 RNR 组件时,接受它的默认 variant、默认尺寸、默认间距、默认调色板。除非有明确的产品需求,不要加包装层、不要做"改进版"默认值、不要造 variant="multicaCustom" 之类的样式。手写遗留代码恰恰就是因为有人去要了标准原语的"稍微改进版"——在 RNR 底下重演这个模式等于白迁。
具体推论:
- Phase 1 原样使用 shadcn 默认 neutral 调色板(light + dark)。Multica 自有自定义 token(
brand、success、warning、info、priority、code-surface)追加进去,但暗色值不在有实证需求之前预先创作——它们先复制亮色值,直到真正使用它们的页面在暗色下出问题为止。 - Phase 2/3:
npx @rnr/cli add <component>写出文件后,不要立刻调它的样式;API 有差异时改调用方。 - Tier C"基础升级"指的是把裸
<Text>换成 RNR 的Text、把内联条件判断换成cva—— 不指重新设计或添加组件原本没有的 variant。
3.2 iOS 原生 > RNR > 讨论
新增任何交互时走这条瀑布,命中即停:
- iOS / RN 有原生 API? 直接用,不要用
Modal包一层去模拟; - RNR 有对应组件?
npx @rnr/cli add <name>,用默认 variant; - 都没有。 停下来问用户,不要闷头手写。
第 4 节 Tier B 的修订分类就依赖这个"原生 API 层"——很多既有 sheet 换成 ActionSheetIOS / Alert.prompt / 原生日期选择器后会整个消失。
| 场景 | 原生 API |
|---|---|
| 文本输入提示(单字段) | Alert.prompt(title, msg, callback) |
| 确认 / 破坏性操作提示 | Alert.alert |
| N 选 1 动作表 | ActionSheetIOS.showActionSheetWithOptions |
| 日期 / 时间选择 | @react-native-community/datetimepicker(已安装) |
| 图片 / 相机 | expo-image-picker(已安装) |
| 文档 | expo-document-picker(已安装) |
| 分享 | react-native 的 Share.share |
| 触感反馈(确认 / 错误 / 选中) | expo-haptics(已安装) |
3.3 主题方案
类级暗色模式(darkMode: 'class')+ CSS 变量,应用内 Settings → Appearance 提供 light / dark / system 三档选择,选择持久化在 expo-secure-store。RNR 的默认 Tailwind 配置就是 class 模式,与"需要显式用户设置"的需求匹配(纯 media-query 方案不允许用户覆盖 OS 偏好)。
3.4 组件三层分类
不做一刀切全量迁移。部分遗留组件是领域 UI 而非通用原语,留在原地,但使用 RNR 的地基(Text 组件、语义化 token、CVA variant)。
3.5 硬规则(写入 CLAUDE.md)
新组件要么来自 RNR,要么先走原生 API 层。 迁移删除手写遗留 variant,这条规则防止再次积累。这一规则如今已固化在 CLAUDE.md 的 "UI components & theming" 章节,作为迁移完成后依然有效的持久规则。
4. 组件清单与三层分类
Tier A —— RNR 直接替换(迁移对象)
通用原语中 RNR 有近乎同形等价物的,直接用 npx @react-native-reusables/cli@latest add <name> 的输出替换手写文件,然后清扫调用方:
| 现有文件 | 行数 | RNR 组件 | 备注 |
|---|---|---|---|
components/ui/button.tsx |
63 | button |
Phase 2 的 canary |
components/ui/input.tsx |
9 | input |
平凡 |
components/ui/text-field.tsx |
34 | input + label |
用 RNR 组合 |
components/ui/card.tsx |
36 | card |
可直接替换 |
components/ui/text.tsx |
18 | text |
到处都在用,要小心做(清扫所有 import) |
components/ui/autosize-textarea.tsx |
89 | textarea |
需核对一致性 —— 自动增高行为可能要基于 RNR textarea 重实现 |
components/ui/otp-input.tsx |
68 | (RNR 目前无等价物) | 继续用 input-otp-native,移入 Tier B |
components/ui/modal-close-button.tsx |
25 | 无 | 平凡 —— sheet 迁移后并入 Dialog 关闭模式 |
合计约 270 行待替换,含调用方清扫约 4 小时工作量。从当前仓库结构看,components/ui/ 目录在文档撰写之后已经出现了 collapsible、dropdown-menu、radio-group、separator、skeleton、switch、tabs 等文件,可以推断 Tier A 之后的组件引入已按 RNR 模式在推进,但按文档 Status 行,Phase 2/3 的收尾工作仍以文档记录为准。
Tier B —— Sheets / Modals(套用 §3.2 瀑布)
这一层收益最大(CLAUDE.md Lesson 6 已给重复出现的 sheet bug 建了档)。按"iOS 原生 > RNR > 讨论"瀑布,18 个 sheet 三分:
B.1 —— 原生 API 替换(文件直接删)
| 现有 sheet | 替换为 | 原因 |
|---|---|---|
components/issue/comment-action-sheet.tsx |
ActionSheetIOS.showActionSheetWithOptions |
N 选 1 动作菜单 —— 正是 ActionSheetIOS 的用途。推荐作 Phase 3 起手(可见的删除、无样式问题) |
components/issue/pickers/due-date-picker-sheet.tsx |
@react-native-community/datetimepicker 内联选择器 |
日期选择 —— 原生 API 已安装 |
B.2 —— 改为 formSheet 路由(已完成)
原计划是把每个 picker-sheet 换成 RNR Select。mobile-sheet-rollout PR 系列最终收敛到了不同形态:每个原 picker-sheet 都拆成 components/<domain>/pickers/ 下的纯 <XxxPickerBody> 组件,嵌入 Expo Router formSheet 路由 app/(app)/[workspace]/<context>/picker/<field>.tsx。这拿到了 iOS UISheetPresentationController 的原生 chrome(grabber + detents + 弹簧拖拽关闭),同时省掉了 RNR Select 仍需要的每个调用点状态与可见性 prop 的周折。本行文件已全部删除,无后续动作。
B.3 —— 真正需要自定义内容 sheet(RNR Dialog pageSheet)
| 现有 sheet | 为什么保留为 Dialog |
|---|---|
components/issue/issue-filter-sheet.tsx |
一个 sheet 里多个控件(筛选表单),不是列表选择 |
components/issue/runs-sheet.tsx |
运行历史(行 + 操作),不是 N 选 1 |
components/chat/session-sheet.tsx |
待定 —— 到时重新审视,可能归 B.2 |
components/chat/agent-picker-sheet.tsx |
大概率 B.2(Select)—— 待重审 |
components/project/add-resource-sheet.tsx |
待定 —— 取决于单选还是小表单 |
B.4 —— RNR 没有的
空。唯一一项 components/issue/emoji-picker-sheet.tsx 已通过采纳 rn-emoji-keyboard 解决,评论表情反应流程迁移到 formSheet 路由 app/(app)/[workspace]/issue/[id]/comment/[commentId]/emoji-picker.tsx。移动端现在在每条评论动作表的"更多表情"溢出项后提供完整表情集,与 Web 对齐。
执行规则:
- 不要批量替换
sheet-shell.tsx。它被 18 个文件引用;原子替换 = 18 处同时坏掉。按 CLAUDE.md Lesson 6:一个 sheet 一个 PR,一个 PR 一次验证。 - B.1 是删除类 —— 应当先做,因为每个都只是删文件、零替换代码(从父组件调
ActionSheetIOS即可)。复合收益:代码更少 + 符合"默认值优先" + 符合"iOS 原生优先"。 - B.2 简化调用点,但引入的组件是新代码,排在 B.1 之后。
- B.3 保留结构复杂度,迁移主要是
Modal换 RNRDialog。视觉变化最小,但 bug 修复最大(拖拽关闭、焦点管理、安全区都由 RNR 处理)。
Tier C —— 领域 UI(保留,升级地基)
这些不是通用 shadcn 组件 —— RNR 没有"优先级图标"或"参与者头像"的等价物。它们留在 components/ui/ 以兼容现有 import,但内部构建块必须迁到 RNR 地基:用 RNR 的 Text 替代裸 <Text>,用 cva 做 variant 表(而非内联条件),用语义化 token(text-foreground,永不用 #1f1f23):
actor-avatar.tsx(158 行)—— 底层用 RNR 的avatar原语,但业务逻辑保留app-header-actions.tsx(51)、avatar-stack.tsx(97)、presence-dot.tsx(44)priority-icon.tsx(80)、project-icon.tsx(49)、project-priority-icon.tsx(71)、project-status-icon.tsx(130)、status-icon.tsx(163)pulse-dot.tsx(52)、screen-header.tsx(37)
Tier C 不排期 —— 某个 bug 或功能触碰到文件时,顺手在该 PR 里升级地基,机会主义而非排期驱动。
5. 主题架构:CSS 变量 + 类级暗色模式
目标:Settings → Appearance 提供 Light / Dark / System 三档,选择持久化在 expo-secure-store(键 theme-preference,值 light / dark / system)。
5.1 分层结构
global.css CSS 变量定义于 :root 与 .dark:root(颜色的唯一事实来源)
tailwind.config.js darkMode: 'class' + 工具类映射到 hsl(var(--...))
lib/theme.ts CSS 变量的 TypeScript 镜像,导出给 React
Navigation 用的 NAV_THEME
lib/use-color-scheme.ts 包装 NativeWind 的 useColorScheme +
expo-secure-store 持久化
app/_layout.tsx 启动时读取持久化偏好,调 setColorScheme(...),
用 ThemeProvider(NAV_THEME[...]) 包 Stack,
为 RNR dialog/popover 挂载 <PortalHost />
5.2 各层实现细节(源码佐证)
global.css:Phase 1 之前 tailwind.config.js 里约 20 个语义化 token 是硬编码 hex。现在它们成了 global.css 中 :root(亮色)与 .dark:root(暗色)下的 CSS 变量。亮色基础值即 shadcn neutral 默认(如 --background: 0 0% 100%、--primary: 0 0% 9%),Multica 自定义 token 追加其后:
/* Multica custom tokens */
--brand: 225 71% 58%;
--brand-foreground: 0 0% 98%;
--success: 142 71% 45%;
--warning: 48 89% 47%;
--info: 217 91% 60%;
--priority: 25 95% 53%;
--code-surface: 240 4% 92%;
实际实现比文档更进一步,补了一套 5 级表面高程阶梯(--surface-1 / --surface-2),按 Refactoring UI"≥5% L 差异为可感知阈值"校准:亮色下 L100 页面底 → L98 surface-1(评论气泡)→ L96.1 shadcn secondary → L90 surface-2(气泡内嵌代码块)→ L84 border;暗色下明度随高程升高(阴影在暗色下不成立,色调抬升才是高程线索),--code-surface 是自定义 token 中唯一需要真实暗色值的例外(240 4% 18%——亮色值近白,暗色下会把代码块衬得比页面还亮)。
tailwind.config.js:darkMode: "class",所有颜色工具类映射为 hsl(var(--background)) 形式,borderWidth 注册了 hairline: hairlineWidth()(来自 nativewind/theme),插件区注册 tailwindcss-animate,并保留移动端的 borderRadius 覆盖:
module.exports = {
darkMode: "class",
content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}"],
presets: [require("nativewind/preset")],
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
primary: { DEFAULT: "hsl(var(--primary))", foreground: "hsl(var(--primary-foreground))" },
// ... 以及 brand / success / warning / code-surface 等自定义 token
},
borderWidth: { hairline: hairlineWidth() },
},
},
plugins: [require("tailwindcss-animate")],
};
lib/theme.ts:CSS 变量的 TS 镜像。THEME 是原始 token 对象,供内联样式、动画等 Tailwind class 够不到的场景;NAV_THEME 是给 @react-navigation/native 的 ThemeProvider 用的主题(header、modal、返回键跟随明暗)。
lib/use-color-scheme.ts:包装 NativeWind 的 useColorScheme(),加上 expo-secure-store 持久化(键 theme-preference)。暴露 { colorScheme, isDarkColorScheme, setPreference }。文档没有细说但源码里写明的一个取舍:首挂载时是异步读取保存的偏好,读取完成前 NativeWind 默认行为(跟随 OS)生效——意味着选了 Dark 的用户在亮色 OS 上冷启动可能短暂闪一下亮色;可接受,因为 secure-store 没有同步后端。
app/_layout.tsx:实际接线处。RootLayout 调用 useColorScheme() 拿到 colorScheme / isDarkColorScheme,用 ThemeProvider value={NAV_THEME[colorScheme]} 包住 Stack,StatusBar 颜色随模式翻转,<PortalHost />(@rn-primitives/portal)挂在 provider 子树末尾供 RNR dialog/popover 使用。
components.json:标准 RNR/shadcn 配置 —— style: "new-york"、baseColor: "neutral"、cssVariables: true,别名指向 @/components、@/lib/utils。
metro.config.js:withNativeWind(config, { input: "./global.css", inlineRem: 16 }) —— inlineRem: 16 是 Phase 1 清单里的关键项,保证 CSS 变量按 16px 基准编译。
5.3 为什么是 class 模式而不是 media-query 模式
media-query 模式(@media (prefers-color-scheme: dark))是更简单的默认,但应用无法覆盖它。Settings → Appearance 需要覆盖 OS 偏好,必须 class 模式。代价是启动时一次 setColorScheme() 调用来应用保存的偏好,首次绘制前一次性付清。
5.4 system 选项
用户选 system 时调用 setColorScheme('system')(NativeWind v4 原生支持),框架通过 Appearance.addChangeListener 跟随 OS,无需自己订阅,isDarkColorScheme 响应式更新。
5.5 暗色调色板策略
旧配置里不存在暗色值,必须创作。两个选项:
- 直接用 shadcn neutral-base 暗色调色板(RNR 默认安装的
--background: 0 0% 3.9%等)—— 最快,立刻有能跑的暗色模式,品牌对齐可能要二遍; - 手工创作暗色对齐 Web/桌面暗色主题 —— 慢,但与桌面视觉一致。
Phase 1 选了选项 1换取速度。现在基础设施已验证,后续可以按桌面的 tokens.css 暗色主题做校准(但注意:桌面用 oklch / Tailwind v4,移动端用 hsl / Tailwind v3.4,跨版本共享不现实,分歧是刻意保留的基线)。
6. 三阶段推进计划
Phase 0 —— 研究与文档(本文档)✅
通读 RNR 安装与定制文档、检查移动端现状(tailwind.config.js、global.css、app/_layout.tsx、metro.config.js、babel.config.js)、更新 CLAUDE.md 的 UI 与主题规则、写下本文档,最后过用户验证关卡。
Phase 1 —— 基础基础设施 ✅ 已完成
目标:装上 RNR 脚手架但不碰任何一个现有组件。验证标准 = App 构建运行与之前完全一致,设置里的主题切换器端到端可用。Phase 1 的 10 项清单全部交付:
- 依赖(按 RNR 手动安装第 3 步):
npx expo install tailwindcss-animate class-variance-authority clsx tailwind-merge @rn-primitives/portal;cva/clsx/tailwind-merge/@rn-primitives/slot确认已存在,新增的只有tailwindcss-animate和@rn-primitives/portal; metro.config.js设inlineRem: 16;- 重写
global.css(:root+.dark:root双调色板,含 Multica 自定义 token); - 重写
tailwind.config.js(hsl(var(--...))映射、darkMode: 'class'、tailwindcss-animate插件、hairlineWidth()边框宽度,保留移动端专属覆盖); - 新建
lib/theme.ts(TS 镜像 +NAV_THEME); - 新建
lib/use-color-scheme.ts(持久化包装); - 新建
components.json(标准 RNR 配置); - 更新
app/_layout.tsx(启动时读持久化偏好、首绘前setColorScheme(...)、ThemeProvider(NAV_THEME[...])包Stack、provider 末尾挂<PortalHost />); - Settings → Appearance 选择器:三行(Light / Dark / System)调
setPreference+ 持久化,UI 复用既有行模式,无新依赖; - 验证:构建通过、亮色下所有既有页面渲染不变、切 Dark 背景/文字翻转、切 System 跟随模拟器 OS 外观、杀进程重开偏好仍在。
Phase 1 不替换任何现有组件。 按钮、输入、sheet 仍是手写版。暗色模式之所以对既有 hex 派生的语义 token "能用",是因为只是把它们改道经过 CSS 变量 —— 同一个 bg-background class 现在解析到有一个值变两个值的变量。
残留风险(带入 Phase 3):硬编码 hex 的组件(如 <Ionicons color="#71717a">、bg-[#fafafa])不响应主题切换。Phase 1 做过一次 #[0-9a-fA-F]{3,6} 的 grep 清扫,每个命中要么换 token,要么标 TODO(rnr-migration): 留给 Phase 3。
Phase 2 —— 首个组件 canary(未开始)
目标:在批量做 20 个之前,用"最简单且非平凡"的组件验证迁移机械流程。选定:button.tsx。理由:调用方数量高(能验证 import 清扫模式)、RNR 的 button 带 variant/size prop 与 shadcn 同形(验证 API 对等)、到处可见(视觉回归立刻明显)。
步骤:
npx @react-native-reusables/cli@latest add button—— CLI 覆盖现有文件(旧文件成为 git 里的 diff 基线);- 新旧 diff,记录 prop 或视觉差异(RNR
variant枚举 vs 自有的、默认尺寸差异); - 清扫所有调用方,API 有差异就在同一 PR 里改调用方;
- 模拟器视觉 diff:逐个打开用到 button 的页面,截图与 main 对比;
- 亮色暗色双模式验证。
Phase 2 就是一个 PR。验收标准:所有既有按钮仍工作;亮色无回归;暗色下默认值合理。
Phase 3 —— 其余全部
顺序:
- 其余 Tier A 原语(input、text-field、card、text、textarea)—— 一个组件或一组一个 PR,同 canary 模式;
text特殊处理:几乎每个文件都 import 它,单独排 PR;codemod 是机械的(import { Text } from "@/components/ui/text"已存在,背后文件变化即可);- Tier B sheets —— 一个 sheet 一个 PR,顺序 B.1(原生 API 替换,删文件)→ B.2(
Select替换)→ B.3(Dialog迁移)。首个 sheet PR =comment-action-sheet.tsx→ActionSheetIOS,干净的删除、且在实践中验证 §3.2 瀑布。每个 PR 后停下来重新验证; - Tier C 地基升级 —— 机会主义,无排期 PR;
- 收尾 PR:删除不再被引用的遗留文件、清掉 Phase 1 hex 清扫留下的 TODO 注释,最终
pnpm typecheck && pnpm lint干净。
停止规则:
- 连续 3 个 PR 引入视觉回归就暂停,重新审视 token 映射,不要硬推;
- Tier B sheet 迁不干净(RNR
dialog不适合该场景)时,在本文档记录分歧,保留手写版并标记为"刻意例外"而非"待办"。
7. 已知陷阱(写进反射弧)
研究阶段踩过的坑,动手前先固化:
darkMode: 'class'+.dark:root是 NativeWind v4 类控制模式下唯一可用的组合。不要用标准.dark选择器 —— NativeWind 需要:root后缀才能全局应用。global.css 中的选择器就是.dark:root。setColorScheme()来自 NativeWind,不是 React Native。 从react-nativeimport 得到的是只读的 OS 值。NativeWind 版支持setColorScheme('light' | 'dark' | 'system')并触发重渲染。- 同步陷阱:
lib/theme.ts与global.css必须互为镜像。只改一边,Tailwind class 样式化的组件看着对,但直接读THEME的地方(内联样式、动画、React Navigation chrome)就错了。两份文件的头部注释都写明"改一边必须改另一边,见 rnr-migration.md §5"。 AbortSignal.timeout/AbortSignal.any在 Hermes 上仍不存在(CLAUDE.md Lesson 5)。与 RNR 迁移无直接关系,但任何自己发网络请求的新组件都要手动 AbortController 模式。- RNR
Dialog内部的useSafeAreaInsets与裸Modal一样不可靠。CLAUDE.md Lesson 6 的 pageSheet 陷阱仍适用 —— 在父组件读 insets,把bottomInset当 prop 传进去。 @rn-primitives/portal的<PortalHost />位置敏感。 必须是所有 provider 的最后一个子节点;如果挂在频繁重渲染的 provider 内,dialog 会被意外卸载。只在 app/_layout.tsx 放一次。- CLI 覆盖文件无确认。
components/ui/button.tsx存在时add button直接替换。对迁移是期望行为,但对 Tier C 文件误跑是灾难 —— 每次add后都查 git status。 - NativeWind 5 尚未采纳。 基线钉在 v4,RNR v1 两者都支持,本项目走 v4 安装路径。
8. 开放问题
- 暗色品牌色:当前品牌色
#4571e0(仅亮色)。暗色等价值要定 —— 保留(深色底上对比度高)还是偏移,留给设计 pass。 code-surfacetoken:亮色下是#e8e8eb(比secondary深 5%)。暗色等价是"比暗色secondary亮 5%"。实际实现已在 .dark:root 中给出240 4% 18%,理由注释齐全。- 设置页 UX:Appearance 选择器可内联(三行直接摆)也可做成打开 picker sheet 的行。v1 选内联更简单;sheet 变体等 Tier B 迁完再说。
- 暗色 token 是否与桌面共享:桌面
packages/ui/styles/tokens.css用 oklch(Tailwind v4),移动端 hsl(Tailwind 3.4),跨版本共享不现实,接受分歧为既有基线;移动端升级到 NativeWind 5 + Tailwind v4 时再议。
9. 小结与延伸阅读
这次迁移的方法论可以浓缩为四条:复制粘贴所有权优先于库绑定;默认值优先,拒绝"稍微改进版"原语;用"原生 > 组件库 > 讨论"瀑布消灭不必要的抽象层;基础设施先行、canary 组件验证、批量推进时设停止规则。配套文档:迁移主文档 rnr-migration.md、持久 UI/主题规则 CLAUDE.md "UI components & theming" 章节、主题实现的四个文件 global.css / tailwind.config.js / lib/theme.ts / lib/use-color-scheme.ts,以及接线入口 app/_layout.tsx。
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