Skip to content

cloud/issues-047: DashboardManager refactor - kill external mini app, move to cloud-internal service#2239

Merged
isaiahb merged 26 commits intodevfrom
cloud/issues-047
Mar 30, 2026
Merged

cloud/issues-047: DashboardManager refactor - kill external mini app, move to cloud-internal service#2239
isaiahb merged 26 commits intodevfrom
cloud/issues-047

Conversation

@isaiahb
Copy link
Copy Markdown
Contributor

@isaiahb isaiahb commented Mar 17, 2026

Dashboard Refactor — Cloud-Internal Service

Kills the system.augmentos.dashboard mini app and replaces it with a
cloud-internal DashboardManager that owns all dashboard data and
rendering directly.

Why

The Dashboard mini app was generating ~900–1,200 errors/minute in
production (>60% of all cloud errors) — a setInterval firing on a
closed WebSocket on every active user session. The fix is not better
cleanup; it's eliminating the external process entirely.

What changed

Core refactor

  • DashboardManager is now an active service — owns weather, notifications,
    calendar, and widget rotation directly. No external process, no WS roundtrip.
  • NotificationService — LLM-ranked notification summaries via direct OpenAI
    API call (no LangChain)
  • WeatherService — moved from mini app to services/core/, cross-user
    geo-bucket caching preserved
  • LocationManager and CalendarManager now notify DashboardManager
    directly on every update (including LocationManager seeding from the DB
    cold cache on connect — weather available immediately)

Layout

  • Hybrid header: column-split row 1 (time + battery left, weather/calendar
    right) via ColumnComposer, full-width rows 2–4 for notifications and
    widgets — nearly 2× more usable space vs the old DoubleTextWall
  • $DATE$, $TIME12$, $GBATT$ tokens — resolved natively at display time,
    no clock-update sends needed
  • metric_system user setting respected — weather shows °C or °F correctly
  • Calendar: Luxon-powered date logic, DST-safe, user-timezone-aware,
    all-day event detection (no time shown for all-day events)

Cleanup

  • SYSTEM_DASHBOARD_PACKAGE_NAME fully removed — replaced by OS_PACKAGE_NAME
    ("com.mentra.os") for internal OS display requests (boot screen, clear)
  • DisplayManager dashboard bypass now view-based (view === DASHBOARD) not
    package-name-based
  • Third-party MiniApps clamped to ViewType.MAIN — cannot overwrite the
    dashboard buffer
  • ColumnComposer overflow bug fixed: Math.ceilMath.floor in
    calculateSpacesForAlignment — prevents right column character wrapping
    to the next line's left position
  • @mentra/display-utils added as workspace dep to packages/cloud
  • luxon@3.7.2 added for calendar date handling

Docs

  • cloud/.architecture/dashboard.md — full architecture doc verified against
    native iOS source (CoreManager.swift, GlassesStore.swift, G1.swift)
  • cloud/issues/047-dashboard-refactor/ — spike, spec, design doc

Deployment

  • OPEN_WEATHER_API_KEY added to all Porter deployments (central-us,
    us-west, france, us-east, east-asia)
  • Tested on cloud-debug

Phase 2 (included in this PR)

  • startApp(SYSTEM_DASHBOARD_PACKAGE_NAME) removed from websocket files
  • All SYSTEM_DASHBOARD_PACKAGE_NAME special-cases removed from
    DisplayManager, app-settings.routes.ts, app.service.ts
  • Dashboard mini app can be scaled to 0 and deleted after merge

isaiahb added 11 commits March 16, 2026 11:10
… service

DashboardManager is now an active cloud service that owns all dashboard
data directly. The external system.augmentos.dashboard mini app is no
longer needed.

Changes:
- DashboardManager: active service with data hooks (onNotification,
  onNotificationDismissed, onLocationUpdate, onCalendarUpdate),
  event-driven scheduleUpdate() + 60s heartbeat, hybrid header+full-width
  body layout, MiniApp terminology throughout (894 → 617 lines)
- NotificationService: new — LLM-ranked notification summaries via direct
  OpenAI API call (no LangChain), fallback to recency sort on failure
