Skip to content

Mobile app for Android - #20

Open
wdembinski wants to merge 14 commits into
developmentfrom
feat/mobile-app-for-android
Open

wdembinski wants to merge 14 commits into
developmentfrom
feat/mobile-app-for-android

Conversation

@wdembinski

Copy link
Copy Markdown
Owner

Let's design a mobile app for Android.
It should have the same features as in the web cloud - to control tasks on the desktop client.
The UI should be designed for mobile though (be responsive).

wdembinski and others added 14 commits August 18, 2026 01:24
… 27)

Step 1 of 12. The interactive prompt for these went unanswered in a headless
session, so they are recorded here instead of guessed: a new apps/mobile (not
a responsive apps/web), an installable PWA (not Capacitor/TWA, since there is
no Android SDK/JDK/keystore on this machine), its own SWA on its own
subdomain, and a one-time human infra setup that blocks no coding step.
…ep 2)

Step 2 of 12. Applies the rule already used at the desktop/apps/web line in
Phase 26 one layer down: share when a file has no host in it, fork when
sharing means threading optional props through. A new packages/cloud absorbs
apps/web's sync layer (auth, presence, the board's cloud plumbing) since two
browser clients cannot each own a copy; @tm/ui's card/detail/chat/dialog
components are already host-agnostic and stay put; the shell, board
navigation, move interaction and detail route fork because a phone has no
rail, no grid, and no drag; the chain overlay and drag handle drop, the
latter for free since TaskCard.tsx already gates it on onLinkStart.
Step 3 of 12. Moves everything step 2 marked shared — auth/, presence.ts, and
out of board/ the poll/SSE loop, HTTP transport, boardSelectors,
useBoardExtras and the three connection-state banners, plus
settings/SettingsScreen.tsx — out of apps/web/src into a new packages/cloud
(@tm/cloud), copying @tm/ui's tsup/exports/peer-deps conventions exactly.
apps/web imports everything back through @tm/cloud/* and is down to the six
files that stay host-bound: App.tsx, main.tsx, env.ts, vite-env.d.ts,
BoardScreen.tsx and BoardToolbar.tsx.

env.ts and vite-env.d.ts stay in apps/web on purpose — import.meta.env is a
Vite build-time replacement esbuild cannot emit in CJS. @tm/cloud exports the
WebConfig interface (packages/cloud/src/config.ts) and takes the config as a
parameter instead, which is already how useCloudBoard and CloudAuth are
called.

@tm/ui is externalized in @tm/cloud's tsup build (alongside its subpaths) so
useBoardExtras's runtime useTransport() reads the host's own TransportContext
rather than a second bundled copy — @tm/ui must never import @tm/cloud in
return, or the two externals cycle.

test/shell-parity.test.ts's WEB_TREE became WEB_TREES (apps/web/src and
packages/cloud/src both), since apps/web/src alone dropped from 27 to 6
non-test sources and the ">10" breach guard would otherwise pass while
scanning a tree too small to ever fail. The SettingsScreen path it reads
moved with the file.

.github/workflows/deploy.yml's web path filter gained packages/cloud/** —
without it, a change here would stop deploying web with nothing red in CI.

pnpm-lock.yaml regenerated for the new workspace package.
Step 4 of 12. A new @tm/mobile — package.json, tsconfig, vite/vitest config and
index.html (viewport-fit=cover), modelled on apps/web's own. Its shell is a
compact top bar (title, ClientPicker, sync dot, sign out) and a bottom tab bar
with the same five destinations in the same order as web/desktop, Scratch run
dimmed "desktop only" — MobileShell is deliberately not AppShell/NavRail, a
phone has no rail and no mouse to hover it. main.tsx mounts the shared theme
(scaleTheme(appDarkTheme, BASE_FONT_PX), useGlobalStyles, Toaster) the same way
both other hosts do, at 100dvh; TransportProvider wraps the tree in @tm/cloud's
HttpTransport, and sign-in reuses SignInScreen/useCloudAuth unchanged.

Performance, Attention and Settings route to their shared components as-is; My
Tasks is a placeholder until step 6 builds the tap-to-move board.

Extends test/shell-parity.test.ts's HOST_TREES/HOST_DOCUMENTS to cover mobile
from this commit, and adds a nav-parity assertion — mobile and web must expose
the same five destination ids in the same order — confirmed red against a
reordered NAV before landing.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
html/body/#root at 100% resolves against the large viewport in a
phone browser tab, leaving MobileShell's tab bar under the address
bar and unreachable — switch to 100dvh, a no-op on desktop and web.
Stack Performance's rail and SettingsScreen's nav below a 599px
breakpoint, and cap AddTaskDialog/ArchivedCardsDialog to the
viewport width, so none of the three overflow a 360px phone.

Asserted as source text in test/shell-parity.test.ts, the same way
the file already asserts scaleTheme(appDarkTheme, — there is no DOM
harness in this repo, and a CSS rule nobody asserts is a rule that
silently reverts.
A chip row (ColumnChips) picks one column at a time; TaskCard renders
each card unchanged, with a Move-to... menu (BoardCardRow) wired to
useCloudBoard's setStatus in place of drag-and-drop. TaskCard's
draggable/dragging/onDragStart/onDragEnd become optional so a phone
never has to pass no-ops for them.

Also wires the toolbar's phone form: a FAB opening the shared
AddTaskDialog, the Show-Done switch, archived cards, JIRA sync, and
the commit graph as a full-screen sheet (GitGraphSheet) rather than
the desktop's 340px pane.
Step 7 of 12. A tap on a card now pushes TaskScreen — @tm/ui's TaskDetail,
the same component the desktop draws in its 40% pane, wrapped for a phone in
a full-screen fixed surface with a back chevron instead of a side-by-side
layout. TaskDetail itself needed no forking: its root is already flex: 1;
minWidth: 0 with no fixed width, and 24 of its ~25 props are optional.

BoardScreen wires selectedTaskId through to it exactly as apps/web's own
BoardScreen does: the pending-overlaid selected task, its chain (subtasks or
step siblings), its parent for the breadcrumb, its merge requests and
attachments (byTask over useBoardExtras, which already carries attachFiles
through @tm/cloud's HttpTransport — no host-only attachment:pick path for a
phone to reach), and chainLinks/chainTasksById/onUnlinkChain so TaskDetail's
own TaskChain section stands in for the chain overlay step 2 dropped. Passes
the same RELAY_NOTICE apps/web's own BoardScreen does, since mobile edits
travel the identical relay. onStatusChanged only records the optimistic
overlay (onStatusNoted, threaded from App.tsx's board.noteStatus) rather than
resending a command the pane's own State dropdown already sent.

index.html's viewport meta gains interactive-widget=resizes-content (Chrome
108+, which is what an installed WebAPK runs on) so 100dvh — already relied
on for MobileShell's tab bar — also shrinks for the on-screen keyboard,
keeping TaskDetail's fixed composer band above it rather than under it.
…step 8)

Push a browser history entry for every navigation Back should undo — tab
switch, task opened, sheet/dialog opened — and unwind it on popstate, so
an installed PWA's hardware Back no longer exits the app from an open
task or settings pane. What Back means given a stack lives in a pure
navStackReducer with its own test; the history wiring around it
(useBackStack) is untested plumbing. BoardScreen's task/dialog/sheet
state is lifted into the shared nav stack so every close (in-app "x" or
hardware Back) goes through the same onBack path.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…tep 9)

manifest.webmanifest (standalone display, #1f1f1f theme/background matching
useGlobalStyles, portrait orientation) plus two dependency-free PNG icons
generated by scripts/make-mobile-icons.mjs via Node's own zlib. A service
worker caches content-hashed /assets/* first and falls back navigations to
cache, with same-origin-GET-only routing in a pure, tested shouldHandle()
so the cloud API's SSE stream and OIDC exchange are never touched. Built by
a second Vite config (vite.sw.config.ts) so the fixed sw.js name and the
hashed main bundle don't have to share one Rollup output. The service
worker's own tsconfig (WebWorker libs) is split from the app's (DOM libs) —
mixing the two in one program conflicts on `self`.

staticwebapp.config.json now excludes webmanifest/json from the SPA
fallback and pins /sw.js to no-cache. scripts/verify-mobile-build.mjs
builds the app and asserts the manifest, icons and sw.js are all real and
wired together.
Add a `mobile` job to deploy.yml mirroring `web`: its own paths-filter
entry, a turbo build with the taskmanager-mobile IAM client id, the
staticwebapp.config.json copy, and Azure/static-web-apps-deploy@v1 into
its own Static Web App.

AZURE_STATIC_WEB_APPS_API_TOKEN_MOBILE is checked in a step rather than
a job-level `if` (secrets aren't available there), so the job stays
inert with a warning until the one-time Azure setup exists, instead of
failing the whole workflow on every push that touches apps/mobile.

Document that one-time setup — the SWA, the DNS record, the secret, and
the taskmanager-mobile IAM client/redirect URI — in docs/09, and update
docs/11's secrets table and deploy matrix to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…p 11)

Step 11 of 12. Re-opened all ten files/areas the plan named on the finished tip
(9c8eabd) and re-ran every gate there rather than trusting an earlier step's numbers
on the same commit.

Two real breakages, both the same shape: step 3 moved httpTransport.ts,
polledEvents.ts, eventBus.ts and sseEvents.ts out of apps/web/src and into
packages/cloud/src, and anything that named their OLD location by path rather than
by import specifier went stale silently, since neither a prose comment nor a script
reading a source file as text sits on the module graph typecheck or build walks.

verify-remote-ipc.mjs and verify-remote-sse.mjs no longer ran at all — both bundle
the moved modules by a literal apps/web/src/board/* path. Neither is wired into
ci.yml or RELEASE.md's gates, so nothing red ever surfaced across eight steps of
this branch. Repointed both at packages/cloud/src and dropped the now-unused @web
alias; 16 remote-IPC checks and 36 push-channel checks pass clean.

test/shell-parity.test.ts's global-CSS guard silently lost packages/cloud/src:
SettingsScreen.tsx moved out of the apps/web/src entry HOST_TREES already covered,
into a path nothing named. Nothing had actually declared a stray rule there, which
is what makes it a coverage hole rather than a caught bug. packages/cloud/src now
joins HOST_TREES, on the same "covered from day one" reasoning already written for
apps/mobile/src.

TaskDetail.tsx's readOnlyNotice docstring named the old path and, more
substantively, still described the pre-Phase-27 three-tier transport ("relays only
a status change and a new card") that httpTransport.ts's own header says stopped
being true before this phase. Fixed both, since the file sits on this step's named
list; left half-corrected would have been worse than left alone.

Two more stale apps/web/src references turned up in packages/shared (ipcRelay.ts,
ipcEventFanout.ts) — outside this step's named list, so recorded in the plan doc
rather than edited.

pnpm format:check also caught two pre-existing Prettier violations in
apps/mobile/src/board (BoardCardRow.tsx, GitGraphSheet.tsx) from steps 6-7,
whitespace-only.

development sits exactly at this branch's merge-base and has not moved, so the
merge ahead is a plain fast-forward with nothing to reconcile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Step 12 of 12. Re-ran the full gate list fresh on 9db1f5b (step 11's own commit,
one past the tip its own numbers were measured on): pnpm format:check, turbo
typecheck --force, turbo build --force, pnpm test, verify-mobile-build.mjs, and
both ad hoc remote-IPC/SSE harnesses step 11 repointed. Every count matches step
11's independently-measured ones exactly — 12/12 typecheck, 8/8 build, 180 test
files (3005 tests) passed, 16 and 36 checks on the two harnesses.

Derived the per-package test sum from `vitest exec vitest list --filesOnly`
rather than the workspace layout, per the plan's own instruction: 181 files
across nine directories, with apps/mobile contributing exactly the two pure-
module tests the "no component tests" constraint predicts (navStack, shouldHandle)
and packages/cloud's 13 confirming the board modules step 3 moved are still
exercised, not just no-longer-flagged by shell-parity's HOST_TREES fix.

Recorded what stays unverifiable from a headless session: installing the PWA,
pressing Back on a device, tap-to-move against a live desktop, and a completed
CI deploy run — none of which any command here can exercise.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
development moved 100 commits ahead of this branch's fork point (PR #18's
single-board/native-tickets feature, PR #19's sync-error fix, and everything
those PRs themselves carried) while this branch did Phase 27's twelve steps.
Six real content conflicts plus one file-location conflict, resolved as follows:

- apps/web/src/App.tsx, board/BoardScreen.tsx: import-path conflicts against
  step 3's apps/web/src -> packages/cloud/src extraction. Kept the @tm/cloud
  paths, folded in development's genuinely new imports (BoardScope, syncGate,
  UnreachableBanner, selectAgentProjects).
- packages/cloud/src/settings/ProjectsSection.tsx: git's directory-rename
  heuristic already placed development's new file at the extracted location;
  confirmed and staged as-is (package-specifier imports only, no path fixes
  needed).
- .github/workflows/deploy.yml: NOT flagged as a conflict, but the auto-merge
  placed apps/client/package.json under the wrong filter (mobile: instead of
  web:), silently reintroducing the "web frozen on the old version" bug
  docs/11 describes fixing. Moved it to the web: filter;
  test/workflow-invariants.test.ts's own assertion now actually exercises it.
- docs/11-ci-cd-pipeline.md, docs/plan/README.md: narrative docs where both
  sides appended after the same anchor. Merged the deploy-filter table (kept
  the mobile row, fixed to match deploy.yml) and reordered README.md so
  development's fixes/phases precede this branch's own Phase 27 section,
  ahead of the shared "Conventions" tail both sides already had.
- test/shell-parity.test.ts: three describe-block conflicts (step 5's phone-fit
  block vs development's renamed agent-projects block, an EDITING-pane test
  using a WEB_TREE variable that no longer exists post-merge, and the
  HOST_ONLY_SECTIONS/ProjectsSection assertions) — kept both sides' tests,
  repointed every apps/web/src/settings/* reference the merge left stale to
  packages/cloud/src/settings/*.

None of the above wired mobile into the merge's new features on their own —
that surfaced only once the gates ran on the merged tree:

- packages/cloud/src/board/useCloudBoard.ts (shared, unmodified by this
  branch) already depended on a sibling ./syncGate module that development
  had only added under apps/web/src/board — a build break, not a conflict.
  Moved syncGate.ts/.test.ts into packages/cloud/src/board alongside it,
  repointed apps/web/src/App.tsx's import. UnreachableBanner.tsx stays
  apps/web-local; nothing shared needs it and mobile has no sync-curtain
  treatment yet (noted, not built here).
- shell-parity's NAV-parity test asserts apps/web and apps/mobile expose the
  same destinations. development added a Projects tile to apps/web; mobile's
  own NAV predates that PR and didn't have it, so the assertion would have
  gone red. Added the same tile to apps/mobile/src/App.tsx, rendering the
  shared <Projects/> component unmodified (same treatment as Performance and
  Attention) with a phone-fit media query added to its two-column layout
  (packages/ui/src/projects/Projects.tsx), matching step 5's own precedent.
  Also fixed the regex the test's own navIds() helper used: NavRail's
  accountItems prop (`{ id: 'signout', ... }`, new on development's side)
  collided with a whole-file scan for `{ id: '...' }`; scoped it to the NAV
  array literal specifically.
- selectBoardTasks/selectArchivedTasks (packages/cloud/src/board/
  boardSelectors.ts) gained a required `scope` parameter for the single-board
  feature. apps/mobile/src/board/BoardScreen.tsx has no scope-picker UI (only
  apps/web's BoardToolbar does) — passed the literal 'all' scope to preserve
  mobile's existing show-everything behaviour; a mobile scope switcher is a
  follow-up, not part of this merge.
- packages/cloud/src/settings/SettingsScreen.tsx's `projects` prop went from
  absent to required; apps/mobile/src/App.tsx's call site wasn't touched by
  either branch's diff so it went stale silently. Passed board.state.projects,
  matching apps/web's own call.

Gates run fresh on the merged tree, forced: pnpm format:check clean; turbo
typecheck --force 12/12; turbo build --force 8/8; pnpm test 192 files / 3256
tests passed (11 skipped), 0 failed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
development moved another 13 commits ahead since the last merge (d93cf0b) —
five PRs (#21-#27): PAT-based cloud connection, icon toggles replacing header
switches, a narrower nav rail, clipboard-paste-to-attach, GitHub PR pipeline
sync-status fix, Linux releases discontinued, and a projects/settings fix —
plus four releases (v0.90.1 through v0.97.4).

Conflicts, all textual or file-location, same shape as the last merge:

- docs/09-deploying-the-cloud-service.md, docs/11-ci-cd-pipeline.md: both sides
  edited the same deploy-matrix tables. Kept the apps/mobile row (ours) and
  folded in development's real changes — Linux dropped from release.yml's
  table (feat/stop-linux-releases removed the whole job), matching what
  release.yml itself now has (no `linux:` job, no §6).
- docs/plan/README.md: both sides appended narrative after the same anchor
  again. Same resolution as before — development's new fix section ("projects
  miss settings, and a plan-less project could not add tasks") precedes this
  branch's Phase 27 section, ahead of the shared "Conventions" tail.
- packages/cloud/src/settings/{TokensSection.tsx,tokensApi.ts,tokensApi.test.ts,
  tokensView.ts,tokensView.test.ts}: git's directory-rename heuristic again
  placed development's new PAT-management files (added under
  apps/web/src/settings) at the extracted packages/cloud/src/settings
  location. Confirmed and staged as-is — package-specifier and relative-
  sibling imports only.
- test/shell-parity.test.ts: the same WEB_TREE-vs-WEB_TREES collision as the
  last merge, recurring because development's own "projects miss settings" fix
  touched this file independently and re-diffed the same line. Same fix: kept
  WEB_TREES.flatMap(...).

Not flagged as conflicts, but broken by the merge and only surfaced once the
gates ran on the merged tree:

- packages/cloud/src/settings/SettingsScreen.tsx's SettingsScreenProps gained
  two required fields — apiBase and getAccessToken — for the new
  TokensSection. Neither branch's diff touched apps/mobile/src/App.tsx's
  <SettingsScreen projects={...} /> call site, so it went stale silently.
  Passed config.cloudApiBase and () => auth.getAccessToken(), matching
  apps/web's own call.

packages/ui/src/projects/Projects.tsx, AddTaskDialog.tsx and theme.ts all
auto-merged cleanly (ProjectAdmin's new repo-capability prop is optional,
paste-to-attach and the nav-rail width change don't touch anything mobile
overrides).

Gates run fresh on the merged tree, forced: pnpm format:check clean; turbo
typecheck --force 12/12; turbo build --force 8/8; pnpm test 204 files / 3382
tests passed (11 skipped), 0 failed. scripts/verify-mobile-build.mjs (21
checks), verify-remote-ipc.mjs (16 checks) and verify-remote-sse.mjs (36
checks) all still pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant