Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
672117e
docs(桌面端): 添加专注工作台设计规格
27xk Jul 15, 2026
4d95cfe
docs(桌面端): 添加专注工作台实现计划
27xk Jul 15, 2026
3b15452
test(桌面端): 锁定工作台同步状态规则
27xk Jul 15, 2026
435993d
fix(桌面端): 保持空闲同步状态静默
27xk Jul 15, 2026
495a499
fix(桌面端): 区分待发送与同步异常计数
27xk Jul 15, 2026
d2be74e
feat(桌面端): 重构专注工作台应用骨架
27xk Jul 15, 2026
8a0b097
fix(桌面端): 修正账户菜单外部关闭
27xk Jul 15, 2026
af48089
chore(桌面端): 补齐工作台运行时类型依赖
27xk Jul 15, 2026
8774cbd
fix(桌面端): 完善账户菜单键盘交互
27xk Jul 15, 2026
d7093ac
test(桌面端): 锁定账户菜单操作接线
27xk Jul 15, 2026
113a23f
feat(桌面端): 添加可靠的任务快捷输入
27xk Jul 15, 2026
f8f2819
fix(桌面端): 保留快捷输入直到保存成功
27xk Jul 15, 2026
d293403
fix(桌面端): 避免异步保存清除新输入
27xk Jul 15, 2026
a907309
feat(桌面端): 重设计今天任务工作区
27xk Jul 15, 2026
222d68f
chore(桌面端): 纳入任务编辑抽屉组件
27xk Jul 15, 2026
c8bef1f
fix(桌面端): 完善任务保存失败反馈
27xk Jul 15, 2026
bed9fc1
fix(桌面端): 锁定任务保存期间的编辑状态
27xk Jul 16, 2026
2574792
feat(桌面端): 收敛全部任务反馈状态
27xk Jul 16, 2026
7c8bfeb
test(桌面端): 更新工作台同步状态守门
27xk Jul 16, 2026
e0a33a0
refactor(桌面端): 清理任务视图失效同步契约
27xk Jul 16, 2026
ba53224
feat(全端): 完成专注工作区与离线同步体验升级
27xk Jul 18, 2026
338276d
fix(CI): 重建 Electron 原生依赖后再运行测试
27xk Jul 18, 2026
adcf419
fix(测试): 统一源码契约的跨平台行尾
27xk Jul 18, 2026
12c47c1
fix(测试): 标准化跨端源码检查的行尾
27xk Jul 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@ jobs:
- name: Audit npm dependencies
run: npm audit --audit-level=high

- name: Rebuild Electron native dependencies
run: npm run rebuild:native

- name: Verify desktop endpoint config
run: npm run check:desktop-endpoint-config

Expand Down Expand Up @@ -204,6 +207,9 @@ jobs:
- name: Verify desktop theme config
run: npm run check:desktop-theme

- name: Verify desktop efficiency config
run: npm run check:desktop-efficiency

- name: Verify desktop docs consistency
run: npm run check:desktop-docs

Expand Down Expand Up @@ -270,6 +276,12 @@ jobs:
- name: Verify security governance config
run: npm run check:security-governance

- name: Verify priority UX polish
run: npm run check:ux-priority-polish

- name: Verify cross-client user experience
run: npm run check:user-experience

- name: Verify Web client shell
run: node ..\scripts\check-web-client.mjs

Expand Down
151 changes: 129 additions & 22 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
workflow_dispatch:
inputs:
version:
description: "Release version, for example v0.1.6"
description: "Release version, for example v0.1.8"
required: true
type: string
publish_latest:
Expand Down Expand Up @@ -47,7 +47,7 @@
shell: bash
run: |
if [[ ! "${RELEASE_VERSION}" =~ ^v[0-9]+(\.[0-9]+){0,2}([-.][0-9A-Za-z.]+)?$ ]]; then
echo "::error::Release version must start with v, for example v0.1.6."
echo "::error::Release version must start with v, for example v0.1.8."
exit 1
fi
expected_version="$(tr -d '[:space:]' < VERSION)"
Expand Down Expand Up @@ -154,6 +154,60 @@
git push origin "refs/tags/${RELEASE_VERSION}"
fi

deployment:
name: Build self-hosting bundle
runs-on: ubuntu-latest
needs: prepare
steps:
- name: Checkout
uses: actions/checkout@v4

Check warning

Code scanning / Scorecard

Pinned-Dependencies Medium

score is 0: GitHub-owned GitHubAction not pinned by hash
Click Remediation section below to solve this issue

- name: Build deployment bundle
shell: bash
run: |
mkdir -p artifacts release-staging
artifact="$(pwd)/artifacts/TaskBridge-${RELEASE_VERSION}-deployment.zip"
bundle_root="release-staging/TaskBridge-${RELEASE_VERSION}"
mkdir -p "$bundle_root"
git archive --format=tar HEAD deploy web README.md VERSION | tar -xf - -C "$bundle_root"

