Files
boss/docs/superpowers/plans/2026-03-27-wechat-native-ui-phase2.md
2026-03-27 14:22:58 +08:00

12 KiB

WeChat Native UI Phase 2 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: Polish the native Android WeChat-style rollback with better chat feel, clearer OTA download/install feedback, and smoother root navigation memory.

Architecture: Keep the approved WeChat-style root surfaces intact and limit this batch to interaction polish. Add small pure-Java helpers for chat UI state and OTA download state so the tricky behavior is unit-tested, then wire those helpers into ProjectDetailActivity, AboutActivity, and MainActivity.

Tech Stack: Android AppCompat, XML layouts, Java 21, Gradle 8, JUnit4, DownloadManager, existing Boss APIs, existing release packaging and deploy scripts.


File Structure

New files

  • android/app/src/main/java/com/hyzq/boss/ProjectChatUiState.java
    • Pure-Java helper for send-button enabled state, optimistic pending bubble state, and auto-scroll policy.
  • android/app/src/main/java/com/hyzq/boss/OtaDownloadStateMapper.java
    • Pure-Java helper for mapping DownloadManager progress/status into UI strings and retry/install states.
  • android/app/src/main/java/com/hyzq/boss/RootTabMemory.java
    • Pure-Java helper for resolving explicit root tabs versus stored root tabs.
  • android/app/src/test/java/com/hyzq/boss/ProjectChatUiStateTest.java
    • Unit tests for chat send/pending/scroll rules.
  • android/app/src/test/java/com/hyzq/boss/OtaDownloadStateMapperTest.java
    • Unit tests for OTA download state mapping.
  • android/app/src/test/java/com/hyzq/boss/RootTabMemoryTest.java
    • Unit tests for remembered root tab selection.

Modified files

  • android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java
  • android/app/src/main/java/com/hyzq/boss/AboutActivity.java
  • android/app/src/main/java/com/hyzq/boss/MainActivity.java
  • android/app/src/main/java/com/hyzq/boss/BossUi.java
  • README.md
  • docs/architecture/current_runtime_and_deploy_status_cn.md
  • docs/architecture/api_and_service_inventory_cn.md

Task 1: Polish Chat Composer Feedback and Scroll Behavior

Files:

  • Create: android/app/src/main/java/com/hyzq/boss/ProjectChatUiState.java

  • Create: android/app/src/test/java/com/hyzq/boss/ProjectChatUiStateTest.java

  • Modify: android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java

  • Modify: android/app/src/main/java/com/hyzq/boss/BossUi.java

  • Step 1: Write the failing test

package com.hyzq.boss;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class ProjectChatUiStateTest {
    @Test
    public void sendEnabled_requiresTextAndNotBusy() {
        assertFalse(ProjectChatUiState.canSend("", false));
        assertFalse(ProjectChatUiState.canSend("   ", false));
        assertFalse(ProjectChatUiState.canSend("你好", true));
        assertTrue(ProjectChatUiState.canSend("你好", false));
    }

    @Test
    public void shouldAutoScroll_onlyWhenNearBottomOrForced() {
        assertTrue(ProjectChatUiState.shouldAutoScroll(true, false));
        assertTrue(ProjectChatUiState.shouldAutoScroll(false, true));
        assertFalse(ProjectChatUiState.shouldAutoScroll(false, false));
    }
}
  • Step 2: Run test to verify it fails

Run:

cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.ProjectChatUiStateTest --no-daemon

Expected: FAIL with missing ProjectChatUiState.

  • Step 3: Write minimal implementation
package com.hyzq.boss;

public final class ProjectChatUiState {
    private ProjectChatUiState() {}

    public static boolean canSend(String text, boolean sending) {
        return !sending && text != null && !text.trim().isEmpty();
    }

    public static boolean shouldAutoScroll(boolean nearBottom, boolean forced) {
        return nearBottom || forced;
    }
}

Then integrate it into ProjectDetailActivity:

  • disable send button when text empty or request busy

  • show a local pending outgoing bubble immediately after tapping send

  • only auto-scroll on refresh when user was already near bottom or after local send

  • Step 4: Run tests and compile verification

Run:

cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.ProjectChatUiStateTest --no-daemon
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android :app:compileDebugJavaWithJavac --no-daemon

Expected: PASS.

  • Step 5: Commit
cd /Users/kris/code/boss
git add android/app/src/main/java/com/hyzq/boss/ProjectChatUiState.java \
  android/app/src/test/java/com/hyzq/boss/ProjectChatUiStateTest.java \
  android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java \
  android/app/src/main/java/com/hyzq/boss/BossUi.java
git commit -m "feat: polish native chat composer feedback"

Task 2: Add OTA Download Progress and Retry Guidance

Files:

  • Create: android/app/src/main/java/com/hyzq/boss/OtaDownloadStateMapper.java

  • Create: android/app/src/test/java/com/hyzq/boss/OtaDownloadStateMapperTest.java

  • Modify: android/app/src/main/java/com/hyzq/boss/AboutActivity.java

  • Modify: android/app/src/main/java/com/hyzq/boss/BossUi.java

  • Step 1: Write the failing test