- WeatherService: moved from Dashboard mini app to services/core/,
  cross-user geo-bucket caching, AppSession dependency removed
- display-utils added as workspace dep to packages/cloud
- ColumnComposer overflow fix: Math.ceil → Math.floor in
  calculateSpacesForAlignment — prevents right column character wrapping
  to next line's left position (fixes 047 spec §4b)
- generateLayout() uses ColumnComposer for pixel-accurate header row
- notifications/calendar/location API handlers wired to dashboardManager
- dashboard/index.ts: removed module-level routing helpers (74 → 8 lines)
- docs: architecture/dashboard.md rewritten from native source audit,
  issue 047 spike + spec + design doc

Phase 2 (after mini app confirmed dead):
  - Remove startApp(SYSTEM_DASHBOARD_PACKAGE_NAME) from websocket files
  - Remove SYSTEM_DASHBOARD_PACKAGE_NAME special-cases from DisplayManager,
    app-settings.routes.ts, app.service.ts
  - Add OPEN_WEATHER_API_KEY to cloud deployment secrets
Prevents rogue or misbehaving MiniApps from overwriting the dashboard
buffer (ViewType.DASHBOARD) by clamping any non-SYSTEM_DASHBOARD_PACKAGE
request that specifies view=DASHBOARD down to MAIN.

Only DashboardManager.render() (cloud-internal) is allowed to write to
the dashboard view — it uses SYSTEM_DASHBOARD_PACKAGE_NAME which bypasses
this check via the early return above it.

Logs a warning so we can detect if any MiniApp is doing this in prod.
… comms already gone

Dashboard mini app is fully removed from the startup path.
SYSTEM_DASHBOARD_PACKAGE_NAME constant deleted and all callers updated.

Changes:
- bun-websocket.ts: remove startApp(SYSTEM_DASHBOARD_PACKAGE_NAME)
- websocket-glasses.service.ts: same
- DisplayManager6.1.ts:
    - replace packageName-based dashboard bypass with view-based bypass
      (view === ViewType.DASHBOARD → sendDisplay directly)
    - clamp now checks packageName !== OS_PACKAGE_NAME for the guard
    - boot screen / clearDisplay use new OS_PACKAGE_NAME ('com.mentra.os')
    - remove dead 'don't boot-screen dashboard' early return
    - remove dead 'skip onboarding for dashboard' condition
    - saved-display check uses view !== ViewType.DASHBOARD
- DashboardManager.ts: use local OS_PACKAGE_NAME instead of import
- app.service.ts: remove SYSTEM_DASHBOARD_PACKAGE_NAME export + env var
- app-settings.routes.ts: remove dashboard special-cases (normalizeAppName
  no longer remaps com.augmentos.dashboard, settings lookup and WS push
  no longer skip the dashboard package)
- developer.service.ts: remove commented-out isSystemApp dead code

Multi-user app communication: already removed from codebase —
no app-communication.routes.ts exists in Hono routes.
…GE_NAME removal

Three layout test files and two dashboard test harnesses were importing
SYSTEM_DASHBOARD_PACKAGE_NAME from app.service.ts which was deleted in
phase 2. Replace with local const 'com.mentra.os' (matching OS_PACKAGE_NAME
in DisplayManager and DashboardManager).

Also fix pre-existing Function type lint errors in DashboardTestHarness.
…root

Importing from @mentra/display-utils/src/profiles/g1 works in dev
(TypeScript resolves source) but crashes at runtime since only dist/
is available. The package root @mentra/display-utils exports both via
dist/index.js in production.
- formatHeaderLeft() was missing the time token — only showed date and
  battery. Now shows: ◌ 3/16, 3:45 PM, 100%
- Add info-level logging to onNotification, onLocationUpdate, onCalendarUpdate
  so we can verify the data hooks are being triggered from the API handlers
Instead of location.api.ts calling dashboardManager.onLocationUpdate()
directly, LocationManager now notifies the dashboard on all three paths:
  - updateFromAPI() — live location from REST
  - updateFromWebsocket() — live location from device WS
  - seedFromDatabaseCache() — cached lat/lng from previous session on connect

