Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
42 changes: 40 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,54 @@ concurrency:

jobs:
check:
name: Code health (TypeScript + Swift metrics)
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: astral-sh/setup-uv@v8.1.0
- uses: actions/setup-node@v4
with:
node-version: 20
node-version: 20.19
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm check

ios:
name: iOS build and Swift quality
runs-on: macos-26
timeout-minutes: 30
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 20.19
cache: pnpm
- run: pnpm install --frozen-lockfile
- name: Install native quality tools
run: brew install xcodegen peripheryapp/periphery/periphery
- name: Generate CocoaPods workspace
working-directory: ios
run: |
xcodegen generate
pod install --deployment
- name: Build iOS app
run: >-
xcodebuild
-workspace ios/Motion.xcworkspace
-scheme Motion
-sdk iphonesimulator
-destination 'generic/platform=iOS Simulator'
-derivedDataPath "$RUNNER_TEMP/motion-derived"
CODE_SIGNING_ALLOWED=NO
build
- name: Check Swift quality ratchets
env:
MOTION_INDEX_STORE: ${{ runner.temp }}/motion-derived/Index.noindex/DataStore
run: pnpm quality:swift
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
node_modules/
.wrangler/
dist/
coverage/
.parcel-cache/
*.tsbuildinfo

Expand Down
11 changes: 7 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,12 @@ pnpm install
pnpm run dev # relay :1999 + web :5173
# Reach & Dodge: http://localhost:5173/?debug=1 → mouse=hands, arrows=lean, space=squat
# Motion Maker: http://localhost:5173/?game=motion-maker&debug=1 → mouse=a hand, hold left mouse/space=grab
pnpm run typecheck
pnpm check # full TypeScript quality gate and debt ratchets
```

iOS builds only on a Mac with Xcode 16+ (`cd ios && xcodegen generate`). It
**compiles for the iOS Simulator**; camera + ReplayKit paths still need a
**physical device** to verify (see `PROJECT_STATUS.md`).
iOS builds only on a Mac with Xcode 16+. Generate the CocoaPods workspace with
`cd ios && xcodegen generate && pod install --deployment`, then build the
`Motion.xcworkspace` scheme. `pnpm quality:swift` checks the Swift formatting
and unused-code ratchets after a build when `MOTION_INDEX_STORE` points to its
index store. Camera + ReplayKit paths still need a **physical device** to verify
(see `PROJECT_STATUS.md`).
12 changes: 11 additions & 1 deletion PROJECT_STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ multiplayer are **v2**, already scaffolded and parked in-repo.

## Timeline

- **2026-08-12** — Adopted the Fleet code-health standard across TypeScript and
Swift: added focused protocol/controller/readiness tests, honest full-source
coverage, unused-code and cycle checks, complexity/duplication/dependency/
suppression/hygiene ratchets, and an unsigned iOS build plus Swift quality CI.
Existing measured debt remains explicit in GitHub issue #26.
- **2026-08-12** — Aligned the universal iPhone/iPad build, permission prompts,
setup guidance, recording states, bundled web game, and App Store copy around
device-neutral language; refreshed the personal-team distribution export.
Expand Down Expand Up @@ -116,7 +121,12 @@ multiplayer are **v2**, already scaffolded and parked in-repo.
- **`server/`** — PartyKit relay. The v2 browser/multiplayer transport (kept
typecheck-green); also the endpoint the live `streamToWebsite` preview connects
to today.
- Shared Ultracite lint baseline with a clean 39-file TypeScript check.
- Shared Ultracite lint baseline with a clean TypeScript check.
- **Code-health gate** — `pnpm check` enforces the cross-platform TypeScript
quality baseline; hosted macOS CI also builds the CocoaPods workspace and
prevents Swift format or unused-code debt from increasing. Full TypeScript
coverage is measured rather than scoped to tested modules, and all temporary
baseline debt is owned by GitHub issue #26.
- **Self-contained app** — the web build is bundled into the app
(`ios/Resources/webgame`, refreshed by `scripts/sync-webgame.sh`); `GameConfig`
auto-prefers it, so the app runs on an iOS device with **no dev server / Mac network**.
Expand Down
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,14 @@ refresh the bundle after changes with `./scripts/sync-webgame.sh`.

## Checks and public landing release

Pushes and pull requests run the repository-owned web/server typecheck, web
build, and static landing contract. Physical-device and signing evidence stays
manual because hosted CI cannot exercise the camera path.
Pushes and pull requests run the repository-owned TypeScript code-health gate
and an unsigned iOS Simulator build. The gate covers formatting and lint,
types, tests and full-source coverage, unused code, complexity, duplication,
cycles, dependency risk, suppressions, and repository hygiene. Existing debt is
ratcheted so it cannot grow and is tracked in
[#26](https://github.com/Significant-Hobbies/motion/issues/26). Physical-device
and signing evidence stays manual because hosted CI cannot exercise the camera
path.

```bash
pnpm check
Expand Down
2 changes: 1 addition & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://biomejs.dev/schemas/2.5.5/schema.json",
"$schema": "https://biomejs.dev/schemas/2.5.8/schema.json",
"extends": ["ultracite/biome/core"],
"vcs": { "enabled": true, "clientKind": "git", "useIgnoreFile": true },
"files": {
Expand Down
11 changes: 5 additions & 6 deletions ios/Sources/Motion/Recording/ScreenRecorder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
// game canvas won't be in the video, OR
// • Switch GameConfig.source to `.bundled` (a file:// load): a same-process/local origin
// is more reliably captured than a remote dev-server origin in some configurations.
// See the TODO at the bottom of this file. Do NOT silently assume capture works.
// Physical-device verification remains tracked in GitHub issue #20. Do NOT
// silently assume capture works.
// ═══════════════════════════════════════════════════════════════════════════════════
//
// Bracketing: the recorder is ARMED by the on-screen Record toggle, then started by the
Expand Down Expand Up @@ -222,11 +223,9 @@ final class ScreenRecorder {
return current
}

// TODO(on-device verify): confirm ReplayKit capture includes the WKWebView game canvas
// AND the AVCaptureVideoPreviewLayer camera inset in the SAVED video. If the game canvas
// is blank, switch GameConfig.source to .bundled (file:// load) and/or fall back to a
// native camera-inset + HUD overlay (drop the webview from the captured frame). See the
// header block above for the full fallback plan.
// GitHub issue #20 tracks physical-device verification that ReplayKit includes both
// the WKWebView game canvas and AVCaptureVideoPreviewLayer camera inset in the saved
// video. The header block above records the fallback if either surface is blank.
}