package com.hyzq.boss;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class OtaDownloadStateMapperTest {
    @Test
    public void toProgressLabel_formatsKnownProgress() {
        assertEquals("已下载 50%", OtaDownloadStateMapper.toProgressLabel(50, true));
    }

    @Test
    public void toProgressLabel_handlesUnknownProgress() {
        assertEquals("正在准备下载", OtaDownloadStateMapper.toProgressLabel(0, false));
    }
}
  • Step 2: Run test to verify it fails

Run:

cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.OtaDownloadStateMapperTest --no-daemon

Expected: FAIL with missing OtaDownloadStateMapper.

  • Step 3: Write minimal implementation
package com.hyzq.boss;

public final class OtaDownloadStateMapper {
    private OtaDownloadStateMapper() {}

    public static String toProgressLabel(int percent, boolean hasKnownTotal) {
        if (!hasKnownTotal) {
            return "正在准备下载";
        }
        return "已下载 " + Math.max(0, Math.min(100, percent)) + "%";
    }
}

Then integrate it into AboutActivity:

  • query DownloadManager while download is active

  • render a visible progress row in page content

  • keep a retry action after failed download

  • show install permission guidance row when unknown-source install permission is missing

  • Step 4: Run tests and compile verification

Run:

cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.OtaDownloadStateMapperTest --no-daemon
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android :app:compileDebugJavaWithJavac --no-daemon

Expected: PASS.

  • Step 5: Commit
cd /Users/kris/code/boss
git add android/app/src/main/java/com/hyzq/boss/OtaDownloadStateMapper.java \
  android/app/src/test/java/com/hyzq/boss/OtaDownloadStateMapperTest.java \
  android/app/src/main/java/com/hyzq/boss/AboutActivity.java \
  android/app/src/main/java/com/hyzq/boss/BossUi.java
git commit -m "feat: add native ota progress feedback"

Task 3: Remember Root Tab and Smooth Root Back Behavior

Files:

  • Create: android/app/src/main/java/com/hyzq/boss/RootTabMemory.java

  • Create: android/app/src/test/java/com/hyzq/boss/RootTabMemoryTest.java

  • Modify: android/app/src/main/java/com/hyzq/boss/MainActivity.java

  • Step 1: Write the failing test

package com.hyzq.boss;

import static org.junit.Assert.assertEquals;

import org.junit.Test;

public class RootTabMemoryTest {
    @Test
    public void resolveInitialTab_prefersExplicitTab() {
        assertEquals("devices", RootTabMemory.resolveInitialTab("devices", "me"));
    }

    @Test
    public void resolveInitialTab_fallsBackToStoredTab() {
        assertEquals("me", RootTabMemory.resolveInitialTab(null, "me"));
    }

    @Test
    public void resolveInitialTab_defaultsToConversations() {
        assertEquals("conversations", RootTabMemory.resolveInitialTab(null, null));
    }
}
  • Step 2: Run test to verify it fails

Run the new unit test target after creating it in the same package:

cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --tests com.hyzq.boss.RootTabMemoryTest --no-daemon

Expected: FAIL with missing helper.

  • Step 3: Write minimal implementation

Implement the helper:

package com.hyzq.boss;

public final class RootTabMemory {
    private RootTabMemory() {}

    public static String resolveInitialTab(String explicitTab, String storedTab) {
        if ("conversations".equals(explicitTab) || "devices".equals(explicitTab) || "me".equals(explicitTab)) {
            return explicitTab;
        }
        if ("conversations".equals(storedTab) || "devices".equals(storedTab) || "me".equals(storedTab)) {
            return storedTab;
        }
        return "conversations";
    }
}

Then wire it into MainActivity so that:

  • last selected root tab is persisted in SharedPreferences

  • explicit deep-link tab still wins over stored tab

  • when already at root conversations tab, back key shows a soft toast then moves task to back

  • Step 4: Run tests and full debug verification

Run:

cd /Users/kris/code/boss
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android testDebugUnitTest --no-daemon
JAVA_HOME=$(/usr/libexec/java_home) ./android/gradlew -p ./android assembleDebug --no-daemon

Expected: PASS.

  • Step 5: Commit
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/RootTabMemory.java \
  android/app/src/test/java/com/hyzq/boss/RootTabMemoryTest.java
git commit -m "feat: polish native root tab memory"

Task 4: Verification, Packaging, Deploy, and Docs

Files:

  • Modify: README.md

  • Modify: docs/architecture/current_runtime_and_deploy_status_cn.md

  • Modify: docs/architecture/api_and_service_inventory_cn.md

  • Modify: android/app/build.gradle

  • Step 1: Update docs and version

  • bump Android version for the next polish release

  • document chat send feedback, OTA progress, and root tab memory behavior

  • Step 2: Run local verification

Run:

cd /Users/kris/code/boss
npm run lint
npm run build
curl -sS http://127.0.0.1:3000/api/health
curl -sS http://127.0.0.1:4317/health
JAVA_HOME=$(/usr/libexec/java_home) npm run apk:release
JAVA_HOME=$(/usr/libexec/java_home) npm run aab:release
  • Step 3: Deploy and verify

Run:

cd /Users/kris/code/boss
BOSS_SERVER_PASS='your-password' ./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
  • Step 4: Commit
cd /Users/kris/code/boss
git add README.md docs/architecture/current_runtime_and_deploy_status_cn.md \
  docs/architecture/api_and_service_inventory_cn.md android/app/build.gradle \
  public/downloads/
git commit -m "chore: publish native ui phase 2 polish release"