Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e9f8f00
chore: reorder dependencies alphabetically, add cos-sdk and multer
Tonnodoubt May 11, 2026
7fd87dc
chore: start rn alignment with current backend
Tonnodoubt May 11, 2026
82ac5a8
fix: address code review findings — security, quality, and performance
Tonnodoubt May 12, 2026
3e7680a
fix: add automatic reconnect fallback when connection drops
Tonnodoubt May 12, 2026
0f381ef
feat: migrate chat UI from main N.E.K.O. project to RN
Tonnodoubt May 12, 2026
54b58cc
feat: add image compression, blur fallback, and remove avatar tools
Tonnodoubt May 12, 2026
92d7601
fix: resize chat panel, fix Live2D gesture conflicts, remove dark ove…
Tonnodoubt May 12, 2026
ae79bd7
feat: 重写口型同步,attack/release 平滑 + 非线性曲线 + 播放头时间对齐
Tonnodoubt May 13, 2026
7cd9d7c
feat(mobile): consolidate RN app shell
Tonnodoubt May 17, 2026
f397fec
docs(mobile): pin baseline checklist
Tonnodoubt May 17, 2026
228d806
docs(mobile): record artifact checks
Tonnodoubt May 17, 2026
fe8b710
docs(mobile): record e2e validation
Tonnodoubt May 17, 2026
c4bef5f
feat(mobile): harden runtime connection states
Tonnodoubt May 17, 2026
488c456
feat(mobile): persist pairing credentials
Tonnodoubt May 17, 2026
da1baa7
feat: add VRM rendering PoC
Tonnodoubt May 17, 2026
bdec62b
完成 VRM v1 行为系统收口
Tonnodoubt May 18, 2026
849d8ba
优化主界面 VRM 状态胶囊
Tonnodoubt May 18, 2026
9a9ad27
fix: update Live2D lip sync module
Tonnodoubt May 18, 2026
44404a0
完成 VRM Motion v2 收口
Tonnodoubt May 18, 2026
e4ec70e
fix(mobile): preserve public proxy pairing host
Tonnodoubt May 18, 2026
b70a4af
收口手机端 VRM 与 VRMA 动画支持
Tonnodoubt May 18, 2026
b724dae
fix: polish mobile stage status UI
Tonnodoubt May 18, 2026
03cfe4c
fix mobile connection and avatar rendering
Tonnodoubt May 19, 2026
7670c6c
fix mobile voice interruption handling
Tonnodoubt May 19, 2026
5580167
improve mobile pairing and live2d handling
Tonnodoubt May 19, 2026
001d7fc
chore: use forked Live2D submodule
Tonnodoubt May 19, 2026
b0c4fe0
chore: use forked PCM submodule
Tonnodoubt May 19, 2026
c6a1bb8
修复 CodeRabbit 审查问题
Tonnodoubt May 19, 2026
4cee0f3
修复 CodeRabbit 后续审查问题
Tonnodoubt May 19, 2026
d981d90
修复自动重启异步清理竞态
Tonnodoubt May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions .agents/skills/source-command-build-debug-apk/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
name: "source-command-build-debug-apk"
description: "编译 Android debug APK,使用 Gradle 本地构建,不启动模拟器"
---

# source-command-build-debug-apk

Use this skill when the user asks to run the migrated source command `build-debug-apk`.

## Command Template

# /build-debug-apk — 编译 Debug APK

使用 Gradle 本地编译 Android debug APK。

## 执行流程

### 1. 检查项目配置

确认以下文件存在:
- `android/gradlew` — Gradle wrapper
- `android/app/build.gradle` — 应用构建配置
- `eas.json` — EAS 配置(可选)

### 2. 执行编译

```bash
cd android && ./gradlew assembleDebug --no-daemon
```

### 3. 验证输出

检查 APK 文件是否生成:
- 标准路径:`android/app/build/outputs/apk/debug/app-debug.apk`

### 4. 输出结果

显示:
- APK 文件路径
- 文件大小
- 安装命令示例

## 输出格式

```
✅ Debug APK 编译成功!

路径:android/app/build/outputs/apk/debug/app-debug.apk
大小:XXX MB

安装到设备:
adb install -r android/app/build/outputs/apk/debug/app-debug.apk
```

## 注意事项

- 首次编译可能需要下载依赖,耗时较长
- 确保已安装 Android SDK 和 Gradle
- 如需清理后重新编译,使用 `./gradlew clean` 后再 assembleDebug
94 changes: 94 additions & 0 deletions .agents/skills/source-command-review/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
---
name: "source-command-review"
description: "提交前代码审查。检查 staged 变更的质量、架构规范、RN 最佳实践,输出问题列表。在 /commit 前运行以保证代码质量。"
---

