Skip to content

feat(pulse): alternate mobile interface at /m - #2

Closed
elhoim wants to merge 1 commit into
mainfrom
feat/pulse-mobile-interface
Closed

feat(pulse): alternate mobile interface at /m#2
elhoim wants to merge 1 commit into
mainfrom
feat/pulse-mobile-interface

Conversation

@elhoim

@elhoim elhoim commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Pulse's dashboard is built for a 1600–1920px canvas. On a phone the header collapses to a hamburger but the content keeps desktop density, and the only navigation is one long drop-down. This adds a phone interface at /m that shows the same information with the same structure — because it renders the same components.

The idea

/m/health isn't a mirror of /health. It is /health:

// src/app/m/health/page.tsx — generated
export { default } from "@/app/health/page";

There is no markup on the mobile side that could drift from the desktop, because there is no markup. scripts/gen-mobile-routes.ts discovers desktop routes from the filesystem, so an install with more pages generates more routes automatically.

What's actually mobile is the layer around it:

  • Density is a React context, not a media query. The shared chrome primitives (PageShell/Panel/PanelHeader/StatTile/TabBar/Pill/EmptyState) read it and tighten themselves. Desktop routes render with no provider above them, so they take the default branch and are unchanged. Measured at an identical 390px viewport: panel padding 15px → 11.25px, shell gap 22.5px → 13.125px, stat value 28px → 21px.
  • Phone shell — compact top bar, thumb-reachable bottom tab bar, slide-up sheet with every page.
  • Navigation derives from the existing nav-manifest. Add a page there and it appears in the mobile menu with no mobile-side edit.

TELOS is the one exception, and it's bounded

The desktop stacks twelve sections into a single scroll. That's the wrong structure on a phone rather than a wrong amount of detail — so /m/telos is a section index and each section gets its own route. Both surfaces render from one shared section manifest + registry, so neither can hold a section the other lacks. A route earns a hand-written mobile treatment only by being named in HAND_WRITTEN in the generator.

Rollback is first-class

Scope How
One visitor Tap the monitor icon — pins the desktop UI for that browser. The desktop header carries the reverse toggle.
Whole layer bun LIFEOS/PULSE/Tools/MobileUi.ts disable — no phone is redirected, and /m sends visitors to the desktop route.
Total removal MobileUi.ts status prints the exact paths to delete and files to revert.

Desktop is untouched: zero route pages under src/app/<page>/page.tsx are modified. The four touched files each take a guarded early-return, a single added element, or a mechanical extraction.

Cost

  • Hand-written mobile layer ≈ 1,000 lines presenting ≈ 11,300 lines of desktop pages.
  • Each mobile page costs ≈ 10 kB more JS than its desktop twin (the shell) rather than bundling every page. An earlier catch-all design pulled 2.1 MB for /m/health against 605 kB for /health; per-route files fixed it.
  • No new dependency.

Verification

Real Chrome at a 390×844 viewport:

  • No horizontal page scroll and no element wider than the viewport outside a scroll container, across the tier-1 routes.
  • Thumb bar position: fixed with its bottom edge exactly at the viewport bottom.
  • All twelve TELOS sections rendering real content.
  • Burger sheet listing every page (12 TELOS sections + 12 sections + meta + system); taps navigate correctly.
  • Redirect and both toggle directions driven by real clicks, surviving reload.
  • Desktop re-checked at 1600px: computed padding, gap and type sizes equal the untouched source literals.

tests/mobile-registry.test.ts adds 17 assertions covering route coverage, re-export purity, manifest-derived navigation, section/renderer parity, layer size, and a regression guard against a dynamic [section] route that built fine and died on hydration.

Note for reviewers

This branch is built against the repo's page set. The repo's nav-manifest lists /projects and /security, but neither src/app/projects/page.tsx nor src/app/security/page.tsx is present here — those are already dead links on desktop in this repo. The mobile tree correctly declines to fabricate pages that don't exist, and the coverage test is gated on the desktop page existing. On an install that has them, the generator produces their mobile routes automatically. Worth a separate look, but out of scope for this PR.

Pulse's dashboard is built for a 1600-1920px canvas. On a phone the header
collapses to a hamburger but the content keeps desktop density, and the only
navigation is one long drop-down. This adds a phone interface at /m that shows
the same information with the same structure — because it renders the same
components.

How it avoids becoming a second copy of Pulse:

- Every mobile route is a generated file that re-exports the desktop page
  (`export { default } from "@/app/health/page"`). There is no markup on the
  mobile side that could drift from the desktop, because there is no markup.
  `scripts/gen-mobile-routes.ts` discovers desktop routes from the filesystem,
  so an install with more pages simply generates more routes.

- Density is a React context, not a media query. The shared chrome primitives
  (PageShell/Panel/PanelHeader/StatTile/TabBar/Pill/EmptyState) read it and
  tighten themselves. Desktop routes render with no provider above them, so
  they take the default branch and are unchanged.

- Navigation derives from the existing nav-manifest. Adding a page there puts
  it in the mobile menu with no mobile-side edit.

TELOS is the one exception, and it is bounded. The desktop stacks twelve
sections into a single scroll, which is the wrong structure on a phone rather
than a wrong amount of detail — so /m/telos is a section index and each section
gets its own route. Both surfaces render from one shared section manifest +
registry, so neither can hold a section the other lacks. Routes that earn a
hand-written mobile treatment must be named in HAND_WRITTEN in the generator.

Rollback is first-class:

- Any visitor: tap the monitor icon to pin the desktop UI for that browser.
  The desktop header carries the reverse toggle.
- Whole layer: `bun LIFEOS/PULSE/Tools/MobileUi.ts disable` — no phone is ever
  redirected, and /m sends visitors to the desktop route. `status` prints the
  exact paths to delete for total removal.
- Desktop is untouched: zero route pages under src/app/<page>/page.tsx are
  modified. The four touched files each take a guarded early-return, a single
  added element, or a mechanical extraction.

Sizes: the hand-written mobile layer is ~1000 lines presenting ~11,300 lines of
desktop pages, and each mobile page costs ~10 kB more JS than its desktop twin
(the shell) rather than bundling every page.

Verified in real Chrome at a 390x844 viewport: no horizontal page scroll and no
element wider than the viewport outside a scroll container across the tier-1
routes; thumb bar fixed at the viewport bottom; all twelve TELOS sections
rendering; the burger sheet listing every page; the redirect and both toggle
directions surviving reload. Desktop re-checked at 1600px — computed padding,
gap, and type sizes equal the untouched source literals.

Includes tests/mobile-registry.test.ts (17 assertions) covering route coverage,
re-export purity, manifest-derived navigation, section/renderer parity, layer
size, and a regression guard against the dynamic [section] route that built
fine and died on hydration.
@elhoim

elhoim commented Jul 29, 2026

Copy link
Copy Markdown
Owner Author

Superseded by the upstream PR: danielmiessler#1697

Same branch (feat/pulse-mobile-interface), same commit. GitHub can't move a PR between repos, so this one closes in favour of the upstream one. The branch stays pushed here — closing this PR doesn't affect the upstream review.

@elhoim elhoim closed this Jul 29, 2026
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