Files
boss/docs/superpowers/plans/2026-03-27-wechat-native-ui-rollback.md
2026-03-27 01:47:28 +08:00

752 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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