# source-command-review

Use this skill when the user asks to run the migrated source command `review`.

## Command Template

# /review — 提交前代码审查

对即将提交的变更做系统性检查,避免把问题带入 commit 历史。

## 执行步骤

### 1. 收集变更上下文

```bash
git status # 查看 staged / unstaged 文件列表
git diff --staged # 分析 staged 变更内容
git diff # 确认是否有未暂存的相关变更
```

### 2. 运行 Lint

```bash
npx expo lint
```

如有错误,先列出并说明修复方向,再继续后续检查。

### 3. 逐项检查

对每个 staged 文件按下列维度审查,只报告**实际存在的问题**,没有问题的维度跳过。

#### A. TypeScript 类型安全
- 有无 `any` 类型滥用(尤其是 API 响应、事件回调)
- 有无缺失 `undefined` / `null` 处理
- 泛型使用是否合理

#### B. React Native 最佳实践
- StyleSheet 是否在组件外定义(避免每次渲染重建对象)
- 平台差异处理:`Platform.OS` 分支、`.android.tsx` / `.ios.tsx` 文件分离
- `FlatList` 是否提供 `keyExtractor`;长列表是否启用 `getItemLayout`
- 异步操作是否在组件卸载后有 cleanup(防止 `setState` on unmounted)
- 图片资源是否使用 `require()` 而非网络 URI 直接展示静态资产

#### C. 项目架构规范(`docs/arch/design.md`)
- **Service 层**(`services/`):不得直接依赖 React 组件生命周期;纯 TS Class,无 hooks 调用
- **Hook 桥接层**(`hooks/`):不得包含业务逻辑,只做事件→状态映射
- **Manager 单例**(MainManager):跨 Service 协调逻辑统一放在 Manager,不散落到 UI 层
- `components/` 不应直接 import Service 实例,应通过 Hook 消费

#### D. 包依赖边界
- `@project_neko/*` 内部包的修改是否需要同步运行 `npm run sync:neko-packages`
- 有无引入新的外部依赖但未更新 `package.json`

#### E. 遗漏项
- 修改了功能但没有更新对应的 `docs/` 文档
- 有无调试用的 `console.log` / `TODO` 未清理
- 新增文件是否加入了正确的 TypeScript path alias

### 4. 输出格式

```
## 审查结果

### Lint
✅ 无错误 / ❌ N 个错误(列出文件和行号)

### 问题列表

🔴 [严重] services/AudioService.ts:42
直接在 Service 内调用了 useState,违反 Service 层规范
建议:将状态提升到 useAudio hook

🟡 [警告] components/VoiceButton.tsx:18
StyleSheet 定义在组件函数内部,每次渲染会重建
建议:移到组件外顶层定义

🔵 [建议] hooks/useLive2D.ts:95
缺少卸载时的 cleanup,可能导致内存泄漏

### 结论

⛔ 建议修复严重问题后再提交 / ✅ 可以提交(附注意事项)
```

严重性说明:
- 🔴 严重:架构违规、潜在 crash、类型不安全
- 🟡 警告:性能问题、代码质量问题
- 🔵 建议:可选优化,不阻断提交
4 changes: 2 additions & 2 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[submodule "packages/react-native-pcm-stream"]
path = packages/react-native-pcm-stream
url = https://github.com/noahzaozao/react-native-pcm-stream.git
url = https://github.com/Tonnodoubt/react-native-pcm-stream.git
[submodule "packages/react-native-live2d"]
path = packages/react-native-live2d
url = https://github.com/noahzaozao/react-native-live2d.git
url = https://github.com/Tonnodoubt/react-native-live2d.git
25 changes: 25 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# N.E.K.O.-RN

## Build

```bash
# 编译 debug APK(唯一方式,不要用 expo run:android)
cd android && ./gradlew assembleDebug
```

APK 输出:`android/app/build/outputs/apk/debug/app-debug.apk`

## 项目结构

- `app/` — Expo Router 页面
- `components/` — 通用 UI 组件
- `services/` — 业务服务层
- `hooks/` — React hooks
- `packages/` — 原生模块(react-native-pcm-stream, react-native-live2d 等)
- `android/` — Android 原生工程
- `i18n/` — 国际化

## 关键约定

- 嘴形同步:JS 层做平滑(LipSyncService),Kotlin 层做时间对齐(PCMStreamPlayer 按 playbackHeadPosition 取振幅)
- 修改 Kotlin 原生代码后需要 `./gradlew assembleDebug` 重新编译,Metro 热更新不生效
25 changes: 25 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# N.E.K.O.-RN

## Build

