From 785db90a7ace650682ad6a23bedace3c4737c0b6 Mon Sep 17 00:00:00 2001 From: kris Date: Fri, 27 Mar 2026 01:47:28 +0800 Subject: [PATCH] docs: add wechat native ui rollback plan --- .../2026-03-27-wechat-native-ui-rollback.md | 751 ++++++++++++++++++ 1 file changed, 751 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-27-wechat-native-ui-rollback.md diff --git a/docs/superpowers/plans/2026-03-27-wechat-native-ui-rollback.md b/docs/superpowers/plans/2026-03-27-wechat-native-ui-rollback.md new file mode 100644 index 0000000..3409655 --- /dev/null +++ b/docs/superpowers/plans/2026-03-27-wechat-native-ui-rollback.md @@ -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 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 +