尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Paired-device Android app for two people to share a persistent drawing canvas directly on the phone's wallpaper and lock screen
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗
Mulberry is a paired-device Android app for two people who share a persistent drawing canvas, with a Next.js landing site in the same Turborepo workspace. The foreground app is where users draw, pair, and manage settings; your wallpaper shows the latest local canvas snapshot through an Android live wallpaper.
The product is built for ambient connection, not group collaboration. Active devices synchronize quickly over WebSocket, while sleeping or backgrounded devices converge later through push-triggered recovery and revision reconciliation.
flowchart LR
A["Android app<br>Device A"] -->|REST + WebSocket ops| B["Fastify backend"]
C["Android app<br>Device B"] -->|REST + WebSocket ops| B
B --> D["PostgreSQL<br>users, sessions, invites, ops, snapshots"]
B --> E["Firebase Cloud Messaging"]
E --> A
E --> C
A --> F["Live wallpaper<br>local cached render"]
C --> G["Live wallpaper<br>local cached render"]
The backend owns authentication, pairing, operation ordering, replay, and snapshots. Android owns touch capture, optimistic rendering, local Room/DataStore persistence, wallpaper rendering, and opportunistic recovery.
[!IMPORTANT] The wallpaper surface is display-only. Android does not allow this app to provide reliable interactive drawing from the wallpaper surface or unrestricted background networking while the device is asleep.
.
├── apps
│ ├── backend # Fastify + TypeScript API, WebSocket sync, Postgres migrations
│ ├── mobile # Android app built with Kotlin, Compose, Hilt, Room, WorkManager
│ └── web # Next.js landing site with ShadCN/Tailwind styling
├── docs
│ ├── product-prd.pdf # Product requirements and platform constraints
│ └── implementation-plan.pdf
├── lib
│ └── banner.png # README banner
├── scripts
│ └── reset-local-onboarding.sh
├── pnpm-workspace.yaml # PNPM workspace package map
├── turbo.json # Turborepo task pipeline
├── docker-compose.yml # Local Postgres + backend
└── .env.example
Clone the repository, then create a local environment file:
cp .env.example .env
Start the local backend and database from the repository root:
docker compose up --build
The compose stack starts:
localhost:5432localhost:8080Check the backend:
curl http://localhost:8080/health
Run the Android dev build from apps/mobile:
./gradlew :app:installDevDebug
The devDebug variant points at http://10.0.2.2:8080/, so an Android emulator can reach the Dockerized backend without additional network configuration.
The backend reads configuration from environment variables:
| Variable | Required | Description |
|---|---|---|
PORT | No | API port. Defaults to 8080. |
DATABASE_URL | No | PostgreSQL connection string. Docker Compose sets this automatically. |
ALLOW_DEV_GOOGLE_TOKENS | No | Allows development Google tokens when true. Defaults to false when NODE_ENV=production; local Compose enables it. |
GOOGLE_SERVER_CLIENT_ID | For real auth | OAuth server client ID used to verify Google ID tokens. |
FIREBASE_SERVICE_ACCOUNT_PATH | For FCM | Path to a Firebase service account JSON file. |
FIREBASE_SERVICE_ACCOUNT_JSON | For FCM | Inline Firebase service account JSON. |
SUPABASE_URL | For wallpaper storage | Supabase project URL for wallpaper admin and catalog storage access. |
SUPABASE_SERVICE_ROLE_KEY | For wallpaper storage | Supabase service-role key used by the backend for wallpaper management. |
SUPABASE_WALLPAPER_BUCKET | For wallpaper storage | Bucket name used to store wallpaper assets. |
WALLPAPER_ADMIN_PASSWORD | For wallpaper admin | Admin header secret required by the wallpaper management routes. |
CANNY_SSO_PRIVATE_KEY | For in-app feedback | Private Canny SSO key used by the backend to sign widget users. |
For real Google Sign-In, update .env:
GOOGLE_SERVER_CLIENT_ID=your-google-oauth-server-client-id
For Firebase Cloud Messaging, provide either FIREBASE_SERVICE_ACCOUNT_PATH or FIREBASE_SERVICE_ACCOUNT_JSON. Without Firebase credentials, the backend uses a no-op sender so local foreground sync still works.
The Android app has two product flavors:
| Flavor | API base URL | Debug menu |
|---|---|---|
dev | http://10.0.2.2:8080/ | Enabled |
prod | PROD_API_BASE_URL, default https://api.mulberry.my/ | Disabled |
GOOGLE_SERVER_CLIENT_ID and PROD_API_BASE_URL can be provided through apps/mobile/local.properties or the shell environment:
GOOGLE_SERVER_CLIENT_ID=your-google-oauth-server-client-id
PROD_API_BASE_URL=https://your-production-api.example/
If you are building from a clean checkout, add the Firebase client config expected by each flavor you build:
apps/mobile/app/src/dev/google-services.json
apps/mobile/app/src/prod/google-services.json
The web app reads its public backend API URL from:
NEXT_PUBLIC_API_BASE_URL=http://localhost:8080
For local web development, copy apps/web/.env.example to apps/web/.env if you need to override the default local backend URL.
Fly.io is the canonical production deployment target for the backend configuration in this repository. The Fly app uses the existing root Dockerfile, serves the same public API contract at https://api.mulberry.my/, and keeps Supabase as the source of truth for Postgres and wallpaper storage.
Use the checked-in Fly config:
Install JavaScript workspace dependencies from the repository root:
pnpm install
Run all Turborepo build tasks:
pnpm build
pnpm --filter @mulberry/backend dev
Useful backend commands:
pnpm --filter @mulberry/backend build
pnpm --filter @mulberry/backend test
Fly.io deployment uses the root Dockerfile. During migration, Railway can continue to use the same image and public domain while DNS rollback remains in place. The Docker image uses the root PNPM workspace lockfile and builds only @mulberry/backend, so the backend deployment path stays independent from the web and Android apps.
pnpm --filter @mulberry/web dev
Useful web commands:
pnpm --filter @mulberry/web build
pnpm --filter @mulberry/web typecheck
pnpm --filter @mulberry/mobile build
pnpm --filter @mulberry/mobile install:dev
pnpm --filter @mulberry/mobile test
From the repository root, reset the local Docker database:
docker compose down -v
docker compose up --build
From the repository root, reset local Android onboarding state and the Docker database:
./scripts/reset-local-onboarding.sh
The backend exposes a small REST API plus one WebSocket endpoint:
| Area | Endpoints |
|---|---|
| Auth | POST /auth/google, POST /auth/refresh, POST /auth/logout |
| Devices | POST /devices/fcm-token, DELETE /devices/fcm-token |
| Bootstrap | GET /bootstrap |
| Profile | PUT /me/profile |
| Pairing | POST /invites, POST /invites/redeem, POST /invites/:inviteId/accept, POST /invites/:inviteId/decline |
| Canvas | GET /canvas/ops, GET /canvas/snapshot, GET /canvas/sync |
| Health | GET /health |
Canvas operations are stored as an ordered log. The server assigns authoritative revisions, clients apply operations in revision order, and recovery starts from the last applied revision.
Run backend tests:
pnpm --filter @mulberry/backend test
Run Android unit tests:
pnpm --filter @mulberry/mobile test
The current test coverage includes route handling, app bootstrap resolution, feature flags, drawing stroke behavior, wallpaper placement/status logic, sync JSON parsing, and recovery policy behavior.
Contributions are welcome. Read CONTRIBUTING.md before opening a pull request, especially for changes that touch product direction, privacy, security, backend API contracts, or Android release behavior.
This repository intentionally keeps the root and workspace packages marked as private to prevent accidental npm publishing.
Please do not open public issues for vulnerabilities. Report security concerns using the process in SECURITY.md.
Mulberry is open source under the MIT License.
Use the devDebug flavor. It is configured for http://10.0.2.2:8080/, which maps the Android emulator back to the host machine.
Confirm that the same GOOGLE_SERVER_CLIENT_ID is available to both the backend and Android build. For quick local backend development, ALLOW_DEV_GOOGLE_TOKENS=true is already set in docker-compose.yml.
This is expected under Android Doze, app standby, and OEM battery policies. Active foreground sessions use WebSocket sync; idle devices catch up through FCM and recovery when Android grants execution time.
docker compose down -v
docker compose up --build