This means weather is populated immediately on first connect (from the
DB cold cache) without needing a setTimeout hack, and the API handler
doesn't need to know about the dashboard at all.
CalendarManager.updateEventsFromAPI() now calls
dashboardManager.onCalendarUpdate(getCachedEvents()) directly after
processing events. Removed the call from calendar.api.ts.

Note: no DB cold cache for calendar — data only arrives after the
first mobile sync (on app start + hourly). Unlike LocationManager,
there is no seed-on-connect path here.
metric_system is already synced from mobile via saveOnServer:true and
loaded into UserSettingsManager.snapshot on session init. Just needed
to read it — no new implementation required.
…ezone

- Add luxon@3.7.2 — zero deps, uses Intl API, server timezone irrelevant
- Replace broken toLocaleString() hack with DateTime.hasSame(other, 'day')
  which compares calendar dates in the user's IANA timezone correctly
- nowDt.plus({ days: 1 }) is calendar-day-aware — no millisecond arithmetic
- All-day event detection: allDay flag OR time is exactly 00:00:00
- All-day format: 'St Patty Day tmr' / 'St Patty Day Today' / 'St Patty Day 3/17'
- Timed format: 'Meeting @ 4pm' / 'tmr: Meeting @ 4pm' / '3/18: Meeting @ 4pm'
@isaiahb isaiahb self-assigned this Mar 17, 2026
@isaiahb isaiahb requested a review from a team as a code owner March 17, 2026 16:56
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b26420a1-a283-43d3-8274-9dd2d108eccd

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch cloud/issues-047

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Copy Markdown

📋 PR Review Helper

📱 Mobile App Build

Waiting for build...

🕶️ ASG Client Build

Waiting for build...


🔀 Test Locally

gh pr checkout 2239

@isaiahb isaiahb changed the title cloud/issues-047 cloud/issues-047: DashboardManager refactor - kill external mini app, move to cloud-internal service Mar 17, 2026
isaiahb added 11 commits March 17, 2026 10:00
… bump shouldn't block build

TypeScript bumped from 5.8.3 to 5.9.3 via bun-types in Docker, surfacing
pre-existing strict violations in unrelated files (string|undefined in
account/org/stream routes). Those need fixing on dev separately.
noEmitOnError:false restores the previous build behaviour.
All errors were 'string | undefined' not assignable to 'string' from
c.req.param() in Hono route handlers. Fixed with guard clauses that
early-return 400 if the param is missing (narrows type for TS).

Files fixed:
- agent/incidents.api.ts (1)
- console/incidents.api.ts (2)
- routes/account.routes.ts (1)
- routes/admin.routes.ts (2)
- routes/apps.routes.ts (8)
- routes/audio.routes.ts (1)
- routes/organization.routes.ts (12)
- routes/streams.routes.ts (5)
- routes/tools.routes.ts (1)
- routes/transcripts.routes.ts (1)

Removed noEmitOnError:false workaround — no longer needed.
…ge.json

Left behind by 'bun add --filter' — caused 'Duplicate dependency' warning.
Problems:
- All-day events have dtStart at UTC midnight (e.g. 2026-03-18T00:00:00Z).
  Converting to user TZ (Pacific) gives 5pm on March 17 — wrong calendar date.
  The midnight-in-user-TZ heuristic fails because hour=17, not 0.
- Timed events today were being skipped in favor of all-day events that
  appeared 'sooner' by raw timestamp.

Fixes:
- Pass allDay flag from Expo through CalendarManager.normalizeFromExpo()
- Detect all-day by: Expo allDay flag OR both dtStart/dtEnd at UTC midnight
  spanning >=1 full day
- For all-day events, interpret dtStart in UTC to get the intended calendar
  date (March 18), not the user-TZ conversion (March 17 5pm)
- Prefer timed events today over all-day events: if a timed event exists
  on the same calendar day as now, it always wins