```bash
# 编译 debug APK(唯一方式,不要用 expo run:android)
cd android && ./gradlew assembleDebug
```

APK 输出:`android/app/build/outputs/apk/debug/app-debug.apk`

## 项目结构

- `app/` — Expo Router 页面
- `components/` — 通用 UI 组件
- `services/` — 业务服务层
- `hooks/` — React hooks
- `packages/` — 原生模块(react-native-pcm-stream, react-native-live2d 等)
- `android/` — Android 原生工程
- `i18n/` — 国际化

## 关键约定

- 嘴形同步:JS 层做平滑(LipSyncService),Kotlin 层做时间对齐(PCMStreamPlayer 按 playbackHeadPosition 取振幅)
- 修改 Kotlin 原生代码后需要 `./gradlew assembleDebug` 重新编译,Metro 热更新不生效
95 changes: 88 additions & 7 deletions app/(tabs)/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,102 @@
import { Tabs } from 'expo-router';

Check warning on line 1 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

Replace `'expo-router'` with `"expo-router"`
import React from 'react';

Check warning on line 2 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

Replace `'react'` with `"react"`
import { Ionicons } from '@expo/vector-icons';

Check warning on line 3 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

Replace `'@expo/vector-icons'` with `"@expo/vector-icons"`

Check warning on line 3 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

`@expo/vector-icons` import should occur before import of `expo-router`
import { Platform, StyleSheet, TouchableOpacity, View, Text } from 'react-native';

Check warning on line 4 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

'View' is defined but never used

Check warning on line 4 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

Replace `·Platform,·StyleSheet,·TouchableOpacity,·View,·Text·}·from·'react-native'` with `⏎··Platform,⏎··StyleSheet,⏎··TouchableOpacity,⏎··View,⏎··Text,⏎}·from·"react-native"`
import { BlurView } from 'expo-blur';

Check warning on line 5 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

Replace `'expo-blur'` with `"expo-blur"`

Check warning on line 5 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

`expo-blur` import should occur before import of `expo-router`
import { useSafeAreaInsets } from 'react-native-safe-area-context';

Check warning on line 6 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

Replace `'react-native-safe-area-context'` with `"react-native-safe-area-context"`
import * as Haptics from 'expo-haptics';

Check warning on line 7 in app/(tabs)/_layout.tsx

View workflow job for this annotation

GitHub Actions / quality (22)

`expo-haptics` import should occur before import of `expo-router`

import { HapticTab } from '@/components/haptic-tab';
import { Colors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';
import { useTheme } from '@/constants/ThemeContext';

function GlassTabBar({ state, descriptors, navigation }: any) {
const theme = useTheme();
const cc = theme.colors;
const insets = useSafeAreaInsets();

return (
<BlurView
intensity={theme.isDark ? 40 : 60}
tint={theme.isDark ? 'dark' : 'light'}
style={[
styles.tabBar,
{
paddingBottom: Math.max(insets.bottom, 8),
borderColor: cc.border,
},
]}
>
{state.routes.map((route: any, index: number) => {
const { options } = descriptors[route.key];
const isFocused = state.index === index;
const label = options.title ?? route.name;
const Icon = options.tabBarIcon;

const onPress = () => {
if (Platform.OS === 'ios') {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
}
const event = navigation.emit({ type: 'tabPress', target: route.key, canPreventDefault: true });
if (!isFocused && !event.defaultPrevented) {
navigation.navigate(route.name);
}
};

return (
<TouchableOpacity
key={route.key}
onPress={onPress}
style={[
styles.tabItem,
isFocused && { backgroundColor: cc.accentSoft },
]}
activeOpacity={0.7}
>
{Icon && <Icon size={22} color={isFocused ? cc.accent : cc.textMuted} />}
<Text
style={[
styles.tabLabel,
{ color: isFocused ? cc.accent : cc.textMuted },
]}
>
{label}
</Text>
</TouchableOpacity>
);
})}
</BlurView>
);
}

const styles = StyleSheet.create({
tabBar: {
flexDirection: 'row',
borderTopWidth: 1,
paddingTop: 6,
},
tabItem: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
gap: 3,
paddingVertical: 6,
borderRadius: 12,
},
tabLabel: {
fontSize: 11,
fontWeight: '500',
},
});

export default function TabLayout() {
const colorScheme = useColorScheme();
const theme = useTheme();

return (
<Tabs
tabBar={(props) => <GlassTabBar {...props} />}
screenOptions={{
tabBarActiveTintColor: Colors[colorScheme ?? 'light'].tint,
headerShown: false,
tabBarButton: HapticTab,
}}>
}}
>
<Tabs.Screen
name="index"
options={{
Expand Down
Loading
Loading