diff --git a/CLAUDE.md b/CLAUDE.md index 2cb5308..e6067fa 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -3,12 +3,12 @@ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. -## Project status: Phase 0–3 on `main`; Phase 5–6 spikes on `main`; Phase 7 (Bluetooth offline connectivity) started 2026-10-28, BLE L2CAP pairing/control channel on `main`, release `v0.2.0` pending the owner's tag (Wi-Fi Direct and relay bulk dropped from this phase's scope); latest tagged release `v0.1.1` (2026-10-22); G1/G2/G5/G6 open; G4 closed (Phase 4 stopped, owner decision 2026-10-12) +## Project status: Phase 0–3 on `main`; Phase 5–6 spikes on `main`; Phase 7 (Bluetooth offline connectivity) released in `v0.2.0` (2026-10-30), BLE L2CAP pairing/control channel on `main` (Wi-Fi Direct and relay bulk dropped from this phase's scope); latest tagged release `v0.2.0` (2026-10-30); G1/G2/G5/G6 open; G4 closed (Phase 4 stopped, owner decision 2026-10-12) Phase 0–3 product code is on `main` (clipboard, SMS, iOS shell, Rust relay, call metadata/control). Phase 4–6 **spike probes** are also on `main` (merged 2026-09-30): `HFPSpike`, `CameraSpike`, Android `tools/web-spike`, Apple `Tools/WebSpike`. Coordinated public betas: **`v0.1.0-beta.1`**, **`v0.1.0-beta.2`** (2026-10-04, the first with installable files) and **`v0.1.0-beta.3`** (2026-10-07) on hub/android/apple/shared/relay -(not a store release), then **`v0.1.1`** (2026-10-22), the first tag without a pre-release suffix (still not a store release; Google Play readiness on Android, paid-team identifiers on iOS). Live progress board: hub `README.md` / `README.vi.md` (Roadmap and progress). Still +(not a store release), then **`v0.1.1`** (2026-10-22), the first tag without a pre-release suffix (still not a store release; Google Play readiness on Android, paid-team identifiers on iOS), then **`v0.2.0`** (2026-10-30), Phase 7 Bluetooth offline pairing (still not a store release). Live progress board: hub `README.md` / `README.vi.md` (Roadmap and progress). Still required before 1.0: gates **G1** (device matrix, TalkBack/VoiceOver) and **G2** (Play Console), Phase 2/3 checks on a real relay / APNs / FCM, and spike gates **G5** (paid Apple Developer team), **G6** (browser go/no-go). Owner decision 2026-09-28: Phases 5 and 6 proceed before Phase 4. Owner decision 2026-10-12 stops Phase 4 itself and @@ -61,7 +61,7 @@ project owner in Vietnamese with diacritics. **Commit identity:** always `Hồ Xuân Dũng ` (GitHub `xuandung38`). Never `dunghx1@viettel.com.vn` or any Viettel email in author/committer/`Signed-off-by`. Use `env -u CURSOR_AGENT git commit` so Cursor does not inject Co-authored-by trailers. -**Workspace tips to treat as current `main`** (ahead of tag `v0.1.1`; release `v0.2.0` pending the owner's tag, Phase 7 Bluetooth offline connectivity): shared `5cbe4aa`, relay `17f367b` (unchanged since `v0.1.1`), android `ed8e41f`, apple `7d3ce0b`, hub `main` tip. Confirm with `tools/workspace.sh status` before coding. **Local JDK:** Gradle needs `JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home` in a non-login shell, else `gradlew` prints "Unable to locate a Java Runtime" and a piped command still exits 0. +**Workspace tips to treat as current `main`:** tagged `v0.2.0` (2026-10-30) at hub `a9c7193`, shared `6e44aa1`, android `cd4527a`, apple `36f8cc8`, relay `17f367b` (unchanged since `v0.1.1`). Confirm with `tools/workspace.sh status` before coding. **Local JDK:** Gradle needs `JAVA_HOME=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home` in a non-login shell, else `gradlew` prints "Unable to locate a Java Runtime" and a piped command still exits 0. ### In progress / blocked (owner or gate) @@ -95,10 +95,12 @@ project owner in Vietnamese with diacritics. logs nothing for the bench) and keep HandLive's Notification access on. Then the Mac must show "Telegram Call"; swiping Telegram's in-call notification must keep the Mac panel in-call with End hidden, and hanging up must close it. Then run `call_latency.py` on a real app call for T3.3. -9. **Next release:** follow `docs/deployment-guide.md` (Cutting a release) and the 0.1.1 scan report: security scan of - everything since the last tag first, then versions, changelogs, website link and badge, tags (hub and shared first). - Open from the 0.1.1 scan: wrap the iOS UI-test seams (`-HLUITest*` launch arguments) and their `/tmp` debug writes - in `#if DEBUG`, fix the `project.yml` comment that calls `HandLiveiOSUITests` uncommitted. +9. **Next release:** follow `docs/deployment-guide.md` (Cutting a release) and the 0.2.0 scan report + (`reports/security-scan-0.2.0-2026-10-30.md`): security scan of everything since the last tag first, then versions, + changelogs, website link and badge, tags (hub and shared first). The 0.2.0 scan found nothing: 0 critical, 0 high, + 0 medium, 0 low. Still open, carried over from the 0.1.1 scan (untouched by Phase 7): wrap the iOS UI-test seams + (`-HLUITest*` launch arguments) and their `/tmp` debug writes in `#if DEBUG`, fix the `project.yml` comment that + calls `HandLiveiOSUITests` uncommitted (it is tracked in git). 10. **Open follow-ups** (no gate): `ManualClock` review debt, apple #5 being merged (`waitForSleepers` ignores cancellation; test helpers' `clock:`/`timer:` swapped); keep `-parallel-testing-enabled NO` (parallel saves ~0 s); measure the app-call label on Android 11–14 to know whether the launcher query matters there. The e2e `setup` check of diff --git a/README.md b/README.md index 4755f53..9fbe5d2 100644 --- a/README.md +++ b/README.md @@ -8,13 +8,13 @@ English | [Tiếng Việt](README.vi.md) [![ci-docs](https://github.com/HandLive/handlive/actions/workflows/ci-docs.yml/badge.svg)](https://github.com/HandLive/handlive/actions/workflows/ci-docs.yml) [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) -[![Release](https://img.shields.io/badge/release-v0.1.1-orange.svg)](https://github.com/HandLive/handlive/releases/tag/v0.1.1) +[![Release](https://img.shields.io/badge/release-v0.2.0-orange.svg)](https://github.com/HandLive/handlive/releases/tag/v0.2.0) HandLive connects Android devices with macOS, iOS, and iPadOS. It syncs your clipboard, bridges SMS conversations, and delivers call notifications with action controls—without cloud lock-in. End-to-end encryption is always on, and the entire project is open source under Apache-2.0. ## Get the beta -Download the latest release [`v0.1.1`](https://github.com/HandLive/handlive/releases/tag/v0.1.1) (2026-10-22): +Download the latest release [`v0.2.0`](https://github.com/HandLive/handlive/releases/tag/v0.2.0) (2026-10-30): - **macOS:** Ad-hoc signed DMG (choose *Open Anyway* on first run). - **Android:** Signed `foss` APK. Its application id is new (`com.handlive.android`): remove a beta install first, then pair again. - **iOS / iPadOS:** Unsigned IPA for sideloading. A beta install updated to this build must pair again (new App Group). @@ -67,7 +67,7 @@ Phases ship in order, each as a usable slice. One exception (owner, 2026-09-28): | **4** Call audio | Bluetooth HFP/SCO and Opus fallback | **Stopped** (owner decision, 2026-10-12) · G4 closed: only macOS 27 is available, so calls stay notification + answer/decline/end, no audio | | **5** Virtual camera / mic | CMIOExtension and AudioServerPlugin | **Spike on `main`** (`CameraSpike`) · G5 needs a paid Apple Developer team | | **6** Continue Browsing | Foreground URL handover across devices | **Spike on `main`** · Chrome and Samsung Internet measured; browser go/no-go pending | -| **7** Connect anywhere | Auto link without a shared Wi-Fi network | **Started** (owner decision, 2026-10-28), no wait on G4/G5/G6 · spike G7 done; Bluetooth L2CAP pairing and control channel on `main`, Wi-Fi Direct and relay bulk dropped from this phase's scope | +| **7** Connect anywhere | Auto link without a shared Wi-Fi network | **Released in `v0.2.0`** (2026-10-30) · Bluetooth offline pairing (QR flow B, PIN flow C via CPace-X25519-SHA512) shipped on `main`; Wi-Fi Direct and relay bulk specced but dropped from this phase's scope | | **G0** Vectors | Shared cross-platform crypto test vectors | **Done** | | **G1** Real devices | Real-device Phase 1 matrix validation | **Open** (before 1.0) · App Store Connect screenshots recaptured at native 1320×2868 (2026-10-12) | | **G2** Play Console | SMS and call log permissions declaration | **Open** (before 1.0; Plan B: F-Droid/APK) · Play Console screenshots recaptured at 1080×1920 (2026-10-12) | diff --git a/README.vi.md b/README.vi.md index 0b74981..e8de585 100644 --- a/README.vi.md +++ b/README.vi.md @@ -8,13 +8,13 @@ [![ci-docs](https://github.com/HandLive/handlive/actions/workflows/ci-docs.yml/badge.svg)](https://github.com/HandLive/handlive/actions/workflows/ci-docs.yml) [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE) -[![Release](https://img.shields.io/badge/release-v0.1.1-orange.svg)](https://github.com/HandLive/handlive/releases/tag/v0.1.1) +[![Release](https://img.shields.io/badge/release-v0.2.0-orange.svg)](https://github.com/HandLive/handlive/releases/tag/v0.2.0) HandLive kết nối điện thoại Android với các thiết bị macOS, iOS và iPadOS. Ứng dụng đồng bộ bảng nhớ tạm, chuyển tiếp tin nhắn SMS và hiển thị thông báo cuộc gọi kèm nút thao tác—hoàn toàn không phụ thuộc vào đám mây. Mã hóa đầu cuối luôn bật, và toàn bộ dự án là mã nguồn mở theo giấy phép Apache-2.0. ## Tải bản beta -Tải bản phát hành mới nhất [`v0.1.1`](https://github.com/HandLive/handlive/releases/tag/v0.1.1) (22/10/2026): +Tải bản phát hành mới nhất [`v0.2.0`](https://github.com/HandLive/handlive/releases/tag/v0.2.0) (30/10/2026): - **macOS:** Tệp DMG ký ad-hoc (chọn *Open Anyway* trong lần mở đầu tiên). - **Android:** Tệp APK `foss` đã ký. Mã ứng dụng đã đổi (`com.handlive.android`): gỡ bản beta cũ trước, rồi ghép đôi lại. - **iOS / iPadOS:** Tệp IPA chưa ký để sideload. Bản beta cập nhật lên bản này phải ghép đôi lại (App Group mới). @@ -67,7 +67,7 @@ HandLive xây lần lượt. Mỗi phase là một phần dùng được. Ngoạ | **4** Âm thanh cuộc gọi | Bluetooth HFP/SCO và dự phòng Opus | **Dừng** (quyết định của chủ dự án, 12/10/2026) · G4 đóng: chỉ có macOS 27, cuộc gọi chỉ còn thông báo và nút nghe/từ chối/kết thúc, không âm thanh | | **5** Camera / mic ảo | CMIOExtension và AudioServerPlugin | **Spike trên `main`** (`CameraSpike`) · G5 cần Apple Developer trả phí | | **6** Duyệt web tiếp | Chuyển tiếp URL đang mở giữa các thiết bị | **Spike trên `main`** · đã đo Chrome và Samsung Internet; còn go/no-go trình duyệt | -| **7** Kết nối mọi nơi | Tự kết nối khi không chung mạng Wi-Fi | **Đã bắt đầu** (quyết định chủ dự án, 28/10/2026), không chờ G4/G5/G6 · spike G7 xong; ghép đôi và kênh điều khiển qua Bluetooth L2CAP đã lên `main`, Wi-Fi Direct và bulk qua relay bị cắt khỏi phạm vi phase này | +| **7** Kết nối mọi nơi | Tự kết nối khi không chung mạng Wi-Fi | **Đã phát hành ở `v0.2.0`** (30/10/2026) · ghép đôi Bluetooth offline (luồng B QR, luồng C PIN qua CPace-X25519-SHA512) đã lên `main`; Wi-Fi Direct và bulk qua relay đã có đặc tả nhưng bị cắt khỏi phạm vi phase này | | **G0** Vector mã hóa | Vector kiểm thử mã hóa dùng chung giữa các nền tảng | **Xong** | | **G1** Máy thật | Kiểm thực tế ma trận máy thật Phase 1 | **Mở** (trước 1.0) · ảnh App Store Connect đã chụp lại ở kích thước gốc 1320×2868 (12/10/2026) | | **G2** Play Console | Tờ khai quyền SMS và nhật ký cuộc gọi | **Mở** (trước 1.0; phương án B: F-Droid/APK) · ảnh Play Console đã chụp lại ở 1080×1920 (12/10/2026) | diff --git a/docs/project-roadmap.md b/docs/project-roadmap.md index 604be26..4bd3862 100644 --- a/docs/project-roadmap.md +++ b/docs/project-roadmap.md @@ -6,11 +6,11 @@ English | [Tiếng Việt](project-roadmap.vi.md) HandLive brings ecosystem-native features, such as Apple Handoff, to Android. The roadmap below is built **in order**. Each phase is a usable piece. Each later phase stands on the infrastructure of the phase before it. The WebSocket, pairing and encryption from Phase 1 are reused by every later phase. One exception to the order (project owner's decision, 2026-09-28): Phases 5 and 6 are done before Phase 4. Phase 4 itself is stopped (project owner's decision, 2026-10-12): only macOS 27 is available, so calls on the computer stay notification + answer/decline/end only, no audio; gate G4 is closed, reopened only on a later owner decision. Phase 7 started on 2026-10-28 (project owner's decision) without waiting for Phases 4–6 or their gates. -## Progress snapshot (2026-10-12) +## Progress snapshot (2026-10-30) -**Latest release:** [`v0.1.1`](https://github.com/HandLive/handlive/releases/tag/v0.1.1) (2026-10-22): the first tag without a pre-release suffix; Google Play readiness on Android (application id `com.handlive.android`, target API 36, R8 on release, signed Play bundle on CI) and the paid-team identifiers on iPhone and iPad (App Group `group.app.hxd.handlive`), the clipboard URI policy (android #13 fixed), the Mac welcome window returns to the menu bar on close; same three files on one page: Mac DMG signed ad hoc (Open Anyway once), iOS IPA (unsigned, for sideloading), Android APK (`foss`, signed). A beta install on Android must be removed first (new application id) and an iPhone beta install must pair again. Every change since beta.3 passed a security scan ([deployment guide](deployment-guide.md)). Live table: hub [`README.md`](../README.md#status). +**Latest release:** [`v0.2.0`](https://github.com/HandLive/handlive/releases/tag/v0.2.0) (2026-10-30): Phase 7 offline connectivity — when both devices are near each other without a network, they connect over Bluetooth L2CAP (control channel only: clipboard text, SMS and call notifications); first pairing over Bluetooth by QR (flow B) or a PIN through CPace-X25519-SHA512 (flow C); Bluetooth admission policy and pre-auth bans; "Connected via Bluetooth" label on Android and Mac. No relay deployment in this release; Wi-Fi Direct and relay bulk are specced but not implemented (tracked for a later release). Every change since v0.1.1 passed a security scan: 0 critical, 0 high, 0 medium, 0 low ([deployment guide](deployment-guide.md)). Live table: hub [`README.md`](../README.md#status). -**Previous release:** [`v0.1.0-beta.3`](https://github.com/HandLive/handlive/releases/tag/v0.1.0-beta.3) (2026-10-07): calls from other apps keep the app's name and survive a swiped in-call notification, text clips keep their HTML, the Liquid Glass app icon on iPhone, iPad and Mac, the iPhone keeps its session 25 s in the background; same files on that one page as beta.2: Mac DMG signed ad hoc (Open Anyway once), iOS IPA (unsigned, for sideloading), Android APK (`foss`, signed); every change since beta.2 passed a security scan ([deployment guide](deployment-guide.md)). Live table: hub [`README.md`](../README.md#status). +**Previous release:** [`v0.1.1`](https://github.com/HandLive/handlive/releases/tag/v0.1.1) (2026-10-22): the first tag without a pre-release suffix; Google Play readiness on Android (application id `com.handlive.android`, target API 36, R8 on release, signed Play bundle on CI) and the paid-team identifiers on iPhone and iPad (App Group `group.app.hxd.handlive`), the clipboard URI policy (android #13 fixed), the Mac welcome window returns to the menu bar on close; same three files on one page: Mac DMG signed ad hoc (Open Anyway once), iOS IPA (unsigned, for sideloading), Android APK (`foss`, signed). A beta install on Android must be removed first (new application id) and an iPhone beta install must pair again. Every change since beta.3 passed a security scan ([deployment guide](deployment-guide.md)). Live table: hub [`README.md`](../README.md#status). | Phase | Code on `main` | Gate / blocker | |-------|----------------|----------------| @@ -21,7 +21,7 @@ HandLive brings ecosystem-native features, such as Apple Handoff, to Android. Th | 4 | Stopped (owner decision, 2026-10-12) | G4 closed: only macOS 27 available, calls stay notification + answer/decline/end, no audio | | 5 | Spike on `main` (`CameraSpike`, merged 2026-09-30) | G5: need paid Apple Developer team | | 6 | Spike probes on `main` (Apple+Android merged 2026-09-30); product cards not started | G6: owner browser decisions; Firefox/Edge/Brave rows | -| 7 | Started (owner decision, 2026-10-28) | Spike G7 first, no wait on G4/G5/G6; both-offline case, no relay, no HFP bonding | +| 7 | Released in `v0.2.0` (2026-10-30) | Spike G7 done; Bluetooth offline pairing (QR flow B, PIN flow C via CPace-X25519-SHA512) shipped; Wi-Fi Direct and relay bulk specced, not implemented | | G0 | Done | Test vectors | | G1 | Open (before 1.0) | Real-device Phase 1 matrix | | G2 | Open (before 1.0) | Play Console SMS / call-log (Plan B: F-Droid/APK) | diff --git a/docs/project-roadmap.vi.md b/docs/project-roadmap.vi.md index 275a2a0..23627d6 100644 --- a/docs/project-roadmap.vi.md +++ b/docs/project-roadmap.vi.md @@ -6,11 +6,11 @@ HandLive đưa các tính năng native riêng trong từng hệ sinh thái, như Handoff trên Apple, lên Android. Lộ trình dưới đây xây **theo thứ tự**. Mỗi phase là một phần dùng được. Phase sau đứng trên hạ tầng phase trước. WebSocket, ghép cặp và mã hóa từ Phase 1 dùng lại cho mọi phase sau. Một ngoại lệ về thứ tự (quyết định của chủ dự án, 28/09/2026): Phase 5 và 6 làm trước Phase 4. Phase 4 đã dừng (quyết định của chủ dự án, 12/10/2026): chỉ có Mac macOS 27, nên cuộc gọi trên máy tính chỉ còn thông báo và nút nghe/từ chối/kết thúc, không có âm thanh; cổng G4 đóng, chỉ mở lại khi chủ dự án quyết định sau. Phase 7 bắt đầu ngày 28/10/2026 (quyết định của chủ dự án) mà không chờ Phase 4–6 hay các cổng của chúng. -## Tiến độ tóm tắt (12/10/2026) +## Tiến độ tóm tắt (30/10/2026) -**Bản phát hành mới nhất:** [`v0.1.1`](https://github.com/HandLive/handlive/releases/tag/v0.1.1) (22/10/2026): thẻ đầu tiên không có hậu tố tiền phát hành; Android sẵn sàng cho Google Play (mã ứng dụng `com.handlive.android`, target API 36, R8 cho bản phát hành, gói Play ký trên CI) và định danh của nhóm trả phí trên iPhone, iPad (App Group `group.app.hxd.handlive`), chính sách URI bảng nhớ tạm (đã sửa android #13), cửa sổ chào trên Mac trở về thanh menu khi đóng; vẫn 3 tệp trên một trang: DMG Mac ký ad hoc (Open Anyway một lần), IPA iOS (chưa ký, để sideload), APK Android (`foss`, đã ký). Bản beta trên Android phải gỡ trước (mã ứng dụng mới) và bản beta trên iPhone phải ghép đôi lại. Mọi thay đổi từ beta.3 đã qua rà soát bảo mật ([hướng dẫn phân phối](deployment-guide.vi.md)). Bảng trực tiếp: hub [`README.vi.md`](../README.vi.md#trạng-thái). +**Bản phát hành mới nhất:** [`v0.2.0`](https://github.com/HandLive/handlive/releases/tag/v0.2.0) (30/10/2026): Phase 7 kết nối offline — khi cả hai thiết bị ở gần nhau và không có mạng, chúng tự kết nối qua Bluetooth L2CAP (chỉ kênh điều khiển: chữ trong bảng nhớ tạm, SMS và thông báo cuộc gọi); ghép đôi đầu qua Bluetooth bằng QR (luồng B) hoặc PIN qua CPace-X25519-SHA512 (luồng C); có chính sách admission và chặn brute-force trên Bluetooth; nhãn "Đã kết nối qua Bluetooth" trên Android và Mac. Không triển khai relay trong bản này; Wi-Fi Direct và bulk qua relay đã có đặc tả nhưng chưa code (để lại cho bản sau). Mọi thay đổi từ v0.1.1 đã qua quét bảo mật: 0 critical, 0 high, 0 medium, 0 low ([hướng dẫn phân phối](deployment-guide.vi.md)). Bảng trực tiếp: hub [`README.vi.md`](../README.vi.md#trạng-thái). -**Bản phát hành trước:** [`v0.1.0-beta.3`](https://github.com/HandLive/handlive/releases/tag/v0.1.0-beta.3) (07/10/2026): cuộc gọi từ ứng dụng khác giữ tên ứng dụng và không kết thúc khi thông báo đang gọi bị vuốt bỏ, clip chữ giữ HTML, biểu tượng ứng dụng Liquid Glass trên iPhone, iPad và Mac, iPhone giữ phiên 25 giây khi chạy nền; vẫn các tệp như beta.2 ở cùng trang đó: DMG Mac ký ad-hoc (bấm Open Anyway một lần), IPA iOS (chưa ký, để sideload), APK Android (`foss`, đã ký); mọi thay đổi từ beta.2 đã qua quét bảo mật ([hướng dẫn phân phối](deployment-guide.vi.md)). Bảng sống: [`README.vi.md`](../README.vi.md#trạng-thái). +**Bản phát hành trước:** [`v0.1.1`](https://github.com/HandLive/handlive/releases/tag/v0.1.1) (22/10/2026): thẻ đầu tiên không có hậu tố tiền phát hành; Android sẵn sàng cho Google Play (mã ứng dụng `com.handlive.android`, target API 36, R8 cho bản phát hành, gói Play ký trên CI) và định danh của nhóm trả phí trên iPhone, iPad (App Group `group.app.hxd.handlive`), chính sách URI bảng nhớ tạm (đã sửa android #13), cửa sổ chào trên Mac trở về thanh menu khi đóng; vẫn 3 tệp trên một trang: DMG Mac ký ad hoc (Open Anyway một lần), IPA iOS (chưa ký, để sideload), APK Android (`foss`, đã ký). Bản beta trên Android phải gỡ trước (mã ứng dụng mới) và bản beta trên iPhone phải ghép đôi lại. Mọi thay đổi từ beta.3 đã qua rà soát bảo mật ([hướng dẫn phân phối](deployment-guide.vi.md)). Bảng sống: [`README.vi.md`](../README.vi.md#trạng-thái). | Phase | Mã trên `main` | Cổng / điểm nghẽn | |-------|----------------|-------------------| @@ -21,7 +21,7 @@ HandLive đưa các tính năng native riêng trong từng hệ sinh thái, như | 4 | Đã dừng (quyết định của chủ dự án, 12/10/2026) | G4 đóng: chỉ có macOS 27, cuộc gọi chỉ còn thông báo và nút nghe/từ chối/kết thúc, không âm thanh | | 5 | Spike trên `main` (`CameraSpike`, merge 30/09/2026) | G5: cần Apple Developer trả phí | | 6 | Probe spike trên `main` (Apple+Android merge 30/09/2026); chưa mở thẻ sản phẩm | G6: quyết định trình duyệt; hàng Firefox/Edge/Brave | -| 7 | Đã bắt đầu (quyết định của chủ dự án, 28/10/2026) | Spike G7 trước, không chờ G4/G5/G6; trường hợp cả hai offline, không relay, không tự ghép đôi HFP | +| 7 | Đã phát hành ở `v0.2.0` (30/10/2026) | Spike G7 xong; ghép đôi Bluetooth offline (luồng B QR, luồng C PIN qua CPace-X25519-SHA512) đã phát hành; Wi-Fi Direct và bulk qua relay đã có đặc tả, chưa code | | G0 | Xong | Vector kiểm thử | | G1 | Mở (trước 1.0) | Ma trận Phase 1 máy thật | | G2 | Mở (trước 1.0) | Tờ khai SMS / nhật ký Play Console (phương án B: F-Droid/APK) | diff --git a/tools/measure-sp1-auto.sh b/tools/measure-sp1-auto.sh index a279342..0268e09 100755 --- a/tools/measure-sp1-auto.sh +++ b/tools/measure-sp1-auto.sh @@ -1,18 +1,18 @@ #!/bin/zsh -# Chay NGOAI sandbox qua nut run cua sep (S25 cam qua USB, Mac nay la may do), khong co nguoi ngoi theo doi: +# Chạy NGOÀI sandbox qua nút run của sếp (S25 cắm qua USB, Mac này là máy đo), không có người ngồi theo dõi: # zsh handlive/tools/measure-sp1-auto.sh --session 1|2|3 [adb_serial] # -# Lam tron mot phien cua runbook handlive/plans/20260925-implementation/reports/phase-07-sp1-measure.md ma -# khong can thao tac tay: goi measure-sp1.sh nen de build/mo app + ghi log HLBENCH, cho link len Bluetooth, -# tu lam cac buoc copy/toggle/doze cua phien, roi dung ghi log va chay clip_latency.py/reconnect_time.py. -# Sandbox cua agent chan moi ket noi adb (xem #3717-#3720), nen toan bo khoi nay phai chay qua nut run. +# Làm trọn một phiên của runbook handlive/plans/20260925-implementation/reports/phase-07-sp1-measure.md mà +# không cần thao tác tay: gọi measure-sp1.sh nền để build/mở app + ghi log HLBENCH, chờ link lên Bluetooth, +# tự làm các bước copy/toggle/doze của phiên, rồi dừng ghi log và chạy clip_latency.py/reconnect_time.py. +# Sandbox của agent chặn mọi kết nối adb (xem #3717-#3720), nên toàn bộ khối này phải chạy qua nút run. # -# --session 1: phien day du (khong --log-only) — build + cai lai APK/app macOS tu dau. -# --session 2|3: dung lai APK/app macOS cua lan --session 1 gan nhat (them --log-only vao measure-sp1.sh). +# --session 1: phiên đầy đủ (không --log-only) — build + cài lại APK/app macOS từ đầu. +# --session 2|3: dùng lại APK/app macOS của lần --session 1 gần nhất (thêm --log-only vào measure-sp1.sh). # -# Chieu S25->Mac tu dong hoa bang broadcast cua app "Clipper" (ca.zgrs.clipper, trick quen thuoc de set -# clipboard Android qua adb ma khong can UI — Android chan ClipboardManager tu background tu Android 10). -# Khong co app do tren S25 thi buoc nay tu bo qua va in ro, chi con chieu Mac->S25 (pbcopy) nhu runbook cho phep. +# Chiều S25->Mac tự động hóa bằng broadcast của app "Clipper" (ca.zgrs.clipper, trick quen thuộc để set +# clipboard Android qua adb mà không cần UI — Android chặn ClipboardManager từ background từ Android 10). +# Không có app đó trên S25 thì bước này tự bỏ qua và in rõ, chỉ còn chiều Mac->S25 (pbcopy) như runbook cho phép. set -uo pipefail zmodload zsh/datetime @@ -30,7 +30,7 @@ while [ $# -gt 0 ]; do shift ;; -*) - echo "LOI: khong biet tuy chon: $1" >&2 + echo "LỖI: không biết tùy chọn: $1" >&2 exit 2 ;; *) @@ -43,7 +43,7 @@ done case "$SESSION" in 1|2|3) ;; *) - echo "Dung: zsh $0 --session 1|2|3 [adb_serial]" >&2 + echo "Dùng: zsh $0 --session 1|2|3 [adb_serial]" >&2 exit 2 ;; esac @@ -58,10 +58,10 @@ mkdir -p "$LOG_DIR" TS="$(date +%Y%m%d-%H%M%S)" : "${TMPDIR:=/tmp}" -[ -f "$MEASURE_SH" ] || { echo "LOI: khong thay $MEASURE_SH" >&2; exit 1; } -command -v adb >/dev/null || { echo "LOI: khong thay adb trong PATH" >&2; exit 1; } -command -v osascript >/dev/null || { echo "LOI: khong thay osascript trong PATH" >&2; exit 1; } -command -v pbcopy >/dev/null || { echo "LOI: khong thay pbcopy trong PATH" >&2; exit 1; } +[ -f "$MEASURE_SH" ] || { echo "LỖI: không thấy $MEASURE_SH" >&2; exit 1; } +command -v adb >/dev/null || { echo "LỖI: không thấy adb trong PATH" >&2; exit 1; } +command -v osascript >/dev/null || { echo "LỖI: không thấy osascript trong PATH" >&2; exit 1; } +command -v pbcopy >/dev/null || { echo "LỖI: không thấy pbcopy trong PATH" >&2; exit 1; } typeset -a ADB_ARGS if [ -n "$SERIAL" ]; then @@ -75,13 +75,13 @@ MEASURE_PID="" LOGGING_STOPPED=0 fail() { - echo "LOI: $1" >&2 + echo "LỖI: $1" >&2 if [ -n "$ANDROID_LOG" ] && [ -f "$ANDROID_LOG" ]; then - echo "--- 20 dong cuoi $ANDROID_LOG ---" >&2 + echo "--- 20 dòng cuối $ANDROID_LOG ---" >&2 tail -n 20 "$ANDROID_LOG" >&2 fi if [ -n "$MAC_LOG" ] && [ -f "$MAC_LOG" ]; then - echo "--- 20 dong cuoi $MAC_LOG ---" >&2 + echo "--- 20 dòng cuối $MAC_LOG ---" >&2 tail -n 20 "$MAC_LOG" >&2 fi exit 1 @@ -89,7 +89,7 @@ fail() { cleanup() { echo - echo "== Don dep: dung ghi log (neu con) va khoi phuc S25 ==" + echo "== Dọn dẹp: dừng ghi log (nếu còn) và khôi phục S25 ==" if [ -n "$MEASURE_PID" ] && [ "$LOGGING_STOPPED" -eq 0 ]; then kill -INT "$MEASURE_PID" 2>/dev/null || true wait "$MEASURE_PID" 2>/dev/null || true @@ -115,11 +115,11 @@ wait_for_connected() { return 1 } -# Trick "Clipper" (ca.zgrs.clipper): `am broadcast -a clipper.set -e text VALUE` set duoc clipboard Android tu -# adb ma khong can UI. `am broadcast` luon tra "result=0" du co receiver nhan hay khong (khong co cach phan -# biet qua ma ket qua), nen kiem truoc bang pm list packages (goi 1 lan trong session 1, bien CLIPPER_INSTALLED -# duoi day): khong cai app nay thi bo qua ngay, co cai thi gui broadcast va coi la thanh cong (khong co cach -# nao xac nhan sau hon tu phia adb). +# Trick "Clipper" (ca.zgrs.clipper): `am broadcast -a clipper.set -e text VALUE` set được clipboard Android từ +# adb mà không cần UI. `am broadcast` luôn trả "result=0" dù có receiver nhận hay không (không có cách phân +# biệt qua mã kết quả), nên kiểm trước bằng pm list packages (gọi 1 lần trong session 1, biến CLIPPER_INSTALLED +# dưới đây): không cài app này thì bỏ qua ngay, có cài thì gửi broadcast và coi là thành công (không có cách +# nào xác nhận sau hơn từ phía adb). CLIPPER_INSTALLED=0 android_copy_text() { @@ -128,8 +128,8 @@ android_copy_text() { adb "${ADB_ARGS[@]}" shell am broadcast -a clipper.set -e text "$text" >/dev/null 2>&1 } -# Tao mot anh ~5 MB (BMP ghi tay bang python3 stdlib roi doi sang PNG bang sips, khong can PIL) de dua len -# pasteboard Mac bang osascript — khong dung noi dung chep tu dau khac vi khong co file anh san co dung kich co. +# Tạo một ảnh ~5 MB (BMP ghi tay bằng python3 stdlib rồi đổi sang PNG bằng sips, không cần PIL) để đưa lên +# pasteboard Mac bằng osascript — không dùng nội dung chép từ đâu khác vì không có file ảnh sẵn có đúng kích cỡ. make_bench_image() { local out_bmp="$1" out_png="$2" python3 - "$out_bmp" <<'PYEOF' @@ -138,7 +138,7 @@ import struct import sys path = sys.argv[1] -width, height = 1700, 1024 # 24-bit BMP, hang khong can padding (1700*3=5100 chia het cho 4) -> khoang 5.2 MB. +width, height = 1700, 1024 # 24-bit BMP, hàng không cần padding (1700*3=5100 chia hết cho 4) -> khoảng 5.2 MB. row_bytes = width * 3 pixel_data_size = row_bytes * height file_size = 54 + pixel_data_size @@ -161,9 +161,9 @@ set_mac_image_clipboard() { echo "== 1. adb devices ==" adb devices -echo "== 2. Tat Wi-Fi + du lieu di dong tren S25 (Mac van bat Wi-Fi) ==" -adb "${ADB_ARGS[@]}" shell svc wifi disable || fail "svc wifi disable that bai" -adb "${ADB_ARGS[@]}" shell svc data disable || fail "svc data disable that bai" +echo "== 2. Tắt Wi-Fi + dữ liệu di động trên S25 (Mac vẫn bật Wi-Fi) ==" +adb "${ADB_ARGS[@]}" shell svc wifi disable || fail "svc wifi disable thất bại" +adb "${ADB_ARGS[@]}" shell svc data disable || fail "svc data disable thất bại" typeset -a MEASURE_ARGS if [ "$SESSION" != "1" ]; then @@ -174,16 +174,16 @@ if [ -n "$SERIAL" ]; then fi MEASURE_OUT="$LOG_DIR/sp1-auto-session${SESSION}-$TS-measure.out" -echo "== 3. Chay measure-sp1.sh nen (session $SESSION, args: ${MEASURE_ARGS[*]:-}) ==" +echo "== 3. Chạy measure-sp1.sh nền (session $SESSION, args: ${MEASURE_ARGS[*]:-}) ==" zsh "$MEASURE_SH" "${MEASURE_ARGS[@]}" > "$MEASURE_OUT" 2>&1 & MEASURE_PID=$! -echo "measure-sp1.sh PID=$MEASURE_PID, output day du tai: $MEASURE_OUT" +echo "measure-sp1.sh PID=$MEASURE_PID, output đầy đủ tại: $MEASURE_OUT" -echo "== 4. Doi measure-sp1.sh in duong dan log (toi da 600s, co build app) ==" +echo "== 4. Đợi measure-sp1.sh in đường dẫn log (tối đa 600s, có build app) ==" waited=0 while [ "$waited" -lt 600 ]; do if ! kill -0 "$MEASURE_PID" 2>/dev/null; then - fail "measure-sp1.sh da thoat truoc khi in duong dan log, xem $MEASURE_OUT" + fail "measure-sp1.sh đã thoát trước khi in đường dẫn log, xem $MEASURE_OUT" fi ANDROID_LOG="$(grep -m1 '^Android: ' "$MEASURE_OUT" 2>/dev/null | sed 's/^Android: //')" MAC_LOG="$(grep -m1 '^Mac:' "$MEASURE_OUT" 2>/dev/null | sed -E 's/^Mac:[[:space:]]*//')" @@ -194,30 +194,30 @@ while [ "$waited" -lt 600 ]; do sleep 2 waited=$((waited + 2)) done -[ -n "$ANDROID_LOG" ] && [ -n "$MAC_LOG" ] || fail "qua 600s khong thay measure-sp1.sh in duong dan log, xem $MEASURE_OUT" +[ -n "$ANDROID_LOG" ] && [ -n "$MAC_LOG" ] || fail "quá 600s không thấy measure-sp1.sh in đường dẫn log, xem $MEASURE_OUT" echo "Android log: $ANDROID_LOG" echo "Mac log: $MAC_LOG" [ -n "$MAC_APP_PATH" ] && echo "App macOS: $MAC_APP_PATH" -echo "== 5. Cho link len Bluetooth (toi da 120s) ==" -wait_for_connected 120 || fail "qua 120s khong thay 'to=Connected channel=bluetooth' trong $MAC_LOG" -echo "Da thay Bluetooth Connected." +echo "== 5. Chờ link lên Bluetooth (tối đa 120s) ==" +wait_for_connected 120 || fail "quá 120s không thấy 'to=Connected channel=bluetooth' trong $MAC_LOG" +echo "Đã thấy Bluetooth Connected." case "$SESSION" in 1) - echo "== 6. AC1 idle: 30 lan copy text, doi chieu, cach 3-4s ==" + echo "== 6. AC1 idle: 30 lần copy text, đối chiều, cách 3-4s ==" ANDROID_COPY_OK=0 if adb "${ADB_ARGS[@]}" shell pm list packages ca.zgrs.clipper 2>/dev/null | grep -q 'package:ca\.zgrs\.clipper'; then CLIPPER_INSTALLED=1 ANDROID_COPY_OK=1 else - echo "CANH BAO: khong thay app Clipper (ca.zgrs.clipper) tren S25 — khong co cach set clipboard Android" \ - "tu adb ma khong co UI, bo qua chieu S25->Mac, chi copy chieu Mac->S25 ca 30 lan." >&2 + echo "CẢNH BÁO: không thấy app Clipper (ca.zgrs.clipper) trên S25 — không có cách set clipboard Android" \ + "từ adb mà không có UI, bỏ qua chiều S25->Mac, chỉ copy chiều Mac->S25 cả 30 lần." >&2 fi for (( i = 1; i <= 30; i++ )); do if [ "$ANDROID_COPY_OK" -eq 1 ] && (( i % 2 == 0 )); then if ! android_copy_text "sp1 auto s25->mac $i $(epoch_ms)"; then - echo "CANH BAO: lan $i copy tu S25 that bai, bo qua lan nay" >&2 + echo "CẢNH BÁO: lần $i copy từ S25 thất bại, bỏ qua lần này" >&2 fi else print -n -- "sp1 auto mac->s25 $i $(epoch_ms)" | pbcopy @@ -225,49 +225,49 @@ case "$SESSION" in sleep $(( 3 + (RANDOM % 2) )) done - echo "== 7. 5 vong tat/bat Bluetooth tren S25, moi vong cho Connected lai (toi da 30s) ==" + echo "== 7. 5 vòng tắt/bật Bluetooth trên S25, mỗi vòng chờ Connected lại (tối đa 30s) ==" for (( round = 1; round <= 5; round++ )); do - echo "-- vong $round/5: tat bluetooth_manager --" + echo "-- vòng $round/5: tắt bluetooth_manager --" adb "${ADB_ARGS[@]}" shell cmd bluetooth_manager disable || true sleep 2 - echo "-- vong $round/5: bat lai bluetooth_manager --" + echo "-- vòng $round/5: bật lại bluetooth_manager --" adb "${ADB_ARGS[@]}" shell cmd bluetooth_manager enable || true if ! wait_for_connected 30; then - echo "CANH BAO: vong $round khong thay Connected lai trong 30s" >&2 + echo "CẢNH BÁO: vòng $round không thấy Connected lại trong 30s" >&2 fi done - echo "== 8. Scan response: khoa man hinh S25, cho 60s, Cmd-Q + mo lai app Mac ==" + echo "== 8. Scan response: khóa màn hình S25, chờ 60s, Cmd-Q + mở lại app Mac ==" BT_ACCEPT_BEFORE="$(grep -c 'ev=bt_accept' "$ANDROID_LOG" 2>/dev/null || true)" : "${BT_ACCEPT_BEFORE:=0}" adb "${ADB_ARGS[@]}" shell input keyevent KEYCODE_SLEEP || true sleep 60 - osascript -e 'quit app "HandLive"' || echo "CANH BAO: osascript quit app HandLive that bai" >&2 + osascript -e 'quit app "HandLive"' || echo "CẢNH BÁO: osascript quit app HandLive thất bại" >&2 if [ -n "$MAC_APP_PATH" ]; then - open "$MAC_APP_PATH" || echo "CANH BAO: mo lai app Mac that bai" >&2 + open "$MAC_APP_PATH" || echo "CẢNH BÁO: mở lại app Mac thất bại" >&2 else - echo "CANH BAO: khong doc duoc duong dan app macOS tu $MEASURE_OUT, bo qua mo lai app" >&2 + echo "CẢNH BÁO: không đọc được đường dẫn app macOS từ $MEASURE_OUT, bỏ qua mở lại app" >&2 fi sleep 5 adb "${ADB_ARGS[@]}" shell input keyevent KEYCODE_WAKEUP || true - wait_for_connected 30 || echo "CANH BAO: sau buoc scan-response khong thay Connected lai trong 30s" >&2 + wait_for_connected 30 || echo "CẢNH BÁO: sau bước scan-response không thấy Connected lại trong 30s" >&2 BT_ACCEPT_AFTER="$(grep -c 'ev=bt_accept' "$ANDROID_LOG" 2>/dev/null || true)" : "${BT_ACCEPT_AFTER:=0}" - echo "bt_accept truoc=$BT_ACCEPT_BEFORE sau=$BT_ACCEPT_AFTER" + echo "bt_accept trước=$BT_ACCEPT_BEFORE sau=$BT_ACCEPT_AFTER" if [ "$BT_ACCEPT_AFTER" -le "$BT_ACCEPT_BEFORE" ]; then - echo "CANH BAO: bt_accept khong tang sau buoc scan-response (xem 00-common-specs.md 0.4.5)" >&2 + echo "CẢNH BÁO: bt_accept không tăng sau bước scan-response (xem 00-common-specs.md 0.4.5)" >&2 fi ;; 2) - echo "== 6. AC1 duoi tai: anh ~5MB len pasteboard Mac, xen 10 lan copy text ==" + echo "== 6. AC1 dưới tải: ảnh ~5MB lên pasteboard Mac, xen 10 lần copy text ==" IMG_BMP="$TMPDIR/handlive-sp1-bench-$TS.bmp" IMG_PNG="$TMPDIR/handlive-sp1-bench-$TS.png" - make_bench_image "$IMG_BMP" "$IMG_PNG" || fail "tao anh bench that bai" + make_bench_image "$IMG_BMP" "$IMG_PNG" || fail "tạo ảnh bench thất bại" rm -f "$IMG_BMP" for (( i = 1; i <= 10; i++ )); do if (( i == 1 || i % 3 == 0 )); then - set_mac_image_clipboard "$IMG_PNG" || echo "CANH BAO: set anh len pasteboard Mac that bai (lan $i)" >&2 + set_mac_image_clipboard "$IMG_PNG" || echo "CẢNH BÁO: set ảnh lên pasteboard Mac thất bại (lần $i)" >&2 sleep 1 fi print -n -- "sp1 auto load mac->s25 $i $(epoch_ms)" | pbcopy @@ -277,25 +277,25 @@ case "$SESSION" in ;; 3) - echo "== 6. Doze: ep S25 vao Doze ==" + echo "== 6. Doze: ép S25 vào Doze ==" adb "${ADB_ARGS[@]}" shell dumpsys battery unplug || true adb "${ADB_ARGS[@]}" shell input keyevent KEYCODE_SLEEP || true adb "${ADB_ARGS[@]}" shell dumpsys deviceidle force-idle || true - echo "== 7. 30 lan copy text Mac->S25, cach 30s, man hinh S25 khong dong vao ==" + echo "== 7. 30 lần copy text Mac->S25, cách 30s, màn hình S25 không đụng vào ==" for (( i = 1; i <= 30; i++ )); do print -n -- "sp1 auto doze mac->s25 $i $(epoch_ms)" | pbcopy sleep 30 done - echo "== 8. Khoi phuc S25 sau Doze ==" + echo "== 8. Khôi phục S25 sau Doze ==" adb "${ADB_ARGS[@]}" shell dumpsys deviceidle unforce || true adb "${ADB_ARGS[@]}" shell dumpsys battery reset || true adb "${ADB_ARGS[@]}" shell input keyevent KEYCODE_WAKEUP || true ;; esac -echo "== 9. Dung ghi log ==" +echo "== 9. Dừng ghi log ==" kill -INT "$MEASURE_PID" 2>/dev/null || true wait "$MEASURE_PID" 2>/dev/null || true LOGGING_STOPPED=1 @@ -303,14 +303,14 @@ LOGGING_STOPPED=1 echo "Android log: $ANDROID_LOG" echo "Mac log: $MAC_LOG" -command -v python3 >/dev/null || fail "khong thay python3 trong PATH" +command -v python3 >/dev/null || fail "không thấy python3 trong PATH" echo "== 10. clip_latency.py (AC1) ==" python3 "$SHARED_DIR/tools/bench/clip_latency.py" "$ANDROID_LOG" "$MAC_LOG" --json \ - || echo "CANH BAO: clip_latency.py tra ma loi khac 0" >&2 + || echo "CẢNH BÁO: clip_latency.py trả mã lỗi khác 0" >&2 echo "== 11. reconnect_time.py (AC2) ==" python3 "$SHARED_DIR/tools/bench/reconnect_time.py" "$MAC_LOG" "$ANDROID_LOG" --json \ - || echo "CANH BAO: reconnect_time.py tra ma loi khac 0" >&2 + || echo "CẢNH BÁO: reconnect_time.py trả mã lỗi khác 0" >&2 echo "Xong session $SESSION." diff --git a/tools/measure-sp1-prep.sh b/tools/measure-sp1-prep.sh index 952d25d..589649b 100755 --- a/tools/measure-sp1-prep.sh +++ b/tools/measure-sp1-prep.sh @@ -1,14 +1,14 @@ #!/bin/zsh -# Chuan bi ghep doi SP1: go sach ban cu, build va cai ban debug len S25 va Mac, -# tat Wi-Fi, ghep qua Bluetooth. -# Chay NGOAI sandbox qua nut run cua sep (S25 cam qua USB): +# Chuẩn bị ghép đôi SP1: gỡ sạch bản cũ, build và cài bản debug lên S25 và Mac, +# tắt Wi-Fi, ghép qua Bluetooth. +# Chạy NGOÀI sandbox qua nút run của sếp (S25 cắm qua USB): # zsh handlive/tools/measure-sp1-prep.sh # -# Sau script thoat thanh cong, sxep tat Wi-Fi ca hai may, bat Bluetooth, roi ghep doi S25 voi Mac 1 lan (quét QR hoac nhap PIN, ~1 phut), -# roi team tu chay phien 1 do qua measure-sp1-auto.sh. +# Sau script thoát thành công, sếp tắt Wi-Fi cả hai máy, bật Bluetooth, rồi ghép đôi S25 với Mac 1 lần (quét QR hoặc nhập PIN, ~1 phút), +# rồi team tự chạy phiên 1 đo qua measure-sp1-auto.sh. # -# Go sach app + du lieu Android cu va ban HandLive.app cu tren Mac truoc khi cai, -# de dam bao cai sach. Khong tac dong Wi-Fi, khong ghi log. +# Gỡ sạch app + dữ liệu Android cũ và bản HandLive.app cũ trên Mac trước khi cài, +# để đảm bảo cài sạch. Không tác động Wi-Fi, không ghi log. set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -21,20 +21,20 @@ APP_ID="com.handlive.android" MAIN_ACTIVITY="$APP_ID/app.handlive.android.MainActivity" fail() { - echo "LOI: $1" >&2 + echo "LỖI: $1" >&2 exit 1 } -command -v adb >/dev/null || fail "khong thay adb trong PATH" -command -v xcodebuild >/dev/null || fail "khong thay xcodebuild trong PATH" -adb get-state >/dev/null 2>&1 || fail "khong thay thiet bi Android qua adb (cam S25, bat USB debugging)" +command -v adb >/dev/null || fail "không thấy adb trong PATH" +command -v xcodebuild >/dev/null || fail "không thấy xcodebuild trong PATH" +adb get-state >/dev/null 2>&1 || fail "không thấy thiết bị Android qua adb (cắm S25, bật USB debugging)" -echo "== 1. Go sach ban Android cu (app + du lieu) ==" +echo "== 1. Gỡ sạch bản Android cũ (app + dữ liệu) ==" if adb shell pm list packages "$APP_ID" 2>/dev/null | grep -q "^package:$APP_ID$"; then - adb uninstall "$APP_ID" || fail "adb uninstall $APP_ID that bai" - echo "Da go $APP_ID (ca du lieu) tren S25" + adb uninstall "$APP_ID" || fail "adb uninstall $APP_ID thất bại" + echo "Đã gỡ $APP_ID (cả dữ liệu) trên S25" else - echo "$APP_ID chua cai tren S25, bo qua go" + echo "$APP_ID chưa cài trên S25, bỏ qua gỡ" fi echo "== 2. Build APK debug (flavor foss) ==" @@ -43,51 +43,51 @@ echo "== 2. Build APK debug (flavor foss) ==" export JAVA_HOME ANDROID_HOME GRADLEW="$ANDROID_DIR/gradlew" -[ -f "$GRADLEW" ] || fail "khong thay $GRADLEW" +[ -f "$GRADLEW" ] || fail "không thấy $GRADLEW" env JAVA_HOME="$JAVA_HOME" ANDROID_HOME="$ANDROID_HOME" "$GRADLEW" -p "$ANDROID_DIR" ":app:assembleFossDebug" \ - || fail "gradlew :app:assembleFossDebug that bai" + || fail "gradlew :app:assembleFossDebug thất bại" APK_PATH="$(find "$ANDROID_DIR/app/build/outputs/apk/foss/debug" -name '*.apk' -print -quit 2>/dev/null)" -[ -n "$APK_PATH" ] || fail "khong tim thay APK debug duoi $ANDROID_DIR/app/build/outputs/apk/foss/debug" +[ -n "$APK_PATH" ] || fail "không tìm thấy APK debug dưới $ANDROID_DIR/app/build/outputs/apk/foss/debug" echo "APK: $APK_PATH" -echo "== 3. Cai APK len S25 ==" -adb install "$APK_PATH" || fail "adb install that bai" +echo "== 3. Cài APK lên S25 ==" +adb install "$APK_PATH" || fail "adb install thất bại" -echo "== 4. Cap quyen Bluetooth ==" +echo "== 4. Cấp quyền Bluetooth ==" adb shell pm grant "$APP_ID" android.permission.BLUETOOTH_ADVERTISE >/dev/null 2>&1 || true adb shell pm grant "$APP_ID" android.permission.BLUETOOTH_CONNECT >/dev/null 2>&1 || true -echo "== 5. Mo app tren S25 ==" -adb shell am start -n "$MAIN_ACTIVITY" || fail "adb shell am start that bai" +echo "== 5. Mở app trên S25 ==" +adb shell am start -n "$MAIN_ACTIVITY" || fail "adb shell am start thất bại" echo -echo "== 6. Go sach ban Mac cu ==" +echo "== 6. Gỡ sạch bản Mac cũ ==" pkill -x HandLive >/dev/null 2>&1 || true -echo "Da dung HandLive dang chay (neu co)" +echo "Đã dừng HandLive đang chạy (nếu có)" MAC_APP_SYSTEM="/Applications/HandLive.app" MAC_APP_USER="$HOME/Applications/HandLive.app" if [ -d "$MAC_APP_SYSTEM" ]; then - rm -rf "$MAC_APP_SYSTEM" || fail "khong xoa duoc $MAC_APP_SYSTEM" - echo "Da xoa $MAC_APP_SYSTEM" + rm -rf "$MAC_APP_SYSTEM" || fail "không xóa được $MAC_APP_SYSTEM" + echo "Đã xóa $MAC_APP_SYSTEM" fi if [ -d "$MAC_APP_USER" ]; then - rm -rf "$MAC_APP_USER" || fail "khong xoa duoc $MAC_APP_USER" - echo "Da xoa $MAC_APP_USER" + rm -rf "$MAC_APP_USER" || fail "không xóa được $MAC_APP_USER" + echo "Đã xóa $MAC_APP_USER" fi DERIVED_DATA="$APPLE_DIR/.build-logs/sp1-derived-data" if [ -d "$DERIVED_DATA" ]; then - rm -rf "$DERIVED_DATA" || fail "khong xoa duoc $DERIVED_DATA" - echo "Da xoa DerivedData cu $DERIVED_DATA" + rm -rf "$DERIVED_DATA" || fail "không xóa được $DERIVED_DATA" + echo "Đã xóa DerivedData cũ $DERIVED_DATA" fi echo echo "== 7. Build app macOS (debug) ==" -command -v xcodegen >/dev/null || fail "khong thay xcodegen trong PATH (brew install xcodegen)" -(cd "$APPLE_DIR" && xcodegen generate) || fail "xcodegen generate that bai" +command -v xcodegen >/dev/null || fail "không thấy xcodegen trong PATH (brew install xcodegen)" +(cd "$APPLE_DIR" && xcodegen generate) || fail "xcodegen generate thất bại" mkdir -p "$APPLE_DIR/.build-logs" xcodebuild build \ @@ -97,17 +97,17 @@ xcodebuild build \ -destination "platform=macOS" \ -derivedDataPath "$DERIVED_DATA" \ CODE_SIGNING_ALLOWED=NO \ - || fail "xcodebuild build HandLive (macOS) that bai" + || fail "xcodebuild build HandLive (macOS) thất bại" MAC_APP_PATH="$(find "$DERIVED_DATA/Build/Products/Debug" -maxdepth 1 -name 'HandLive.app' -print -quit 2>/dev/null)" -[ -n "$MAC_APP_PATH" ] || fail "khong tim thay HandLive.app duoi $DERIVED_DATA/Build/Products/Debug" +[ -n "$MAC_APP_PATH" ] || fail "không tìm thấy HandLive.app dưới $DERIVED_DATA/Build/Products/Debug" echo "App macOS: $MAC_APP_PATH" -echo "== 8. Ky ad-hoc de khong can provisioning profile ==" -codesign --force --deep --sign - "$MAC_APP_PATH" || fail "codesign ad-hoc that bai" +echo "== 8. Ký ad-hoc để không cần provisioning profile ==" +codesign --force --deep --sign - "$MAC_APP_PATH" || fail "codesign ad-hoc thất bại" -echo "== 9. Mo app tren Mac ==" -open "$MAC_APP_PATH" || fail "mo HandLive.app that bai (sandbox chan 'open'; chay script nay ngoai sandbox)" +echo "== 9. Mở app trên Mac ==" +open "$MAC_APP_PATH" || fail "mở HandLive.app thất bại (sandbox chặn 'open'; chạy script này ngoài sandbox)" echo -echo "SAN SANG: tat Wi-Fi ca hai may, bat Bluetooth, mo man ghep doi tren Mac roi quet QR (hoac nhap PIN) tren S25 — ghep qua Bluetooth" +echo "SẴN SÀNG: tắt Wi-Fi cả hai máy, bật Bluetooth, mở màn ghép đôi trên Mac rồi quét QR (hoặc nhập PIN) trên S25 — ghép qua Bluetooth" diff --git a/tools/measure-sp1.sh b/tools/measure-sp1.sh index 1403d18..f21d28a 100755 --- a/tools/measure-sp1.sh +++ b/tools/measure-sp1.sh @@ -1,22 +1,22 @@ #!/bin/zsh -# Chay NGOAI sandbox, voi S25 cam qua USB (adb) va may Mac nay la may do: +# Chạy NGOÀI sandbox, với S25 cắm qua USB (adb) và máy Mac này là máy đo: # zsh handlive/tools/measure-sp1.sh [--log-only] [adb_serial] # -# Chuan bi SP1 (kenh dieu khien Bluetooth) de do AC1/AC2 tren may that, theo +# Chuẩn bị SP1 (kênh điều khiển Bluetooth) để đo AC1/AC2 trên máy thật, theo # handlive/plans/20260925-implementation/reports/phase-07-sp1-measure.md: -# 1. Build APK debug (:app:assembleDebug), cai len S25 qua adb, cap quyen -# BLUETOOTH_ADVERTISE/BLUETOOTH_CONNECT (chua co man xin quyen), mo app. -# 2. Build app macOS (xcodegen generate + xcodebuild, scheme HandLive, cau hinh Debug) va mo app. -# 3. Goi logcat (Android) va `log stream` (Mac) loc dong HLBENCH vao -# handlive/.build-logs/sp1--android.log va sp1--mac.log. +# 1. Build APK debug (:app:assembleDebug), cài lên S25 qua adb, cấp quyền +# BLUETOOTH_ADVERTISE/BLUETOOTH_CONNECT (chưa có màn xin quyền), mở app. +# 2. Build app macOS (xcodegen generate + xcodebuild, scheme HandLive, cấu hình Debug) và mở app. +# 3. Gọi logcat (Android) và `log stream` (Mac) lọc dòng HLBENCH vào +# handlive/.build-logs/sp1--android.log và sp1--mac.log. # -# --log-only: bo qua build+cai APK va bo qua xcodegen+xcodebuild; dung lai APK/app macOS cua lan -# chay full gan nhat (danh cho phien 2/3 cua runbook, do lai nhanh khong phai build lai). Van cap -# quyen Bluetooth (idempotent) va mo lai app o hai may. That bai ngay voi thong bao ro neu chua -# chay full it nhat mot lan (chua co .app macOS de mo). +# --log-only: bỏ qua build+cài APK và bỏ qua xcodegen+xcodebuild; dùng lại APK/app macOS của lần +# chạy full gần nhất (dành cho phiên 2/3 của runbook, đo lại nhanh không phải build lại). Vẫn cấp +# quyền Bluetooth (idempotent) và mở lại app ở hai máy. Thất bại ngay với thông báo rõ nếu chưa +# chạy full ít nhất một lần (chưa có .app macOS để mở). # -# Dung tay: lam cac buoc do trong runbook (phase-07-sp1-measure.md), roi Ctrl-C de dung ghi log. -# Khong dung mang ngoai; chi dung JAVA_HOME/ANDROID_HOME nhu verify-phase7.sh. +# Dùng tay: làm các bước đo trong runbook (phase-07-sp1-measure.md), rồi Ctrl-C để dừng ghi log. +# Không dùng mạng ngoài; chỉ dùng JAVA_HOME/ANDROID_HOME như verify-phase7.sh. set -uo pipefail LOG_ONLY=0 @@ -24,7 +24,7 @@ SERIAL="" for arg in "$@"; do case "$arg" in --log-only) LOG_ONLY=1 ;; - -*) echo "LOI: khong biet tuy chon: $arg" >&2; exit 2 ;; + -*) echo "LỖI: không biết tùy chọn: $arg" >&2; exit 2 ;; *) SERIAL="$arg" ;; esac done @@ -49,52 +49,52 @@ if [ -n "$SERIAL" ]; then fi fail() { - echo "LOI: $1" >&2 + echo "LỖI: $1" >&2 exit 1 } -command -v adb >/dev/null || fail "khong thay adb (Android platform-tools) trong PATH" -command -v xcodebuild >/dev/null || fail "khong thay xcodebuild trong PATH" +command -v adb >/dev/null || fail "không thấy adb (Android platform-tools) trong PATH" +command -v xcodebuild >/dev/null || fail "không thấy xcodebuild trong PATH" -adb "${ADB_ARGS[@]}" get-state >/dev/null 2>&1 || fail "khong thay thiet bi Android qua adb (cam S25, bat USB debugging)" +adb "${ADB_ARGS[@]}" get-state >/dev/null 2>&1 || fail "không thấy thiết bị Android qua adb (cắm S25, bật USB debugging)" if [ "$LOG_ONLY" -eq 0 ]; then - echo "== 1. Build + cai APK debug len S25 ==" + echo "== 1. Build + cài APK debug lên S25 ==" : "${JAVA_HOME:=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home}" : "${ANDROID_HOME:=/opt/homebrew/share/android-commandlinetools}" export JAVA_HOME ANDROID_HOME GRADLEW="$ANDROID_DIR/gradlew" - [ -f "$GRADLEW" ] || fail "khong thay $GRADLEW" + [ -f "$GRADLEW" ] || fail "không thấy $GRADLEW" env JAVA_HOME="$JAVA_HOME" ANDROID_HOME="$ANDROID_HOME" "$GRADLEW" -p "$ANDROID_DIR" ":app:assembleFossDebug" \ - || fail "gradlew :app:assembleFossDebug that bai" + || fail "gradlew :app:assembleFossDebug thất bại" APK_PATH="$(find "$ANDROID_DIR/app/build/outputs/apk/foss/debug" -name '*.apk' -print -quit 2>/dev/null)" - [ -n "$APK_PATH" ] || fail "khong tim thay APK debug duoi $ANDROID_DIR/app/build/outputs/apk/foss/debug" + [ -n "$APK_PATH" ] || fail "không tìm thấy APK debug dưới $ANDROID_DIR/app/build/outputs/apk/foss/debug" echo "APK: $APK_PATH" - adb "${ADB_ARGS[@]}" install -r "$APK_PATH" || fail "adb install that bai" + adb "${ADB_ARGS[@]}" install -r "$APK_PATH" || fail "adb install thất bại" else - echo "== 1. --log-only: bo qua build + cai APK (dung lai ban da cai tu lan chay full) ==" + echo "== 1. --log-only: bỏ qua build + cài APK (dùng lại bản đã cài từ lần chạy full) ==" fi -echo "== 2. Cap quyen Bluetooth (chua co man xin quyen trong app) ==" +echo "== 2. Cấp quyền Bluetooth (chưa có màn xin quyền trong app) ==" adb "${ADB_ARGS[@]}" shell pm grant "$APP_ID" android.permission.BLUETOOTH_ADVERTISE \ - || echo " CANH BAO: pm grant BLUETOOTH_ADVERTISE that bai (co the da cap san)" + || echo " CẢNH BÁO: pm grant BLUETOOTH_ADVERTISE thất bại (có thể đã cấp sẵn)" adb "${ADB_ARGS[@]}" shell pm grant "$APP_ID" android.permission.BLUETOOTH_CONNECT \ - || echo " CANH BAO: pm grant BLUETOOTH_CONNECT that bai (co the da cap san)" + || echo " CẢNH BÁO: pm grant BLUETOOTH_CONNECT thất bại (có thể đã cấp sẵn)" -echo "== 3. Mo app tren S25 ==" -adb "${ADB_ARGS[@]}" shell am start -n "$MAIN_ACTIVITY" || fail "adb shell am start that bai" +echo "== 3. Mở app trên S25 ==" +adb "${ADB_ARGS[@]}" shell am start -n "$MAIN_ACTIVITY" || fail "adb shell am start thất bại" DERIVED_DATA="$APPLE_DIR/.build-logs/sp1-derived-data" echo if [ "$LOG_ONLY" -eq 0 ]; then echo "== 4. Build app macOS (debug) ==" - command -v xcodegen >/dev/null || fail "khong thay xcodegen trong PATH (brew install xcodegen)" - (cd "$APPLE_DIR" && xcodegen generate) || fail "xcodegen generate that bai" + command -v xcodegen >/dev/null || fail "không thấy xcodegen trong PATH (brew install xcodegen)" + (cd "$APPLE_DIR" && xcodegen generate) || fail "xcodegen generate thất bại" mkdir -p "$APPLE_DIR/.build-logs" xcodebuild build \ @@ -104,28 +104,28 @@ if [ "$LOG_ONLY" -eq 0 ]; then -destination "platform=macOS" \ -derivedDataPath "$DERIVED_DATA" \ CODE_SIGNING_ALLOWED=NO \ - || fail "xcodebuild build HandLive (macOS) that bai" + || fail "xcodebuild build HandLive (macOS) thất bại" else - echo "== 4. --log-only: bo qua xcodegen + xcodebuild (dung lai ban da build tu lan chay full) ==" + echo "== 4. --log-only: bỏ qua xcodegen + xcodebuild (dùng lại bản đã build từ lần chạy full) ==" fi MAC_APP_PATH="$(find "$DERIVED_DATA/Build/Products/Debug" -maxdepth 1 -name 'HandLive.app' -print -quit 2>/dev/null)" if [ -z "$MAC_APP_PATH" ]; then if [ "$LOG_ONLY" -eq 1 ]; then - fail "khong thay HandLive.app duoi $DERIVED_DATA/Build/Products/Debug — chay mot lan khong co --log-only truoc" + fail "không thấy HandLive.app dưới $DERIVED_DATA/Build/Products/Debug — chạy một lần không có --log-only trước" fi - fail "khong tim thay HandLive.app duoi $DERIVED_DATA/Build/Products/Debug" + fail "không tìm thấy HandLive.app dưới $DERIVED_DATA/Build/Products/Debug" fi echo "App macOS: $MAC_APP_PATH" -echo "Ky ad-hoc de khong can provisioning profile" -codesign --force --deep --sign - "$MAC_APP_PATH" || fail "codesign ad-hoc that bai" +echo "Ký ad-hoc để không cần provisioning profile" +codesign --force --deep --sign - "$MAC_APP_PATH" || fail "codesign ad-hoc thất bại" -echo "== 5. Mo app tren Mac ==" -open "$MAC_APP_PATH" || fail "mo HandLive.app that bai (sandbox chan 'open'; chay script nay ngoai sandbox)" +echo "== 5. Mở app trên Mac ==" +open "$MAC_APP_PATH" || fail "mở HandLive.app thất bại (sandbox chặn 'open'; chạy script này ngoài sandbox)" echo -echo "== 6. Ghi log HLBENCH (Ctrl-C de dung) ==" +echo "== 6. Ghi log HLBENCH (Ctrl-C để dừng) ==" echo "Android: $ANDROID_LOG" echo "Mac: $MAC_LOG" @@ -140,12 +140,12 @@ stop() { kill "$android_pid" "$mac_pid" 2>/dev/null || true wait 2>/dev/null || true echo - echo "Da dung ghi log." - echo " Android: $(grep -c 'HLBENCH/1 ' "$ANDROID_LOG" 2>/dev/null || echo 0) dong, tai $ANDROID_LOG" - echo " Mac: $(grep -c 'HLBENCH/1 ' "$MAC_LOG" 2>/dev/null || echo 0) dong, tai $MAC_LOG" - echo "Doc ket qua theo handlive/plans/20260925-implementation/reports/phase-07-sp1-measure.md" + echo "Đã dừng ghi log." + echo " Android: $(grep -c 'HLBENCH/1 ' "$ANDROID_LOG" 2>/dev/null || echo 0) dòng, tại $ANDROID_LOG" + echo " Mac: $(grep -c 'HLBENCH/1 ' "$MAC_LOG" 2>/dev/null || echo 0) dòng, tại $MAC_LOG" + echo "Đọc kết quả theo handlive/plans/20260925-implementation/reports/phase-07-sp1-measure.md" } trap stop INT TERM -echo "Lam cac buoc do trong runbook (phase-07-sp1-measure.md), roi an Ctrl-C de dung ghi log." +echo "Làm các bước đo trong runbook (phase-07-sp1-measure.md), rồi ấn Ctrl-C để dừng ghi log." wait diff --git a/tools/release-push-website.sh b/tools/release-push-website.sh new file mode 100644 index 0000000..3d0e37f --- /dev/null +++ b/tools/release-push-website.sh @@ -0,0 +1,140 @@ +#!/bin/zsh +# Đẩy nhánh website và tài liệu v0.2.0 lên origin và mở PR. +# Chạy NGOÀI sandbox qua nút run của sếp: +# zsh handlive/tools/release-push-website.sh # chạy thực +# zsh handlive/tools/release-push-website.sh --dry-run # chỉ in lệnh, không push/tạo PR +# +# Với từng repo hub và .github-org (theo thứ tự đó): +# Kiểm nhánh main, fetch origin main, so ahead (0 thì báo và dừng), +# ls-remote nhánh feat/docs-v0-2-0 (đã có đúng SHA thì bỏ qua push, khác SHA thì fail), +# push main:feat/docs-v0-2-0 (không -u), gh pr list, có thì in link, chưa có thì create. +# Trước push, kiểm .github-org có remote origin; không có thì dừng. +set -uo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +HUB_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +WORKSPACE_ROOT="$(cd "$HUB_ROOT/.." && pwd)" + +RELEASE_BRANCH="feat/docs-v0-2-0" +PR_TITLE="docs: v0.2.0 release links and status" +PR_BODY="Website (#265) và tài liệu (README, CLAUDE.md, roadmap, profile tổ chức) trỏ tới v0.2.0 đã phát hành. Không đổi mã nguồn app, không ảnh hưởng release đã tag." + +# Cập nhật mô tả PR nếu đã mở (dùng bản có dấu từ Bao commit c6ff51c) +update_pr_body() { + local repo_path="$1" + local existing_pr_url="$2" + + if [ -z "$existing_pr_url" ]; then + return 0 + fi + + echo "+ gh pr edit $existing_pr_url --title ... --body ..." + if [ "$DRY_RUN" -eq 1 ]; then + echo "$name: (dry-run) không cập nhật PR" + return 0 + fi + + (cd "$repo_path" && gh pr edit "$existing_pr_url" --title "$PR_TITLE" --body "$PR_BODY") \ + || fail "$name: gh pr edit thất bại" +} + +typeset -a REPO_NAMES +typeset -a REPO_PATHS +REPO_NAMES=(hub github-org) +REPO_PATHS=( + "$HUB_ROOT" + "$WORKSPACE_ROOT/.github-org" +) + +DRY_RUN=0 +if [ "${1:-}" = "--dry-run" ]; then + DRY_RUN=1 +fi + +fail() { + echo "LỖI: $1" >&2 + exit 1 +} + +i=1 +while [ "$i" -le "${#REPO_NAMES[@]}" ]; do + name="${REPO_NAMES[$i]}" + repo_path="${REPO_PATHS[$i]}" + i=$((i + 1)) + + echo + echo "== $name ($repo_path) ==" + [ -d "$repo_path" ] || fail "không thấy thư mục $repo_path" + + (cd "$repo_path" && git rev-parse --is-inside-work-tree >/dev/null 2>&1) \ + || fail "$name: không phải git repo" + + (cd "$repo_path" && git remote get-url origin >/dev/null 2>&1) \ + || fail "$name: không có remote origin" + + current_branch="$(cd "$repo_path" && git branch --show-current)" + [ "$current_branch" = "main" ] || fail "$name: đang ở nhánh '$current_branch', cần 'main'" + + echo "+ git fetch origin main" + (cd "$repo_path" && git fetch origin main) || fail "$name: git fetch thất bại" + + ahead="$(cd "$repo_path" && git rev-list --count origin/main..main 2>/dev/null)" + if [ -z "$ahead" ]; then + fail "$name: không đọc được số commit đi trước origin/main (fetch trước?)" + fi + if [ "$ahead" -eq 0 ]; then + echo "$name: main không đi trước origin/main, bỏ qua" + continue + fi + echo "$name: main đi trước origin/main $ahead commit" + + local_sha="$(cd "$repo_path" && git rev-parse main)" + remote_branch_sha="$(cd "$repo_path" && git ls-remote origin "refs/heads/$RELEASE_BRANCH" | awk '{print $1}')" + + echo "+ git ls-remote origin refs/heads/$RELEASE_BRANCH" + should_push=1 + if [ -n "$remote_branch_sha" ] && [ "$remote_branch_sha" = "$local_sha" ]; then + echo "$name: origin/$RELEASE_BRANCH đã đúng SHA ($local_sha), bỏ qua push" + should_push=0 + elif [ -n "$remote_branch_sha" ]; then + echo "+ git fetch origin $RELEASE_BRANCH" + (cd "$repo_path" && git fetch origin "$RELEASE_BRANCH") \ + || fail "$name: git fetch $RELEASE_BRANCH thất bại" + + (cd "$repo_path" && git merge-base --is-ancestor "$remote_branch_sha" "$local_sha") \ + || fail "$name: origin/$RELEASE_BRANCH ($remote_branch_sha) không phải tổ tiên của main ($local_sha), không tự --force" + echo "$name: origin/$RELEASE_BRANCH ($remote_branch_sha) là tổ tiên của main, đẩy fast-forward" + fi + + if [ "$should_push" -eq 1 ]; then + echo "+ git push origin main:$RELEASE_BRANCH" + if [ "$DRY_RUN" -eq 1 ]; then + echo "$name: (dry-run) không push" + else + (cd "$repo_path" && git push origin "main:$RELEASE_BRANCH") \ + || fail "$name: git push thất bại" + fi + fi + + echo "+ gh pr list --head $RELEASE_BRANCH --state open --json url" + if [ "$DRY_RUN" -eq 1 ]; then + echo "+ gh pr create --base main --head $RELEASE_BRANCH --title ... --body ..." + echo "$name: (dry-run) không tạo PR" + continue + fi + + existing_pr_url="$(cd "$repo_path" && gh pr list --head "$RELEASE_BRANCH" --state open --json url --jq '.[0].url' 2>/dev/null)" + if [ -n "$existing_pr_url" ]; then + echo "$name: PR đã có -> $existing_pr_url" + update_pr_body "$repo_path" "$existing_pr_url" + continue + fi + + pr_url="$(cd "$repo_path" && gh pr create --base main --head "$RELEASE_BRANCH" --title "$PR_TITLE" --body "$PR_BODY")" \ + || fail "$name: gh pr create thất bại" + + echo "$name: PR -> $pr_url" +done + +echo +echo "XONG. Merge PR trên GitHub bằng 'Create a merge commit'." diff --git a/tools/release-push.sh b/tools/release-push.sh index 0a4d2a3..9cd5fee 100755 --- a/tools/release-push.sh +++ b/tools/release-push.sh @@ -1,18 +1,18 @@ #!/bin/zsh -# Day nhanh release len origin va mo PR cho tung repo con. -# Chay NGOAI sandbox qua nut run cua sep: -# zsh handlive/tools/release-push.sh # chay thuc -# zsh handlive/tools/release-push.sh --dry-run # chi in lenh, khong push/tao PR +# Đẩy nhánh release lên origin và mở PR cho từng repo con. +# Chạy NGOÀI sandbox qua nút run của sếp: +# zsh handlive/tools/release-push.sh # chạy thực +# zsh handlive/tools/release-push.sh --dry-run # chỉ in lệnh, không push/tạo PR # -# Voi tung repo shared, android, apple, hub (theo thu tu do): -# - bo qua neu nhanh main local KHONG di truoc origin/main (khong co gi de day) -# - bo qua push neu origin/feat/p07-ghep-bluetooth-offline da co dung SHA cua main local -# - dung va bao loi neu origin/feat/p07-ghep-bluetooth-offline co SHA khac (khong --force) -# - git push origin main:feat/p07-ghep-bluetooth-offline (khong doi upstream cua main local) -# - bo qua tao PR neu da co PR open cho nhanh nay, chi in link +# Với từng repo shared, android, apple, hub (theo thứ tự đó): +# - bỏ qua nếu nhánh main local KHÔNG đi trước origin/main (không có gì để đẩy) +# - bỏ qua push nếu origin/feat/p07-ghep-bluetooth-offline đã có đúng SHA của main local +# - dừng và báo lỗi nếu origin/feat/p07-ghep-bluetooth-offline có SHA khác (không --force) +# - git push origin main:feat/p07-ghep-bluetooth-offline (không đổi upstream của main local) +# - bỏ qua tạo PR nếu đã có PR open cho nhánh này, chỉ in link # - gh pr create --base main --head feat/p07-ghep-bluetooth-offline --title ... --body ... # - in link PR -# Dung ngay khi mot buoc loi (khong tiep tuc repo sau). Chay lai nhieu lan an toan. +# Dừng ngay khi một bước lỗi (không tiếp tục repo sau). Chạy lại nhiều lần an toàn. set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -21,7 +21,7 @@ WORKSPACE_ROOT="$(cd "$HUB_ROOT/.." && pwd)" RELEASE_BRANCH="feat/p07-ghep-bluetooth-offline" PR_TITLE="Phase 7: Bluetooth offline pairing (v0.2.0)" -PR_BODY="Ghep doi Bluetooth lan dau khong can mang (QR luong B, PIN luong C dung CPace-X25519-SHA512), nhan 'Connected via Bluetooth'. Bao cao bao mat PASS o hub reports/security-scan-0.2.0-2026-10-30.md. Merge bang 'Create a merge commit'." +PR_BODY="Ghép đôi Bluetooth lần đầu không cần mạng (QR luồng B, PIN luồng C dùng CPace-X25519-SHA512), nhãn 'Connected via Bluetooth'. Báo cáo bảo mật PASS ở hub reports/security-scan-0.2.0-2026-10-30.md. Merge bằng 'Create a merge commit'." DRY_RUN=0 if [ "${1:-}" = "--dry-run" ]; then @@ -39,7 +39,7 @@ REPO_PATHS=( ) fail() { - echo "LOI: $1" >&2 + echo "LỖI: $1" >&2 exit 1 } @@ -51,63 +51,63 @@ while [ "$i" -le "${#REPO_NAMES[@]}" ]; do echo echo "== $name ($repo_path) ==" - [ -d "$repo_path" ] || fail "khong thay thu muc $repo_path" + [ -d "$repo_path" ] || fail "không thấy thư mục $repo_path" (cd "$repo_path" && git rev-parse --is-inside-work-tree >/dev/null 2>&1) \ - || fail "$name: khong phai git repo" + || fail "$name: không phải git repo" current_branch="$(cd "$repo_path" && git branch --show-current)" - [ "$current_branch" = "main" ] || fail "$name: dang o nhanh '$current_branch', can 'main'" + [ "$current_branch" = "main" ] || fail "$name: đang ở nhánh '$current_branch', cần 'main'" echo "+ git fetch origin main" - (cd "$repo_path" && git fetch origin main) || fail "$name: git fetch that bai" + (cd "$repo_path" && git fetch origin main) || fail "$name: git fetch thất bại" ahead="$(cd "$repo_path" && git rev-list --count origin/main..main 2>/dev/null)" if [ -z "$ahead" ]; then - fail "$name: khong doc duoc so commit di truoc origin/main (fetch truoc?)" + fail "$name: không đọc được số commit đi trước origin/main (fetch trước?)" fi if [ "$ahead" -eq 0 ]; then - echo "$name: main khong di truoc origin/main, bo qua" + echo "$name: main không đi trước origin/main, bỏ qua" continue fi - echo "$name: main di truoc origin/main $ahead commit" + echo "$name: main đi trước origin/main $ahead commit" local_sha="$(cd "$repo_path" && git rev-parse main)" remote_branch_sha="$(cd "$repo_path" && git ls-remote origin "refs/heads/$RELEASE_BRANCH" | awk '{print $1}')" echo "+ git ls-remote origin refs/heads/$RELEASE_BRANCH" if [ -n "$remote_branch_sha" ] && [ "$remote_branch_sha" = "$local_sha" ]; then - echo "$name: origin/$RELEASE_BRANCH da dung SHA ($local_sha), bo qua push" + echo "$name: origin/$RELEASE_BRANCH đã đúng SHA ($local_sha), bỏ qua push" elif [ -n "$remote_branch_sha" ]; then - fail "$name: origin/$RELEASE_BRANCH da ton tai voi SHA khac ($remote_branch_sha != $local_sha), khong tu --force" + fail "$name: origin/$RELEASE_BRANCH đã tồn tại với SHA khác ($remote_branch_sha != $local_sha), không tự --force" else echo "+ git push origin main:$RELEASE_BRANCH" if [ "$DRY_RUN" -eq 1 ]; then - echo "$name: (dry-run) khong push" + echo "$name: (dry-run) không push" else (cd "$repo_path" && git push origin "main:$RELEASE_BRANCH") \ - || fail "$name: git push that bai" + || fail "$name: git push thất bại" fi fi echo "+ gh pr list --head $RELEASE_BRANCH --state open --json url" if [ "$DRY_RUN" -eq 1 ]; then echo "+ gh pr create --base main --head $RELEASE_BRANCH --title ... --body ..." - echo "$name: (dry-run) khong tao PR" + echo "$name: (dry-run) không tạo PR" continue fi existing_pr_url="$(cd "$repo_path" && gh pr list --head "$RELEASE_BRANCH" --state open --json url --jq '.[0].url' 2>/dev/null)" if [ -n "$existing_pr_url" ]; then - echo "$name: PR da co -> $existing_pr_url" + echo "$name: PR đã có -> $existing_pr_url" continue fi pr_url="$(cd "$repo_path" && gh pr create --base main --head "$RELEASE_BRANCH" --title "$PR_TITLE" --body "$PR_BODY")" \ - || fail "$name: gh pr create that bai" + || fail "$name: gh pr create thất bại" echo "$name: PR -> $pr_url" done echo -echo "XONG. Merge tung PR tren GitHub bang 'Create a merge commit', roi chay: zsh handlive/tools/release-tag.sh " +echo "XONG. Merge từng PR trên GitHub bằng 'Create a merge commit', rồi chạy: zsh handlive/tools/release-tag.sh " diff --git a/tools/release-tag.sh b/tools/release-tag.sh index 79b9a78..a9b39a3 100755 --- a/tools/release-tag.sh +++ b/tools/release-tag.sh @@ -1,24 +1,24 @@ #!/bin/zsh -# Gan tag release va day len tung repo, sau khi PR cua release-push.sh da merge vao main. -# Chay NGOAI sandbox qua nut run cua sep: +# Gắn tag release và đẩy lên từng repo, sau khi PR của release-push.sh đã merge vào main. +# Chạy NGOÀI sandbox qua nút run của sếp: # zsh handlive/tools/release-tag.sh v0.2.0 # -# Voi tag (dang vN.N.N): -# 1. git fetch origin cho tung repo -# 2. neu main local khac origin/main: noi dung cay (tree) cua hai ben phai giong het nhau, -# khac la dung (origin/main co thu chua kiem, hoac main local co commit chua vao PR). -# Cay giong nhau thi dua main local ve origin/main: -# - main local la ancestor (merge commit): git merge --ff-only origin/main -# - lich su bi viet lai (rebase/squash, SHA moi): git reset --soft origin/main -# (cay giong het nen index va working tree khong doi; commit cu van con trong reflog) -# 3. kiem tag chua ton tai o local va tren origin (git ls-remote) -# Buoc 1-3 (Pha 1) chay du 4 repo truoc; repo nao loi thi dung, KHONG repo nao duoc gan tag. -# 4. (Pha 2) chi khi ca 4 repo qua Pha 1: git tag roi git push, thu tu hub -> shared -> android -> apple -# Truoc Pha 1: phan loi cua tag phai khop versionName (Android) va MARKETING_VERSION (Apple, ca 3 target). +# Với tag (dạng vN.N.N): +# 1. git fetch origin cho từng repo +# 2. nếu main local khác origin/main: nội dung cây (tree) của hai bên phải giống hệt nhau, +# khác là dừng (origin/main có thứ chưa kiểm, hoặc main local có commit chưa vào PR). +# Cây giống nhau thì đưa main local về origin/main: +# - main local là ancestor (merge commit): git merge --ff-only origin/main +# - lịch sử bị viết lại (rebase/squash, SHA mới): git reset --soft origin/main +# (cây giống hệt nên index và working tree không đổi; commit cũ vẫn còn trong reflog) +# 3. kiểm tag chưa tồn tại ở local và trên origin (git ls-remote) +# Bước 1-3 (Pha 1) chạy đủ 4 repo trước; repo nào lỗi thì dừng, KHÔNG repo nào được gắn tag. +# 4. (Pha 2) chỉ khi cả 4 repo qua Pha 1: git tag rồi git push, thứ tự hub -> shared -> android -> apple +# Trước Pha 1: phần lõi của tag phải khớp versionName (Android) và MARKETING_VERSION (Apple, cả 3 target). # -# Merge PR tren GitHub nen dung "Create a merge commit" (giu nguyen SHA cua commit da kiem). -# "Rebase and merge" va "Squash and merge" deu tao SHA moi; script van chay duoc nho buoc so -# cay o tren, nhung commit tren GitHub se khong trung SHA voi commit da review. +# Merge PR trên GitHub nên dùng "Create a merge commit" (giữ nguyên SHA của commit đã kiểm). +# "Rebase and merge" và "Squash and merge" đều tạo SHA mới; script vẫn chạy được nhờ bước so +# cây ở trên, nhưng commit trên GitHub sẽ không trùng SHA với commit đã review. set -uo pipefail SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" @@ -32,51 +32,51 @@ ANDROID_GRADLE="$ANDROID_DIR/app/build.gradle.kts" APPLE_PROJECT_YML="$APPLE_DIR/project.yml" fail() { - echo "LOI: $1" >&2 + echo "LỖI: $1" >&2 exit 1 } TAG="${1:-}" -[ -n "$TAG" ] || fail "thieu tham so tag, dung: zsh release-tag.sh (vi du v0.2.0)" +[ -n "$TAG" ] || fail "thiếu tham số tag, dùng: zsh release-tag.sh (ví dụ v0.2.0)" case "$TAG" in v*) TAG_CORE="${TAG#v}" ;; - *) fail "tag '$TAG' phai bat dau bang 'v' (vi du v0.2.0)" ;; + *) fail "tag '$TAG' phải bắt đầu bằng 'v' (ví dụ v0.2.0)" ;; esac -echo "== Doc version hien tai ==" -[ -f "$ANDROID_GRADLE" ] || fail "khong thay $ANDROID_GRADLE" +echo "== Đọc version hiện tại ==" +[ -f "$ANDROID_GRADLE" ] || fail "không thấy $ANDROID_GRADLE" VERSION_NAME="$(grep -E '^\s*versionName\s*=' "$ANDROID_GRADLE" | head -1 | sed -E 's/.*"([^"]+)".*/\1/')" -[ -n "$VERSION_NAME" ] || fail "khong doc duoc versionName tu $ANDROID_GRADLE" +[ -n "$VERSION_NAME" ] || fail "không đọc được versionName từ $ANDROID_GRADLE" echo "Android versionName = $VERSION_NAME" -[ -f "$APPLE_PROJECT_YML" ] || fail "khong thay $APPLE_PROJECT_YML" +[ -f "$APPLE_PROJECT_YML" ] || fail "không thấy $APPLE_PROJECT_YML" typeset -a MARKETING_VALUES MARKETING_VALUES=("${(@f)$(grep -E '^\s*MARKETING_VERSION:' "$APPLE_PROJECT_YML" | sed -E 's/.*"([^"]+)".*/\1/')}") -[ "${#MARKETING_VALUES[@]}" -gt 0 ] || fail "khong doc duoc MARKETING_VERSION tu $APPLE_PROJECT_YML" +[ "${#MARKETING_VALUES[@]}" -gt 0 ] || fail "không đọc được MARKETING_VERSION từ $APPLE_PROJECT_YML" MARKETING_VERSION="${MARKETING_VALUES[1]}" for v in "${MARKETING_VALUES[@]}"; do [ "$v" = "$MARKETING_VERSION" ] \ - || fail "$APPLE_PROJECT_YML co nhieu gia tri MARKETING_VERSION khac nhau ('$MARKETING_VERSION' va '$v') — bump version chua dong bo het target" + || fail "$APPLE_PROJECT_YML có nhiều giá trị MARKETING_VERSION khác nhau ('$MARKETING_VERSION' và '$v') — bump version chưa đồng bộ hết target" done -echo "Apple MARKETING_VERSION = $MARKETING_VERSION (${#MARKETING_VALUES[@]} target, deu khop)" +echo "Apple MARKETING_VERSION = $MARKETING_VERSION (${#MARKETING_VALUES[@]} target, đều khớp)" typeset -a PROJECT_VERSION_VALUES PROJECT_VERSION_VALUES=("${(@f)$(grep -E '^\s*CURRENT_PROJECT_VERSION:' "$APPLE_PROJECT_YML" | sed -E 's/.*"([^"]+)".*/\1/')}") -[ "${#PROJECT_VERSION_VALUES[@]}" -gt 0 ] || fail "khong doc duoc CURRENT_PROJECT_VERSION tu $APPLE_PROJECT_YML" +[ "${#PROJECT_VERSION_VALUES[@]}" -gt 0 ] || fail "không đọc được CURRENT_PROJECT_VERSION từ $APPLE_PROJECT_YML" FIRST_PROJECT_VERSION="${PROJECT_VERSION_VALUES[1]}" for v in "${PROJECT_VERSION_VALUES[@]}"; do [ "$v" = "$FIRST_PROJECT_VERSION" ] \ - || fail "$APPLE_PROJECT_YML co nhieu gia tri CURRENT_PROJECT_VERSION khac nhau ('$FIRST_PROJECT_VERSION' va '$v') — bump version chua dong bo het target" + || fail "$APPLE_PROJECT_YML có nhiều giá trị CURRENT_PROJECT_VERSION khác nhau ('$FIRST_PROJECT_VERSION' và '$v') — bump version chưa đồng bộ hết target" done -echo "Apple CURRENT_PROJECT_VERSION = $FIRST_PROJECT_VERSION (${#PROJECT_VERSION_VALUES[@]} target, deu khop)" +echo "Apple CURRENT_PROJECT_VERSION = $FIRST_PROJECT_VERSION (${#PROJECT_VERSION_VALUES[@]} target, đều khớp)" [ "$TAG_CORE" = "$VERSION_NAME" ] \ - || fail "tag '$TAG' (phan loi '$TAG_CORE') khong khop versionName Android '$VERSION_NAME'" + || fail "tag '$TAG' (phần lõi '$TAG_CORE') không khớp versionName Android '$VERSION_NAME'" [ "$TAG_CORE" = "$MARKETING_VERSION" ] \ - || fail "tag '$TAG' (phan loi '$TAG_CORE') khong khop MARKETING_VERSION Apple '$MARKETING_VERSION'" -echo "Tag '$TAG' khop versionName va MARKETING_VERSION ($TAG_CORE)" + || fail "tag '$TAG' (phần lõi '$TAG_CORE') không khớp MARKETING_VERSION Apple '$MARKETING_VERSION'" +echo "Tag '$TAG' khớp versionName và MARKETING_VERSION ($TAG_CORE)" typeset -a REPO_NAMES typeset -a REPO_PATHS @@ -90,9 +90,9 @@ REPO_PATHS=( typeset -a TAG_SHAS -# Pha 1: kiem du 4 repo, chua gan tag nao. Repo nao loi thi dung, khong repo nao co tag. +# Pha 1: kiểm đủ 4 repo, chưa gắn tag nào. Repo nào lỗi thì dừng, không repo nào có tag. echo -echo "==== Pha 1: kiem 4 repo ====" +echo "==== Pha 1: kiểm 4 repo ====" i=1 while [ "$i" -le "${#REPO_NAMES[@]}" ]; do name="${REPO_NAMES[$i]}" @@ -100,55 +100,55 @@ while [ "$i" -le "${#REPO_NAMES[@]}" ]; do echo echo "== $name ($repo_path) ==" - [ -d "$repo_path" ] || fail "khong thay thu muc $repo_path" + [ -d "$repo_path" ] || fail "không thấy thư mục $repo_path" (cd "$repo_path" && git rev-parse --is-inside-work-tree >/dev/null 2>&1) \ - || fail "$name: khong phai git repo" + || fail "$name: không phải git repo" echo "+ git fetch origin" - (cd "$repo_path" && git fetch origin) || fail "$name: git fetch that bai" + (cd "$repo_path" && git fetch origin) || fail "$name: git fetch thất bại" current_branch="$(cd "$repo_path" && git branch --show-current)" - [ "$current_branch" = "main" ] || fail "$name: dang o nhanh '$current_branch', can 'main'" + [ "$current_branch" = "main" ] || fail "$name: đang ở nhánh '$current_branch', cần 'main'" local_sha="$(cd "$repo_path" && git rev-parse main)" remote_sha="$(cd "$repo_path" && git rev-parse origin/main)" if [ "$local_sha" != "$remote_sha" ]; then (cd "$repo_path" && git diff --quiet main origin/main) \ - || fail "$name: noi dung main local khac origin/main — PR chua merge, origin/main co commit khac, hoac main local co commit chua vao PR" + || fail "$name: nội dung main local khác origin/main — PR chưa merge, origin/main có commit khác, hoặc main local có commit chưa vào PR" if (cd "$repo_path" && git merge-base --is-ancestor main origin/main); then echo "+ git merge --ff-only origin/main" (cd "$repo_path" && git merge --ff-only origin/main) \ - || fail "$name: git merge --ff-only origin/main that bai" + || fail "$name: git merge --ff-only origin/main thất bại" else - echo "+ git reset --soft origin/main (PR merge bang rebase/squash, cay giong het)" + echo "+ git reset --soft origin/main (PR merge bằng rebase/squash, cây giống hệt)" (cd "$repo_path" && git reset --soft origin/main) \ - || fail "$name: git reset --soft origin/main that bai" + || fail "$name: git reset --soft origin/main thất bại" fi local_sha="$(cd "$repo_path" && git rev-parse main)" fi [ "$local_sha" = "$remote_sha" ] \ - || fail "$name: main local ($local_sha) khac origin/main ($remote_sha) sau khi dong bo" - echo "$name: main khop origin/main ($local_sha)" + || fail "$name: main local ($local_sha) khác origin/main ($remote_sha) sau khi đồng bộ" + echo "$name: main khớp origin/main ($local_sha)" if (cd "$repo_path" && git rev-parse -q --verify "refs/tags/$TAG" >/dev/null); then - fail "$name: tag '$TAG' da ton tai o local" + fail "$name: tag '$TAG' đã tồn tại ở local" fi (cd "$repo_path" && git ls-remote --exit-code --tags origin "refs/tags/$TAG" >/dev/null) ls_code=$? case "$ls_code" in 2) ;; - 0) fail "$name: tag '$TAG' da ton tai tren origin" ;; - *) fail "$name: git ls-remote that bai (ma $ls_code)" ;; + 0) fail "$name: tag '$TAG' đã tồn tại trên origin" ;; + *) fail "$name: git ls-remote thất bại (mã $ls_code)" ;; esac - echo "$name: tag '$TAG' chua co o local va origin" + echo "$name: tag '$TAG' chưa có ở local và origin" TAG_SHAS[$i]="$local_sha" i=$((i + 1)) done -# Pha 2: ca 4 repo da qua Pha 1, moi gan tag va day. +# Pha 2: cả 4 repo đã qua Pha 1, mới gắn tag và đẩy. echo -echo "==== Pha 2: tag va day ====" +echo "==== Pha 2: tag và đẩy ====" i=1 while [ "$i" -le "${#REPO_NAMES[@]}" ]; do name="${REPO_NAMES[$i]}" @@ -159,14 +159,14 @@ while [ "$i" -le "${#REPO_NAMES[@]}" ]; do echo echo "== $name ==" echo "+ git tag $TAG $sha" - (cd "$repo_path" && git tag "$TAG" "$sha") || fail "$name: git tag that bai" + (cd "$repo_path" && git tag "$TAG" "$sha") || fail "$name: git tag thất bại" echo "+ git push origin refs/tags/$TAG" (cd "$repo_path" && git push origin "refs/tags/$TAG") \ - || fail "$name: git push tag that bai — cac repo truoc da co tag tren origin; sua loi roi day tay: git -C $repo_path push origin refs/tags/$TAG" + || fail "$name: git push tag thất bại — các repo trước đã có tag trên origin; sửa lỗi rồi đẩy tay: git -C $repo_path push origin refs/tags/$TAG" - echo "$name: da tag va day $TAG" + echo "$name: đã tag và đẩy $TAG" done echo -echo "XONG: $TAG da tag va day theo thu tu hub -> shared -> android -> apple." +echo "XONG: $TAG đã tag và đẩy theo thứ tự hub -> shared -> android -> apple." diff --git a/tools/verify-android-ui.sh b/tools/verify-android-ui.sh index e01a460..d200213 100755 --- a/tools/verify-android-ui.sh +++ b/tools/verify-android-ui.sh @@ -1,14 +1,14 @@ #!/bin/zsh -# Chay NGOAI sandbox: zsh handlive/tools/verify-android-ui.sh +# Chạy NGOÀI sandbox: zsh handlive/tools/verify-android-ui.sh # -# Kiem tra Android UI (nhan Bluetooth trong danh sach thiet bi): -# 1. core:design:check — hieu ung, thanh phan UI -# 2. core:strings:check — chuoi tim kiem, catalog -# 3. feature:pairing:check — logic cap, UI pairing -# 4. app:testFossDebugUnitTest — test toan bo app (Foss flavor, Debug) -# Toan bo output ghi vao handlive/.build-logs/gradle-android-ui.log (gitignore), -# in 60 dong cuoi. Khong dung khi mot buoc do — chay het roi in tom tat; -# thoat 0 chi khi tat ca buoc xanh. +# Kiểm tra Android UI (nhãn Bluetooth trong danh sách thiết bị): +# 1. core:design:check — hiệu ứng, thành phần UI +# 2. core:strings:check — chuỗi tìm kiếm, catalog +# 3. feature:pairing:check — logic ghép, UI pairing +# 4. app:testFossDebugUnitTest — test toàn bộ app (Foss flavor, Debug) +# Toàn bộ output ghi vào handlive/.build-logs/gradle-android-ui.log (gitignore), +# in 60 dòng cuối. Không dừng khi một bước đỏ — chạy hết rồi in tóm tắt; +# thoát 0 chỉ khi tất cả bước xanh. set -uo pipefail HUB_ROOT="$(cd "$(dirname "$0")/.." && pwd)" @@ -22,14 +22,14 @@ GRADLE_LOG="$LOG_DIR/gradle-android-ui.log" overall_ok=1 TEST_SUMMARY="" -echo "== Android: gradlew check cho core:design, core:strings, feature:pairing va app:testFossDebugUnitTest ==" +echo "== Android: gradlew check cho core:design, core:strings, feature:pairing và app:testFossDebugUnitTest ==" : "${JAVA_HOME:=/opt/homebrew/opt/openjdk@21/libexec/openjdk.jdk/Contents/Home}" : "${ANDROID_HOME:=/opt/homebrew/share/android-commandlinetools}" export JAVA_HOME ANDROID_HOME GRADLEW="$ANDROID_DIR/gradlew" if [ ! -f "$GRADLEW" ]; then - echo " LOI: khong thay $GRADLEW" + echo " LỖI: không thấy $GRADLEW" overall_ok=0 EXIT=1 else @@ -37,11 +37,11 @@ else ":core:design:check" ":core:strings:check" ":feature:pairing:check" ":app:testFossDebugUnitTest" \ > "$GRADLE_LOG" 2>&1 EXIT=$? - echo " == 60 dong cuoi cua $GRADLE_LOG ==" + echo " == 60 dòng cuối của $GRADLE_LOG ==" tail -n 60 "$GRADLE_LOG" - # Gradle khong in so test ra console theo mac dinh (khong co testLogging trong build.gradle.kts), - # nen cong thuoc tinh tests= tu cac file JUnit XML ma gradle tu ghi cho moi module. + # Gradle không in số test ra console theo mặc định (không có testLogging trong build.gradle.kts), + # nên cộng thuộc tính tests= từ các file JUnit XML mà gradle tự ghi cho mỗi module. total_tests=0 xml_files=0 while IFS= read -r xml; do @@ -53,24 +53,24 @@ else -path '*/build/test-results/*' -name 'TEST-*.xml' 2>/dev/null) if [ "$xml_files" -gt 0 ]; then - TEST_SUMMARY="$total_tests test trong $xml_files file XML duoi */build/test-results" + TEST_SUMMARY="$total_tests test trong $xml_files file XML dưới */build/test-results" else - TEST_SUMMARY="khong tim thay file XML ket qua test duoi */build/test-results, xem $GRADLE_LOG" + TEST_SUMMARY="không tìm thấy file XML kết quả test dưới */build/test-results, xem $GRADLE_LOG" fi fi echo -echo "== Tom tat ==" +echo "== Tóm tắt ==" if [ "$EXIT" -eq 0 ]; then echo " core:design:check — XANH" echo " core:strings:check — XANH" echo " feature:pairing:check — XANH" echo " app:testFossDebugUnitTest — XANH ($TEST_SUMMARY)" - echo "XONG: tat ca buoc xanh." + echo "XONG: tất cả bước xanh." else echo " Gradle exit $EXIT" echo " Test summary: $TEST_SUMMARY" - echo "LOI: co buoc do, xem log tren." + echo "LỖI: có bước đỏ, xem log trên." overall_ok=0 fi diff --git a/tools/verify-phase7.sh b/tools/verify-phase7.sh index 7139337..24aac8a 100755 --- a/tools/verify-phase7.sh +++ b/tools/verify-phase7.sh @@ -1,14 +1,14 @@ #!/bin/zsh -# Chay NGOAI sandbox: zsh handlive/tools/verify-phase7.sh +# Chạy NGOÀI sandbox: zsh handlive/tools/verify-phase7.sh # -# Gop toan bo kiem tra Phase 7 (Bluetooth/Wi-Fi Direct offline) thanh mot lan bam: +# Gộp toàn bộ kiểm tra Phase 7 (Bluetooth/Wi-Fi Direct offline) thành một lần bấm: # 1. Apple: swift test cho HLProtocol, HLCrypto, HLTransport qua -# ../handlive-apple/Tools/run-package-tests.sh +# ../handlive-apple/Tools/run-package-tests.sh # 2. Android: ../handlive-android/gradlew check cho core/protocol, core/crypto, -# core/transport va testDebugUnitTest cho feature/connection; toan bo output -# ghi vao handlive/.build-logs/gradle-phase7.log (gitignore), in 60 dong cuoi. +# core/transport và testDebugUnitTest cho feature/connection; toàn bộ output +# ghi vào handlive/.build-logs/gradle-phase7.log (gitignore), in 60 dòng cuối. # -# Khong dung khi mot buoc do — chay het roi in tom tat; thoat 0 chi khi moi buoc xanh. +# Không dừng khi một bước đỏ — chạy hết rồi in tóm tắt; thoát 0 chỉ khi mọi bước xanh. set -uo pipefail HUB_ROOT="$(cd "$(dirname "$0")/.." && pwd)" @@ -28,18 +28,18 @@ RUNNER="$APPLE_DIR/Tools/run-package-tests.sh" for PKG in HLProtocol HLCrypto HLTransport; do echo "-- $PKG --" if [ ! -f "$RUNNER" ]; then - echo " LOI: khong thay $RUNNER" - RESULTS+=("Apple $PKG: LOI (khong thay run-package-tests.sh)") + echo " LỖI: không thấy $RUNNER" + RESULTS+=("Apple $PKG: LỖI (không thấy run-package-tests.sh)") overall_ok=0 continue fi if zsh "$RUNNER" "$PKG"; then PKG_LOG="$APPLE_DIR/.build-logs/$PKG-test.log" COUNT_LINE="$(grep -E 'Test run with [0-9]+ tests? in [0-9]+ suites? (passed|failed)' "$PKG_LOG" 2>/dev/null | tail -1 | sed 's/^[[:space:]]*//')" - [ -z "$COUNT_LINE" ] && COUNT_LINE="khong doc duoc so test tu $PKG_LOG" + [ -z "$COUNT_LINE" ] && COUNT_LINE="không đọc được số test từ $PKG_LOG" RESULTS+=("Apple $PKG: XANH — $COUNT_LINE") else - RESULTS+=("Apple $PKG: DO (xem $APPLE_DIR/.build-logs/$PKG-test.log)") + RESULTS+=("Apple $PKG: ĐỎ (xem $APPLE_DIR/.build-logs/$PKG-test.log)") overall_ok=0 fi done @@ -52,19 +52,19 @@ export JAVA_HOME ANDROID_HOME GRADLEW="$ANDROID_DIR/gradlew" if [ ! -f "$GRADLEW" ]; then - echo " LOI: khong thay $GRADLEW" - RESULTS+=("Android: LOI (khong thay gradlew)") + echo " LỖI: không thấy $GRADLEW" + RESULTS+=("Android: LỖI (không thấy gradlew)") overall_ok=0 else env JAVA_HOME="$JAVA_HOME" ANDROID_HOME="$ANDROID_HOME" "$GRADLEW" -p "$ANDROID_DIR" --continue \ ":core:protocol:check" ":core:crypto:check" ":core:transport:check" ":feature:connection:testDebugUnitTest" \ > "$GRADLE_LOG" 2>&1 GRADLE_EXIT=$? - echo " == 60 dong cuoi cua $GRADLE_LOG ==" + echo " == 60 dòng cuối của $GRADLE_LOG ==" tail -n 60 "$GRADLE_LOG" - # Gradle khong in so test ra console theo mac dinh (khong co testLogging trong build.gradle.kts), - # nen cong thuoc tinh tests= tu cac file JUnit XML ma gradle tu ghi cho moi module. + # Gradle không in số test ra console theo mặc định (không có testLogging trong build.gradle.kts), + # nên cộng thuộc tính tests= từ các file JUnit XML mà gradle tự ghi cho mỗi module. total_tests=0 xml_files=0 while IFS= read -r xml; do @@ -76,28 +76,28 @@ else -path '*/build/test-results/*' -name 'TEST-*.xml' 2>/dev/null) if [ "$xml_files" -gt 0 ]; then - TEST_SUMMARY="$total_tests test trong $xml_files file XML duoi */build/test-results" + TEST_SUMMARY="$total_tests test trong $xml_files file XML dưới */build/test-results" else - TEST_SUMMARY="khong tim thay file XML ket qua test duoi */build/test-results, xem $GRADLE_LOG" + TEST_SUMMARY="không tìm thấy file XML kết quả test dưới */build/test-results, xem $GRADLE_LOG" fi if [ "$GRADLE_EXIT" -eq 0 ]; then RESULTS+=("Android (4 module): XANH — $TEST_SUMMARY") else - RESULTS+=("Android (4 module): DO (exit=$GRADLE_EXIT) — $TEST_SUMMARY") + RESULTS+=("Android (4 module): ĐỎ (exit=$GRADLE_EXIT) — $TEST_SUMMARY") overall_ok=0 fi fi echo -echo "== Tom tat ==" +echo "== Tóm tắt ==" for r in "${RESULTS[@]}"; do echo " $r" done if [ "$overall_ok" -eq 1 ]; then - echo "XONG: tat ca buoc xanh." + echo "XONG: tất cả bước xanh." exit 0 else - echo "LOI: co buoc do, xem log tren." + echo "LỖI: có bước đỏ, xem log trên." exit 1 fi diff --git a/website/seed/content/en/home.html b/website/seed/content/en/home.html index cd1872a..f1e4022 100644 --- a/website/seed/content/en/home.html +++ b/website/seed/content/en/home.html @@ -1,7 +1,7 @@
-