release_image="ghcr.io/${GITHUB_REPOSITORY_OWNER,,}/taskbridge:${RELEASE_VERSION}"
release_number="${RELEASE_VERSION#v}"
for env_file in \
"$bundle_root/deploy/.env.example" \
"$bundle_root/deploy/.env.local.example"; do
sed -i "s#^TASKBRIDGE_BACKEND_IMAGE=.*#TASKBRIDGE_BACKEND_IMAGE=${release_image}#" "$env_file"
sed -i "s#^APP_VERSION=.*#APP_VERSION=${release_number}#" "$env_file"
done
sed -i \
"s#ghcr.io/27xk/taskbridge:latest#${release_image}#g" \
"$bundle_root/deploy/docker-compose.release.yml"

(
cd release-staging
zip -qr "$artifact" "TaskBridge-${RELEASE_VERSION}"
)
unzip -l "$artifact" | tee deployment-files.txt
for required in \
"deploy/docker-compose.release.yml" \
"deploy/.env.example" \
"deploy/nginx.web.conf" \
"deploy/start-local.cmd" \
"deploy/start-local.sh" \
"web/index.html"; do
if ! grep -Fq "$required" deployment-files.txt; then
echo "::error::Deployment bundle is missing ${required}."
exit 1
fi
done

- name: Upload deployment artifact
uses: actions/upload-artifact@v4

Check warning

Code scanning / Scorecard

Pinned-Dependencies Medium