Layout before:
  Row 1: '◌ date, time, batt' | 'Mentra Financi...'  ← truncated
  Row 2-4: notifications + widgets

Layout after:
  Row 1: '◌ date, time, batt' | 'Cloudy, 62°F'       ← weather always visible
  Row 2: 'Mentra Financials + Equity... @ 5pm'         ← full width, not truncated
  Row 3-4: notifications + widgets

Calendar text was getting crushed in the column-split header (10-12 chars
after the left side). Now it has the full ~40+ character line width.
…l line width

Before: 'Isaiah: bug report email' (vague, no app context, 30 char limit)
After:  '[Gmail] OpenAI: Excel plugin down' (app tag, specific, 35 char summary)

Changes:
- Display format: '[Gmail] summary' / '[Slack] Carl: design ready'
- App name tags with shortening for verbose names (WhatsApp→WA, etc.)
- Rewritten LLM prompt: lead with WHO/WHAT, never repeat app name,
  terse style, ranked by message type (DMs > alerts > group > spam)
- Summary limit raised from 30→35 chars (full row 2 available now)
- appName preserved through ranking pipeline (snapshot → RankedNotification)
- Fallback ranking also uses appName tags
…hboard app

Adds a code-level blocklist for apps that should no longer run. Works
per-deployment without a DB migration (all envs share the same DB).

