cloud/issues-047: DashboardManager refactor - kill external mini app, move to cloud-internal service#2239
cloud/issues-047: DashboardManager refactor - kill external mini app, move to cloud-internal service#2239
Conversation
… 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'
|
Important Review skippedAuto reviews are disabled on base/target branches other than the default branch. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
📋 PR Review Helper📱 Mobile App Build⏳ Waiting for build... 🕶️ ASG Client Build⏳ Waiting for build... 🔀 Test Locallygh pr checkout 2239 |
… 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
This reverts commit 9a4ee23.
…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.
Deploying mentra-live-ota-site with
|
| 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 |
Deploying mentra-store-dev with
|
| 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 |
Deploying dev-augmentos-console with
|
| 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 |
Deploying prod-augmentos-account with
|
| 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 |
…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.
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).
Dashboard Refactor — Cloud-Internal Service
Kills the
system.augmentos.dashboardmini app and replaces it with acloud-internal
DashboardManagerthat owns all dashboard data andrendering directly.
Why
The Dashboard mini app was generating ~900–1,200 errors/minute in
production (>60% of all cloud errors) — a
setIntervalfiring on aclosed WebSocket on every active user session. The fix is not better
cleanup; it's eliminating the external process entirely.
What changed
Core refactor
DashboardManageris 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 OpenAIAPI call (no LangChain)
WeatherService— moved from mini app toservices/core/, cross-usergeo-bucket caching preserved
LocationManagerandCalendarManagernow notifyDashboardManagerdirectly on every update (including
LocationManagerseeding from the DBcold cache on connect — weather available immediately)
Layout
right) via
ColumnComposer, full-width rows 2–4 for notifications andwidgets — nearly 2× more usable space vs the old
DoubleTextWall$DATE$,$TIME12$,$GBATT$tokens — resolved natively at display time,no clock-update sends needed
metric_systemuser setting respected — weather shows °C or °F correctlyall-day event detection (no time shown for all-day events)
Cleanup
SYSTEM_DASHBOARD_PACKAGE_NAMEfully removed — replaced byOS_PACKAGE_NAME(
"com.mentra.os") for internal OS display requests (boot screen, clear)DisplayManagerdashboard bypass now view-based (view === DASHBOARD) notpackage-name-based
ViewType.MAIN— cannot overwrite thedashboard buffer
ColumnComposeroverflow bug fixed:Math.ceil→Math.floorincalculateSpacesForAlignment— prevents right column character wrappingto the next line's left position
@mentra/display-utilsadded as workspace dep topackages/cloudluxon@3.7.2added for calendar date handlingDocs
cloud/.architecture/dashboard.md— full architecture doc verified againstnative iOS source (
CoreManager.swift,GlassesStore.swift,G1.swift)cloud/issues/047-dashboard-refactor/— spike, spec, design docDeployment
OPEN_WEATHER_API_KEYadded to all Porter deployments (central-us,us-west, france, us-east, east-asia)
cloud-debug✅Phase 2 (included in this PR)
startApp(SYSTEM_DASHBOARD_PACKAGE_NAME)removed from websocket filesSYSTEM_DASHBOARD_PACKAGE_NAMEspecial-cases removed fromDisplayManager,app-settings.routes.ts,app.service.ts