docs: add wechat native ui rollback plan
This commit is contained in:
751
docs/superpowers/plans/2026-03-27-wechat-native-ui-rollback.md
Normal file
751
docs/superpowers/plans/2026-03-27-wechat-native-ui-rollback.md
Normal 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
|
||||
Reference in New Issue
Block a user