// MARK: - Off-actor asset writer
Expand Down
27 changes: 27 additions & 0 deletions knip.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"$schema": "https://unpkg.com/knip@latest/schema.json",
"ignoreExportsUsedInFile": true,
"workspaces": {
".": {
"entry": ["scripts/**/*.mjs", "vitest.config.ts", "protocol/**/*.test.ts"],
"project": ["scripts/**/*.mjs", "vitest.config.ts", "protocol/**/*.ts"],
"ignoreDependencies": ["@biomejs/biome", "@vitest/coverage-v8", "jscpd"],
"ignoreIssues": {
"protocol/protocol.ts": ["types"]
}
},
"server": {
"entry": ["src/server.ts"],
"project": ["src/**/*.ts"]
},
"web": {
"entry": ["src/app/main.ts", "src/sdk/index.ts", "src/**/*.test.ts"],
"project": ["src/**/*.ts"],
"ignoreIssues": {
"src/sdk/controller.ts": ["types"],
"src/sdk/game.ts": ["types"],
"src/sdk/types.ts": ["types"]
}
}
}
}
34 changes: 30 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,46 @@
"dev": "concurrently -n server,web -c cyan,magenta \"pnpm run dev:server\" \"pnpm run dev:web\"",
"dev:server": "pnpm --filter motion-server run dev",
"dev:web": "pnpm --filter motion-web run dev",
"format:check": "node scripts/check-code-health.mjs format",
"lint": "ultracite check .",
"typecheck": "pnpm --filter motion-server run typecheck && pnpm --filter motion-web run typecheck",
"build": "pnpm --filter motion-web run build",
"test": "vitest run",
"test:coverage": "vitest run --coverage",
"knip": "knip --no-exit-code --reporter symbols",
"knip:strict": "knip --reporter symbols --no-config-hints",
"quality:coverage": "node scripts/check-code-health.mjs coverage",
"quality:unused": "pnpm run knip:strict",
"quality:complexity": "node scripts/check-code-health.mjs complexity",
"quality:duplication": "node scripts/check-code-health.mjs duplication",
"quality:cycles": "knip --cycles --no-config-hints",
"quality:dependencies": "node scripts/check-code-health.mjs dependencies",
"quality:suppressions": "node scripts/check-code-health.mjs suppressions",
"quality:hygiene": "node scripts/check-code-health.mjs hygiene",
"quality:swift-format": "node scripts/check-code-health.mjs swift-format",
"quality:swift-unused": "node scripts/check-code-health.mjs swift-unused",
"quality:swift": "pnpm run quality:swift-format && pnpm run quality:swift-unused",
"check:landing": "node scripts/check-landing.mjs",
"check": "pnpm lint && pnpm typecheck && pnpm build && pnpm check:landing",
"quality": "pnpm format:check && pnpm lint && pnpm typecheck && pnpm test && pnpm build && pnpm check:landing && pnpm quality:coverage && pnpm quality:unused && pnpm quality:complexity && pnpm quality:duplication && pnpm quality:cycles && pnpm quality:dependencies && pnpm quality:suppressions && pnpm quality:hygiene",
"check": "pnpm quality",
"deploy": "pnpm exec wrangler pages deploy landing --project-name motion --branch main"
},
"devDependencies": {
"@biomejs/biome": "2.5.5",
"@biomejs/biome": "2.5.8",
"@vitest/coverage-v8": "4.1.10",
"concurrently": "^9.1.0",
"jscpd": "5.0.14",
"knip": "6.32.2",
"ultracite": "7.10.2",
"wrangler": "4.120.0"
"vitest": "4.1.10",
"wrangler": "4.121.0"
},
"engines": {
"node": ">=20"
"node": ">=20.19"
},
"pnpm": {
"overrides": {
"nanoid@>=3.0.0 <4.0.0": "3.3.17"
}
}
}
Loading
Loading