# 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 rootMeMenuTitles() { return Arrays.asList("账号与安全", "AI 账号", "设置", "技能", "关于"); } public static List 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 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 ``` ```xml