score is 0: GitHub-owned GitHubAction not pinned by hash
Click Remediation section below to solve this issue
with:
name: taskbridge-deployment
path: artifacts/*.zip
if-no-files-found: error

android:
name: Build Android APK
runs-on: ubuntu-latest
Expand Down Expand Up @@ -198,13 +252,13 @@
fi
done
if [ "$configured" -eq 0 ]; then
echo "::notice::Android signing secrets are not configured; building an unsigned release APK."
echo "::notice::Skipping public Android APK because signing secrets are not configured."
echo "TASKBRIDGE_ANDROID_SIGNED_RELEASE=false" >> "$GITHUB_ENV"
exit 0
fi
if [ "${#missing[@]}" -gt 0 ]; then
echo "::error::Incomplete Android signing secrets: ${missing[*]}."
echo "Configure all Android signing secrets or leave all of them empty for an unsigned APK." >&2
echo "Configure all Android signing secrets, or remove all of them to omit the APK from the public release." >&2
exit 1
fi
echo "$ANDROID_KEYSTORE_BASE64" | base64 --decode > release.keystore
Expand All @@ -226,6 +280,7 @@
run: ./gradlew testReleaseUnitTest -PTASKBRIDGE_USE_CHINA_MIRRORS=false -PTASKBRIDGE_BASE_URL="${TASKBRIDGE_BASE_URL}" -PTASKBRIDGE_WS_URL="${TASKBRIDGE_WS_URL}" --stacktrace

- name: Build release APK
if: env.TASKBRIDGE_ANDROID_SIGNED_RELEASE == 'true'
run: ./gradlew :app:assembleRelease -PTASKBRIDGE_USE_CHINA_MIRRORS=false -PTASKBRIDGE_BASE_URL="${TASKBRIDGE_BASE_URL}" -PTASKBRIDGE_WS_URL="${TASKBRIDGE_WS_URL}" --stacktrace
env:
ANDROID_KEYSTORE_PATH: ${{ github.workspace }}/android/release.keystore
Expand All @@ -234,12 +289,9 @@
ANDROID_KEY_PASSWORD: ${{ secrets.ANDROID_KEY_PASSWORD }}

- name: Verify Android APK signature
if: env.TASKBRIDGE_ANDROID_SIGNED_RELEASE == 'true'
shell: bash
run: |
if [ "$TASKBRIDGE_ANDROID_SIGNED_RELEASE" != "true" ]; then
echo "::notice::Skipping Android APK signature verification because this release is unsigned."
exit 0
fi
apk_path="$(find app/build/outputs/apk/release -maxdepth 1 -type f -name 'app-release.apk' | head -n 1)"
if [ -z "$apk_path" ]; then
echo "No release APK was produced." >&2
Expand All @@ -258,24 +310,21 @@
"$apksigner" verify --verbose --print-certs "$apk_path"

- name: Prepare Android artifact
if: env.TASKBRIDGE_ANDROID_SIGNED_RELEASE == 'true'
shell: bash
run: |
mkdir -p ../artifacts
find app/build/outputs/apk -maxdepth 3 -type f -print
if [ "$TASKBRIDGE_ANDROID_SIGNED_RELEASE" = "true" ]; then
apk_path="$(find app/build/outputs/apk/release -maxdepth 1 -type f -name 'app-release.apk' | head -n 1)"
artifact_name="TaskBridge-${RELEASE_VERSION}-android.apk"
else
apk_path="$(find app/build/outputs/apk/release -maxdepth 1 -type f -name 'app-release-unsigned.apk' | head -n 1)"
artifact_name="TaskBridge-${RELEASE_VERSION}-android-unsigned.apk"
fi
apk_path="$(find app/build/outputs/apk/release -maxdepth 1 -type f -name 'app-release.apk' | head -n 1)"
artifact_name="TaskBridge-${RELEASE_VERSION}-android.apk"
if [ -z "$apk_path" ]; then
echo "No release APK was produced." >&2
exit 1
fi
cp "$apk_path" "../artifacts/${artifact_name}"

- name: Upload Android artifact
if: env.TASKBRIDGE_ANDROID_SIGNED_RELEASE == 'true'
uses: actions/upload-artifact@v4
with:
name: taskbridge-android
Expand Down Expand Up @@ -314,6 +363,9 @@
- name: Audit npm dependencies
run: npm audit --audit-level=high

- name: Rebuild Electron native dependencies
run: npm run rebuild:native

- name: Verify desktop endpoint config
run: npm run check:desktop-endpoint-config

Expand Down Expand Up @@ -353,6 +405,9 @@
- name: Verify desktop theme config
run: npm run check:desktop-theme

- name: Verify desktop efficiency config
run: npm run check:desktop-efficiency

- name: Verify desktop docs consistency
run: npm run check:desktop-docs

Expand Down Expand Up @@ -419,6 +474,12 @@
- name: Verify security governance config
run: npm run check:security-governance

- name: Verify priority UX polish
run: npm run check:ux-priority-polish

- name: Verify cross-client user experience
run: npm run check:user-experience

- name: Verify Web client shell
run: node ..\scripts\check-web-client.mjs

Expand All @@ -434,6 +495,9 @@
- name: Smoke test Web client shell
run: node ..\scripts\smoke-web-client.mjs

- name: Build desktop application
run: npm run build

- name: Prepare Windows signing certificate
shell: pwsh
run: |
Expand All @@ -442,11 +506,11 @@
if (-not $hasCertificate -and -not $hasPassword) {
"TASKBRIDGE_WINDOWS_SIGNED_RELEASE=false" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
"CSC_IDENTITY_AUTO_DISCOVERY=false" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8
Write-Host "::notice::Windows signing certificate is not configured; building an unsigned installer."
Write-Host "::notice::Skipping public Windows installer because signing secrets are not configured."
exit 0
}
if (-not $hasCertificate -or -not $hasPassword) {
throw "Incomplete Windows signing secrets. Configure both WINDOWS_CERTIFICATE_BASE64 and WINDOWS_CERTIFICATE_PASSWORD, or leave both empty for an unsigned installer."
throw "Incomplete Windows signing secrets. Configure both values, or remove both to omit the installer from the public release."
}
$certificatePath = Join-Path $env:RUNNER_TEMP "taskbridge-codesign.pfx"
[IO.File]::WriteAllBytes($certificatePath, [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE_BASE64))
Expand All @@ -461,6 +525,7 @@
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}

- name: Build Windows installer
if: env.TASKBRIDGE_WINDOWS_SIGNED_RELEASE == 'true'
run: npm run dist
env:
TASKBRIDGE_BASE_URL: ${{ env.TASKBRIDGE_BASE_URL }}
Expand All @@ -469,12 +534,9 @@
ELECTRON_BUILDER_CACHE: ${{ github.workspace }}\\.cache\\electron-builder

- name: Verify Windows installer signatures
if: env.TASKBRIDGE_WINDOWS_SIGNED_RELEASE == 'true'
shell: pwsh
run: |
if ($env:TASKBRIDGE_WINDOWS_SIGNED_RELEASE -ne "true") {
Write-Host "::notice::Skipping Windows installer signature verification because this release is unsigned."
exit 0
}
$installers = @(Get-ChildItem -Path release -File | Where-Object { $_.Extension -eq '.exe' })
if ($installers.Count -eq 0) {
throw "No Windows installer .exe was produced."
Expand All @@ -487,17 +549,19 @@
}

- name: Prepare desktop artifacts
if: env.TASKBRIDGE_WINDOWS_SIGNED_RELEASE == 'true'
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path ..\artifacts | Out-Null
Get-ChildItem -Path release -Recurse -File | Select-Object FullName, Length
Get-ChildItem -Path release -File | Where-Object {
$_.Extension -in @('.exe', '.yml', '.blockmap') -or $_.Name -eq 'latest.yml'
$_.Extension -in @('.exe', '.blockmap') -or $_.Name -eq 'latest.yml'
} | ForEach-Object {
Copy-Item -LiteralPath $_.FullName -Destination ..\artifacts\$($_.Name)
}

- name: Validate desktop update artifacts
if: env.TASKBRIDGE_WINDOWS_SIGNED_RELEASE == 'true'
shell: pwsh
run: |
# Desktop auto-update publishes to the stable latest channel.
Expand All @@ -513,6 +577,7 @@
}

- name: Upload Windows artifacts
if: env.TASKBRIDGE_WINDOWS_SIGNED_RELEASE == 'true'
uses: actions/upload-artifact@v4
with:
name: taskbridge-desktop
Expand Down Expand Up @@ -617,6 +682,7 @@
runs-on: ubuntu-latest
needs:
- android
- deployment
- desktop
- docker
steps:
Expand All @@ -642,6 +708,18 @@
with:
subject-path: release-artifacts/*

- name: Delete assets from an existing release
shell: bash
run: |
if gh release view "$RELEASE_VERSION" >/dev/null 2>&1; then
gh release view "$RELEASE_VERSION" --json assets --jq '.assets[].name' |
while IFS= read -r asset_name; do
[ -z "$asset_name" ] || gh release delete-asset "$RELEASE_VERSION" "$asset_name" --yes
done
fi
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Publish GitHub Release
uses: softprops/action-gh-release@v2
with:
Expand All @@ -652,6 +730,11 @@
files: release-artifacts/*
fail_on_unmatched_files: true
body: |
The deployment bundle is ready for Docker Compose self-hosting. Android and
Windows downloads are attached only when their signing secrets were configured
and the generated files passed signature verification. Unsigned client files are
never attached to a public release.

Backend Docker images:

- `${{ needs.docker.outputs.ghcr_image }}:${{ env.RELEASE_VERSION }}`
Expand All @@ -663,3 +746,27 @@
- `${{ needs.docker.outputs.dockerhub_image }}:latest`
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Verify published release assets
shell: bash
run: |
asset_names="$(gh release view "$RELEASE_VERSION" --json assets --jq '.assets[].name')"
required_assets=("TaskBridge-${RELEASE_VERSION}-deployment.zip" "SHA256SUMS.txt")
for required_asset in "${required_assets[@]}"; do
if ! grep -Fxq "$required_asset" <<<"$asset_names"; then
echo "::error::Published release is missing ${required_asset}."
exit 1
fi
done

forbidden_assets=("*unsigned*" "builder-debug.yml")
while IFS= read -r asset_name; do
for forbidden_pattern in "${forbidden_assets[@]}"; do
if [[ "$asset_name" == $forbidden_pattern ]]; then
echo "::error::Published release contains forbidden asset ${asset_name}."
exit 1
fi
done
done <<<"$asset_names"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
7 changes: 5 additions & 2 deletions PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ product

## Users

TaskBridge is for people who manage personal or work tasks across a desktop computer, an Android phone, and a small desktop or home-server deployment. They need to capture work quickly, see what matters today, keep using the app offline after first login, and sync changes when the network returns.
TaskBridge is for people who manage personal or work tasks across a desktop computer, an Android phone, a browser, and a small desktop or home-server deployment. They need to capture work quickly, see what matters today, keep using cached tasks after an initial sync, and understand when signing in again is required before changes can reach other devices.

## Product Purpose

TaskBridge provides a local-first, self-hostable task system with desktop, Android, widget, floating-window, and static Web/PWA clients. Success means a user can connect to a server once, trust local task handling afterward, and move between devices without relearning the workflow.
TaskBridge provides a local-first, self-hostable task system with desktop, Android, widget, floating-window, and static Web/PWA clients. Success means users can trust local task handling, move between devices without relearning the workflow, and distinguish local availability from authenticated online sync. Each client can retain the scoped local workspace after natural session expiry, but sending queued changes always requires signing in again.

## Brand Personality

Expand All @@ -27,6 +27,9 @@ Avoid marketing-first landing-page patterns inside the product UI, including ove
- Hide complexity until it is useful: schedule, reminder, priority, template, repeat, and checklist controls should remain available without dominating the first screen.
- Keep destructive or secondary actions out of the main path: completion and recovery are primary list actions; editing, template use, and deletion can sit behind a secondary menu.
- Preserve self-hosting clarity: server setup and connection language should help non-developers choose the right entry point without duplicate decision trees.
- Give regular users one server URL: Release Compose clients use the shared `8080` entry; direct API and WebSocket endpoints remain an advanced deployment option.
- Keep healthy infrastructure quiet: sync panels should remain visible only when a user needs to make a decision or recover an item.
- Make installed-version and update discovery available inside each packaged client instead of requiring users to infer it from filenames.

## Accessibility & Inclusion

Expand Down
Loading
Loading