system.augmentos.dashboard is the first entry — replaced by the
cloud-internal DashboardManager (issue #47).

Three enforcement points:
1. startApp() — early return with error for deprecated apps
2. startPreviouslyRunningApps() — filters deprecated apps from the DB
   list and removes them from user.runningApps (organic cleanup)
3. handleAppInit() — rejects inbound WebSocket connections from
   deprecated apps with APP_DEPRECATED error code and closes the socket

This stops the old dashboard mini app from:
- Being auto-started on user reconnect (DB had it in runningApps)
- Reconnecting on its own after cloud restart
- Overwriting the new DashboardManager's display output
Implementation plan for @mentra/sdk v3. The API surface design is in
039-sdk-v3-api-surface/v2-v3-api-map.md — this doc covers how to build it.

Key architectural decisions:
- MentraApp (Hono, callbacks) is the v3 implementation
- AppServer (class inheritance) is a v2 compat shim wrapping MentraApp
- LegacyEventShim maps session.events.* to v3 managers
- Deprecated getters on AppSession for layouts/simpleStorage/settings
- All shims removed in v3.1

Includes:
- TranslationManager design (pulled from v3.1 into v3.0)
- DataStreamRouter for message dispatch (replaces 412-line if/else)
- Route namespacing (/api/_mentraos/*) with legacy aliases
- 14-day implementation plan across 6 phases
- Full dead code inventory and bug fix list
…to dev

Resolved 2 conflicts:
- developer.service.ts: kept debug log with hashedKey/apiKey fields
- DisplayManager6.1.ts: kept 047's view-based dashboard bypass + dev's performance timing

Also fixed: CloudflareStreamService stale config.quality reference (SRT refactor)

Build verified: types, sdk, cloud all clean.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages bot commented Mar 30, 2026

Deploying mentra-live-ota-site with  Cloudflare Pages  Cloudflare Pages

Latest commit: b6694f8
Status: ✅  Deploy successful!
Preview URL: https://d895bf46.mentra-live-ota-site.pages.dev
Branch Preview URL: https://cloud-issues-047.mentra-live-ota-site.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying mentra-store-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: b6694f8
Status: ✅  Deploy successful!
Preview URL: https://8b8f584b.augmentos-appstore-2.pages.dev
Branch Preview URL: https://cloud-issues-047.augmentos-appstore-2.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying dev-augmentos-console with  Cloudflare Pages  Cloudflare Pages

Latest commit: b6694f8
Status: ✅  Deploy successful!
Preview URL: https://457805f1.dev-augmentos-console.pages.dev
Branch Preview URL: https://cloud-issues-047.dev-augmentos-console.pages.dev

View logs

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying prod-augmentos-account with  Cloudflare Pages  Cloudflare Pages

Latest commit: b6694f8
Status: ✅  Deploy successful!
Preview URL: https://491c65a1.augmentos-e84.pages.dev
Branch Preview URL: https://cloud-issues-047.augmentos-e84.pages.dev

View logs

isaiahb added 4 commits March 30, 2026 13:46
…lved text, not tokens

The header left column uses tokens ($DATE$, $TIME12$, $GBATT$) that
are resolved by the native display layer. ColumnComposer was measuring the
token text (288px) instead of the resolved text (≤228px), causing the
weather column to start too far left with wrong spacing.

Fix: pre-compute the worst-case resolved width (228px for 12h clock,
e.g. '◌ 09/30, 12:00 AM, 100%') and use that for gap calculation.
The right column now starts at a consistent position regardless of
actual date/time/battery values.

Also added dashboard display preference constants (date format, clock
format) as future-ready settings — currently hardcoded, easy to wire
up to UserSettingsManager later.

Pixel analysis:
  Token text:     288px (what server was measuring — WRONG)
  Worst 12h:      228px (39.6% of 576px display)
  Worst 24h:      192px (33.3%)
  Typical:        198px (34.4%)
  Right column:   ≥318px available for weather
… from connected glasses

The dashboard header layout was hardcoded to G1_PROFILE (576px, G1 font).
This produces wrong column spacing on Vuzix Z100/Mach1 (390px, different
font) and wouldn't adapt to any future display hardware.

Fix: resolve DisplayProfile dynamically from the connected glasses model
via DeviceManager.getModel() at render time.

Model → Profile mapping:
  Even Realities G1  → G1_PROFILE  (576px)
  Even Realities G2  → G2_PROFILE  (576px, same font as G1)
  Mentra Display/Nex → NEX_PROFILE (576px, placeholder G1 values)
  Mentra Live        → G1_PROFILE  (same display as G1)
  Vuzix Z100/Mach1   → Z100_PROFILE (390px, Noto Sans 21px)
  Simulated          → G1_PROFILE  (fallback)
  Unknown model      → G1_PROFILE  (fallback)

Header metrics (worst-case left width, space width) are cached per profile
to avoid creating TextMeasurer on every render cycle.
On the Z100 (390px display), the double-column header doesn't work:
the left column (date/time/battery) takes 61% of the display in the
worst case, leaving only 132px for weather. 'Partly Cloudy, 72°F'
(189px) overflows.

Fix: displays narrower than 500px use a stacked layout where each
data element gets its own full-width line:
  Line 1: date, time, battery
  Line 2: weather
  Line 3: calendar
  Lines 4-7: notifications + widgets

This works well because the Z100 has 7 lines (vs G1's 5), so the
extra line for weather is a better tradeoff than a cramped column.

Wide displays (G1, G2, Mentra Display — 576px) keep the double-column
header layout unchanged.
…gger

Dashboard header: weather now starts at the display midpoint (50%) instead
of right after the left text. Pads from worst-case left width (228px on G1)
to midpoint (288px on G1) = 10 spaces of fixed padding.

Also cleaned up porter-debug.yml to only active branches.
@isaiahb isaiahb merged commit eb97614 into dev Mar 30, 2026
10 checks passed
isaiahb added a commit that referenced this pull request Mar 30, 2026
Brings in the full dashboard refactor (PR #2239):
- DashboardManager is now cloud-internal (no external mini app)
- SYSTEM_DASHBOARD_PACKAGE_NAME removed, replaced by OS_PACKAGE_NAME
- Model-aware display profiles (G1, G2, Z100, Nex)
- Stacked layout for narrow displays (Z100/Mach1)
- Header column alignment fix (worst-case token measurement)
- DEPRECATED_APPS blocklist for old dashboard app
- Weather at 50% display midpoint

Also fixed:
- China deploy: Dockerfile.livekit → Dockerfile.porter (file didn't exist)
- China deploy: removed SYSTEM_DASHBOARD_PACKAGE_NAME env var
- docker-compose.porter.local.yml: same Dockerfile fix

All 5 packages build clean (bun run ci).
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