docs: add wechat native ui rollback plan

This commit is contained in:
kris
2026-03-27 01:47:28 +08:00
parent 8439428479
commit 785db90a7a

View File

@@ -0,0 +1,751 @@
# WeChat Native UI Rollback Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Restore the Android app to the previously approved WeChat-like UI and interaction model while keeping the current native Android architecture, Boss API integration, login recovery, and OTA pipeline.
**Architecture:** Keep `BossApiClient`, current activities, login restore, OTA delivery, and backend routes intact. Add a small pure-Java surface-mapping helper with unit tests so the “allowed” WeChat-style information density is explicit, then rework `MainActivity`, shared UI helpers, and the core activities to render simple list-driven surfaces and a chat-first conversation page. Advanced ops capability stays in the codebase but leaves the first-level UI.
**Tech Stack:** Android AppCompat, XML layouts, Java 21, Gradle 8, JUnit4, existing Boss APIs, adb, existing `npm run apk:release` / `npm run aab:release` / `scripts/deploy-server.sh`.
---
## File Structure
### New files
- `android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java`
- Pure-Java contract for root tabs, conversation rows, device rows, my-page menus, and project quick actions.
- `android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java`
- Unit tests for WeChat-style information trimming and tab/menu contract.
- `android/app/src/main/res/layout/activity_project_chat.xml`
- Chat-first project detail layout with lightweight header strip and bottom composer.
- `android/app/src/main/res/drawable/bg_list_row.xml`
- Flat white list-cell background with subtle divider feel.
- `android/app/src/main/res/drawable/bg_tab_active.xml`
- Active bottom-tab background.
- `android/app/src/main/res/drawable/bg_tab_inactive.xml`
- Inactive bottom-tab background.
- `android/app/src/main/res/drawable/bg_message_incoming.xml`
- Incoming message bubble background.
- `android/app/src/main/res/drawable/bg_message_outgoing.xml`
- Outgoing message bubble background.
- `scripts/verify-native-wechat-release.sh`
- Local verification wrapper for build, health checks, artifacts, and docs.
### Modified files
- `android/app/src/main/java/com/hyzq/boss/BossUi.java`
- Shared list-row, avatar, unread badge, bubble, and chip builders.
- `android/app/src/main/java/com/hyzq/boss/BossScreenActivity.java`
- Base screen support for alternate layouts and lighter headers.
- `android/app/src/main/java/com/hyzq/boss/MainActivity.java`
- Root shell, tab state, conversations list, devices list, my-page menu, and return behavior.
- `android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java`
- Chat-first surface with only `项目目标 / 版本记录` as lightweight actions.
- `android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java`
- Simpler device detail first screen.
- `android/app/src/main/java/com/hyzq/boss/SecurityActivity.java`
- WeChat-style simple list and session summary.
- `android/app/src/main/java/com/hyzq/boss/SettingsActivity.java`
- Lighter settings presentation.
- `android/app/src/main/java/com/hyzq/boss/SkillInventoryActivity.java`
- Skill rows instead of stacked heavy cards.
- `android/app/src/main/java/com/hyzq/boss/AiAccountsActivity.java`
- Simpler account list presentation.
- `android/app/src/main/java/com/hyzq/boss/AboutActivity.java`
- Cleaner About/OTA surface with advanced entry placement.
- `android/app/src/main/res/layout/activity_main.xml`
- Root login and tab shell layout.
- `android/app/src/main/res/layout/activity_screen.xml`
- Standard secondary-page shell layout.
- `android/app/src/main/res/values/colors.xml`
- WeChat-like flat palette.
- `android/app/src/main/res/values/styles.xml`
- White window background, no dashboard gradient.
- `android/app/build.gradle`
- Version bump for the rollback release build.
- `README.md`
- New native UI direction and build status.
- `docs/architecture/current_runtime_and_deploy_status_cn.md`
- Runtime truth after rollback.
- `docs/architecture/ai_handoff_index_cn.md`
- Effective Android surface summary.
- `docs/architecture/repo_map_cn.md`
- New layout / helper file map if structure changes.
## Task 1: Freeze the WeChat Surface Contract in Unit Tests
**Files:**
- Create: `android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java`
- Create: `android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java`
- Test: `android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java`
- [ ] **Step 1: Write the failing test**
```java
package com.hyzq.boss;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import org.json.JSONObject;
import org.junit.Test;
import java.util.Arrays;
public class WechatSurfaceMapperTest {
@Test
public void toConversationRow_keepsOnlyWechatFields() throws Exception {
JSONObject source = new JSONObject()
.put("projectTitle", "Boss 移动控制台")
.put("preview", "主 Agent 已回复")
.put("latestReplyLabel", "昨天")
.put("unreadCount", 3)
.put("riskLevel", "urgent")
.put("activeDeviceCount", 2);
WechatSurfaceMapper.ConversationRow row = WechatSurfaceMapper.toConversationRow(source);
assertEquals("Boss 移动控制台", row.title);
assertEquals("主 Agent 已回复", row.preview);
assertEquals("昨天", row.timeLabel);
assertEquals(3, row.unreadCount);
assertFalse(row.preview.contains("设备"));
}
@Test
public void toDeviceRow_keepsOnlySimpleSubtitle() throws Exception {
JSONObject source = new JSONObject()
.put("name", "Mac Studio")
.put("status", "online")
.put("account", "17600003315")
.put("quota5h", 68)
.put("quota7d", 81);
WechatSurfaceMapper.DeviceRow row = WechatSurfaceMapper.toDeviceRow(source);
assertEquals("Mac Studio", row.title);
assertEquals("在线 · 17600003315", row.subtitle);
}
@Test
public void rootMeMenuTitles_matchApprovedSimpleMenu() {
assertEquals(
Arrays.asList("账号与安全", "AI 账号", "设置", "技能", "关于"),
WechatSurfaceMapper.rootMeMenuTitles()
);
}
@Test
public void projectQuickActions_keepOnlyGoalsAndVersions() {
assertEquals(
Arrays.asList("项目目标", "版本记录"),
WechatSurfaceMapper.projectQuickActions()
);
}
}
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.WechatSurfaceMapperTest --no-daemon
```
Expected: FAIL with `cannot find symbol` or `ClassNotFoundException` for `WechatSurfaceMapper`.
- [ ] **Step 3: Write minimal implementation**
```java
package com.hyzq.boss;
import org.json.JSONObject;
import java.util.Arrays;
import java.util.List;
public final class WechatSurfaceMapper {
private WechatSurfaceMapper() {}
public static final class ConversationRow {
public final String title;
public final String preview;
public final String timeLabel;
public final int unreadCount;
public ConversationRow(String title, String preview, String timeLabel, int unreadCount) {
this.title = title;
this.preview = preview;
this.timeLabel = timeLabel;
this.unreadCount = unreadCount;
}
}
public static final class DeviceRow {
public final String title;
public final String subtitle;
public DeviceRow(String title, String subtitle) {
this.title = title;
this.subtitle = subtitle;
}
}
public static ConversationRow toConversationRow(JSONObject item) {
return new ConversationRow(
item.optString("projectTitle", "未命名会话"),
item.optString("preview", "暂无消息"),
item.optString("latestReplyLabel", ""),
item.optInt("unreadCount", 0)
);
}
public static DeviceRow toDeviceRow(JSONObject item) {
String status = "online".equals(item.optString("status")) ? "在线" : "离线";
String account = item.optString("account", "");
String subtitle = account.isEmpty() ? status : status + " · " + account;
return new DeviceRow(item.optString("name", "未命名设备"), subtitle);
}
public static List<String> rootMeMenuTitles() {
return Arrays.asList("账号与安全", "AI 账号", "设置", "技能", "关于");
}
public static List<String> projectQuickActions() {
return Arrays.asList("项目目标", "版本记录");
}
}
```
- [ ] **Step 4: Run test to verify it passes**
Run:
```bash
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.WechatSurfaceMapperTest --no-daemon
```
Expected: PASS, `BUILD SUCCESSFUL`.
- [ ] **Step 5: Commit**
```bash
cd /Users/kris/code/boss
git add android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java
git commit -m "test: freeze wechat surface contract"
```
### Task 2: Rebuild the Root Shell and Conversation List
**Files:**
- Modify: `android/app/src/main/java/com/hyzq/boss/BossUi.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/MainActivity.java`
- Modify: `android/app/src/main/res/layout/activity_main.xml`
- Modify: `android/app/src/main/res/layout/activity_screen.xml`
- Modify: `android/app/src/main/res/values/colors.xml`
- Modify: `android/app/src/main/res/values/styles.xml`
- Create: `android/app/src/main/res/drawable/bg_list_row.xml`
- Create: `android/app/src/main/res/drawable/bg_tab_active.xml`
- Create: `android/app/src/main/res/drawable/bg_tab_inactive.xml`
- Modify: `android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java`
- [ ] **Step 1: Write the failing test**
Extend `WechatSurfaceMapperTest.java` with root-shell expectations:
```java
@Test
public void rootTabOrder_isWechatStyle() {
assertEquals(Arrays.asList("会话", "设备", "我的"), WechatSurfaceMapper.rootTabLabels());
}
@Test
public void mainPage_doesNotExposeOpsEntry() {
assertFalse(WechatSurfaceMapper.rootMeMenuTitles().contains("运维与修复"));
}
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.WechatSurfaceMapperTest --no-daemon
```
Expected: FAIL because `rootTabLabels()` does not exist.
- [ ] **Step 3: Write minimal implementation**
Add the missing contract method:
```java
public static List<String> rootTabLabels() {
return Arrays.asList("会话", "设备", "我的");
}
```
Then wire the UI around that contract.
Update `BossUi.java` to add list-oriented builders:
```java
public static LinearLayout buildListRow(
Context context,
String leadingText,
String title,
String subtitle,
String trailingText,
int unreadCount,
@Nullable View.OnClickListener listener
) {
LinearLayout row = new LinearLayout(context);
row.setOrientation(LinearLayout.HORIZONTAL);
row.setBackgroundResource(R.drawable.bg_list_row);
row.setPadding(dp(context, 16), dp(context, 14), dp(context, 16), dp(context, 14));
if (listener != null) row.setOnClickListener(listener);
return row;
}
```
Update `MainActivity.java` so the root render paths use `WechatSurfaceMapper` instead of card-heavy metadata:
```java
private void renderConversationsRoot() {
screenContent.removeAllViews();
if (conversationsData == null || conversationsData.length() == 0) {
screenContent.addView(BossUi.buildEmptyCard(this, "当前没有会话数据。"));
return;
}
for (int i = 0; i < conversationsData.length(); i++) {
JSONObject item = conversationsData.optJSONObject(i);
if (item == null) continue;
WechatSurfaceMapper.ConversationRow row = WechatSurfaceMapper.toConversationRow(item);
String projectId = item.optString("projectId", "");
screenContent.addView(BossUi.buildListRow(
this,
item.optString("avatar", item.optString("projectTitle", "会").substring(0, 1)),
row.title,
row.preview,
row.timeLabel,
row.unreadCount,
v -> openProject(projectId, row.title)
));
}
}
```
Update `activity_main.xml` / `activity_screen.xml` to remove dashboard framing:
```xml
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/boss_surface">
```
```xml
<Button
android:id="@+id/tab_conversations"
android:background="@drawable/bg_tab_active"
android:text="会话" />
```
- [ ] **Step 4: Run tests and compile verification**
Run:
```bash
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.WechatSurfaceMapperTest --no-daemon
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android :app:compileDebugJavaWithJavac --no-daemon
```
Expected:
- Unit tests PASS
- Java compile PASS
- [ ] **Step 5: Commit**
```bash
cd /Users/kris/code/boss
git add android/app/src/main/java/com/hyzq/boss/BossUi.java android/app/src/main/java/com/hyzq/boss/MainActivity.java android/app/src/main/res/layout/activity_main.xml android/app/src/main/res/layout/activity_screen.xml android/app/src/main/res/values/colors.xml android/app/src/main/res/values/styles.xml android/app/src/main/res/drawable/bg_list_row.xml android/app/src/main/res/drawable/bg_tab_active.xml android/app/src/main/res/drawable/bg_tab_inactive.xml android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java
git commit -m "feat: restore wechat-style root shell"
```
### Task 3: Rebuild Project Detail into a Chat-First Surface
**Files:**
- Modify: `android/app/src/main/java/com/hyzq/boss/BossScreenActivity.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java`
- Modify: `android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/BossUi.java`
- Create: `android/app/src/main/res/layout/activity_project_chat.xml`
- Create: `android/app/src/main/res/drawable/bg_message_incoming.xml`
- Create: `android/app/src/main/res/drawable/bg_message_outgoing.xml`
- [ ] **Step 1: Write the failing test**
Extend `WechatSurfaceMapperTest.java`:
```java
@Test
public void projectPrimarySections_keepOnlyChatEssentials() {
assertEquals(
Arrays.asList("quick_actions", "messages", "composer"),
WechatSurfaceMapper.projectPrimarySections()
);
}
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.WechatSurfaceMapperTest --no-daemon
```
Expected: FAIL because the helper still allows the old action shape or the assertions are not yet satisfied.
- [ ] **Step 3: Write minimal implementation**
Teach `BossScreenActivity` to allow an alternate layout:
```java
protected int getLayoutResId() {
return R.layout.activity_screen;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayoutResId());
...
}
```
Then make `ProjectDetailActivity` use a chat layout and strip the heavy cards:
```java
@Override
protected int getLayoutResId() {
return R.layout.activity_project_chat;
}
private void renderProject(JSONObject payload) {
JSONObject project = payload.optJSONObject("project");
JSONArray messages = project == null ? null : project.optJSONArray("messages");
configureScreen(project == null ? "项目聊天" : project.optString("name", "项目聊天"), "");
replaceContent();
appendQuickActions("项目目标", v -> openGoals(), "版本记录", v -> openVersions());
renderMessages(messages);
}
```
and add the missing helper contract:
```java
public static List<String> projectPrimarySections() {
return Arrays.asList("quick_actions", "messages", "composer");
}
```
Render message bubbles through `BossUi` instead of generic cards:
```java
public static LinearLayout buildMessageBubble(
Context context,
boolean self,
String sender,
String body,
String meta
) {
LinearLayout bubble = new LinearLayout(context);
bubble.setBackgroundResource(self ? R.drawable.bg_message_outgoing : R.drawable.bg_message_incoming);
return bubble;
}
```
Do **not** render these old sections in the main chat surface:
- `当前主控身份`
- `主 Agent 调度结论`
- `线程预算卡片`
- `实时 APP 日志`
- `媒体与转发说明`
- [ ] **Step 4: Run tests and screen compile verification**
Run:
```bash
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.WechatSurfaceMapperTest --no-daemon
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android :app:compileDebugJavaWithJavac --no-daemon
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android assembleDebug --no-daemon
```
Expected:
- Unit tests PASS
- Java compile PASS
- Debug assemble PASS
- [ ] **Step 5: Commit**
```bash
cd /Users/kris/code/boss
git add android/app/src/main/java/com/hyzq/boss/BossScreenActivity.java android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java android/app/src/main/java/com/hyzq/boss/BossUi.java android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java android/app/src/main/res/layout/activity_project_chat.xml android/app/src/main/res/drawable/bg_message_incoming.xml android/app/src/main/res/drawable/bg_message_outgoing.xml
git commit -m "feat: restore wechat-style project chat page"
```
### Task 4: Simplify Devices and Me Surfaces, Demote Advanced Ops
**Files:**
- Modify: `android/app/src/main/java/com/hyzq/boss/MainActivity.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/SecurityActivity.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/SettingsActivity.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/SkillInventoryActivity.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/AiAccountsActivity.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/AboutActivity.java`
- Modify: `android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java`
- Modify: `android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java`
- [ ] **Step 1: Write the failing test**
Extend `WechatSurfaceMapperTest.java`:
```java
@Test
public void rootMeMenuTitles_keepApprovedOrder() {
assertEquals(
Arrays.asList("账号与安全", "AI 账号", "设置", "技能", "关于"),
WechatSurfaceMapper.rootMeMenuTitles()
);
}
@Test
public void advancedEntryTitle_movesOpsOutOfMainMePage() {
assertEquals("高级与调试", WechatSurfaceMapper.advancedEntryTitle());
}
```
- [ ] **Step 2: Run test to verify it fails**
Run:
```bash
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.WechatSurfaceMapperTest --no-daemon
```
Expected: FAIL until the mapper and page rendering fully stop leaking quota / ops content.
- [ ] **Step 3: Write minimal implementation**
Use `WechatSurfaceMapper` rows in `MainActivity.renderDevicesRoot()`:
```java
private void renderDevicesRoot() {
screenContent.removeAllViews();
screenContent.addView(BossUi.buildMenuRow(this, "添加设备", "通过绑定码接入新设备", null, v -> {
startActivity(new Intent(this, DeviceEnrollmentActivity.class));
}));
for (int i = 0; i < devicesData.length(); i++) {
JSONObject item = devicesData.optJSONObject(i);
if (item == null) continue;
WechatSurfaceMapper.DeviceRow row = WechatSurfaceMapper.toDeviceRow(item);
screenContent.addView(BossUi.buildListRow(
this,
item.optString("avatar", "设"),
row.title,
row.subtitle,
"",
0,
v -> openDevice(item.optString("id"), row.title)
));
}
}
```
Simplify `renderMeRoot()` to only approved rows:
```java
screenContent.addView(BossUi.buildMenuRow(this, "账号与安全", "登录与会话", null, v -> startActivity(new Intent(this, SecurityActivity.class))));
screenContent.addView(BossUi.buildMenuRow(this, "AI 账号", "主 GPT / 备用 GPT / API 容灾", null, v -> startActivity(new Intent(this, AiAccountsActivity.class))));
screenContent.addView(BossUi.buildMenuRow(this, "设置", "默认首页与提醒行为", null, v -> startActivity(new Intent(this, SettingsActivity.class))));
screenContent.addView(BossUi.buildMenuRow(this, "技能", "当前设备 Skill 清单", null, v -> startActivity(new Intent(this, SkillInventoryActivity.class))));
screenContent.addView(BossUi.buildMenuRow(this, "关于", "版本与更新", null, v -> startActivity(new Intent(this, AboutActivity.class))));
```
Move advanced entry deeper by placing it under `AboutActivity` as a non-primary menu row:
```java
Button advanced = BossUi.buildSecondaryButton(this, "高级与调试");
advanced.setOnClickListener(v -> startActivity(new Intent(this, OpsCenterActivity.class)));
```
and add the helper contract:
```java
public static String advancedEntryTitle() {
return "高级与调试";
}
```
`DeviceDetailActivity` should keep only:
- simple device summary
- `查看技能`
- `编辑`
and stop rendering related thread cards / enrollment draft cards on the first screen.
- [ ] **Step 4: Run tests and full debug verification**
Run:
```bash
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.WechatSurfaceMapperTest --no-daemon
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android assembleDebug --no-daemon
adb -s 8KE0219724012168 shell am start -W -n com.hyzq.boss/.MainActivity
adb -s 8KE0219724012168 shell pidof com.hyzq.boss
```
Expected:
- Unit tests PASS
- Debug assemble PASS
- `MainActivity` starts
- `pidof` returns a process id
- [ ] **Step 5: Commit**
```bash
cd /Users/kris/code/boss
git add android/app/src/main/java/com/hyzq/boss/MainActivity.java android/app/src/main/java/com/hyzq/boss/DeviceDetailActivity.java android/app/src/main/java/com/hyzq/boss/SecurityActivity.java android/app/src/main/java/com/hyzq/boss/SettingsActivity.java android/app/src/main/java/com/hyzq/boss/SkillInventoryActivity.java android/app/src/main/java/com/hyzq/boss/AiAccountsActivity.java android/app/src/main/java/com/hyzq/boss/AboutActivity.java android/app/src/main/java/com/hyzq/boss/WechatSurfaceMapper.java android/app/src/test/java/com/hyzq/boss/WechatSurfaceMapperTest.java
git commit -m "feat: simplify device and me surfaces"
```
### Task 5: Verification, Release Packaging, Deployment, and Docs
**Files:**
- Create: `scripts/verify-native-wechat-release.sh`
- Modify: `android/app/build.gradle`
- Modify: `README.md`
- Modify: `docs/architecture/current_runtime_and_deploy_status_cn.md`
- Modify: `docs/architecture/ai_handoff_index_cn.md`
- Modify: `docs/architecture/repo_map_cn.md`
- [ ] **Step 1: Write the failing release verification script**
Create `scripts/verify-native-wechat-release.sh`:
```bash
#!/bin/zsh
set -euo pipefail
cd /Users/kris/code/boss
npm run lint
npm run build
curl -fsS http://127.0.0.1:3000/api/health >/dev/null
curl -fsS http://127.0.0.1:4317/health >/dev/null
test -f android/app/build/outputs/apk/release/boss-android-v2.1.2-release.apk
test -f android/app/build/outputs/bundle/release/boss-android-v2.1.2-release.aab
rg -q '微信式' README.md
rg -q '2.1.2' docs/architecture/current_runtime_and_deploy_status_cn.md
```
- [ ] **Step 2: Run it to verify it fails**
Run:
```bash
cd /Users/kris/code/boss
zsh ./scripts/verify-native-wechat-release.sh
```
Expected: FAIL because the `2.1.2` artifacts and updated docs do not exist yet.
- [ ] **Step 3: Write minimal release implementation**
Bump the Android version:
```gradle
versionCode 9
versionName "2.1.2"
```
Update docs to state:
- native UI has returned to WeChat-style interaction
- root tabs are `会话 / 设备 / 我的`
- chat page keeps only `项目目标 / 版本记录`
Then build and publish:
```bash
cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) npm run apk:release
JAVA_HOME=$(/usr/libexec/java_home) npm run aab:release
```
- [ ] **Step 4: Run release verification and deploy**
Run:
```bash
cd /Users/kris/code/boss
zsh ./scripts/verify-native-wechat-release.sh
adb -s 8KE0219724012168 install -r /Users/kris/code/boss/android/app/build/outputs/apk/release/boss-android-v2.1.2-release.apk
adb -s 8KE0219724012168 shell am start -W -n com.hyzq.boss/.MainActivity
./scripts/deploy-server.sh
"$HOME/.codex/skills/boss-server-debug/scripts/server_ssh.sh" exec "curl -sS http://127.0.0.1:3000/api/health"
curl -sS https://boss.hyzq.net/api/health
curl -sS https://boss.hyzq.net/downloads/boss-android-latest.json
curl -sS https://boss.hyzq.net/downloads/boss-android-latest-aab.json
```
Expected:
- Verification script PASS
- APK install PASS
- `MainActivity` launch PASS
- remote `/api/health` PASS
- public health PASS
- public APK/AAB metadata show `2.1.2 / versionCode 9`
- [ ] **Step 5: Commit and publish**
```bash
cd /Users/kris/code/boss
git add scripts/verify-native-wechat-release.sh android/app/build.gradle README.md docs/architecture/current_runtime_and_deploy_status_cn.md docs/architecture/ai_handoff_index_cn.md docs/architecture/repo_map_cn.md public/downloads
git commit -m "feat: ship wechat-style native rollback release"
git push gitea HEAD:refs/heads/codex/native-boss-android-2-1-0
```
## Self-Review Checklist
- Spec coverage:
- 一级导航微信式化Task 2
- 会话首页极简列表Task 2
- 聊天页只保留项目目标/版本记录Task 3
- 设备页 / 我的页简单列表Task 4
- 返回逻辑与状态保持Task 2 + Task 4
- 构建 / 真机 / 部署 / 文档Task 5
- Placeholder scan:
- No `TBD` / `TODO` / “similar to”
- Every task has exact files, commands, and code snippets
- Type consistency:
- `WechatSurfaceMapper` is the single contract source for root tabs, conversation rows, device rows, and quick actions