Public beta · v0.1.1

+

Public beta · v0.2.0

diff --git a/website/seed/content/site.json b/website/seed/content/site.json index c401570..2fc102a 100644 --- a/website/seed/content/site.json +++ b/website/seed/content/site.json @@ -4,7 +4,7 @@ { "slug": "vi", "name": "Tiếng Việt", "locale": "vi", "flag": "vn" } ], "links": { - "release": "https://github.com/HandLive/handlive/releases/tag/v0.1.1", + "release": "https://github.com/HandLive/handlive/releases/tag/v0.2.0", "github": "https://github.com/HandLive" }, "category": { "en": "News", "vi": "Tin tức" }, diff --git a/website/seed/content/vi/home.html b/website/seed/content/vi/home.html index ad10cb8..8a4529b 100644 --- a/website/seed/content/vi/home.html +++ b/website/seed/content/vi/home.html @@ -1,7 +1,7 @@
-

Bản beta công khai · v0.1.1

+

Bản beta công khai · v0.2.0

diff --git a/website/theme/handlive/inc/setup.php b/website/theme/handlive/inc/setup.php index 2e66db6..17eceb5 100644 --- a/website/theme/handlive/inc/setup.php +++ b/website/theme/handlive/inc/setup.php @@ -9,7 +9,7 @@ const HANDLIVE_VERSION = '1.0.0'; const HANDLIVE_GITHUB_URL = 'https://github.com/HandLive'; -const HANDLIVE_RELEASE_URL = 'https://github.com/HandLive/handlive/releases/tag/v0.1.1'; +const HANDLIVE_RELEASE_URL = 'https://github.com/HandLive/handlive/releases/tag/v0.2.0'; add_action( 'after_setup_theme',