Skip to content

Add the mobile app shell with Inbox, Note and Tasks tabs - #623

Merged
JSdotNet merged 1 commit into
mainfrom
claude/mobile-inbox-slice-app-shell-56ff5c
Sep 24, 2026
Merged

JSdotNet merged 1 commit into
mainfrom
claude/mobile-inbox-slice-app-shell-56ff5c

Conversation

@JSdotNet

Copy link
Copy Markdown
Owner

Summary

Backlog plan item app-shell of plan mobile-inbox-slice. The phone app was one route deep (Inbox). It now has a shell that the Note and Tasks screens can live in:

  • a bottom tab bar;
  • a pairing gate that covers every route;
  • a sync status line in the app bar;
  • an Inbox draft that survives switching tabs.

Changes

  • Tab bar: MainLayout.razor shows a TabBar with Inbox (/), Note (/note) and Tasks (/tasks). Note and Tasks are placeholder pages (an h1 and an empty state) so the routes resolve.
  • Pairing gate in the layout: it moved out of Inbox.razor into a new PairingGate component that MainLayout renders in place of the page while the device is unpaired. Every route is gated once, the same way, and the tabs are hidden until the device is paired. When pairing succeeds, the credential store's Changed event puts the page back, so the Inbox loads on its own.
  • Sync status line: SyncStatus in the app bar reads ISyncStatusSource and shows "Not paired", "Synced 3m ago", "Not synced yet" or "Offline — n waiting". SyncStatusTracker supplies pairing state (from IDeviceCredentialStore) and the time of the Inbox's last successful load. RecordOffline(waiting) is where the outbox entry of this plan will report queue counts.
  • Draft survives tab switches: CaptureDraft is scoped and holds the Inbox's unsent text. The Router remounts pages on navigation, so the old _draft field lost the text on every tab switch.
  • One registration for both hosts: AddMobileShell() is called by the MAUI head and by the browser harness.
  • Library components: TabBar, SyncStatusLine and SyncStatusReading live in a new Backlog.UI.Components/Shell folder, with CSS in components.css (48px touch targets; the active tab is marked by weight and a top bar, not colour alone).
  • Storybook: a new App shell page under Structure and navigation. It also answers /app-shell/{Tab}, so the demo tabs are live. StorybookOrderTests gets the Shell chapter.
  • Fix found along the way: neither mobile host linked its scoped-CSS bundle, so MainLayout.razor.css has never applied. index.html now links Backlog.Mobile.styles.css and the harness App.razor links Backlog.Mobile.WebHarness.styles.css.
  • Harness phone frame fits a 375px viewport: width: min(390px, 100%), no padding, and height: calc(100vh - 2rem).

Testing

  • dotnet build Backlog.sln: 0 errors, MAUI head included. No new warnings in the changed code.
  • Unit tests: every test project run directly, because dotnet test flapped to "Zero tests ran". 23 projects, 0 failures. ArchitectureTests 173, UI.Components 2397, Desktop.UI 3092, Mobile.UI 39.
    • New: PairingGateTests (replaces InboxPairingTests; runs on every route), ShellNavigationTests and library ShellTests.
    • ShellNavigationTests covers three tabs with the current one lit, a page per tab, the draft surviving Inbox → Tasks → Inbox, the synced status line, and the tracker's state changes.
  • Browser QA at 375×812 (Playwright, Aspire logs monitored):
    • ✅ The unpaired gate on /, /note and /tasks shows the pairing box and "Not paired", no tab bar, and no horizontal overflow.
    • ✅ Storybook /app-shell: all five stories render. Real touch taps move the URL, aria-current and the active class, and tabs are 48px tall. No console errors.
    • ⚠️ Not browser-validated: focus landing on the h1 after tab navigation, the draft round trip, "Synced just now", and the tab bar pinned to the bottom of the frame. They need a paired device, and pairing was blocked by the environment: Docker ran out of network subnets, so the Cosmos emulator never started. The draft round trip and the status line are covered by the unit tests above. Focus-on-h1 relies on the existing FocusOnNavigate Selector="h1", and every page has one h1.
    • The frame-height fix came after QA and has not been re-checked in a browser.
  • Aspire logs: no errors except the Polly timeouts from a deliberate pairing attempt against the missing Cosmos backend. The gate showed the timeout message and stored no credential.

Related

Plan item app-shell of mobile-inbox-slice (Backlog entry d5b2199b-bcf7-4d98-9277-2ec2e0f220da). No GitHub issue.

Follow-ups

  • Update the Mobile App block in .arc42/05-building-block-view.md once this lands.
  • The outbox entry of this plan wires real queue counts into SyncStatusTracker.RecordOffline.
  • Re-run the paired-state browser checks once the Docker network pool is freed.

Checklist

  • Scope is focused and excludes unrelated changes
  • Documentation or instruction files were updated when governance or behavior changed. The arc42 Mobile App block is a follow-up, per the plan item.
  • Running Aspire instances for this checkout were stopped before PR creation
  • Follow-up work is captured when the implementation is intentionally partial
  • Issues this PR closes are linked with a closing keyword, or there are none

🤖 Generated with Claude Code

The phone was one route deep. It now has a shell two more screens can live in:

- A bottom TabBar (Inbox /, Note /note, Tasks /tasks) and placeholder
  Note and Tasks pages so the routes resolve.
- The pairing gate lifted out of Inbox into MainLayout, so every route is
  gated once, the same way, and the tabs are withheld until paired.
- A SyncStatus line in the app bar ("Not paired", "Synced 3m ago",
  "Offline - n waiting") read through ISyncStatusSource; SyncStatusTracker
  supplies pairing state and the Inbox's last successful load until the
  outbox entry supplies queue counts.
- A scoped CaptureDraft so the Inbox's unsent text survives the Router
  remounting the page on a tab switch.
- TabBar and SyncStatusLine as library components in a new Shell folder,
  with an App shell storybook page.

Also: neither mobile host linked its scoped-CSS bundle, so
MainLayout.razor.css never applied; both now do. The harness phone frame
fits a 375px viewport.

Plan item app-shell of plan mobile-inbox-slice.

Co-Authored-By: Claude Opus 5.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