Skip to content

DevDashboard Mobile + Cloud + Agent extraction - #193

Open
genesiscz wants to merge 45 commits into
masterfrom
feat/dev-dashboard-mobile
Open

DevDashboard Mobile + Cloud + Agent extraction#193
genesiscz wants to merge 45 commits into
masterfrom
feat/dev-dashboard-mobile

Conversation

@genesiscz

@genesiscz genesiscz commented May 31, 2026

Copy link
Copy Markdown
Owner

DevDashboard Mobile + Cloud + Agent extraction

Stacked on feat/tmux-cmux-dev-dashboard (the parent dev-dashboard line) — base this PR on that branch, not master.

Turns the personal src/dev-dashboard/ web app into a commercial product across three deliverables, all merged into this branch.

What's here

  • DevDashboard Mobile — Expo SDK 55 (RN 0.83, New Arch, Hermes) app at DevDashboard/mobile/. 5 tabs: Pulse, Terminals (D12 dual WebView/xterm driver), QA (SSE live stream), Obsidian (vault + WebView note render), More → Claude Usage / Daemon / Containers / Weather / Connections. NativeWind v4, victory-native/Skia charts, TanStack Query v5 (D32), Zustand, pluggable transport (LAN/mDNS, Tailscale, self-cloudflared, managed-with-E2E).
  • Standalone Agentsrc/dev-dashboard/server/ extracted from ui/vite-middleware.ts, dev-dashboard agent subcommand, mDNS advertiser, E2E rpc (tweetnacl X25519), device-code pairing.
  • DevDashboard CloudDevDashboard/cloud/web/ (TanStack Start + Nitro): Obsidian-Terminal landing, Better-Auth + SQLite (Postgres-ready, pluggable), customer dashboard + setup wizard, env-gated Cloudflare-for-SaaS + Stripe.

Parent merge

Integrates the parent's tmux/cmux work into the extraction branch (merge 3b2a36f63, 19 conflicts resolved): tmux create --attach, session presets/restart-matching, /api/tmux/sessions perf, NO_COLOR/COLORTERM color fix, keep-dialog-open-on-attach + focus-new-tab — preserved on top of the extracted architecture.

Simulator status (iPhone 17 Pro Max, iOS 26.5) — ✅ verified end-to-end

App builds, installs, launches; Connect screen renders cleanly; the full post-Connect tab walkthrough is now verified via a real-assertion Appium suite (13 specs, 53 assertions green — not smoke clicks). Native/RN fixes that got us there: iOS bundleId dev.foltyn.dev-dashboard + deploymentTarget 16.0, removed unused lottie, Skia binary install, Hermes Event/EventTarget/CloseEvent polyfill via metro getPolyfills (partysocket startup crash), /pair deep-link route (was Unmatched Route), iOS ATS local-networking + Bonjour, ATS localhost cleartext exception for sim LAN connect.

This session — bug list cleared + backend join + e2e suite

Filed bug list (all fixed + live-verified on sim):

  • QA — live indicator reflects the real connection (not row 0); filter chips render labels as-is (dropped uppercase/wide-tracking); all chips pinned outside the horizontal scroll; multi-select project + tag filters (OR within facet); answers render as web-parity HTML in a WebView when expanded.
  • Terminals — tmux attach/open/rename, in-detail-header ttyd rename + keyboard-avoiding view, swipe-to-reveal row primitive, binary-safe xterm.js (Driver B) frame encoding, cmux panes made useful, table layout.
  • More — killed the white nav bar (dark Obsidian header for the (more) stack), regrouped into spaced sections with icons, normal-case section titles, real glowing-dot live indicator (StatusPill web-parity).
  • Obsidian — vault tree surfaced inline on phones (no longer empty), restyle + note render parity.
  • Connections — multi-connection persisted model (SecureStore-per-id passwords), list/activate/add/edit/delete screen, refresh-on-focus + concurrent-op guards, active connection restored at boot.

Boot-restore fix (20768fc2d) — a paired relaunch now lands in the app, not /connect: gate boot on restore() behind an Obsidian-style splash so the expo-router Stack.Protected guard sees the final baseUrl on first render (the guard gates access but doesn't auto-navigate).

3 backend items (4d2814858, d8007462e, eb39e3179) — cmux ↔ tmux ↔ ttyd join, with manual renames always sticky:

  1. ttyd auto-names from the active pane's lastCommand (precedence: manual name → meaningful command → tmux session name → command); automatic naming never overwrites a hand-set name.
  2. cmux surface rename drives the ttyd display name (resolves the ttyd id by the surface's pre-rename title before renaming) — counts as a manual rename.
  3. /api/cmux/snapshot resolves each pane → ttyd session id so a client can open a cmux pane as a real terminal. Unit-tested (naming.test.ts, enrich-ttyd.test.ts, sessions.test.ts).

testID / a11y polish (b2c296396) — a full testID sweep (282 ids, consistent naming, zero broken e2e selectors) surfaced a few accessibility-identifier gaps, now closed: the QA filter Chip exposes accessibilityState={{ selected }} (parity with the range/driver/cmux-tab selectors); ListRow (passive mode) + KeyValueRow forward an accessibilityLabel; the QA answer WebView gets a distinct qa-answer-<id>-webview id (parity with obsidian-note-webview); and the active connection now carries an explicit connection-active-<id> marker (the Connections e2e page-object prefers it over inferring active-by-absence). Additive only — no testID renamed or removed. Deliberately left session-row-<name> ids keyed on the tmux session name: names are unique by tmux and are the cmux↔tmux↔ttyd join key, so changing them would break both the e2e selectors and the join.

Gates

mobile tsc clean (1 pre-existing QA test-fixture error), e2e tsc 0, bun test src/ green, backend tsgo 0. Cloud tsc/biome/build clean + tests. CI on this PR green (CodeRabbit pass, Socket Security pass).

Needs follow-up

Real Cloudflare-for-SaaS + Stripe creds (provisioning/billing are env-gated stubs); on-device (non-sim) build + Appium device run; confirm Better-Auth vs WorkOS (D35); the 1 pre-existing QA queries.test.ts fixture typing.

Summary by CodeRabbit

  • New Features

    • Added a new DevDashboard Cloud web experience with a landing page, sign-in/sign-up, dashboard overview, billing, device management, setup wizard, and settings.
    • Added mobile connection and pairing flows, including QR-based setup and reachability feedback.
    • Introduced new mobile screens for Pulse, Terminals, QA, Obsidian, Claude Usage, Daemon, Weather, and a larger “More” hub with tools like activity timeline, network status, reminders, process monitor, quick commands, ports, containers, and tmux presets.
  • Bug Fixes

    • Improved connection reachability handling and strengthened reliability around pairing, billing, and setup flows.
  • Documentation

    • Added comprehensive product, setup, pricing, security, troubleshooting, and roadmap documentation.

@coderabbitai

coderabbitai Bot commented May 31, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Too many files!

This PR contains 492 files, which is 342 over the limit of 150.

To get a review, narrow the scope:
• coderabbit review --type committed # exclude uncommitted changes
• coderabbit review --dir # limit to a subdirectory
• coderabbit review --base # compare against a closer base

Upgrade to Pro+ to raise the limit.

Usage-priced reviews support at most 300 files.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 98a4309c-d7f9-47ea-a93b-92713dc0d872

📥 Commits

Reviewing files that changed from the base of the PR and between fc66ea9 and 2418627.

⛔ Files ignored due to path filters (25)
  • DevDashboard/cloud/web/bun.lock is excluded by !**/*.lock
  • DevDashboard/mobile/assets/expo.icon/Assets/expo-symbol 2.svg is excluded by !**/*.svg
  • DevDashboard/mobile/assets/expo.icon/Assets/grid.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/android-icon-background.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/android-icon-foreground.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/android-icon-monochrome.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/expo-badge-white.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/expo-badge.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/expo-logo.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/favicon.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/icon.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/logo-glow.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/react-logo.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/react-logo@2x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/react-logo@3x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/splash-icon.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/explore.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/explore@2x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/explore@3x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/home.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/home@2x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/home@3x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tutorial-web.png is excluded by !**/*.png
  • DevDashboard/mobile/bun.lock is excluded by !**/*.lock
  • DevDashboard/mobile/src/features/terminals/xterm-host.generated.ts is excluded by !**/*.generated.*
📒 Files selected for processing (492)
  • DevDashboard/DECISIONS.md
  • DevDashboard/PRODUCT-ROADMAP.md
  • DevDashboard/README.md
  • DevDashboard/cloud/landing/daylight/index.html
  • DevDashboard/cloud/landing/field-notes/index.html
  • DevDashboard/cloud/landing/obsidian-terminal/index.html
  • DevDashboard/cloud/shared/account-model.ts
  • DevDashboard/cloud/shared/data-boundary.test.ts
  • DevDashboard/cloud/shared/data-boundary.ts
  • DevDashboard/cloud/shared/tier-policy.test.ts
  • DevDashboard/cloud/shared/tier-policy.ts
  • DevDashboard/cloud/web/.env.example
  • DevDashboard/cloud/web/.gitignore
  • DevDashboard/cloud/web/README.md
  • DevDashboard/cloud/web/biome.json
  • DevDashboard/cloud/web/db/migrations/0000_true_energizer.sql
  • DevDashboard/cloud/web/db/migrations/meta/0000_snapshot.json
  • DevDashboard/cloud/web/db/migrations/meta/_journal.json
  • DevDashboard/cloud/web/docs/getting-started.md
  • DevDashboard/cloud/web/docs/install-the-agent.md
  • DevDashboard/cloud/web/docs/pricing-and-tiers.md
  • DevDashboard/cloud/web/docs/security-and-trust.md
  • DevDashboard/cloud/web/docs/troubleshooting.md
  • DevDashboard/cloud/web/drizzle.config.ts
  • DevDashboard/cloud/web/package.json
  • DevDashboard/cloud/web/playwright.config.ts
  • DevDashboard/cloud/web/src/components/RouteError.tsx
  • DevDashboard/cloud/web/src/components/RouteNotFound.tsx
  • DevDashboard/cloud/web/src/components/auth/AuthCard.tsx
  • DevDashboard/cloud/web/src/components/dashboard/Card.tsx
  • DevDashboard/cloud/web/src/components/landing/Features.tsx
  • DevDashboard/cloud/web/src/components/landing/Footer.tsx
  • DevDashboard/cloud/web/src/components/landing/Hero.tsx
  • DevDashboard/cloud/web/src/components/landing/HowItWorks.tsx
  • DevDashboard/cloud/web/src/components/landing/Nav.tsx
  • DevDashboard/cloud/web/src/components/landing/Pricing.tsx
  • DevDashboard/cloud/web/src/components/landing/TrustStory.tsx
  • DevDashboard/cloud/web/src/components/landing/icons.tsx
  • DevDashboard/cloud/web/src/content/copy.ts
  • DevDashboard/cloud/web/src/integrations/tanstack-query/root-provider.tsx
  • DevDashboard/cloud/web/src/lib/auth/auth-client.ts
  • DevDashboard/cloud/web/src/lib/auth/auth-service.ts
  • DevDashboard/cloud/web/src/lib/auth/auth.functions.ts
  • DevDashboard/cloud/web/src/lib/auth/auth.server.ts
  • DevDashboard/cloud/web/src/lib/auth/auth.test.ts
  • DevDashboard/cloud/web/src/lib/billing/billing.functions.ts
  • DevDashboard/cloud/web/src/lib/billing/env-gating.test.ts
  • DevDashboard/cloud/web/src/lib/billing/stripe.ts
  • DevDashboard/cloud/web/src/lib/dashboard/dashboard.functions.ts
  • DevDashboard/cloud/web/src/lib/db/cloud-store.ts
  • DevDashboard/cloud/web/src/lib/db/index.ts
  • DevDashboard/cloud/web/src/lib/db/migrate.ts
  • DevDashboard/cloud/web/src/lib/db/schema.ts
  • DevDashboard/cloud/web/src/lib/provision/cloudflare.ts
  • DevDashboard/cloud/web/src/lib/server/env.ts
  • DevDashboard/cloud/web/src/router.tsx
  • DevDashboard/cloud/web/src/routes/__root.tsx
  • DevDashboard/cloud/web/src/routes/api.auth.$.ts
  • DevDashboard/cloud/web/src/routes/api.stripe.webhook.ts
  • DevDashboard/cloud/web/src/routes/dashboard.billing.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.devices.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.index.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.settings.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.setup.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.tsx
  • DevDashboard/cloud/web/src/routes/index.tsx
  • DevDashboard/cloud/web/src/routes/signin.tsx
  • DevDashboard/cloud/web/src/routes/signup.tsx
  • DevDashboard/cloud/web/src/start.ts
  • DevDashboard/cloud/web/src/styles/app.css
  • DevDashboard/cloud/web/tests-e2e/api.spec.ts
  • DevDashboard/cloud/web/tests-e2e/auth-flow.spec.ts
  • DevDashboard/cloud/web/tests-e2e/auth-guard.spec.ts
  • DevDashboard/cloud/web/tests-e2e/auth.setup.ts
  • DevDashboard/cloud/web/tests-e2e/constants.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-billing.spec.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-devices.spec.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-overview.spec.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-settings.spec.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-setup.spec.ts
  • DevDashboard/cloud/web/tests-e2e/helpers.ts
  • DevDashboard/cloud/web/tests-e2e/landing.spec.ts
  • DevDashboard/cloud/web/tests-e2e/prepare-db.ts
  • DevDashboard/cloud/web/tests-e2e/signin.spec.ts
  • DevDashboard/cloud/web/tests-e2e/signup.spec.ts
  • DevDashboard/cloud/web/tsconfig.json
  • DevDashboard/cloud/web/vite.config.ts
  • DevDashboard/mobile/.claude/settings.json
  • DevDashboard/mobile/.gitignore
  • DevDashboard/mobile/.vscode/extensions.json
  • DevDashboard/mobile/.vscode/settings.json
  • DevDashboard/mobile/AGENTS.md
  • DevDashboard/mobile/CLAUDE.md
  • DevDashboard/mobile/README.md
  • DevDashboard/mobile/app.config.ts
  • DevDashboard/mobile/app.json
  • DevDashboard/mobile/assets/expo.icon/icon.json
  • DevDashboard/mobile/babel.config.js
  • DevDashboard/mobile/e2e/README.md
  • DevDashboard/mobile/e2e/pages/ActivityTimelinePage.page.ts
  • DevDashboard/mobile/e2e/pages/BuildLogTailPage.page.ts
  • DevDashboard/mobile/e2e/pages/ClaudeUsagePage.page.ts
  • DevDashboard/mobile/e2e/pages/ConnectPage.page.ts
  • DevDashboard/mobile/e2e/pages/ConnectionsPage.page.ts
  • DevDashboard/mobile/e2e/pages/ContainersPage.page.ts
  • DevDashboard/mobile/e2e/pages/DaemonPage.page.ts
  • DevDashboard/mobile/e2e/pages/DiskJanitorPage.page.ts
  • DevDashboard/mobile/e2e/pages/MoreNav.page.ts
  • DevDashboard/mobile/e2e/pages/NeedsInputInboxPage.page.ts
  • DevDashboard/mobile/e2e/pages/NetworkStatusPage.page.ts
  • DevDashboard/mobile/e2e/pages/ObsidianPage.page.ts
  • DevDashboard/mobile/e2e/pages/PortKillerPage.page.ts
  • DevDashboard/mobile/e2e/pages/ProcessMonitorPage.page.ts
  • DevDashboard/mobile/e2e/pages/PulsePage.page.ts
  • DevDashboard/mobile/e2e/pages/QaPage.page.ts
  • DevDashboard/mobile/e2e/pages/QuickCommandsPage.page.ts
  • DevDashboard/mobile/e2e/pages/RemindersTodosPage.page.ts
  • DevDashboard/mobile/e2e/pages/TerminalsPage.page.ts
  • DevDashboard/mobile/e2e/pages/TmuxPresetsPage.page.ts
  • DevDashboard/mobile/e2e/pages/WeatherPage.page.ts
  • DevDashboard/mobile/e2e/pages/app.page.ts
  • DevDashboard/mobile/e2e/pages/base.page.ts
  • DevDashboard/mobile/e2e/pages/testAgent.ts
  • DevDashboard/mobile/e2e/specs/activity-timeline.spec.ts
  • DevDashboard/mobile/e2e/specs/build-log-tail.spec.ts
  • DevDashboard/mobile/e2e/specs/claude-usage.spec.ts
  • DevDashboard/mobile/e2e/specs/connect.spec.ts
  • DevDashboard/mobile/e2e/specs/connections.spec.ts
  • DevDashboard/mobile/e2e/specs/containers.spec.ts
  • DevDashboard/mobile/e2e/specs/daemon.spec.ts
  • DevDashboard/mobile/e2e/specs/disk-janitor.spec.ts
  • DevDashboard/mobile/e2e/specs/features-rest.smoke.spec.ts
  • DevDashboard/mobile/e2e/specs/more.spec.ts
  • DevDashboard/mobile/e2e/specs/needs-input-inbox.spec.ts
  • DevDashboard/mobile/e2e/specs/network-status.spec.ts
  • DevDashboard/mobile/e2e/specs/obsidian.spec.ts
  • DevDashboard/mobile/e2e/specs/port-killer.spec.ts
  • DevDashboard/mobile/e2e/specs/process-monitor.spec.ts
  • DevDashboard/mobile/e2e/specs/pulse.spec.ts
  • DevDashboard/mobile/e2e/specs/qa.spec.ts
  • DevDashboard/mobile/e2e/specs/quick-commands.spec.ts
  • DevDashboard/mobile/e2e/specs/reminders-todos.spec.ts
  • DevDashboard/mobile/e2e/specs/smoke.spec.ts
  • DevDashboard/mobile/e2e/specs/terminals.spec.ts
  • DevDashboard/mobile/e2e/specs/tmux-presets.spec.ts
  • DevDashboard/mobile/e2e/specs/weather.spec.ts
  • DevDashboard/mobile/e2e/tsconfig.json
  • DevDashboard/mobile/e2e/wdio.conf.ts
  • DevDashboard/mobile/eslint.config.js
  • DevDashboard/mobile/metro.config.js
  • DevDashboard/mobile/nativewind-env.d.ts
  • DevDashboard/mobile/package.json
  • DevDashboard/mobile/patches/react-native-css-interop@0.2.4.patch
  • DevDashboard/mobile/patches/react-native-webview@13.16.0.patch
  • DevDashboard/mobile/scripts/reset-project.js
  • DevDashboard/mobile/src/api/client-provider.tsx
  • DevDashboard/mobile/src/api/mock-client.ts
  • DevDashboard/mobile/src/api/query-keys.ts
  • DevDashboard/mobile/src/app/(more)/_layout.tsx
  • DevDashboard/mobile/src/app/(more)/activity-timeline.tsx
  • DevDashboard/mobile/src/app/(more)/build-log-tail.tsx
  • DevDashboard/mobile/src/app/(more)/claude-usage.tsx
  • DevDashboard/mobile/src/app/(more)/connections.tsx
  • DevDashboard/mobile/src/app/(more)/containers.tsx
  • DevDashboard/mobile/src/app/(more)/daemon.tsx
  • DevDashboard/mobile/src/app/(more)/disk-janitor.tsx
  • DevDashboard/mobile/src/app/(more)/needs-input-inbox.tsx
  • DevDashboard/mobile/src/app/(more)/network-status.tsx
  • DevDashboard/mobile/src/app/(more)/port-killer.tsx
  • DevDashboard/mobile/src/app/(more)/process-monitor.tsx
  • DevDashboard/mobile/src/app/(more)/quick-commands.tsx
  • DevDashboard/mobile/src/app/(more)/reminders-todos.tsx
  • DevDashboard/mobile/src/app/(more)/tmux-presets.tsx
  • DevDashboard/mobile/src/app/(more)/weather.tsx
  • DevDashboard/mobile/src/app/(tabs)/_layout.tsx
  • DevDashboard/mobile/src/app/(tabs)/index.tsx
  • DevDashboard/mobile/src/app/(tabs)/more.tsx
  • DevDashboard/mobile/src/app/(tabs)/obsidian.tsx
  • DevDashboard/mobile/src/app/(tabs)/qa.tsx
  • DevDashboard/mobile/src/app/(tabs)/terminals.tsx
  • DevDashboard/mobile/src/app/_layout.tsx
  • DevDashboard/mobile/src/app/connect.tsx
  • DevDashboard/mobile/src/app/pair.tsx
  • DevDashboard/mobile/src/components/animated-icon.module.css
  • DevDashboard/mobile/src/components/animated-icon.tsx
  • DevDashboard/mobile/src/components/animated-icon.web.tsx
  • DevDashboard/mobile/src/components/connect/QrScanner.tsx
  • DevDashboard/mobile/src/components/connect/ReachabilityBadge.tsx
  • DevDashboard/mobile/src/components/connect/TierPicker.tsx
  • DevDashboard/mobile/src/components/external-link.tsx
  • DevDashboard/mobile/src/components/hint-row.tsx
  • DevDashboard/mobile/src/components/themed-text.tsx
  • DevDashboard/mobile/src/components/themed-view.tsx
  • DevDashboard/mobile/src/components/ui/collapsible.tsx
  • DevDashboard/mobile/src/components/web-badge.tsx
  • DevDashboard/mobile/src/constants/theme.ts
  • DevDashboard/mobile/src/features/activity-timeline/components/EventRow.tsx
  • DevDashboard/mobile/src/features/activity-timeline/components/HourGroup.tsx
  • DevDashboard/mobile/src/features/activity-timeline/components/Timeline.tsx
  • DevDashboard/mobile/src/features/activity-timeline/hooks.ts
  • DevDashboard/mobile/src/features/activity-timeline/queries.test.ts
  • DevDashboard/mobile/src/features/activity-timeline/queries.ts
  • DevDashboard/mobile/src/features/activity-timeline/types.ts
  • DevDashboard/mobile/src/features/activity-timeline/units.test.ts
  • DevDashboard/mobile/src/features/activity-timeline/units.ts
  • DevDashboard/mobile/src/features/build-log-tail/components/LogStream.tsx
  • DevDashboard/mobile/src/features/build-log-tail/components/RunPicker.tsx
  • DevDashboard/mobile/src/features/build-log-tail/hooks.ts
  • DevDashboard/mobile/src/features/build-log-tail/queries.test.ts
  • DevDashboard/mobile/src/features/build-log-tail/queries.ts
  • DevDashboard/mobile/src/features/build-log-tail/subscription.test.ts
  • DevDashboard/mobile/src/features/build-log-tail/subscription.ts
  • DevDashboard/mobile/src/features/build-log-tail/types.ts
  • DevDashboard/mobile/src/features/build-log-tail/units.test.ts
  • DevDashboard/mobile/src/features/build-log-tail/units.ts
  • DevDashboard/mobile/src/features/claude-usage/components/AccountHistoryCharts.tsx
  • DevDashboard/mobile/src/features/claude-usage/components/AccountUsageCard.tsx
  • DevDashboard/mobile/src/features/claude-usage/components/RangeSelector.tsx
  • DevDashboard/mobile/src/features/claude-usage/hooks.ts
  • DevDashboard/mobile/src/features/claude-usage/queries.test.ts
  • DevDashboard/mobile/src/features/claude-usage/queries.ts
  • DevDashboard/mobile/src/features/claude-usage/units.test.ts
  • DevDashboard/mobile/src/features/claude-usage/units.ts
  • DevDashboard/mobile/src/features/connections/ConnectionForm.tsx
  • DevDashboard/mobile/src/features/connections/ConnectionRow.tsx
  • DevDashboard/mobile/src/features/connections/ConnectionsScreen.tsx
  • DevDashboard/mobile/src/features/connections/components.tsx
  • DevDashboard/mobile/src/features/connections/format.ts
  • DevDashboard/mobile/src/features/connections/index.ts
  • DevDashboard/mobile/src/features/connections/store.ts
  • DevDashboard/mobile/src/features/connections/types.ts
  • DevDashboard/mobile/src/features/containers/components/ContainerRow.tsx
  • DevDashboard/mobile/src/features/containers/hooks.ts
  • DevDashboard/mobile/src/features/containers/queries.test.ts
  • DevDashboard/mobile/src/features/containers/queries.ts
  • DevDashboard/mobile/src/features/containers/units.test.ts
  • DevDashboard/mobile/src/features/containers/units.ts
  • DevDashboard/mobile/src/features/daemon/components/DaemonStatusHeader.tsx
  • DevDashboard/mobile/src/features/daemon/components/RunLogSheet.tsx
  • DevDashboard/mobile/src/features/daemon/components/RunRow.tsx
  • DevDashboard/mobile/src/features/daemon/hooks.ts
  • DevDashboard/mobile/src/features/daemon/queries.test.ts
  • DevDashboard/mobile/src/features/daemon/queries.ts
  • DevDashboard/mobile/src/features/daemon/units.test.ts
  • DevDashboard/mobile/src/features/daemon/units.ts
  • DevDashboard/mobile/src/features/disk-janitor/components/UsageBars.tsx
  • DevDashboard/mobile/src/features/disk-janitor/hooks.ts
  • DevDashboard/mobile/src/features/disk-janitor/queries.test.ts
  • DevDashboard/mobile/src/features/disk-janitor/queries.ts
  • DevDashboard/mobile/src/features/disk-janitor/units.test.ts
  • DevDashboard/mobile/src/features/disk-janitor/units.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/attention-target-store.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/components/AttentionItem.tsx
  • DevDashboard/mobile/src/features/needs-input-inbox/components/AttentionList.tsx
  • DevDashboard/mobile/src/features/needs-input-inbox/hooks.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/index.tsx
  • DevDashboard/mobile/src/features/needs-input-inbox/queries.test.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/queries.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/select.test.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/select.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/types.ts
  • DevDashboard/mobile/src/features/network-status/components/RepairButton.tsx
  • DevDashboard/mobile/src/features/network-status/components/StatusCard.tsx
  • DevDashboard/mobile/src/features/network-status/hooks.ts
  • DevDashboard/mobile/src/features/network-status/queries.test.ts
  • DevDashboard/mobile/src/features/network-status/queries.ts
  • DevDashboard/mobile/src/features/network-status/types.ts
  • DevDashboard/mobile/src/features/network-status/units.test.ts
  • DevDashboard/mobile/src/features/network-status/units.ts
  • DevDashboard/mobile/src/features/obsidian/components/NewFolderModal.tsx
  • DevDashboard/mobile/src/features/obsidian/components/NoteReader.tsx
  • DevDashboard/mobile/src/features/obsidian/components/NoteRenderer.tsx
  • DevDashboard/mobile/src/features/obsidian/components/VaultTree.tsx
  • DevDashboard/mobile/src/features/obsidian/components/VaultTreeNode.tsx
  • DevDashboard/mobile/src/features/obsidian/expanded-dirs.test.ts
  • DevDashboard/mobile/src/features/obsidian/expanded-dirs.ts
  • DevDashboard/mobile/src/features/obsidian/hooks.ts
  • DevDashboard/mobile/src/features/obsidian/note-html.test.ts
  • DevDashboard/mobile/src/features/obsidian/note-html.ts
  • DevDashboard/mobile/src/features/obsidian/queries.test.ts
  • DevDashboard/mobile/src/features/obsidian/queries.ts
  • DevDashboard/mobile/src/features/obsidian/vault-filter.test.ts
  • DevDashboard/mobile/src/features/obsidian/vault-filter.ts
  • DevDashboard/mobile/src/features/port-killer/components/KillConfirmDialog.tsx
  • DevDashboard/mobile/src/features/port-killer/components/PortRow.tsx
  • DevDashboard/mobile/src/features/port-killer/hooks.ts
  • DevDashboard/mobile/src/features/port-killer/queries.test.ts
  • DevDashboard/mobile/src/features/port-killer/queries.ts
  • DevDashboard/mobile/src/features/port-killer/units.test.ts
  • DevDashboard/mobile/src/features/port-killer/units.ts
  • DevDashboard/mobile/src/features/process-monitor/components/ProcessRow.tsx
  • DevDashboard/mobile/src/features/process-monitor/components/ProcessTable.tsx
  • DevDashboard/mobile/src/features/process-monitor/components/SortToggle.tsx
  • DevDashboard/mobile/src/features/process-monitor/hooks.ts
  • DevDashboard/mobile/src/features/process-monitor/queries.test.ts
  • DevDashboard/mobile/src/features/process-monitor/queries.ts
  • DevDashboard/mobile/src/features/process-monitor/types.ts
  • DevDashboard/mobile/src/features/process-monitor/units.test.ts
  • DevDashboard/mobile/src/features/process-monitor/units.ts
  • DevDashboard/mobile/src/features/pulse/components/KpiCard.tsx
  • DevDashboard/mobile/src/features/pulse/components/NetworkInfo.tsx
  • DevDashboard/mobile/src/features/pulse/components/ProcessTable.tsx
  • DevDashboard/mobile/src/features/pulse/components/RangeSelector.tsx
  • DevDashboard/mobile/src/features/pulse/components/SparklineRow.tsx
  • DevDashboard/mobile/src/features/pulse/components/WeatherCard.tsx
  • DevDashboard/mobile/src/features/pulse/hooks.ts
  • DevDashboard/mobile/src/features/pulse/queries.test.ts
  • DevDashboard/mobile/src/features/pulse/queries.ts
  • DevDashboard/mobile/src/features/pulse/units.test.ts
  • DevDashboard/mobile/src/features/pulse/units.ts
  • DevDashboard/mobile/src/features/qa/components/QaAnswerHtml.tsx
  • DevDashboard/mobile/src/features/qa/components/QaCard.tsx
  • DevDashboard/mobile/src/features/qa/components/QaFeed.tsx
  • DevDashboard/mobile/src/features/qa/components/QaFilterBar.tsx
  • DevDashboard/mobile/src/features/qa/components/QaLiveDot.tsx
  • DevDashboard/mobile/src/features/qa/hooks.ts
  • DevDashboard/mobile/src/features/qa/live-feed.test.ts
  • DevDashboard/mobile/src/features/qa/live-feed.ts
  • DevDashboard/mobile/src/features/qa/queries.test.ts
  • DevDashboard/mobile/src/features/qa/queries.ts
  • DevDashboard/mobile/src/features/qa/subscription.test.ts
  • DevDashboard/mobile/src/features/qa/subscription.ts
  • DevDashboard/mobile/src/features/qa/units.test.ts
  • DevDashboard/mobile/src/features/qa/units.ts
  • DevDashboard/mobile/src/features/quick-commands/QuickCommandsScreen.tsx
  • DevDashboard/mobile/src/features/quick-commands/components/CommandGrid.tsx
  • DevDashboard/mobile/src/features/quick-commands/components/EditCommandSheet.tsx
  • DevDashboard/mobile/src/features/quick-commands/components/TargetPicker.tsx
  • DevDashboard/mobile/src/features/quick-commands/hooks.ts
  • DevDashboard/mobile/src/features/quick-commands/index.ts
  • DevDashboard/mobile/src/features/quick-commands/queries.test.ts
  • DevDashboard/mobile/src/features/quick-commands/queries.ts
  • DevDashboard/mobile/src/features/quick-commands/types.ts
  • DevDashboard/mobile/src/features/reminders-todos/components/AddTodoForm.tsx
  • DevDashboard/mobile/src/features/reminders-todos/components/PermissionBanner.tsx
  • DevDashboard/mobile/src/features/reminders-todos/components/TodoList.tsx
  • DevDashboard/mobile/src/features/reminders-todos/components/TodoRow.tsx
  • DevDashboard/mobile/src/features/reminders-todos/hooks.ts
  • DevDashboard/mobile/src/features/reminders-todos/queries.test.ts
  • DevDashboard/mobile/src/features/reminders-todos/queries.ts
  • DevDashboard/mobile/src/features/reminders-todos/types.ts
  • DevDashboard/mobile/src/features/terminals/TerminalRenderer.ts
  • DevDashboard/mobile/src/features/terminals/bridge.test.ts
  • DevDashboard/mobile/src/features/terminals/bridge.ts
  • DevDashboard/mobile/src/features/terminals/components/DriverSwitcher.tsx
  • DevDashboard/mobile/src/features/terminals/components/MobileKeyBar.tsx
  • DevDashboard/mobile/src/features/terminals/components/SessionsList.tsx
  • DevDashboard/mobile/src/features/terminals/components/SwipeableActionRow.tsx
  • DevDashboard/mobile/src/features/terminals/components/WebViewHtmlRenderer.tsx
  • DevDashboard/mobile/src/features/terminals/components/WebViewTtydRenderer.tsx
  • DevDashboard/mobile/src/features/terminals/components/drivers.ts
  • DevDashboard/mobile/src/features/terminals/driver-store.ts
  • DevDashboard/mobile/src/features/terminals/hooks.ts
  • DevDashboard/mobile/src/features/terminals/inject.ts
  • DevDashboard/mobile/src/features/terminals/keymap.test.ts
  • DevDashboard/mobile/src/features/terminals/keymap.ts
  • DevDashboard/mobile/src/features/terminals/queries.test.ts
  • DevDashboard/mobile/src/features/terminals/queries.ts
  • DevDashboard/mobile/src/features/terminals/registry.test.ts
  • DevDashboard/mobile/src/features/terminals/registry.ts
  • DevDashboard/mobile/src/features/terminals/scripts/build-xterm-host.ts
  • DevDashboard/mobile/src/features/tmux-presets/components/CaptureSheet.tsx
  • DevDashboard/mobile/src/features/tmux-presets/components/PresetCard.tsx
  • DevDashboard/mobile/src/features/tmux-presets/components/RestoreConfirm.tsx
  • DevDashboard/mobile/src/features/tmux-presets/hooks.ts
  • DevDashboard/mobile/src/features/tmux-presets/queries.test.ts
  • DevDashboard/mobile/src/features/tmux-presets/queries.ts
  • DevDashboard/mobile/src/features/tmux-presets/types.ts
  • DevDashboard/mobile/src/features/tmux-presets/units.test.ts
  • DevDashboard/mobile/src/features/tmux-presets/units.ts
  • DevDashboard/mobile/src/features/weather/components/WeatherCard.tsx
  • DevDashboard/mobile/src/features/weather/hooks.ts
  • DevDashboard/mobile/src/features/weather/queries.test.ts
  • DevDashboard/mobile/src/features/weather/queries.ts
  • DevDashboard/mobile/src/features/weather/units.test.ts
  • DevDashboard/mobile/src/features/weather/units.ts
  • DevDashboard/mobile/src/global.css
  • DevDashboard/mobile/src/hooks/use-color-scheme.ts
  • DevDashboard/mobile/src/hooks/use-color-scheme.web.ts
  • DevDashboard/mobile/src/hooks/use-theme.ts
  • DevDashboard/mobile/src/lib/__tests__/contract-import.test.ts
  • DevDashboard/mobile/src/lib/__tests__/sse.test.ts
  • DevDashboard/mobile/src/lib/abort-timeout.ts
  • DevDashboard/mobile/src/lib/apply-pairing.ts
  • DevDashboard/mobile/src/lib/cn.ts
  • DevDashboard/mobile/src/lib/contract-client.ts
  • DevDashboard/mobile/src/lib/qr.test.ts
  • DevDashboard/mobile/src/lib/qr.ts
  • DevDashboard/mobile/src/lib/query.ts
  • DevDashboard/mobile/src/lib/sse-frame.ts
  • DevDashboard/mobile/src/lib/sse.ts
  • DevDashboard/mobile/src/lib/storage/__tests__/kv.test.ts
  • DevDashboard/mobile/src/lib/storage/db.ts
  • DevDashboard/mobile/src/lib/storage/kv.ts
  • DevDashboard/mobile/src/lib/storage/secure.ts
  • DevDashboard/mobile/src/shims/event-polyfill.ts
  • DevDashboard/mobile/src/shims/safe-json.ts
  • DevDashboard/mobile/src/state/connection-store.ts
  • DevDashboard/mobile/src/state/connection.ts
  • DevDashboard/mobile/src/theme/colors.ts
  • DevDashboard/mobile/src/theme/tokens.css
  • DevDashboard/mobile/src/transport/Transport.ts
  • DevDashboard/mobile/src/transport/e2e-transport.ts
  • DevDashboard/mobile/src/transport/e2e/box-cipher.test.ts
  • DevDashboard/mobile/src/transport/e2e/box-cipher.ts
  • DevDashboard/mobile/src/transport/e2e/envelope.test.ts
  • DevDashboard/mobile/src/transport/e2e/envelope.ts
  • DevDashboard/mobile/src/transport/lan-discovery.ts
  • DevDashboard/mobile/src/transport/plain-transport.test.ts
  • DevDashboard/mobile/src/transport/plain-transport.ts
  • DevDashboard/mobile/src/transport/qa-stream.test.ts
  • DevDashboard/mobile/src/transport/qa-stream.ts
  • DevDashboard/mobile/src/transport/reachability.test.ts
  • DevDashboard/mobile/src/transport/reachability.ts
  • DevDashboard/mobile/src/transport/sse-parser.test.ts
  • DevDashboard/mobile/src/transport/sse-parser.ts
  • DevDashboard/mobile/src/transport/terminal-ws.test.ts
  • DevDashboard/mobile/src/transport/terminal-ws.ts
  • DevDashboard/mobile/src/transport/tiers/cloudflared.ts
  • DevDashboard/mobile/src/transport/tiers/lan.ts
  • DevDashboard/mobile/src/transport/tiers/managed.test.ts
  • DevDashboard/mobile/src/transport/tiers/managed.ts
  • DevDashboard/mobile/src/transport/tiers/tailscale.ts
  • DevDashboard/mobile/src/types/assets.d.ts
  • DevDashboard/mobile/src/types/css-modules.d.ts
  • DevDashboard/mobile/src/types/react-native-zeroconf.d.ts
  • DevDashboard/mobile/src/ui/Banner.tsx
  • DevDashboard/mobile/src/ui/Card.tsx
  • DevDashboard/mobile/src/ui/Empty.tsx
  • DevDashboard/mobile/src/ui/ErrorBoundary.tsx
  • DevDashboard/mobile/src/ui/KeyValueRow.tsx
  • DevDashboard/mobile/src/ui/ListRow.tsx
  • DevDashboard/mobile/src/ui/Loading.tsx
  • DevDashboard/mobile/src/ui/MetricChart.tsx
  • DevDashboard/mobile/src/ui/MockBadge.tsx
  • DevDashboard/mobile/src/ui/Screen.tsx
  • DevDashboard/mobile/src/ui/ScreenLoader.tsx
  • DevDashboard/mobile/src/ui/SectionHeader.tsx
  • DevDashboard/mobile/src/ui/StatTile.tsx
  • DevDashboard/mobile/src/ui/StatusPill.tsx
  • DevDashboard/mobile/src/ui/TabPlaceholder.tsx
  • DevDashboard/mobile/src/ui/index.ts
  • DevDashboard/mobile/tailwind.config.js
  • DevDashboard/mobile/tsconfig.json
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-00-Overview.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-01-ServerExtraction.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-02-TransportTrust.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-03-SharedContract.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-04-MobileFoundation.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-05-FeaturePulse.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-06-FeatureTerminals.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-07-FeatureQA.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-08-FeatureObsidian.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-09-FeaturesRest.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-10-LandingAndManaged.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-11-Distribution.md
  • DevDashboard/plans/2026-05-29-DevDashboardMobile-ADR.md
  • DevDashboard/research/00-current-architecture.md
  • DevDashboard/research/01-terminal-webview-ttyd.md
  • DevDashboard/research/02-terminal-native-emulators.md
  • DevDashboard/research/03-terminal-xterm-dom-and-osapps.md
  • DevDashboard/research/04-transport-and-trust.md
  • DevDashboard/research/05-metrics-charts-and-sdk55.md
  • DevDashboard/research/06-terminal-recommendation.md
  • DevDashboard/research/07-terminal-opensource-hunt-a.md
  • DevDashboard/research/08-terminal-opensource-hunt-b.md
  • DevDashboard/research/09-nativewind-v5-readiness.md
  • DevDashboard/research/10-terminal-3rd-driver-recommendation.md
  • DevDashboard/research/12-impl-01-notes.md
  • DevDashboard/research/13-impl-04-notes.md
  • DevDashboard/research/14-impl-02-agentside-notes.md
  • DevDashboard/research/15-impl-02-mobileside-notes.md
  • DevDashboard/research/16-impl-05-pulse-notes.md
  • DevDashboard/research/17-impl-06-terminals-notes.md
  • DevDashboard/research/18-impl-07-qa-notes.md
  • DevDashboard/research/19-impl-08-obsidian-notes.md
  • DevDashboard/research/20-impl-09-rest-notes.md
  • DevDashboard/research/21-impl-appium-foundation-notes.md
  • DevDashboard/research/22-impl-10-cloud-notes.md
  • DevDashboard/roadmap-ideas/01-competitor.md
  • DevDashboard/roadmap-ideas/02-markets.md
  • DevDashboard/roadmap-ideas/03-game-design.md
  • DevDashboard/roadmap-ideas/04-infinite-budget.md
  • DevDashboard/roadmap-ideas/SYNTHESIS.md
  • DevDashboard/roadmap-ideas/v2/01-feature-depth.md
  • DevDashboard/roadmap-ideas/v2/02-integrations-automations.md
  • DevDashboard/roadmap-ideas/v2/03-speedrunner.md
  • DevDashboard/roadmap-ideas/v2/04-3am-oncall.md
  • DevDashboard/roadmap-ideas/v2/05-ten-year-old.md
  • DevDashboard/roadmap-ideas/v2/SYNTHESIS.md
  • biome.json
  • src/dev-dashboard-cloud/index.ts

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/dev-dashboard-mobile

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

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request scaffolds the DevDashboard Mobile Expo app and the DevDashboard Cloud web stack, implementing core features such as Pulse metrics, live terminals with swappable WebView drivers, a QA live-stream, and Obsidian note sharing, alongside an Appium E2E testing harness. The code review identifies several critical issues and improvement opportunities: a bug where Stripe Checkout metadata does not propagate to the subscription object, potential race conditions in the terminal socket connection and SSE event stream, a data boundary bypass in settings upserts, and a type error when injecting modified single characters. Additionally, feedback suggests persisting the mobile connection state, properly rendering Markdown in QA cards, fixing a rounding bug in daemon duration formatting, and defensively handling non-JSON responses from the Cloudflare API.

Comment on lines +84 to +93
const session = await stripe.checkout.sessions.create({
mode: "subscription",
line_items: [{ price, quantity: 1 }],
customer: opts.existingCustomerId ?? undefined,
customer_email: opts.existingCustomerId ? undefined : opts.email,
client_reference_id: opts.accountId,
metadata: { accountId: opts.accountId, tier: opts.tier },
success_url: `${opts.appBaseUrl}/dashboard/billing?status=success`,
cancel_url: `${opts.appBaseUrl}/dashboard/billing?status=cancelled`,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The metadata set directly on the Stripe Checkout Session does not automatically propagate to the created Subscription object. As a result, the webhook handler for customer.subscription.updated and customer.subscription.deleted will find subscription.metadata?.accountId to be undefined, failing to update the database. To fix this, pass the metadata inside subscription_data.metadata as well.

    const session = await stripe.checkout.sessions.create({
        mode: "subscription",
        line_items: [{ price, quantity: 1 }],
        customer: opts.existingCustomerId ?? undefined,
        customer_email: opts.existingCustomerId ? undefined : opts.email,
        client_reference_id: opts.accountId,
        subscription_data: {
            metadata: { accountId: opts.accountId, tier: opts.tier },
        },
        metadata: { accountId: opts.accountId, tier: opts.tier },
        success_url: opts.appBaseUrl + "/dashboard/billing?status=success",
        cancel_url: opts.appBaseUrl + "/dashboard/billing?status=cancelled",
    });

Comment on lines +31 to +43
export function injectKey(key: TerminalKey, mods: { shift?: boolean; ctrl?: boolean; alt?: boolean } = {}): string {
const spec = KEY_TABLE[key];

return `(function(){
var ta=document.querySelector(".xterm-helper-textarea");
if(!ta){return;}
ta.focus();
ta.dispatchEvent(new KeyboardEvent("keydown",{
key:${str(spec.key)},code:${str(spec.code)},keyCode:${spec.keyCode},which:${spec.keyCode},
shiftKey:${!!mods.shift},ctrlKey:${!!mods.ctrl},altKey:${!!mods.alt},
bubbles:true,cancelable:true
}));
})();true;`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

In MobileKeyBar.tsx, single characters with the Ctrl modifier (e.g., Ctrl+C) are sent via renderer.sendKey(char as TerminalKey, { ctrl: true }). However, in inject.ts, KEY_TABLE only contains mappings for named TerminalKeys. Passing a single character like "c" results in spec being undefined, causing a runtime TypeError: Cannot read properties of undefined (reading 'key') when evaluating spec.key. We should update injectKey to handle single characters defensively.

export function injectKey(key: TerminalKey | string, mods: { shift?: boolean; ctrl?: boolean; alt?: boolean } = {}): string {
    const spec = KEY_TABLE[key as TerminalKey];

    if (!spec) {
        const keyCode = key.toUpperCase().charCodeAt(0);
        return "(function(){\n" +
            "    var ta=document.querySelector(\".xterm-helper-textarea\");\n" +
            "    if(!ta){return;}\n" +
            "    ta.focus();\n" +
            "    ta.dispatchEvent(new KeyboardEvent(\"keydown\",{\n" +
            "        key:" + str(key) + ",code:" + str("Key" + key.toUpperCase()) + ",keyCode:" + keyCode + ",which:" + keyCode + ",\n" +
            "        shiftKey:" + !!mods.shift + ",ctrlKey:" + !!mods.ctrl + ",altKey:" + !!mods.alt + ",\n" +
            "        bubbles:true,cancelable:true\n" +
            "    }));\n" +
            "})();true;";
    }

    return "(function(){\n" +
        "    var ta=document.querySelector(\".xterm-helper-textarea\");\n" +
        "    if(!ta){return;}\n" +
        "    ta.focus();\n" +
        "    ta.dispatchEvent(new KeyboardEvent(\"keydown\",{\n" +
        "        key:" + str(spec.key) + ",code:" + str(spec.code) + ",keyCode:" + spec.keyCode + ",which:" + spec.keyCode + ",\n" +
        "        shiftKey:" + !!mods.shift + ",ctrlKey:" + !!mods.ctrl + ",altKey:" + !!mods.alt + ",\n" +
        "        bubbles:true,cancelable:true\n" +
        "    }));\n" +
        "})();true;";
}

Comment on lines +143 to +148
if (existing) {
await db
.update(accountSettings)
.set({ ...patch, updatedAt: new Date().toISOString() })
.where(eq(accountSettings.accountId, accountId));
return;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

To strictly enforce the data boundary (D11) and prevent any forbidden key material or unpermitted fields from being written, the update path in upsertSettings must also run through assertNoKeyMaterial defensively, just like the insert path and updateSubscription do.

        if (existing) {
            assertNoKeyMaterial("account_settings", {
                accountId,
                ...patch,
                updatedAt: new Date().toISOString(),
            });
            await db
                .update(accountSettings)
                .set({ ...patch, updatedAt: new Date().toISOString() })
                .where(eq(accountSettings.accountId, accountId));
            return;
        }

Comment on lines +37 to +63
eventSourceFactory: (url: string): EventSourceLike => {
let onmessage: ((ev: { data: string }) => void) | null = null;
let onerror: ((ev: unknown) => void) | null = null;
const auth = opts.authHeader();
const handle: SseHandle = streamSse({
url,
headers: auth ? { Authorization: auth } : undefined,
onEvent: (event) => onmessage?.({ data: event.data }),
onError: (err) => onerror?.(err),
});

return {
close: () => handle.close(),
get onmessage() {
return onmessage;
},
set onmessage(handler) {
onmessage = handler;
},
get onerror() {
return onerror;
},
set onerror(handler) {
onerror = handler;
},
};
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When eventSourceFactory is called, streamSse is initiated synchronously. If the connection is established or events are received before the caller can assign onmessage or onerror to the returned EventSourceLike object, those early events will be silently dropped. Implementing a simple queueing mechanism ensures no events are lost during this initial setup phase.

            eventSourceFactory: (url: string): EventSourceLike => {
                let onmessage: ((ev: { data: string }) => void) | null = null;
                let onerror: ((ev: unknown) => void) | null = null;
                const queue: string[] = [];
                const auth = opts.authHeader();
                const handle: SseHandle = streamSse({
                    url,
                    headers: auth ? { Authorization: auth } : undefined,
                    onEvent: (event) => {
                        if (onmessage) {
                            onmessage({ data: event.data });
                        } else {
                            queue.push(event.data);
                        }
                    },
                    onError: (err) => {
                        if (onerror) {
                            onerror(err);
                        }
                    },
                });

                return {
                    close: () => handle.close(),
                    get onmessage() {
                        return onmessage;
                    },
                    set onmessage(handler) {
                        onmessage = handler;
                        if (handler) {
                            while (queue.length > 0) {
                                const data = queue.shift();
                                if (data !== undefined) {
                                    handler({ data });
                                }
                            }
                        }
                    },
                    get onerror() {
                        return onerror;
                    },
                    set onerror(handler) {
                        onerror = handler;
                    },
                };
            },

Comment on lines +117 to +134
ws.onStatus((s) => {
if (s === "open") {
setStatus("connected");
} else if (s === "reconnecting") {
setStatus("connecting", "reconnecting");
} else if (s === "closed") {
setStatus("disconnected");
}
});

ws.onMessage((data) => {
const chunk = typeof data === "string" ? data : new Uint8Array(data);
if (chunk instanceof Uint8Array) {
cbRef.current.onData?.(chunk);
}

run(injectBytes(bufToBase64(data)));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

When openSocket is called multiple times (e.g., during rapid session switches or driver toggles), the previous socket is closed asynchronously. If the closed socket emits status or message events after the new socket has been opened, it can overwrite the active terminal's status or inject stale data. Guarding the callbacks by checking if (wsRef.current !== ws) return; prevents this race condition.

                ws.onStatus((s) => {
                    if (wsRef.current !== ws) return;
                    if (s === "open") {
                        setStatus("connected");
                    } else if (s === "reconnecting") {
                        setStatus("connecting", "reconnecting");
                    } else if (s === "closed") {
                        setStatus("disconnected");
                    }
                });

                ws.onMessage((data) => {
                    if (wsRef.current !== ws) return;
                    const chunk = typeof data === "string" ? data : new Uint8Array(data);
                    if (chunk instanceof Uint8Array) {
                        cbRef.current.onData?.(chunk);
                    }

                    run(injectBytes(bufToBase64(data)));
                });

sendInput(text) {
wsRef.current?.send(text);
},
sendKey(key: TerminalKey, mods?: TerminalKeyMods) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the sendKey signature to accept TerminalKey | string to match the updated TerminalRenderer interface.

Suggested change
sendKey(key: TerminalKey, mods?: TerminalKeyMods) {
sendKey(key: TerminalKey | string, mods?: TerminalKeyMods) {

setActive(next);
setStatus("connecting");
// Remount-via-key: a fresh initial mount sidesteps the #3863 update-path bug.
setMountKey((k) => k + 1);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Update the sendKey signature to accept TerminalKey | string to match the updated TerminalRenderer interface.

                sendKey(key: TerminalKey | string, mods?: TerminalKeyMods) {

Comment on lines +15 to +23

export const useConnection = create<ConnectionState>((set) => ({
tier: "lan",
baseUrl: null,
authHeader: null,
status: "disconnected",
setEndpoint: (tier, baseUrl, authHeader) => set({ tier, baseUrl, authHeader }),
setStatus: (status) => set({ status }),
reset: () => set({ baseUrl: null, authHeader: null, status: "disconnected" }),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The useConnection store is currently a plain, non-persisted Zustand store. Since baseUrl and authHeader are not persisted to storage (e.g., via Zustand's persist middleware or manual hydration on startup), the connection state will be lost every time the app restarts, forcing the user to re-connect or re-pair on every launch. Consider persisting these fields or re-hydrating them on startup.

Comment on lines +74 to +76
Answer
</Text>
<Text testID={`qa-answer-${id}`} className="text-[13px]" style={{ color: c.textPrimary }}>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Rendering entry.answerMd directly inside a standard React Native <Text> component will display raw Markdown syntax (such as **bold**, _italic_, or backticks) as plain text to the user. To provide a clean reading experience, consider stripping the Markdown syntax for the preview or using a lightweight Markdown renderer.

Comment on lines +79 to +84
const detail = body.errors?.map((e) => e.message).join("; ") || `HTTP ${res.status}`;
throw new Error(`Cloudflare custom-hostname provisioning failed: ${detail}`);
}

return {
configured: true,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If the Cloudflare API returns a non-JSON error response (e.g., an HTML error page during a 502/504 gateway error), calling res.json() directly will throw a SyntaxError, masking the actual HTTP status code. It is safer to check the Content-Type header or handle JSON parsing defensively, while ensuring any parsing errors are logged rather than silently swallowed.

    let body: CloudflareCustomHostnameResponse | null = null;
    if (res.headers.get("content-type")?.includes("application/json")) {
        try {
            body = await res.json() as CloudflareCustomHostnameResponse;
        } catch (err) {
            console.error("Failed to parse Cloudflare response as JSON", err);
        }
    }

    if (!res.ok || !body || !body.success) {
        const detail = (body?.errors?.map((e) => e.message).join("; ") || "HTTP ") + res.status;
        throw new Error("Cloudflare custom-hostname provisioning failed: " + detail);
    }
References
  1. When catching errors, avoid swallowing all errors silently. Instead, catch specific error types or re-throw unexpected errors to prevent silent failures. Logging unexpected errors is also a good practice.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 51

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
DevDashboard/mobile/src/features/qa/subscription.test.ts (1)

110-117: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clean up the timeout in the subscribe smoke test to avoid latent timer-driven flakiness.

The current promise can resolve true while the 2s timeout is still pending, which can keep extra timers alive and make this test slower/less deterministic than needed.

Suggested fix
     it("qa.subscribe emits then closes cleanly", async () => {
-        const received = await new Promise<boolean>((resolve) => {
-            const sub = mockDashboardClient.qa.subscribe(() => resolve(true));
-            setTimeout(() => {
-                sub.close();
-                resolve(false);
-            }, 2_000);
+        const received = await new Promise<boolean>((resolve) => {
+            const sub = mockDashboardClient.qa.subscribe(() => {
+                clearTimeout(timer);
+                sub.close();
+                resolve(true);
+            });
+            const timer = setTimeout(() => {
+                sub.close();
+                resolve(false);
+            }, 2_000);
         });
         expect(received).toBe(true);
     });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/features/qa/subscription.test.ts` around lines 110 -
117, The smoke test for "subscribe" leaves a 2s setTimeout pending; modify the
test so the timeout ID is stored (e.g., const timer = setTimeout(...)) and call
clearTimeout(timer) whenever the test promise resolves or rejects (before
returning/ending the test) so no latent timers remain; ensure both the success
path and error/cleanup path clear the timeout to avoid flakiness in the
subscribe smoke test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@DevDashboard/cloud/landing/field-notes/index.html`:
- Line 664: The email <input type="email" ...> lacks an accessible label; add a
properly associated label element or aria-label/aria-labelledby so screen
readers can identify it. Specifically, give the input a unique id (e.g.,
id="email") and add a <label for="email">Email address</label> (or update the
surrounding form to reference that id via aria-labelledby), ensuring the visual
styling preserves the existing classes on the input and that the label text
conveys that the field is required if applicable.
- Around line 213-214: The two interactive buttons ("Approve" and "Open shell")
are missing explicit type attributes causing them to default to type="submit"
inside forms; update the two button elements rendering the "Approve" and "Open
shell" controls in the landing field-notes markup to include type="button"
(i.e., add type="button" to the button elements for the Approve and Open shell
buttons) so they no longer trigger form submission.

In `@DevDashboard/cloud/landing/obsidian-terminal/index.html`:
- Around line 168-171: The hamburger button with id="menuBtn" (containing spans
with ids "ham1" and "ham2") is missing an explicit type and can accidentally
submit a parent form; add type="button" to the <button id="menuBtn"> element so
it won't trigger form submission while preserving the existing attributes and
behavior.
- Around line 721-724: The email <input> element in the form (the element with
type="email" and placeholder="you@yourmachine.dev") is missing an accessible
label; add one by either wrapping the input with a <label> that contains
descriptive text (e.g., "Email") or by adding an aria-label attribute (e.g.,
aria-label="Email address") to the input, and ensure the input keeps required
and placeholder attributes unchanged so screen readers can announce it
correctly.

In `@DevDashboard/cloud/web/.gitignore`:
- Around line 22-24: The .gitignore entry "*.png" is too broad and will ignore
PNGs project-wide; narrow it to only Playwright scratch artifacts by replacing
the global pattern with a directory-scoped pattern such as
".playwright-mcp/**/*.png" (or ".playwright-mcp/*.png") so only PNGs inside the
.playwright-mcp directory are ignored and legitimate assets (favicons, public
images, OG images) remain tracked; update the entry referencing the
.playwright-mcp/ and "*.png" patterns accordingly.

In `@DevDashboard/cloud/web/db/migrations/0000_true_energizer.sql`:
- Around line 63-72: The subscriptions table allows multiple rows per
account_id; add a unique constraint on the account_id column in the
subscriptions table (e.g., ALTER TABLE or CREATE TABLE with UNIQUE
(`account_id`)) so only one current subscription row exists per account; update
any related constraints/triggers or downstream code that assumes multiple rows
and, if history is required, move historical records to a separate table rather
than keeping duplicates in the subscriptions table (reference: table
`subscriptions`, column `account_id`, primary key `id`).

In `@DevDashboard/cloud/web/db/migrations/meta/0000_snapshot.json`:
- Around line 159-171: The foreign key account_settings_account_id_user_id_fk
indicates account_settings.account_id references user.id but other tables use
account_id to mean user.id (e.g., devices.account_id, subscriptions.account_id);
either rename the column to user_id in the account_settings schema and update
the FK definition (rename account_settings.account_id → account_settings.user_id
and adjust account_settings_account_id_user_id_fk to reference columnsFrom
["user_id"]), or if the current name is intentional, add a clear schema
comment/documentation entry explaining that account_id in account_settings
semantically refers to the user identifier so consumers and future migrations
remain consistent; update any code, indices, and tests that reference
account_settings.account_id accordingly.

In `@DevDashboard/cloud/web/package.json`:
- Line 30: The package.json currently uses an unpinned dependency "nitro":
"latest" — replace this with a specific stable version (e.g. "nitro": "2.x.x")
or switch to "nitropack" with a pinned version (e.g. "nitropack": "2.x.x") to
avoid accidentally pulling Nitro v3 beta; update the other package.json that
also uses "nitro" so both are consistent, then run your package manager to
update pnpm-lock.yaml so the resolved version is recorded in the lockfile.

In `@DevDashboard/cloud/web/src/components/auth/AuthCard.tsx`:
- Around line 28-45: AuthCard drops the signup plan from /signup?plan=... so new
accounts always default to free; preserve and propagate the plan by including it
in the signup flow and/or navigation. Update AuthCard (component and its
onSubmit handling where signUp.email(...) is called) to accept the passed-in
plan prop and forward it: either add the plan to the signUp request payload
(e.g., as metadata/tier so backend can persist it and
cloudStore.ensureSubscription can pick it up) or include the plan when
navigating after signup (e.g., navigate({ to: "/dashboard?plan=..." }) or to
/dashboard/setup?plan=...) so getOverview/ensureSubscription can create the
correct tier; ensure the same plan prop name is used across Signup route,
AuthCard, signUp.email call, and any subsequent
cloudStore.ensureSubscription/getOverview handling.

In `@DevDashboard/cloud/web/src/integrations/tanstack-query/root-provider.tsx`:
- Around line 4-15: getContext() currently constructs a new QueryClient on every
call which creates multiple isolated caches; instead create and export a single
shared QueryClient instance (or memoize the call) so consumers reuse the same
client. Replace the pattern in getContext()/QueryClient with a module-level
singleton named queryClient (or ensure getContext() is invoked once and its
return stored) so all consumers import/use the same QueryClient instance rather
than recreating one each call.

In `@DevDashboard/cloud/web/src/lib/db/schema.ts`:
- Line 7: The schema documentation in schema.ts incorrectly lists an `accounts`
domain table even though that table is not defined anywhere in the schema.
Update the top-level domain table list to match the actual schema symbols
(`subscriptions`, `devices`, `managedSubdomains`, and `accountSettings`) and
remove the stale `accounts` reference. Make sure the comment stays consistent
with the table definitions exposed in this file.

In `@DevDashboard/DECISIONS.md`:
- Around line 67-150: Several level-3 headings (e.g., "### 2026-05-29 14:xx —
Kickoff", "### 2026-05-29 — Architecture forks answered", "### 2026-05-29 —
Stack steer", and other `###` headings in this block) lack a blank line after
the heading which triggers MD022; insert a single blank line immediately after
each `###` heading in DECISIONS.md so every heading is followed by an empty line
before the subsequent paragraph or list, then re-run markdownlint to confirm the
MD022 warnings are resolved.

In `@DevDashboard/mobile/AGENTS.md`:
- Around line 1-3: AGENTS.md currently only contains an Expo version note;
replace and expand it into a proper agent reference by adding a concise
Overview, Agent Implementations (list classes/modules and their
responsibilities, e.g., AgentName or class names), Capabilities (what each agent
can/can't do), Usage Patterns and Examples (how to instantiate/configure/invoke
each agent including expected inputs/outputs), Configuration/Environment
requirements, and Troubleshooting/Errors; ensure each agent entry references the
actual implementation symbols (class/function names) and includes minimal code
snippets or command examples illustrating common workflows and configuration
keys.

In `@DevDashboard/mobile/app.config.ts`:
- Around line 3-8: Update the header comment to remove the incorrect claim that
"react-native-zeroconf" is added as a config plugin; instead state that
"react-native-zeroconf" must NOT be included in the plugins array and is handled
natively, while "expo-camera" remains a config plugin. Edit the top comment
block (the dynamic config description) to match the actual behavior of the
plugins array and avoid restating obvious code — reference the plugins array and
the plugin names "react-native-zeroconf" and "expo-camera" in the revised
comment so future maintainers are not misled.

In `@DevDashboard/mobile/e2e/pages/MoreNav.page.ts`:
- Around line 22-28: The deep-link fallback bundleId in the open(route:
MoreRoute) method currently defaults to "dev.genesistools.devdashboard"; update
this fallback to match the iOS bundleIdentifier "dev.foltyn.dev-dashboard" (and
make the same change where DD_BUNDLE_ID is used in ConnectPage.page.ts) or
alternatively ensure CI/e2e always sets DD_BUNDLE_ID to the configured iOS
bundle id; modify the bundleId expression in MoreNav.open and ConnectPage usage
so the default matches "dev.foltyn.dev-dashboard".

In `@DevDashboard/mobile/e2e/README.md`:
- Around line 13-27: Update the fenced code block in
DevDashboard/mobile/e2e/README.md to include a language identifier (e.g.,
"text") on the opening fence so the directory tree block satisfies markdownlint
MD040; locate the triple-backtick block that begins the directory tree and
change it from ``` to ```text (leaving the contents and closing fence
unchanged).
- Around line 140-143: Add a blank line before the fenced code block that begins
with ``` in the README so the code block is surrounded by blank lines per MD031;
locate the triple-backtick fence in DevDashboard/mobile/e2e/README.md around the
list item mentioning "e2e/specs/<feature>.spec.ts" and insert a single empty
line immediately above the opening ``` fence.

In `@DevDashboard/mobile/README.md`:
- Around line 26-37: The README currently references the top-level "app" and
"app-example" directories but the project uses "src/app" in this PR; update the
onboarding paths and reset description to reference "src/app" and
"src/app-example" instead of "app" and "app-example" and ensure the text around
"npm run reset-project" explains it moves starter code to src/app-example and
creates a blank src/app. While editing README.md, keep the file-based routing
link but verify the "npm run reset-project" explanation matches the actual reset
script in package.json (adjust wording if the script targets different paths).

In `@DevDashboard/mobile/src/app/_layout.tsx`:
- Line 14: The useEffect in _layout.tsx currently calls wireAppStateFocus() but
does not clean up its subscription; change the effect to capture the unsubscribe
returned by wireAppStateFocus() and return it from the effect (e.g., const
unsubscribe = wireAppStateFocus(); return unsubscribe;) so the AppState listener
established by wireAppStateFocus() is removed on unmount; update the effect
surrounding symbols: useEffect and wireAppStateFocus to implement this cleanup.

In `@DevDashboard/mobile/src/app/`(tabs)/terminals.tsx:
- Around line 146-150: The MobileKeyBar can receive a null/stale renderer
because rendererRef.current is read during render before DriverComponent's ref
is assigned; change the wiring to promote the renderer to React state and set it
via a ref/onReady callback from DriverComponent so MobileKeyBar always receives
a stable value. Concretely: add state (e.g., renderer and setRenderer) in the
component, pass a callback prop or ref callback into DriverComponent (the same
place currently using rendererRef) that calls setRenderer(ref) once
mounted/committed, and pass the state value into MobileKeyBar instead of reading
rendererRef.current; keep rendererRef if still needed internally but ensure
MobileKeyBar uses the state-updated renderer to avoid the initial null window.

In `@DevDashboard/mobile/src/app/connect.tsx`:
- Around line 57-60: Validate the parsed port before calling setLan: when
computing port from url.port (using Number.parseInt), check for NaN or
out-of-range values (<=0 or >65535) and fall back to the default 3042; then pass
that validated port into setLan and use it to build baseUrl (e.g.,
`http://${url.hostname}:${validatedPort}`) so an invalid parse never gets
written into setLan or baseUrl; update the code around the setLan call and the
local port variable accordingly (references: url.port, setLan, baseUrl).
- Around line 91-101: Wrap the call to applyPairingUri in a try/catch inside the
async block so thrown exceptions are handled: call applyPairingUri(data,
password) inside try, on success keep the existing dispatchReach({ type:
"probe-ok" }); on error (catch) call setError with the caught error message (or
a fallback like "Pairing failed.") and run the same tier-aware dispatchReach({
type: "probe-fail", tier, paired: tier !== "managed" }) logic as in the current
else branch; ensure you reference applyPairingUri, dispatchReach, setError, and
the "probe-ok"/"probe-fail" actions so the UI/state updates even when
applyPairingUri throws.

In `@DevDashboard/mobile/src/components/connect/QrScanner.tsx`:
- Around line 37-44: The handler in onBarcodeScanned is setting scannedOnce
unconditionally which blocks all future scans; change it so you call the
onScanned/onQrScanned prop and only setScannedOnce(true) when that call
indicates success (have the parent return a boolean or Promise<boolean>), i.e.
await the result of onScanned/onQrScanned and if it returns true then call
setScannedOnce(true), otherwise leave scannedOnce false to allow retries; also
handle both sync and async returns and guard against exceptions by catching
errors and treating them as a failed scan (do not set scannedOnce on failure).

In `@DevDashboard/mobile/src/features/claude-usage/units.ts`:
- Around line 38-53: Remove the redundant JSDoc-style comment lines that only
restate the function signatures for bucketLabel, utilizationPct, and clock:
delete the three comment blocks immediately above the functions bucketLabel,
utilizationPct, and clock so the code is self-explanatory; if any comment
contains non-obvious behavior (e.g., return formats like "em dash" or percent
rounding) replace it with a single concise phrase that adds meaning rather than
repeating the signature.

In `@DevDashboard/mobile/src/features/containers/units.test.ts`:
- Around line 10-13: The test title is inaccurate: it claims only lowercase
"running" is accepted while the assertions expect case-insensitive normalization
(e.g., "Running" -> "running"); update the test's description string to reflect
that runState normalizes case and treats any case-variant of "running" as
"running" and everything else as "stopped" (refer to the test and the runState({
state: ... }) calls to locate and change the it(...) description).

In `@DevDashboard/mobile/src/features/daemon/queries.ts`:
- Around line 59-60: The query treats an empty string as "no log file" in the
early guard (if (!logFile) return []), but the react-query option `enabled` is
using `logFile != null`, which still enables the query for empty strings; change
the query's `enabled` condition to `Boolean(logFile)` so it matches the guard
and prevents running the `queryFn` when `logFile` is empty. Update the `enabled`
option where the query is created (referencing the `logFile` variable and the
query's `enabled` property) to use Boolean(logFile).
- Around line 35-37: The helper asArray currently converts any non-array into []
which masks server/contract failures; update asArray<T>(value: unknown): T[] so
it treats undefined or null as an acceptable empty response (return []), but for
any other non-array type it throws a descriptive error (or returns a
Result/Error) so callers like the code using asArray at the daemon run/log query
sites surface contract violations instead of silently succeeding; keep the
function name asArray and update all callsites to handle the thrown error or
Result accordingly.

In `@DevDashboard/mobile/src/features/daemon/units.ts`:
- Around line 36-38: The current logic computes mins and rem from seconds but
rounds the remainder which can produce rem === 60 (e.g. "1m60s"); instead, round
the total seconds first and then split into minutes and seconds so the
minute/second relationship stays consistent: compute a roundedSeconds =
Math.round(seconds) (or Math.round(ms/1000) if input is ms), then set mins =
Math.floor(roundedSeconds / 60) and rem = roundedSeconds % 60, and format using
String(rem).padStart(2,"0"); update the function that defines mins and rem to
use this approach.

In `@DevDashboard/mobile/src/features/obsidian/components/NewFolderModal.tsx`:
- Around line 18-28: The submit function concatenates the user-entered trimmed
into relativeDir, allowing path separators or traversal like "a/b" or "../x";
update submit (and any helper it calls) to validate trimmed before building
relativeDir: reject if trimmed contains '/' or path separators, equals "." or
"..", or contains any ".." segment, and on violation do not call onCreate
(instead setName("") or set an error state/return early); reference the submit
function, the trimmed/local variables, parentDir, onCreate and setName when
making the guard so only a single path segment is accepted.

In `@DevDashboard/mobile/src/features/obsidian/components/NoteRenderer.tsx`:
- Line 18: Replace the overly-broad jsDelivr host allowlist in NoteRenderer.tsx:
tighten CDN_HOST_RE and the request gating logic to only permit the exact pinned
asset URLs (HLJS_CSS_URL, KATEX_CSS_URL, MERMAID_JS_URL) used by note-html.ts
rather than any cdn.jsdelivr.net path; update the check around the request gate
(the logic that currently matches CDN_HOST_RE) to compare the requested URL
string against those three constants (or a Set of those constants) so only those
exact URLs are allowed.

In `@DevDashboard/mobile/src/features/obsidian/note-html.ts`:
- Around line 69-75: Move the duplicated CDN constants into a single shared
module and import them from both places to avoid drift; specifically extract
HLJS_CSS_URL, HLJS_CSS_SRI, KATEX_CSS_URL, KATEX_CSS_SRI and MERMAID_JS_URL into
a new exported constants file and replace the hard-coded values in note-html.ts
(and src/dev-dashboard/lib/obsidian/share-template.ts) with imports of those
symbols so both files reference the same source of truth.

In `@DevDashboard/mobile/src/features/obsidian/queries.ts`:
- Around line 33-39: noteQuery currently uses a type assertion in queryFn (path
as string); remove the assertion by capturing path in a local const and doing an
explicit null-check before using it: inside noteQuery do const p = path; if (p
=== null) throw new Error("noteQuery: path is null"); then use queryFn: () =>
client.obsidian.note(p); keep enabled: path !== null and reference
obsidianKeys.note and queryFn to locate the change.

In `@DevDashboard/mobile/src/features/qa/components/QaFeed.tsx`:
- Around line 19-27: The current resolveUnread uses a shared empty-string
fallback (row.id ?? "") which makes id-less rows collide; change it to guard
against missing ids instead of defaulting to "", e.g. remove the "?? ''" and add
an early check in resolveUnread that if row.id is null/undefined then return a
safe default (e.g. false) so locallyUnread/locallyRead checks only run for real
IDs; update references to id, locallyUnread, and locallyRead in resolveUnread
accordingly.

In `@DevDashboard/mobile/src/features/qa/hooks.ts`:
- Around line 78-105: The effect's open() unconditionally sets status to
"connecting" causing a flicker on AppState resume even when an active
subscription already exists; modify open() to check handleRef.current (or the
subscription's active flag returned by openQaSubscription) and only call
setStatus("connecting") and create a new handle when there is no active
subscription, leaving status untouched on resume; use the existing symbols
open(), close(), handleRef, setStatus, openQaSubscription, and onResumeRef to
implement this guard so AppState "active" only opens a new SSE when needed.

In `@DevDashboard/mobile/src/features/qa/subscription.ts`:
- Around line 44-63: The subscription currently calls callbacks.onStatus("live")
for every row; modify the subscription logic (in client.qa.subscribe / the
handler using seen, closed, onRow) to track a local boolean like isLive
initialized false and only call callbacks.onStatus?.("live") when isLive is
false, then set isLive = true so subsequent rows won't re-emit "live"; ensure
this flag is scoped to the subscriber handler so it resets only when the
subscription is torn down.

In `@DevDashboard/mobile/src/features/qa/units.ts`:
- Around line 65-76: Update the docstring for the tagTone function to reflect
the actual mapping used by tagTone (action → "accent", directive → "danger", all
other tags including "question" → "muted"); edit the comment above the tagTone
function (which references QaRow["tag"] and returns QaTagTone) so it no longer
says "question→accent" but correctly documents that non-action/non-directive
tags (e.g., "question") map to "muted".

In `@DevDashboard/mobile/src/features/terminals/components/SessionsList.tsx`:
- Around line 136-141: The onPress currently calls void openTmux(s) which
discards the returned promise so any errors from spawn.mutateAsync inside
openTmux are unhandled; update the ActionButton onPress to await and handle
errors (or call a wrapper that does) by using an async handler with try/catch or
by checking and using the mutation's error/status to surface feedback, and
ensure openTmux and the mutation (spawn.mutateAsync / spawn) are referenced so
you catch and display spawn errors to the user instead of letting them be
unhandled.

In
`@DevDashboard/mobile/src/features/terminals/components/WebViewTtydRenderer.tsx`:
- Around line 161-162: The current onLoadEnd handler in WebViewTtydRenderer
unconditionally calls setStatus("connected") which overwrites an error state set
by onError; change the onLoadEnd usage to onLoad (success-only) or add a guard
inside the onLoadEnd callback to only call setStatus("connected") if the current
status is not "error" (e.g., read status before calling setStatus), ensuring
setStatus is only set to "connected" on a successful load; adjust the handlers
where onLoad, onError and setStatus are referenced to keep error status intact.

In `@DevDashboard/mobile/src/features/terminals/scripts/build-xterm-host.ts`:
- Around line 20-22: The current script uses
dirname(fileURLToPath(import.meta.url)) to compute paths (symbols: here,
mobileRoot, out) which is verbose for Bun; replace that expression with
import.meta.dir and keep the rest of the path logic using resolve/join (update
usages of here, mobileRoot, out accordingly) so paths are derived from
import.meta.dir instead of dirname(fileURLToPath(import.meta.url)); ensure
imports of fileURLToPath/dirname are removed if no longer used.

In `@DevDashboard/mobile/src/global.css`:
- Around line 8-14: The CSS custom properties --font-display, --font-mono,
--font-rounded, and --font-serif contain multi-word font family names that need
to be quoted to satisfy stylelint; update each variable so any family name with
spaces (e.g., Spline Sans, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol,
Noto Color Emoji, Courier New, Times New Roman, MS PGothic, SF Pro Rounded if
not already) is wrapped in quotes (choose single or double quotes consistently)
so the font stacks are treated as single tokens and the
value-keyword-case/stylelint errors are resolved.
- Around line 1-5: The `@import` "./theme/tokens.css" is placed after Tailwind
directives which violates CSS ordering rules; move the `@import` line to precede
any other at-rules so it appears above the `@tailwind` base/components/utilities
directives in global.css, ensuring the tokens.css is imported first and the
`@tailwind` statements remain unchanged (look for the literal string `@import`
"./theme/tokens.css" and the three `@tailwind` lines).

In `@DevDashboard/mobile/src/hooks/use-theme.ts`:
- Around line 10-13: The hook currently sets theme via scheme === "unspecified"
? "light" : scheme but doesn't handle useColorScheme() returning null, causing
Colors[theme] to be undefined; update useTheme (where scheme is assigned from
useColorScheme(), the theme variable is computed, and Colors[...] is returned)
to treat both null and "unspecified" as "light" (e.g., default scheme to "light"
before indexing) so Colors[theme] always yields a valid theme object and
downstream components like themed-text/themed-view don't crash.

In `@DevDashboard/mobile/src/lib/sse-frame.ts`:
- Around line 11-16: The SSE parser currently splits on "\n" and pushes lines
matching "data:" without removing a trailing "\r", which breaks CRLF-terminated
messages; update the logic in the loop that processes frame.split("\n") (the
block using line.startsWith("data:") and dataParts.push(...)) to strip any
trailing carriage return(s) from the extracted payload (after slicing off
"data:" and removing the optional single leading space) before pushing into
dataParts so CRLF line endings are normalized.

In `@DevDashboard/mobile/src/lib/sse.ts`:
- Around line 52-54: In makeExpoEventSource, the catch block currently forwards
all stream errors to es.onerror even when they are caused by an intentional
teardown via close()/controller.abort(); update the catch to check
controller.signal.aborted (or err.name === 'AbortError') and skip calling
es.onerror when the abort is intentional, otherwise call es.onerror?.(err);
reference the controller, close(), and es.onerror symbols to locate and change
the catch handler.

In `@DevDashboard/mobile/src/lib/storage/db.ts`:
- Around line 15-27: getDb currently assigns a failing initialization to the
module-level dbPromise and never clears it, so subsequent calls always reject;
modify getDb to wrap the async initialization (the async IIFE assigned to
dbPromise) in try/catch and on any error set dbPromise back to undefined before
rethrowing the error so future calls can retry; refer to the getDb function and
the dbPromise symbol (and leave the MIGRATIONS/SQLite calls unchanged) when
adding the try/catch and reset logic.

In `@DevDashboard/mobile/src/lib/storage/kv.ts`:
- Around line 11-15: The getPref function currently parses stored JSON directly
which can throw for corrupted values; wrap the JSON.parse call in a try/catch
inside getPref (after awaiting Storage.getItem(key)) and return null if parsing
fails, otherwise return the parsed value cast to Prefs[K]; keep the
Storage.getItem call and the function signature intact and ensure any caught
error does not rethrow so callers get null instead of a rejected promise.

In `@DevDashboard/mobile/src/lib/storage/secure.ts`:
- Around line 33-40: loadBasicAuthHeader currently uses global btoa which is
Latin-1 only; replace it with a UTF-8-safe path by encoding
`${creds.username}:${creds.password}` to UTF-8 bytes (e.g., via TextEncoder or
Buffer) and then base64-encoding those bytes (use a byte-safe base64 helper or
Buffer.from(...).toString('base64') / a platform polyfill) before returning
`Basic <base64>`; update the implementation in loadBasicAuthHeader (and add any
small helper or import) so non-Latin-1 characters encode correctly while still
returning the same string shape.

In `@DevDashboard/mobile/src/shims/event-polyfill.ts`:
- Line 54: Remove the unused EventCtor extraction and its suppressing usage:
delete the declaration "const EventCtor = g.Event ..." and the no-op "void
EventCtor;" used to silence warnings in the EventTargetPolyfill implementation;
ensure no other code relies on EventCtor and keep EventTargetPolyfill behavior
unchanged.

In `@DevDashboard/mobile/src/transport/e2e/box-cipher.ts`:
- Around line 35-49: loadOrCreateDeviceKeys has a race: concurrent callers can
both see empty SecureStore, generate different keypairs and write conflicting
values; fix by memoizing the in-flight promise so only the first call performs
read/create/write and all concurrent callers await the same Promise. Implement a
module-scoped variable (e.g., let inFlightLoadOrCreate: Promise<KeyPair> | null)
and in loadOrCreateDeviceKeys return that promise if non-null; clear it on
success or failure; keep using SECRET_KEY_ITEM, PUBLIC_KEY_ITEM,
SecureStore.getItemAsync/setItemAsync and cipher.keyPair() so callers always
receive the same persisted keypair.

In `@DevDashboard/mobile/src/transport/lan-discovery.ts`:
- Around line 36-71: The rescan currently only clears agents and cannot restart
discovery because the Zeroconf instance is scoped inside useEffect; change to
store the Zeroconf instance in a useRef (e.g., zeroconfRef) created outside
useEffect, move creation/uses to ref.current, wrap startScan and rescan in
useCallback, and implement rescan to call zeroconfRef.current.stop(),
setAgents([]), then startScan() so discovery restarts; also ensure scanning
state is set true in startScan and set to false on both "error" and the Zeroconf
"stop" event, and keep cleanup to stop() and removeDeviceListeners() on the ref.

In `@DevDashboard/README.md`:
- Around line 18-29: The fenced artifact-map block in README.md lacks a language
info string which triggers MD040; update the opening triple-backtick for the
DevDashboard tree block to include a language identifier (e.g., add "text" so it
becomes ```text) so the block is properly annotated; locate the fenced block in
README.md (the directory listing snippet shown under "DevDashboard/") and modify
only the opening fence to include the language token.

---

Outside diff comments:
In `@DevDashboard/mobile/src/features/qa/subscription.test.ts`:
- Around line 110-117: The smoke test for "subscribe" leaves a 2s setTimeout
pending; modify the test so the timeout ID is stored (e.g., const timer =
setTimeout(...)) and call clearTimeout(timer) whenever the test promise resolves
or rejects (before returning/ending the test) so no latent timers remain; ensure
both the success path and error/cleanup path clear the timeout to avoid
flakiness in the subscribe smoke test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2d91475d-bf90-4006-8a49-2ee3188cd831

📥 Commits

Reviewing files that changed from the base of the PR and between c58f7d2 and 068f2a5.

⛔ Files ignored due to path filters (24)
  • DevDashboard/cloud/web/bun.lock is excluded by !**/*.lock
  • DevDashboard/mobile/assets/expo.icon/Assets/expo-symbol 2.svg is excluded by !**/*.svg
  • DevDashboard/mobile/assets/expo.icon/Assets/grid.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/android-icon-background.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/android-icon-foreground.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/android-icon-monochrome.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/expo-badge-white.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/expo-badge.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/expo-logo.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/favicon.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/icon.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/logo-glow.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/react-logo.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/react-logo@2x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/react-logo@3x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/splash-icon.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/explore.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/explore@2x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/explore@3x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/home.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/home@2x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/home@3x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tutorial-web.png is excluded by !**/*.png
  • DevDashboard/mobile/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (276)
  • DevDashboard/DECISIONS.md
  • DevDashboard/PRODUCT-ROADMAP.md
  • DevDashboard/README.md
  • DevDashboard/cloud/landing/daylight/index.html
  • DevDashboard/cloud/landing/field-notes/index.html
  • DevDashboard/cloud/landing/obsidian-terminal/index.html
  • DevDashboard/cloud/shared/account-model.ts
  • DevDashboard/cloud/shared/data-boundary.test.ts
  • DevDashboard/cloud/shared/data-boundary.ts
  • DevDashboard/cloud/shared/tier-policy.test.ts
  • DevDashboard/cloud/shared/tier-policy.ts
  • DevDashboard/cloud/web/.env.example
  • DevDashboard/cloud/web/.gitignore
  • DevDashboard/cloud/web/biome.json
  • DevDashboard/cloud/web/db/migrations/0000_true_energizer.sql
  • DevDashboard/cloud/web/db/migrations/meta/0000_snapshot.json
  • DevDashboard/cloud/web/db/migrations/meta/_journal.json
  • DevDashboard/cloud/web/drizzle.config.ts
  • DevDashboard/cloud/web/package.json
  • DevDashboard/cloud/web/src/components/RouteError.tsx
  • DevDashboard/cloud/web/src/components/RouteNotFound.tsx
  • DevDashboard/cloud/web/src/components/auth/AuthCard.tsx
  • DevDashboard/cloud/web/src/components/dashboard/Card.tsx
  • DevDashboard/cloud/web/src/components/landing/Features.tsx
  • DevDashboard/cloud/web/src/components/landing/Footer.tsx
  • DevDashboard/cloud/web/src/components/landing/Hero.tsx
  • DevDashboard/cloud/web/src/components/landing/HowItWorks.tsx
  • DevDashboard/cloud/web/src/components/landing/Nav.tsx
  • DevDashboard/cloud/web/src/components/landing/Pricing.tsx
  • DevDashboard/cloud/web/src/components/landing/TrustStory.tsx
  • DevDashboard/cloud/web/src/components/landing/icons.tsx
  • DevDashboard/cloud/web/src/content/copy.ts
  • DevDashboard/cloud/web/src/integrations/tanstack-query/root-provider.tsx
  • DevDashboard/cloud/web/src/lib/auth/auth-client.ts
  • DevDashboard/cloud/web/src/lib/auth/auth-service.ts
  • DevDashboard/cloud/web/src/lib/auth/auth.functions.ts
  • DevDashboard/cloud/web/src/lib/auth/auth.server.ts
  • DevDashboard/cloud/web/src/lib/auth/auth.test.ts
  • DevDashboard/cloud/web/src/lib/billing/billing.functions.ts
  • DevDashboard/cloud/web/src/lib/billing/env-gating.test.ts
  • DevDashboard/cloud/web/src/lib/billing/stripe.ts
  • DevDashboard/cloud/web/src/lib/dashboard/dashboard.functions.ts
  • DevDashboard/cloud/web/src/lib/db/cloud-store.ts
  • DevDashboard/cloud/web/src/lib/db/index.ts
  • DevDashboard/cloud/web/src/lib/db/migrate.ts
  • DevDashboard/cloud/web/src/lib/db/schema.ts
  • DevDashboard/cloud/web/src/lib/provision/cloudflare.ts
  • DevDashboard/cloud/web/src/lib/server/env.ts
  • DevDashboard/cloud/web/src/router.tsx
  • DevDashboard/cloud/web/src/routes/__root.tsx
  • DevDashboard/cloud/web/src/routes/api.auth.$.ts
  • DevDashboard/cloud/web/src/routes/api.stripe.webhook.ts
  • DevDashboard/cloud/web/src/routes/dashboard.billing.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.devices.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.index.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.settings.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.setup.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.tsx
  • DevDashboard/cloud/web/src/routes/index.tsx
  • DevDashboard/cloud/web/src/routes/signin.tsx
  • DevDashboard/cloud/web/src/routes/signup.tsx
  • DevDashboard/cloud/web/src/start.ts
  • DevDashboard/cloud/web/src/styles/app.css
  • DevDashboard/cloud/web/tsconfig.json
  • DevDashboard/cloud/web/vite.config.ts
  • DevDashboard/mobile/.claude/settings.json
  • DevDashboard/mobile/.gitignore
  • DevDashboard/mobile/.vscode/extensions.json
  • DevDashboard/mobile/.vscode/settings.json
  • DevDashboard/mobile/AGENTS.md
  • DevDashboard/mobile/CLAUDE.md
  • DevDashboard/mobile/README.md
  • DevDashboard/mobile/app.config.ts
  • DevDashboard/mobile/app.json
  • DevDashboard/mobile/assets/expo.icon/icon.json
  • DevDashboard/mobile/babel.config.js
  • DevDashboard/mobile/e2e/README.md
  • DevDashboard/mobile/e2e/pages/ClaudeUsagePage.page.ts
  • DevDashboard/mobile/e2e/pages/ConnectPage.page.ts
  • DevDashboard/mobile/e2e/pages/ContainersPage.page.ts
  • DevDashboard/mobile/e2e/pages/DaemonPage.page.ts
  • DevDashboard/mobile/e2e/pages/MoreNav.page.ts
  • DevDashboard/mobile/e2e/pages/ObsidianPage.page.ts
  • DevDashboard/mobile/e2e/pages/PulsePage.page.ts
  • DevDashboard/mobile/e2e/pages/QaPage.page.ts
  • DevDashboard/mobile/e2e/pages/TerminalsPage.page.ts
  • DevDashboard/mobile/e2e/pages/WeatherPage.page.ts
  • DevDashboard/mobile/e2e/pages/app.page.ts
  • DevDashboard/mobile/e2e/pages/base.page.ts
  • DevDashboard/mobile/e2e/specs/claude-usage.spec.ts
  • DevDashboard/mobile/e2e/specs/connect.spec.ts
  • DevDashboard/mobile/e2e/specs/containers.spec.ts
  • DevDashboard/mobile/e2e/specs/daemon.spec.ts
  • DevDashboard/mobile/e2e/specs/features-rest.smoke.spec.ts
  • DevDashboard/mobile/e2e/specs/obsidian.spec.ts
  • DevDashboard/mobile/e2e/specs/pulse.spec.ts
  • DevDashboard/mobile/e2e/specs/qa.spec.ts
  • DevDashboard/mobile/e2e/specs/smoke.spec.ts
  • DevDashboard/mobile/e2e/specs/terminals.spec.ts
  • DevDashboard/mobile/e2e/specs/weather.spec.ts
  • DevDashboard/mobile/e2e/tsconfig.json
  • DevDashboard/mobile/e2e/wdio.conf.ts
  • DevDashboard/mobile/eslint.config.js
  • DevDashboard/mobile/metro.config.js
  • DevDashboard/mobile/nativewind-env.d.ts
  • DevDashboard/mobile/package.json
  • DevDashboard/mobile/patches/react-native-webview@13.16.0.patch
  • DevDashboard/mobile/scripts/reset-project.js
  • DevDashboard/mobile/src/api/client-provider.tsx
  • DevDashboard/mobile/src/api/mock-client.ts
  • DevDashboard/mobile/src/api/query-keys.ts
  • DevDashboard/mobile/src/app/(more)/_layout.tsx
  • DevDashboard/mobile/src/app/(more)/claude-usage.tsx
  • DevDashboard/mobile/src/app/(more)/containers.tsx
  • DevDashboard/mobile/src/app/(more)/daemon.tsx
  • DevDashboard/mobile/src/app/(more)/weather.tsx
  • DevDashboard/mobile/src/app/(tabs)/_layout.tsx
  • DevDashboard/mobile/src/app/(tabs)/index.tsx
  • DevDashboard/mobile/src/app/(tabs)/more.tsx
  • DevDashboard/mobile/src/app/(tabs)/obsidian.tsx
  • DevDashboard/mobile/src/app/(tabs)/qa.tsx
  • DevDashboard/mobile/src/app/(tabs)/terminals.tsx
  • DevDashboard/mobile/src/app/_layout.tsx
  • DevDashboard/mobile/src/app/connect.tsx
  • DevDashboard/mobile/src/app/pair.tsx
  • DevDashboard/mobile/src/components/animated-icon.module.css
  • DevDashboard/mobile/src/components/animated-icon.tsx
  • DevDashboard/mobile/src/components/animated-icon.web.tsx
  • DevDashboard/mobile/src/components/connect/QrScanner.tsx
  • DevDashboard/mobile/src/components/connect/ReachabilityBadge.tsx
  • DevDashboard/mobile/src/components/connect/TierPicker.tsx
  • DevDashboard/mobile/src/components/external-link.tsx
  • DevDashboard/mobile/src/components/hint-row.tsx
  • DevDashboard/mobile/src/components/themed-text.tsx
  • DevDashboard/mobile/src/components/themed-view.tsx
  • DevDashboard/mobile/src/components/ui/collapsible.tsx
  • DevDashboard/mobile/src/components/web-badge.tsx
  • DevDashboard/mobile/src/constants/theme.ts
  • DevDashboard/mobile/src/features/claude-usage/components/AccountHistoryCharts.tsx
  • DevDashboard/mobile/src/features/claude-usage/components/AccountUsageCard.tsx
  • DevDashboard/mobile/src/features/claude-usage/components/RangeSelector.tsx
  • DevDashboard/mobile/src/features/claude-usage/hooks.ts
  • DevDashboard/mobile/src/features/claude-usage/queries.test.ts
  • DevDashboard/mobile/src/features/claude-usage/queries.ts
  • DevDashboard/mobile/src/features/claude-usage/units.test.ts
  • DevDashboard/mobile/src/features/claude-usage/units.ts
  • DevDashboard/mobile/src/features/containers/components/ContainerRow.tsx
  • DevDashboard/mobile/src/features/containers/hooks.ts
  • DevDashboard/mobile/src/features/containers/queries.test.ts
  • DevDashboard/mobile/src/features/containers/queries.ts
  • DevDashboard/mobile/src/features/containers/units.test.ts
  • DevDashboard/mobile/src/features/containers/units.ts
  • DevDashboard/mobile/src/features/daemon/components/DaemonStatusHeader.tsx
  • DevDashboard/mobile/src/features/daemon/components/RunLogSheet.tsx
  • DevDashboard/mobile/src/features/daemon/components/RunRow.tsx
  • DevDashboard/mobile/src/features/daemon/hooks.ts
  • DevDashboard/mobile/src/features/daemon/queries.test.ts
  • DevDashboard/mobile/src/features/daemon/queries.ts
  • DevDashboard/mobile/src/features/daemon/units.test.ts
  • DevDashboard/mobile/src/features/daemon/units.ts
  • DevDashboard/mobile/src/features/obsidian/components/NewFolderModal.tsx
  • DevDashboard/mobile/src/features/obsidian/components/NoteReader.tsx
  • DevDashboard/mobile/src/features/obsidian/components/NoteRenderer.tsx
  • DevDashboard/mobile/src/features/obsidian/components/VaultTree.tsx
  • DevDashboard/mobile/src/features/obsidian/components/VaultTreeNode.tsx
  • DevDashboard/mobile/src/features/obsidian/expanded-dirs.test.ts
  • DevDashboard/mobile/src/features/obsidian/expanded-dirs.ts
  • DevDashboard/mobile/src/features/obsidian/hooks.ts
  • DevDashboard/mobile/src/features/obsidian/note-html.test.ts
  • DevDashboard/mobile/src/features/obsidian/note-html.ts
  • DevDashboard/mobile/src/features/obsidian/queries.test.ts
  • DevDashboard/mobile/src/features/obsidian/queries.ts
  • DevDashboard/mobile/src/features/obsidian/vault-filter.test.ts
  • DevDashboard/mobile/src/features/obsidian/vault-filter.ts
  • DevDashboard/mobile/src/features/pulse/components/KpiCard.tsx
  • DevDashboard/mobile/src/features/pulse/components/NetworkInfo.tsx
  • DevDashboard/mobile/src/features/pulse/components/ProcessTable.tsx
  • DevDashboard/mobile/src/features/pulse/components/RangeSelector.tsx
  • DevDashboard/mobile/src/features/pulse/components/SparklineRow.tsx
  • DevDashboard/mobile/src/features/pulse/components/WeatherCard.tsx
  • DevDashboard/mobile/src/features/pulse/hooks.ts
  • DevDashboard/mobile/src/features/pulse/queries.test.ts
  • DevDashboard/mobile/src/features/pulse/queries.ts
  • DevDashboard/mobile/src/features/pulse/units.test.ts
  • DevDashboard/mobile/src/features/pulse/units.ts
  • DevDashboard/mobile/src/features/qa/components/QaCard.tsx
  • DevDashboard/mobile/src/features/qa/components/QaFeed.tsx
  • DevDashboard/mobile/src/features/qa/components/QaFilterBar.tsx
  • DevDashboard/mobile/src/features/qa/components/QaLiveDot.tsx
  • DevDashboard/mobile/src/features/qa/hooks.ts
  • DevDashboard/mobile/src/features/qa/live-feed.test.ts
  • DevDashboard/mobile/src/features/qa/live-feed.ts
  • DevDashboard/mobile/src/features/qa/queries.test.ts
  • DevDashboard/mobile/src/features/qa/queries.ts
  • DevDashboard/mobile/src/features/qa/subscription.test.ts
  • DevDashboard/mobile/src/features/qa/subscription.ts
  • DevDashboard/mobile/src/features/qa/units.test.ts
  • DevDashboard/mobile/src/features/qa/units.ts
  • DevDashboard/mobile/src/features/terminals/TerminalRenderer.ts
  • DevDashboard/mobile/src/features/terminals/bridge.test.ts
  • DevDashboard/mobile/src/features/terminals/bridge.ts
  • DevDashboard/mobile/src/features/terminals/components/DriverSwitcher.tsx
  • DevDashboard/mobile/src/features/terminals/components/MobileKeyBar.tsx
  • DevDashboard/mobile/src/features/terminals/components/SessionsList.tsx
  • DevDashboard/mobile/src/features/terminals/components/WebViewHtmlRenderer.tsx
  • DevDashboard/mobile/src/features/terminals/components/WebViewTtydRenderer.tsx
  • DevDashboard/mobile/src/features/terminals/components/drivers.ts
  • DevDashboard/mobile/src/features/terminals/driver-store.ts
  • DevDashboard/mobile/src/features/terminals/hooks.ts
  • DevDashboard/mobile/src/features/terminals/inject.ts
  • DevDashboard/mobile/src/features/terminals/keymap.test.ts
  • DevDashboard/mobile/src/features/terminals/keymap.ts
  • DevDashboard/mobile/src/features/terminals/queries.test.ts
  • DevDashboard/mobile/src/features/terminals/queries.ts
  • DevDashboard/mobile/src/features/terminals/registry.test.ts
  • DevDashboard/mobile/src/features/terminals/registry.ts
  • DevDashboard/mobile/src/features/terminals/scripts/build-xterm-host.ts
  • DevDashboard/mobile/src/features/terminals/xterm-host.generated.ts
  • DevDashboard/mobile/src/features/weather/components/WeatherCard.tsx
  • DevDashboard/mobile/src/features/weather/hooks.ts
  • DevDashboard/mobile/src/features/weather/queries.test.ts
  • DevDashboard/mobile/src/features/weather/queries.ts
  • DevDashboard/mobile/src/features/weather/units.test.ts
  • DevDashboard/mobile/src/features/weather/units.ts
  • DevDashboard/mobile/src/global.css
  • DevDashboard/mobile/src/hooks/use-color-scheme.ts
  • DevDashboard/mobile/src/hooks/use-color-scheme.web.ts
  • DevDashboard/mobile/src/hooks/use-theme.ts
  • DevDashboard/mobile/src/lib/__tests__/contract-import.test.ts
  • DevDashboard/mobile/src/lib/__tests__/sse.test.ts
  • DevDashboard/mobile/src/lib/apply-pairing.ts
  • DevDashboard/mobile/src/lib/cn.ts
  • DevDashboard/mobile/src/lib/contract-client.ts
  • DevDashboard/mobile/src/lib/qr.test.ts
  • DevDashboard/mobile/src/lib/qr.ts
  • DevDashboard/mobile/src/lib/query.ts
  • DevDashboard/mobile/src/lib/sse-frame.ts
  • DevDashboard/mobile/src/lib/sse.ts
  • DevDashboard/mobile/src/lib/storage/__tests__/kv.test.ts
  • DevDashboard/mobile/src/lib/storage/db.ts
  • DevDashboard/mobile/src/lib/storage/kv.ts
  • DevDashboard/mobile/src/lib/storage/secure.ts
  • DevDashboard/mobile/src/shims/event-polyfill.ts
  • DevDashboard/mobile/src/shims/safe-json.ts
  • DevDashboard/mobile/src/state/connection-store.ts
  • DevDashboard/mobile/src/state/connection.ts
  • DevDashboard/mobile/src/theme/colors.ts
  • DevDashboard/mobile/src/theme/tokens.css
  • DevDashboard/mobile/src/transport/Transport.ts
  • DevDashboard/mobile/src/transport/e2e-transport.ts
  • DevDashboard/mobile/src/transport/e2e/box-cipher.test.ts
  • DevDashboard/mobile/src/transport/e2e/box-cipher.ts
  • DevDashboard/mobile/src/transport/e2e/envelope.test.ts
  • DevDashboard/mobile/src/transport/e2e/envelope.ts
  • DevDashboard/mobile/src/transport/lan-discovery.ts
  • DevDashboard/mobile/src/transport/plain-transport.test.ts
  • DevDashboard/mobile/src/transport/plain-transport.ts
  • DevDashboard/mobile/src/transport/qa-stream.test.ts
  • DevDashboard/mobile/src/transport/qa-stream.ts
  • DevDashboard/mobile/src/transport/reachability.test.ts
  • DevDashboard/mobile/src/transport/reachability.ts
  • DevDashboard/mobile/src/transport/sse-parser.test.ts
  • DevDashboard/mobile/src/transport/sse-parser.ts
  • DevDashboard/mobile/src/transport/terminal-ws.test.ts
  • DevDashboard/mobile/src/transport/terminal-ws.ts
  • DevDashboard/mobile/src/transport/tiers/cloudflared.ts
  • DevDashboard/mobile/src/transport/tiers/lan.ts
  • DevDashboard/mobile/src/transport/tiers/managed.test.ts
  • DevDashboard/mobile/src/transport/tiers/managed.ts
  • DevDashboard/mobile/src/transport/tiers/tailscale.ts
  • DevDashboard/mobile/src/types/assets.d.ts
  • DevDashboard/mobile/src/types/css-modules.d.ts
  • DevDashboard/mobile/src/types/react-native-zeroconf.d.ts
  • DevDashboard/mobile/src/ui/Banner.tsx
  • DevDashboard/mobile/src/ui/Card.tsx
  • DevDashboard/mobile/src/ui/Empty.tsx

Comment on lines +213 to +214
<button class="flex-1 rounded-lg bg-oxblood py-1.5 text-[11px] font-medium text-cream">Approve</button>
<button class="flex-1 rounded-lg border border-cream/15 py-1.5 text-[11px] text-cream/70">Open shell</button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add type="button" to interactive buttons.

The buttons at lines 213-214 lack explicit type attributes. Add type="button" to prevent form submission behavior.

♿ Proposed fix
-            <button class="flex-1 rounded-lg bg-oxblood py-1.5 text-[11px] font-medium text-cream">Approve</button>
-            <button class="flex-1 rounded-lg border border-cream/15 py-1.5 text-[11px] text-cream/70">Open shell</button>
+            <button type="button" class="flex-1 rounded-lg bg-oxblood py-1.5 text-[11px] font-medium text-cream">Approve</button>
+            <button type="button" class="flex-1 rounded-lg border border-cream/15 py-1.5 text-[11px] text-cream/70">Open shell</button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button class="flex-1 rounded-lg bg-oxblood py-1.5 text-[11px] font-medium text-cream">Approve</button>
<button class="flex-1 rounded-lg border border-cream/15 py-1.5 text-[11px] text-cream/70">Open shell</button>
<button type="button" class="flex-1 rounded-lg bg-oxblood py-1.5 text-[11px] font-medium text-cream">Approve</button>
<button type="button" class="flex-1 rounded-lg border border-cream/15 py-1.5 text-[11px] text-cream/70">Open shell</button>
🧰 Tools
🪛 HTMLHint (1.9.2)

[warning] 213-213: The type attribute must be present on elements.

(button-type-require)


[warning] 214-214: The type attribute must be present on

elements.

(button-type-require)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/landing/field-notes/index.html` around lines 213 - 214,
The two interactive buttons ("Approve" and "Open shell") are missing explicit
type attributes causing them to default to type="submit" inside forms; update
the two button elements rendering the "Approve" and "Open shell" controls in the
landing field-notes markup to include type="button" (i.e., add type="button" to
the button elements for the Approve and Open shell buttons) so they no longer
trigger form submission.

<form class="flex flex-col gap-3" onsubmit="return false;">
<div class="rounded-full border border-cream/15 bg-cream/[0.06] p-1.5">
<div class="flex items-center gap-2">
<input type="email" required placeholder="you@yourmachine.dev" class="w-full bg-transparent px-4 py-2.5 text-[14px] text-cream placeholder:text-cream/35 focus:outline-none" />

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add accessible label for email input.

The email input lacks an associated <label> element for screen reader accessibility.

♿ Proposed fix
-            <div class="flex items-center gap-2">
-              <input type="email" required placeholder="you@yourmachine.dev" class="w-full bg-transparent px-4 py-2.5 text-[14px] text-cream placeholder:text-cream/35 focus:outline-none" />
+            <label class="flex items-center gap-2">
+              <span class="sr-only">Email address</span>
+              <input type="email" required placeholder="you@yourmachine.dev" class="w-full bg-transparent px-4 py-2.5 text-[14px] text-cream placeholder:text-cream/35 focus:outline-none" />
🧰 Tools
🪛 HTMLHint (1.9.2)

[warning] 664-664: No matching [ label ] tag found.

(input-requires-label)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/landing/field-notes/index.html` at line 664, The email
<input type="email" ...> lacks an accessible label; add a properly associated
label element or aria-label/aria-labelledby so screen readers can identify it.
Specifically, give the input a unique id (e.g., id="email") and add a <label
for="email">Email address</label> (or update the surrounding form to reference
that id via aria-labelledby), ensuring the visual styling preserves the existing
classes on the input and that the label text conveys that the field is required
if applicable.

Comment on lines +168 to +171
<button id="menuBtn" aria-label="Open menu" class="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white/[0.06] ring-1 ring-white/10 md:hidden">
<span id="ham1" class="absolute h-px w-4 bg-zinc-200 transition-transform duration-500 ease-silk -translate-y-1"></span>
<span id="ham2" class="absolute h-px w-4 bg-zinc-200 transition-transform duration-500 ease-silk translate-y-1"></span>
</button>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add type="button" to prevent unintended form submission.

The hamburger menu button should have an explicit type="button" attribute to prevent it from submitting a form if it's accidentally nested within one.

♿ Proposed fix
-        <button id="menuBtn" aria-label="Open menu" class="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white/[0.06] ring-1 ring-white/10 md:hidden">
+        <button type="button" id="menuBtn" aria-label="Open menu" class="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white/[0.06] ring-1 ring-white/10 md:hidden">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button id="menuBtn" aria-label="Open menu" class="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white/[0.06] ring-1 ring-white/10 md:hidden">
<span id="ham1" class="absolute h-px w-4 bg-zinc-200 transition-transform duration-500 ease-silk -translate-y-1"></span>
<span id="ham2" class="absolute h-px w-4 bg-zinc-200 transition-transform duration-500 ease-silk translate-y-1"></span>
</button>
<button type="button" id="menuBtn" aria-label="Open menu" class="relative flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-white/[0.06] ring-1 ring-white/10 md:hidden">
<span id="ham1" class="absolute h-px w-4 bg-zinc-200 transition-transform duration-500 ease-silk -translate-y-1"></span>
<span id="ham2" class="absolute h-px w-4 bg-zinc-200 transition-transform duration-500 ease-silk translate-y-1"></span>
</button>
🧰 Tools
🪛 HTMLHint (1.9.2)

[warning] 168-168: The type attribute must be present on elements.

(button-type-require)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/landing/obsidian-terminal/index.html` around lines 168 -
171, The hamburger button with id="menuBtn" (containing spans with ids "ham1"
and "ham2") is missing an explicit type and can accidentally submit a parent
form; add type="button" to the <button id="menuBtn"> element so it won't trigger
form submission while preserving the existing attributes and behavior.

Comment on lines +721 to +724
<form class="relative mx-auto mt-10 flex max-w-md flex-col items-center gap-3 sm:flex-row" onsubmit="return false;">
<div class="w-full rounded-full border border-white/10 bg-white/[0.04] p-1.5 sm:flex-1">
<input type="email" required placeholder="you@yourmachine.dev" class="w-full rounded-full bg-transparent px-4 py-2.5 text-sm text-zinc-100 placeholder:text-zinc-600 focus:outline-none" />
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add accessible label for email input.

The email input lacks an associated <label> element. Add an aria-label or wrap it with a <label> for screen reader accessibility.

♿ Proposed fix
-            <div class="w-full rounded-full border border-white/10 bg-white/[0.04] p-1.5 sm:flex-1">
-              <input type="email" required placeholder="you@yourmachine.dev" class="w-full rounded-full bg-transparent px-4 py-2.5 text-sm text-zinc-100 placeholder:text-zinc-600 focus:outline-none" />
+            <label class="w-full rounded-full border border-white/10 bg-white/[0.04] p-1.5 sm:flex-1">
+              <span class="sr-only">Email address</span>
+              <input type="email" required placeholder="you@yourmachine.dev" class="w-full rounded-full bg-transparent px-4 py-2.5 text-sm text-zinc-100 placeholder:text-zinc-600 focus:outline-none" />
+            </label>
🧰 Tools
🪛 HTMLHint (1.9.2)

[warning] 723-723: No matching [ label ] tag found.

(input-requires-label)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/landing/obsidian-terminal/index.html` around lines 721 -
724, The email <input> element in the form (the element with type="email" and
placeholder="you@yourmachine.dev") is missing an accessible label; add one by
either wrapping the input with a <label> that contains descriptive text (e.g.,
"Email") or by adding an aria-label attribute (e.g., aria-label="Email address")
to the input, and ensure the input keeps required and placeholder attributes
unchanged so screen readers can announce it correctly.

Comment on lines +22 to +24
# Playwright-mcp scratch (verification artifacts, never committed)
.playwright-mcp/
*.png

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

*.png is broader than the stated intent and may silently drop committed assets.

The comment scopes this to Playwright scratch, but *.png ignores every PNG anywhere under cloud/web — including legitimate assets you may later add (favicons, OG images, public/ icons). Consider scoping the ignore to the artifacts directory.

🔧 Proposed scoping
 # Playwright-mcp scratch (verification artifacts, never committed)
 .playwright-mcp/
-*.png
+.playwright-mcp/**/*.png
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Playwright-mcp scratch (verification artifacts, never committed)
.playwright-mcp/
*.png
# Playwright-mcp scratch (verification artifacts, never committed)
.playwright-mcp/
.playwright-mcp/**/*.png
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/web/.gitignore` around lines 22 - 24, The .gitignore entry
"*.png" is too broad and will ignore PNGs project-wide; narrow it to only
Playwright scratch artifacts by replacing the global pattern with a
directory-scoped pattern such as ".playwright-mcp/**/*.png" (or
".playwright-mcp/*.png") so only PNGs inside the .playwright-mcp directory are
ignored and legitimate assets (favicons, public images, OG images) remain
tracked; update the entry referencing the .playwright-mcp/ and "*.png" patterns
accordingly.

Comment on lines +11 to +15
export async function getPref<K extends keyof Prefs>(key: K): Promise<Prefs[K] | null> {
const v = await Storage.getItem(key);

return v === null ? null : (JSON.parse(v) as Prefs[K]);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect the mobile safe-json shim API surface
fd -t f 'safe-json.ts' DevDashboard/mobile | xargs -I{} sh -c 'echo "== {} =="; cat "{}"'

Repository: genesiscz/GenesisTools

Length of output: 2122


Guard JSON.parse in getPref to avoid crashing on corrupt stored prefs.

DevDashboard/mobile/src/lib/storage/kv.ts currently calls JSON.parse directly; malformed/legacy values will throw and propagate to callers as an unhandled rejection. The mobile SafeJSON.parse shim also ignores { strict: true } for error handling (it still uses native JSON.parse), so keep the explicit try/catch and return null on failure.

♻️ Proposed fix
 export async function getPref<K extends keyof Prefs>(key: K): Promise<Prefs[K] | null> {
     const v = await Storage.getItem(key);
-
-    return v === null ? null : (JSON.parse(v) as Prefs[K]);
+    if (v === null) {
+        return null;
+    }
+    try {
+        return JSON.parse(v) as Prefs[K];
+    } catch {
+        return null;
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function getPref<K extends keyof Prefs>(key: K): Promise<Prefs[K] | null> {
const v = await Storage.getItem(key);
return v === null ? null : (JSON.parse(v) as Prefs[K]);
}
export async function getPref<K extends keyof Prefs>(key: K): Promise<Prefs[K] | null> {
const v = await Storage.getItem(key);
if (v === null) {
return null;
}
try {
return JSON.parse(v) as Prefs[K];
} catch {
return null;
}
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/lib/storage/kv.ts` around lines 11 - 15, The getPref
function currently parses stored JSON directly which can throw for corrupted
values; wrap the JSON.parse call in a try/catch inside getPref (after awaiting
Storage.getItem(key)) and return null if parsing fails, otherwise return the
parsed value cast to Prefs[K]; keep the Storage.getItem call and the function
signature intact and ensure any caught error does not rethrow so callers get
null instead of a rejected promise.

Comment on lines +33 to +40
export async function loadBasicAuthHeader(): Promise<string | null> {
const creds = await loadBasicCreds();

if (!creds) {
return null;
}

return `Basic ${btoa(`${creds.username}:${creds.password}`)}`;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

In Expo SDK 55 / React Native 0.83 with Hermes, is the global btoaguaranteed to exist, and doesbtoa accept arbitrary Unicode input or only Latin-1?

💡 Result:

Yes, in Expo SDK 55 (which uses React Native 0.83) with the Hermes JavaScript engine, the global btoa function is guaranteed to exist [1][2]. Hermes added native support for the global btoa and atob functions in recent versions to improve compatibility with the broader JavaScript ecosystem [3][2]. Regarding its behavior, btoa does not accept arbitrary Unicode input. Like its browser counterpart, it only supports characters within the Latin-1 range (U+0000 to U+00FF) [1][4][5]. If you pass a string containing characters outside this range (such as emojis or non-Latin script), Hermes will throw an error: "Found invalid character when converting to base64" [1][5]. To encode Unicode strings in React Native, you should first encode the string to a UTF-8 byte sequence—typically using TextEncoder or by manually escaping the string—before passing it to btoa [5][6]. Alternatively, you can use specialized libraries like base-64 or react-native-quick-base64 to handle Unicode-safe base64 conversion [2][7][8].

Citations:


Use a UTF-8-safe base64 encoding for the Basic auth header (don’t use global btoa).

loadBasicAuthHeader() uses btoa to encode ${creds.username}:${creds.password}. In Hermes/React Native, btoa only supports Latin-1; non-Latin-1 characters can throw or encode incorrectly, causing authentication failures. Encode the credentials to UTF-8 bytes (e.g., via TextEncoder) and base64-encode those bytes with a UTF-8/byte-safe helper instead of btoa.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/lib/storage/secure.ts` around lines 33 - 40,
loadBasicAuthHeader currently uses global btoa which is Latin-1 only; replace it
with a UTF-8-safe path by encoding `${creds.username}:${creds.password}` to
UTF-8 bytes (e.g., via TextEncoder or Buffer) and then base64-encoding those
bytes (use a byte-safe base64 helper or Buffer.from(...).toString('base64') / a
platform polyfill) before returning `Basic <base64>`; update the implementation
in loadBasicAuthHeader (and add any small helper or import) so non-Latin-1
characters encode correctly while still returning the same string shape.

Comment on lines +35 to +49
export async function loadOrCreateDeviceKeys(cipher: BoxCipher = naclBoxCipher): Promise<KeyPair> {
const storedSecret = await SecureStore.getItemAsync(SECRET_KEY_ITEM);
const storedPublic = await SecureStore.getItemAsync(PUBLIC_KEY_ITEM);

if (storedSecret && storedPublic) {
return { publicKey: fromBase64(storedPublic), secretKey: fromBase64(storedSecret) };
}

const kp = cipher.keyPair();
await SecureStore.setItemAsync(SECRET_KEY_ITEM, toBase64(kp.secretKey), {
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
});
await SecureStore.setItemAsync(PUBLIC_KEY_ITEM, toBase64(kp.publicKey));

return kp;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Serialize first-time device key creation.

Two concurrent calls can both observe empty storage, generate different keypairs, and race the writes. The loser still returns its in-memory keypair, so that caller can proceed with a device identity that no longer matches what was persisted. Memoize the in-flight load/create path so every caller gets the same keys.

🐛 Proposed fix
+let deviceKeysPromise: Promise<KeyPair> | null = null;
+
 export async function loadOrCreateDeviceKeys(cipher: BoxCipher = naclBoxCipher): Promise<KeyPair> {
-    const storedSecret = await SecureStore.getItemAsync(SECRET_KEY_ITEM);
-    const storedPublic = await SecureStore.getItemAsync(PUBLIC_KEY_ITEM);
-
-    if (storedSecret && storedPublic) {
-        return { publicKey: fromBase64(storedPublic), secretKey: fromBase64(storedSecret) };
-    }
-
-    const kp = cipher.keyPair();
-    await SecureStore.setItemAsync(SECRET_KEY_ITEM, toBase64(kp.secretKey), {
-        keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
-    });
-    await SecureStore.setItemAsync(PUBLIC_KEY_ITEM, toBase64(kp.publicKey));
-
-    return kp;
+    deviceKeysPromise ??= (async () => {
+        const storedSecret = await SecureStore.getItemAsync(SECRET_KEY_ITEM);
+        const storedPublic = await SecureStore.getItemAsync(PUBLIC_KEY_ITEM);
+
+        if (storedSecret && storedPublic) {
+            return { publicKey: fromBase64(storedPublic), secretKey: fromBase64(storedSecret) };
+        }
+
+        const kp = cipher.keyPair();
+        await SecureStore.setItemAsync(SECRET_KEY_ITEM, toBase64(kp.secretKey), {
+            keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
+        });
+        await SecureStore.setItemAsync(PUBLIC_KEY_ITEM, toBase64(kp.publicKey));
+        return kp;
+    })();
+
+    return deviceKeysPromise;
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/transport/e2e/box-cipher.ts` around lines 35 - 49,
loadOrCreateDeviceKeys has a race: concurrent callers can both see empty
SecureStore, generate different keypairs and write conflicting values; fix by
memoizing the in-flight promise so only the first call performs
read/create/write and all concurrent callers await the same Promise. Implement a
module-scoped variable (e.g., let inFlightLoadOrCreate: Promise<KeyPair> | null)
and in loadOrCreateDeviceKeys return that promise if non-null; clear it on
success or failure; keep using SECRET_KEY_ITEM, PUBLIC_KEY_ITEM,
SecureStore.getItemAsync/setItemAsync and cipher.keyPair() so callers always
receive the same persisted keypair.

Comment on lines +36 to +71
useEffect(() => {
const zeroconf = new Zeroconf();

const startScan = (): void => {
setScanning(true);
zeroconf.scan(SERVICE_TYPE, "tcp", "local.");
};

zeroconf.on("resolved", (service: Service) => {
const agent = toAgent(service);

if (agent) {
setAgents((prev) => (prev.some((a) => a.baseUrl === agent.baseUrl) ? prev : [...prev, agent]));
}
});

zeroconf.on("error", () => setScanning(false));
startScan();

const sub = AppState.addEventListener("change", (next: AppStateStatus) => {
if (next === "active") {
setAgents([]);
zeroconf.stop();
startScan();
}
});

return () => {
sub.remove();
zeroconf.stop();
zeroconf.removeDeviceListeners();
};
}, []);

return { agents, scanning, rescan: () => setAgents([]) };
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd 'package.json' DevDashboard/mobile -d 2 --exec rg -n 'react-native-zeroconf' {}
rg -nP --type=ts -C2 '\b(scan|removeDeviceListeners|stop)\s*\(' --glob '**/react-native-zeroconf/**'

Repository: genesiscz/GenesisTools

Length of output: 276


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Inspect the reviewed file around the referenced lines
FILE="DevDashboard/mobile/src/transport/lan-discovery.ts"
echo "---- $FILE (context) ----"
nl -ba "$FILE" | sed -n '1,220p'

# 2) Find other uses of react-native-zeroconf in the repo (ts only)
echo "---- usages of react-native-zeroconf ----"
rg -n --type=ts 'react-native-zeroconf' -S .

# 3) Find all references to the hook's rescan return value (if any)
echo "---- references to useZeroconfDiscovery / rescan ----"
rg -n --type=ts 'useZeroconfDiscovery\(|rescan' DevDashboard/mobile/src -S

Repository: genesiscz/GenesisTools

Length of output: 198


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="DevDashboard/mobile/src/transport/lan-discovery.ts"
echo "---- $FILE (context) ----"
sed -n '1,220p' "$FILE" | cat -n

echo "---- usages of react-native-zeroconf ----"
rg -n --type=ts 'react-native-zeroconf' -S DevDashboard/mobile/src || true

echo "---- references to useZeroconfDiscovery / rescan ----"
rg -n --type=ts 'useZeroconfDiscovery|rescan' -S DevDashboard/mobile/src || true

echo "---- Zeroconf constructor usage ----"
rg -n --type=ts '\bnew\s+Zeroconf\b' -S DevDashboard/mobile/src || true

Repository: genesiscz/GenesisTools

Length of output: 3568


🏁 Script executed:

#!/bin/bash
set -euo pipefail

TFILE="DevDashboard/mobile/src/types/react-native-zeroconf.d.ts"
echo "---- $TFILE ----"
sed -n '1,200p' "$TFILE" | cat -n

Repository: genesiscz/GenesisTools

Length of output: 1910


Fix rescan to restart Zeroconf discovery (and address scanning getting stuck).

  • In DevDashboard/mobile/src/transport/lan-discovery.ts, rescan only clears agents (rescan: () => setAgents([])); it never calls zeroconf.scan(...) again because the zeroconf instance is created inside the useEffect closure and not accessible to rescan.
  • A manual refresh can therefore leave the list empty until the network re-advertises services.
  • scanning is set to true when startScan() runs and is only reset to false on "error"; there’s no handler for a terminal discovery event, so the UI can remain “scanning” indefinitely.
  • react-native-zeroconf@0.14.0 supports scan(...) and stop() (see DevDashboard/mobile/src/types/react-native-zeroconf.d.ts), so store the instance in a useRef and implement rescan as stop+clear+scan again (optionally also set scanning to false on "stop").
🔧 Proposed fix
-import { useEffect, useState } from "react";
+import { useEffect, useRef, useState } from "react";
 import { AppState, type AppStateStatus } from "react-native";
 import Zeroconf, { type Service } from "react-native-zeroconf";
@@
 export function useZeroconfDiscovery(): ZeroconfDiscovery {
     const [agents, setAgents] = useState<DiscoveredAgent[]>([]);
     const [scanning, setScanning] = useState(false);
+    const zeroconfRef = useRef<Zeroconf | null>(null);
+
+    const restartScan = useCallback(() => {
+        const zc = zeroconfRef.current;
+        if (!zc) return;
+        setAgents([]);
+        zc.stop();
+        setScanning(true);
+        zc.scan(SERVICE_TYPE, "tcp", "local.");
+    }, []);
 
     useEffect(() => {
         const zeroconf = new Zeroconf();
+        zeroconfRef.current = zeroconf;
@@
         return () => {
             sub.remove();
             zeroconf.stop();
             zeroconf.removeDeviceListeners();
+            zeroconfRef.current = null;
         };
     }, []);
 
-    return { agents, scanning, rescan: () => setAgents([]) };
+    return { agents, scanning, rescan: restartScan };
 }

(Also add useCallback to the React import.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/transport/lan-discovery.ts` around lines 36 - 71, The
rescan currently only clears agents and cannot restart discovery because the
Zeroconf instance is scoped inside useEffect; change to store the Zeroconf
instance in a useRef (e.g., zeroconfRef) created outside useEffect, move
creation/uses to ref.current, wrap startScan and rescan in useCallback, and
implement rescan to call zeroconfRef.current.stop(), setAgents([]), then
startScan() so discovery restarts; also ensure scanning state is set true in
startScan and set to false on both "error" and the Zeroconf "stop" event, and
keep cleanup to stop() and removeDeviceListeners() on the ref.

Comment thread DevDashboard/README.md
Comment on lines +18 to +29
```
DevDashboard/
README.md ← you are here (product hub)
DECISIONS.md ← canonical decision log (always read first)
PRODUCT-ROADMAP.md ← expanded audiences + feature roadmap + monetization + positioning
research/ ← 11 verified research reports (00 baseline → 10 terminal synthesis)
cloud/landing/ ← 3 landing-page directions to choose from (obsidian-terminal / field-notes / daylight)
mobile/ ← the Expo SDK 55 app (scaffolded by plan 04)
DevDashboard/plans/ ← the 13 implementation plans (ADR + 00–11), committed for durability
.claude/plans/2026-05-29-DevDashboardMobile-*.md ← same plans, local working copies (gitignored)
src/dev-dashboard/ ← the DevDashboard Agent (extracted backend) + contract + existing web UI
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language to the fenced code block.

The artifact-map fenced block is missing a language info string, which triggers markdown lint (MD040).

Suggested fix
-```
+```text
 DevDashboard/
   README.md            ← you are here (product hub)
   DECISIONS.md         ← canonical decision log (always read first)
   PRODUCT-ROADMAP.md   ← expanded audiences + feature roadmap + monetization + positioning
   research/            ← 11 verified research reports (00 baseline → 10 terminal synthesis)
   cloud/landing/       ← 3 landing-page directions to choose from (obsidian-terminal / field-notes / daylight)
   mobile/              ← the Expo SDK 55 app (scaffolded by plan 04)
 DevDashboard/plans/    ← the 13 implementation plans (ADR + 00–11), committed for durability
 .claude/plans/2026-05-29-DevDashboardMobile-*.md   ← same plans, local working copies (gitignored)
 src/dev-dashboard/     ← the DevDashboard Agent (extracted backend) + contract + existing web UI
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 18-18: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/README.md` around lines 18 - 29, The fenced artifact-map block
in README.md lacks a language info string which triggers MD040; update the
opening triple-backtick for the DevDashboard tree block to include a language
identifier (e.g., add "text" so it becomes ```text) so the block is properly
annotated; locate the fenced block in README.md (the directory listing snippet
shown under "DevDashboard/") and modify only the opening fence to include the
language token.

@genesiscz
genesiscz force-pushed the feat/dev-dashboard-mobile branch from 79a3db0 to b31a369 Compare June 3, 2026 00:21

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 28

♻️ Duplicate comments (12)
DevDashboard/cloud/web/src/components/auth/AuthCard.tsx (1)

28-45: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Plan selection from signup is not persisted or propagated.

The plan prop from /signup?plan=pro|team is only used for display text (line 69-70) but never persisted. When signUp.email is called (line 30), the plan is not included in the request. After signup, navigation to /dashboard (line 45) does not preserve the plan. Consequently, getOverviewensureSubscription defaults all new accounts to tier: "free".

To fix: either include plan in the signUp.email payload (so Better Auth can persist it as metadata/tier), or append ?plan=... when navigating to /dashboard so the dashboard setup flow can read and apply the tier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/web/src/components/auth/AuthCard.tsx` around lines 28 -
45, The signup flow in AuthCard (signUp.email) does not persist the selected
plan and navigation to dashboard loses it; update the signUp.email call in
AuthCard.tsx to include the plan value (e.g., pass plan as part of the
payload/metadata) so the backend can store tier, and also ensure navigate({ to:
"/dashboard" }) preserves the plan by appending ?plan=... (or include plan in
the post-signup response handling) so downstream getOverview/ensureSubscription
can read and apply the selected tier; adjust the signUp.email payload and the
navigate invocation accordingly (references: signUp.email, navigate in
AuthCard).
DevDashboard/cloud/web/.gitignore (1)

22-24: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

*.png is broader than the stated intent and may silently drop committed assets.

The comment scopes this to Playwright scratch, but *.png ignores every PNG anywhere under cloud/web — including legitimate assets you may later add (favicons, OG images, public/ icons). Consider scoping the ignore to the artifacts directory.

🔧 Proposed scoping
 # Playwright-mcp scratch (verification artifacts, never committed)
 .playwright-mcp/
-*.png
+.playwright-mcp/**/*.png
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/web/.gitignore` around lines 22 - 24, The .gitignore entry
'*.png' is too broad and will ignore all PNGs under the repo; narrow it to only
the Playwright scratch artifacts by replacing the global '*.png' pattern with a
path-scoped pattern that targets the .playwright-mcp directory (e.g., use
'.playwright-mcp/*.png' or '.playwright-mcp/**/.png' or similar), keeping the
existing '.playwright-mcp/' entry intact so only Playwright verification
artifacts are ignored and legitimate assets (favicons, public images) are not
accidentally excluded.
DevDashboard/mobile/app.config.ts (1)

3-8: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix contradictory header comment about plugin behavior.

The header comment claims both react-native-zeroconf and expo-camera are config plugins, but Lines 42-45 correctly state that react-native-zeroconf must NOT be in plugins. Update the header to match the actual implementation.

📝 Suggested fix
-// Dynamic config (plan 02). Expo loads the static `app.json` first and passes it in as
-// `config`; we MERGE the transport/trust native requirements onto it so no app.json key is
-// lost. Adds: iOS Bonjour (`_devdashboard._tcp`) + local-network + camera usage strings;
-// Android INTERNET / network-state / Wi-Fi-multicast / camera permissions; the
-// `react-native-zeroconf` + `expo-camera` config plugins; and the `devdashboard` deep-link
-// scheme used by the pairing QR.
+// Dynamic config (plan 02). Expo loads the static `app.json` first and passes it in as
+// `config`; we MERGE the transport/trust native requirements onto it so no app.json key is
+// lost. Adds: iOS Bonjour (`_devdashboard._tcp`) + local-network + camera usage strings;
+// Android INTERNET / network-state / Wi-Fi-multicast / camera permissions; the
+// `expo-camera` config plugin; and the `devdashboard` deep-link scheme used by the pairing QR.
+// Note: `react-native-zeroconf` is a native module with NO config plugin.

As per coding guidelines: "Do not add comments that restate what the code already says; avoid obvious comments..."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/app.config.ts` around lines 3 - 8, The header comment
incorrectly states both react-native-zeroconf and expo-camera are config
plugins; update it to match the implementation by removing the claim that
react-native-zeroconf is a config plugin and instead note that
react-native-zeroconf must NOT be listed in the plugins array while expo-camera
is applied via a config plugin. Reference the existing symbols: the header text
describing iOS Bonjour/_devdashboard._tcp, local-network/camera usage strings,
and the plugins array (which contains expo-camera but not react-native-zeroconf)
and adjust the wording accordingly.
DevDashboard/mobile/README.md (1)

26-36: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update onboarding paths to match actual directory structure.

The README references top-level app and app-example directories, but this project uses src/app based on the file tree.

📝 Proposed fix
-You can start developing by editing the files inside the **app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).
+You can start developing by editing the files inside the **src/app** directory. This project uses [file-based routing](https://docs.expo.dev/router/introduction).

...

-This command will move the starter code to the **app-example** directory and create a blank **app** directory where you can start developing.
+This command will move the starter code to the **src/app-example** directory and create a blank **src/app** directory where you can start developing.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/README.md` around lines 26 - 36, Update the README text
so the onboarding paths reflect the actual project structure: replace references
to the top-level "app" and "app-example" directories with "src/app" and
"src/app-example" and clarify that the npm script "npm run reset-project" will
move starter code to src/app-example and create a blank src/app; edit the
paragraph containing the command and the two directory names to use these
updated paths and wording.
DevDashboard/mobile/src/app/(tabs)/terminals.tsx (1)

205-209: ⚠️ Potential issue | 🟡 Minor | 🏗️ Heavy lift

MobileKeyBar may receive stale/null renderer on initial terminal open.

Per the previous review, rendererRef.current is read during render (line 209) before DriverComponent's ref assignment completes. When open transitions from null to a session, the first render pass sends null to MobileKeyBar, which won't re-render until another state change. Promoting the renderer to state (set via DriverComponent's ref callback) would ensure MobileKeyBar always receives a valid, up-to-date renderer reference.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/app/`(tabs)/terminals.tsx around lines 205 - 209, The
MobileKeyBar is getting rendererRef.current during initial render and can be
null; instead promote the renderer to React state and set it from
DriverComponent's ref callback so MobileKeyBar always gets a stable value.
Introduce a state variable (e.g., renderer, setRenderer) and change the
DriverComponent ref to a callback that assigns rendererRef.current and also
calls setRenderer(rendererRef.current); then pass the state variable (renderer)
into MobileKeyBar rather than rendererRef.current so it updates when the ref is
first assigned. Ensure any existing uses of rendererRef.current continue to work
by keeping the ref in sync in the callback.
DevDashboard/cloud/landing/obsidian-terminal/index.html (2)

168-168: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add type="button" to prevent unintended form submission.

The hamburger menu button should have an explicit type="button" attribute.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/landing/obsidian-terminal/index.html` at line 168, The
menu button with id "menuBtn" is missing an explicit type and can submit
surrounding forms unintentionally; update the <button id="menuBtn" ...> element
(the hamburger menu) to include type="button" so it does not act as a submit
button.

721-724: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add accessible label for email input.

The email input in the footer form lacks an associated label or aria-label for screen reader accessibility.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/landing/obsidian-terminal/index.html` around lines 721 -
724, The email input lacks an accessible label; add one by giving the input an
id (e.g., id="newsletter-email") and either add a visually-hidden <label
for="newsletter-email">Email address</label> or add an
aria-label/aria-labelledby on the input (e.g., aria-label="Email address"),
targeting the input element (input[type="email"]) inside the form with
onsubmit="return false;"; ensure the required attribute remains and the label
text is descriptive such as "Email address".
DevDashboard/mobile/e2e/specs/quick-commands.spec.ts (1)

25-25: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error swallowing could hide real test failures.

Same issue as in reminders-todos.spec.ts and network-status.spec.ts: the .catch(() => false) pattern silently converts all errors into false, making debugging harder when tests fail unexpectedly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/e2e/specs/quick-commands.spec.ts` at line 25, The test
currently swallows all errors by using .catch(() => false) on
connectPage.isShown(), which hides real failures; update the check around
connectPage.isShown() to stop swallowing errors—either await
connectPage.isShown() directly (letting the test fail on unexpected exceptions)
or catch only expected/known errors and rethrow or log unexpected ones; locate
the use of connectPage.isShown() in quick-commands.spec.ts and replace the
blanket .catch(() => false) with proper error handling that surfaces real
failures.
DevDashboard/mobile/e2e/specs/network-status.spec.ts (1)

18-18: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Error swallowing could hide real test failures.

Same issue as in reminders-todos.spec.ts: the .catch(() => false) pattern silently converts all errors into false, making debugging harder when tests fail unexpectedly.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/e2e/specs/network-status.spec.ts` at line 18, The test is
swallowing errors by using connectPage.isShown().catch(() => false), which hides
real failures; change this to await the call inside a try/catch (or remove the
catch) so errors propagate or are explicitly asserted/logged. For example, wrap
await connectPage.isShown() in a try { const shown = await
connectPage.isShown(); if (shown) { ... } } catch (err) { throw err; } or
replace the catch handler with one that logs and rethrows—target the
connectPage.isShown() call in network-status.spec.ts (same pattern as in
reminders-todos.spec.ts).
DevDashboard/mobile/src/components/connect/QrScanner.tsx (1)

40-47: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

scannedOnce permanently blocks retries after the first scan.

After one QR detection (including invalid codes or failed pairing), scannedOnce is set and further scans are blocked for the component's lifetime. If the user scans an invalid QR or pairing fails, they cannot retry without remounting.

🔄 Proposed fix

Change onScanned to return a boolean indicating success, and only set scannedOnce(true) when successful:

-export function QrScanner({ onScanned }: { onScanned: (data: string) => void }) {
+export function QrScanner({ onScanned }: { onScanned: (data: string) => Promise<boolean> | boolean }) {
@@
-            onBarcodeScanned={({ data }) => {
+            onBarcodeScanned={async ({ data }) => {
                 if (scannedOnce) {
                     return;
                 }
 
-                setScannedOnce(true);
-                onScanned(data);
+                try {
+                    const ok = await onScanned(data);
+                    if (ok) {
+                        setScannedOnce(true);
+                    }
+                } catch {
+                    // Allow retry on exception
+                }
             }}

Then update the parent onQrScanned in connect.tsx to return true on success, false on failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/components/connect/QrScanner.tsx` around lines 40 -
47, The onBarcodeScanned handler currently sets the scannedOnce flag
unconditionally, blocking all further scans; change the contract so onScanned
(the prop passed from connect.tsx, e.g. onQrScanned) returns a boolean
indicating success, then only call setScannedOnce(true) when onScanned(data)
returns true; update the handler in QrScanner.tsx (onBarcodeScanned,
scannedOnce, setScannedOnce) to await/capture the boolean result and
conditionally set the flag, and update the parent onQrScanned implementation in
connect.tsx to return true on successful pairing and false on failure.
DevDashboard/mobile/src/app/connect.tsx (2)

153-153: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate the parsed port before using it.

Number.parseInt can return NaN or out-of-range values, which would then be written into setLan and baseUrl.

🛡️ Proposed fix
             const url = new URL(cleaned.startsWith("http") ? cleaned : `http://${cleaned}`);
-            const port = url.port ? Number.parseInt(url.port, 10) : 3042;
+            const parsedPort = url.port ? Number.parseInt(url.port, 10) : 3042;
+            if (!Number.isInteger(parsedPort) || parsedPort < 1 || parsedPort > 65535) {
+                throw new Error("Invalid port. Use a value between 1 and 65535.");
+            }
+            const port = parsedPort;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/app/connect.tsx` at line 153, The parsed port
assigned to port via Number.parseInt(url.port, 10) must be validated before use:
replace the direct assignment with logic that parses url.port, checks
Number.isInteger and that the value is between 1 and 65535, and falls back to
the default 3042 if it is NaN or out of range; then ensure subsequent uses
(setLan and baseUrl) consume this validated port variable rather than the raw
parse result so no NaN or invalid port ever flows into setLan/baseUrl.

192-205: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Wrap applyPairingUri in try/catch to handle exceptions.

If applyPairingUri throws, the reachability state remains stuck in "probing" and no error message is shown to the user.

🔒 Proposed fix
         dispatchReach({ type: "probe-start" });
-        const result = await applyPairingUri(data, password);
-
-        if (result.ok) {
-            console.log("[connect] reachability dispatch: probe-ok (pairing)");
-            dispatchReach({ type: "probe-ok" });
-        } else {
-            setError(result.error ?? "Pairing failed.");
-
-            if (tier) {
-                console.log(`[connect] reachability dispatch: probe-fail tier=${tier} (pairing)`);
-                dispatchReach({ type: "probe-fail", tier, paired: tier !== "managed" });
+        try {
+            const result = await applyPairingUri(data, password);
+            if (result.ok) {
+                console.log("[connect] reachability dispatch: probe-ok (pairing)");
+                dispatchReach({ type: "probe-ok" });
+            } else {
+                setError(result.error ?? "Pairing failed.");
+                if (tier) {
+                    console.log(`[connect] reachability dispatch: probe-fail tier=${tier} (pairing)`);
+                    dispatchReach({ type: "probe-fail", tier, paired: tier !== "managed" });
+                }
             }
+        } catch (err) {
+            setError(err instanceof Error ? err.message : "Pairing failed.");
+            if (tier) {
+                dispatchReach({ type: "probe-fail", tier, paired: tier !== "managed" });
+            }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/app/connect.tsx` around lines 192 - 205, The call to
applyPairingUri can throw and leaves reachability stuck in "probing"; wrap the
await applyPairingUri(data, password) call in a try/catch inside the same block
so any exception sets an error and updates reachability. In the catch, call
setError with the caught error message (e.g. error?.message ?? "Pairing
failed.") and ensure you dispatchReach({ type: "probe-fail", tier, paired: tier
!== "managed" }) when tier is present; also log the caught error for debugging.
This keeps probe-start paired with a corresponding probe-fail on exceptions
while reusing the existing dispatchReach, setError, and tier variables.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@DevDashboard/cloud/shared/account-model.ts`:
- Around line 78-103: CLOUD_PERSISTABLE_FIELDS contains an obsolete "accounts"
entry and a corresponding unused Account interface which causes CloudTable (and
runtime guards like assertNoKeyMaterial) to include a non-existent table; remove
the "accounts" key from CLOUD_PERSISTABLE_FIELDS and delete the unused Account
interface (or alternatively reconcile names if you intended to map to
schema.ts's account/accountSettings naming), then run TypeScript checks to
ensure CloudTable and any usages in cloud-store.ts and assertNoKeyMaterial are
updated to only reference real tables defined in schema.ts (subscriptions,
devices, managed_subdomains/managedSubdomains,
account_settings/accountSettings).

In `@DevDashboard/cloud/web/docs/install-the-agent.md`:
- Around line 56-61: The fenced code blocks containing the commands `tools
dev-dashboard tunnel setup` and `tools dev-dashboard pair` need blank lines
before and after the triple-backtick fences; update the markdown in
install-the-agent.md so each fenced block is preceded and followed by an empty
line (i.e., ensure there is a blank line above the opening ``` and a blank line
below the closing ``` surrounding the `tools dev-dashboard tunnel setup` and
`tools dev-dashboard pair` blocks and the surrounding explanatory text).

In `@DevDashboard/cloud/web/docs/security-and-trust.md`:
- Around line 40-43: The fenced code block showing forbidden field names
(privateKey, secretKey, sessionKey, sharedSecret, derivedSecret, pairingSecret,
symmetricKey, aeadKey, nonceSecret) lacks a language identifier; update the
markdown fenced block to include an explicit language tag (e.g., "text") so it
renders/accesses correctly — locate the block containing those symbol names and
add the language identifier immediately after the opening triple backticks.

In `@DevDashboard/cloud/web/package.json`:
- Line 32: The package.json currently pins the "nitro" dependency to "latest",
which breaks reproducible builds; run npm list nitro to determine the installed
version and replace the "nitro": "latest" entry in package.json with that exact
version (e.g., "nitro": "X.Y.Z"), then run npm install to update the lockfile so
the change is persisted; target the "nitro" key in package.json to make this
edit.
- Line 30: The package.json currently depends on a prerelease h3 ("h3":
"^2.0.1-rc.7"); either replace that dependency with the latest stable 2.x
release ("h3": "^2.0.0") or explicitly pin the RC (e.g., "h3": "2.0.1-rc.7") and
add a short justification comment in your repo docs explaining why the
prerelease is required; update the DevDashboard/cloud/web/package.json entry for
"h3" and commit the reasoning if you choose to keep the RC.

In `@DevDashboard/cloud/web/src/components/landing/Features.tsx`:
- Around line 59-65: In the mapping inside the Features component where {[42,
64, 38, 80, 52, 70, 46, 88].map((h, i) => ( ... ))} is used, replace the fragile
key={`${h}-${i}`} with the index-only key={i} so each generated <span> has a
stable identity for this static list; update the key attribute in that map
callback to use i instead of the template string.

In `@DevDashboard/cloud/web/src/lib/provision/cloudflare.ts`:
- Around line 28-47: The NAME_RE currently permits single-character names but
the validation message in provisionManagedSubdomain claims a 3–32 char minimum;
update validation to enforce the intended 3–32 length or adjust the message.
Specifically, change the regex constant NAME_RE (used by isValidSubdomainName)
so it requires at least 3 characters (e.g., ensure pattern enforces start char +
middle {1,30} + end char for 3–32 total), or alternatively modify the error
string thrown in provisionManagedSubdomain to reflect the actual allowed length;
keep references to NAME_RE, isValidSubdomainName, and provisionManagedSubdomain
when applying the fix.

In `@DevDashboard/cloud/web/src/routes/api.stripe.webhook.ts`:
- Around line 62-77: The handler for events "customer.subscription.updated" and
"customer.subscription.deleted" silently skips when
subscription.metadata?.accountId is missing; add a warning log before the break
that includes identifying details (e.g., event.type, subscription.id and full
subscription.metadata) so missing metadata can be diagnosed. Update the block
around where you call cloudStore.updateSubscription to log something like a
processLogger.warn or logger.warn when accountId is falsy, referencing the
subscription and event type for context.

In `@DevDashboard/cloud/web/src/routes/dashboard.settings.tsx`:
- Around line 83-114: The Toggle component lacks an accessible name; update the
Toggle function signature to accept a label prop (e.g., label: string) and apply
it as the accessible name (use aria-label={label} or aria-labelledby pointing to
a visible label) on the button element so screen readers can announce the
control, then update the place where Toggle is rendered (the call site that
currently renders Toggle) to pass the actual label text such as "Push alerts".

In `@DevDashboard/cloud/web/tests-e2e/api.spec.ts`:
- Line 17: Replace the complex boolean assertion that checks body === "null" ||
body === "" || body === "{}" with a clearer containment assertion: assert that
the array of allowed string values contains the body value (use the testing
framework's toContain-style assertion) so the test reads as "body is one of
these allowed values" rather than evaluating a chained boolean expression.

In `@DevDashboard/cloud/web/tests-e2e/auth.setup.ts`:
- Line 32: The for-loop iterates over a long inline array of routes which is
hard to scan; update the for (const path of [...]) loop so the array of route
strings is written one-per-line (vertical list) for readability — locate the for
(const path of ["/", "/signin", "/signup", "/dashboard", "/dashboard/setup",
"/dashboard/devices", "/dashboard/settings", "/dashboard/billing"]) loop and
break the array into multiple lines inside the brackets, keeping the same string
values and preserving the surrounding loop logic.

In `@DevDashboard/cloud/web/tests-e2e/dashboard-setup.spec.ts`:
- Line 48: Replace the direct call to .fill() with the hydration-aware helper
.fillHydrated() on the setup input; specifically, change the invocation on
page.getByTestId("setup-subdomain-input") from .fill("A_B C!") to
.fillHydrated("A_B C!") so the test uses the same hydration timing helper as the
valid-subdomain and pairing tests and avoids potential hydration races.

In `@DevDashboard/mobile/app.config.ts`:
- Line 28: The NSCameraUsageDescription string is duplicated between the
top-level infoPlist key (NSCameraUsageDescription) and the expo-camera plugin's
cameraPermission option; extract the permission text into a single constant
(e.g., CAMERA_PERMISSION_MSG) and reference that constant in the iOS infoPlist
(the system-controlled entry) while removing the duplicate cameraPermission
setting from the expo-camera plugin config so only the infoPlist key
(NSCameraUsageDescription) supplies the runtime permission message.

In `@DevDashboard/mobile/e2e/pages/QuickCommandsPage.page.ts`:
- Around line 96-106: The waitForLabelPresent function interpolates label
directly into an XPath which is vulnerable to breaking or injection when label
contains quotes or XPath metacharacters; update waitForLabelPresent to
sanitize/escape the label before building the XPath (or switch to a safer
selector API) so that characters like ", ', [, ] are handled correctly; locate
the XPath expression in waitForLabelPresent (the
$(`//*[contains(`@label`,"${label}")]`) usage) and replace it with an
escaped/quoted-safe version of label or a parameterized selector, ensuring the
check still uses this.ids.screen visibility plus the sanitized lookup.

In `@DevDashboard/mobile/e2e/README.md`:
- Around line 127-140: Add a blank line immediately before the TypeScript code
fence that begins the TerminalsPage example so the markdown linter rule MD031 is
satisfied; update the README so there is an empty line before the block that
defines class TerminalsPage (and its isShown method) and the exported
terminalsPage instance.

In `@DevDashboard/mobile/e2e/specs/connections.spec.ts`:
- Around line 54-56: The tests repeat the defensive null-guard `if (activeId ===
null) { return; }`; extract this into a shared helper (e.g., getActiveIdOrThrow
or ensureActiveId) or move the lookup into a beforeEach that sets a non-null
`activeId` for the tests to use, and replace the inline guards with calls to
that helper or rely on the shared setup; update occurrences referencing the
`activeId` variable in connections.spec.ts (the three spots around lines where
`activeId` is read) so tests either assert/throw when null or always have a
valid value from beforeEach.

In `@DevDashboard/mobile/e2e/specs/port-killer.spec.ts`:
- Around line 45-46: The current assertion uses (await
portKillerPage.rowExists(3042)) || (await portKillerPage.isShown()), which
falsely passes whenever the screen is visible; change it to explicitly check for
one of the valid resolved states by replacing the fallback to isShown() with
checks for either a specific empty-state or any row. Update the test to await
portKillerPage.hasAnyRow() || portKillerPage.isEmptyStateShown() ||
portKillerPage.rowExists(3042) (or add hasAnyRow()/isEmptyStateShown() helpers
to the portKillerPage if they don't exist) so the expectation verifies the
screen resolved to lsof-unavailable, an empty state, or at least one port row
before asserting truthy.

In `@DevDashboard/mobile/e2e/specs/reminders-todos.spec.ts`:
- Line 25: The current call to connectPage.isShown().catch(() => false) swallows
all errors; replace it with an explicit try/catch around the await
connectPage.isShown() call (referencing connectPage.isShown()) and either log
the caught error (e.g., console.error or your test logger) before returning
false, or only catch specific expected errors (e.g., a WebDriver/TimeoutError)
and rethrow other exceptions so real failures surface; ensure the catch block
records the error message and stack so debugging remains possible.

In `@DevDashboard/mobile/e2e/specs/tmux-presets.spec.ts`:
- Around line 56-59: The test contains an unreachable "return" immediately after
calling this.skip(); remove the redundant "return" following this.skip() in the
tmux-presets.spec.ts test (the block using tmuxPresetsPage.rowExists(name) and
this.skip())—just leave the this.skip() call and delete the trailing return to
clean up the code (same cleanup as done for the other occurrence around the
earlier tmuxPresetsPage.rowExists check).
- Around line 42-45: In the test block that checks
tmuxPresetsPage.rowExists(name), remove the unreachable "return" following
"this.skip()" since Mocha's this.skip() throws to abort the test; keep the
conditional that calls this.skip() when !await tmuxPresetsPage.rowExists(name)
but delete the subsequent "return" to clean up dead code.

In `@DevDashboard/mobile/e2e/specs/weather.spec.ts`:
- Around line 51-54: The return after calling this.skip() is unreachable because
Mocha's this.skip() throws to abort the test; locate the block using
weatherPage.hasTemp() (the async check) and remove the trailing return so only
this.skip() is invoked when the condition is false (i.e., delete the `return;`
following `this.skip();` in the conditional).

In `@DevDashboard/mobile/package.json`:
- Line 59: The package.json declares nativewind@4.2.4 but still depends on
tailwindcss@^3; update the Tailwind dependency to a v4-compatible range (e.g.,
"tailwindcss": "^4") in package.json so NativeWind v4 requirements are met, then
run your package manager (npm/yarn/pnpm install) and re-generate any Tailwind
artifacts (postcss/tailwind config) if needed to ensure config keys match
Tailwind v4; verify compatibility with the NativeWind usage in your project
after installation.

In `@DevDashboard/mobile/README.md`:
- Line 55: Summary: The phrase "open source platform" is used as a compound
adjective and should be hyphenated. Replace the occurrence of the string "open
source platform" in the README line (the link text "Expo on GitHub: View our
open source platform and contribute.") with "open-source platform" so it reads
"open-source platform". Ensure the change is made only to that descriptive text
and preserves the link and punctuation.

In `@DevDashboard/mobile/src/app/connect.tsx`:
- Line 173: The parsed port value (const port = portStr ?
Number.parseInt(portStr, 10) : 3042;) can be NaN or out of valid TCP range;
update the parsing logic in connect (and the same logic in connectLan) to
validate Number.parseInt output with Number.isInteger and ensure 1 <= port <=
65535, and if validation fails fall back to the default port (3042) or surface a
clear error; apply this to the port variable in the connect function and the
port parsing in connectLan to prevent using invalid ports.

In `@DevDashboard/mobile/src/components/animated-icon.web.tsx`:
- Around line 43-56: The keyframe step in glowKeyframe is using [DURATION /
1000] (which evaluates to 0.3) but Reanimated Keyframe keys are percentages
0–100; replace that dynamic numeric key with the intended percentage (e.g., 30)
or compute percent correctly (e.g., (DURATION / totalDuration)*100) so the
middle keyframe is at the proper 30% position; update the Keyframe object used
by glowKeyframe and ensure DURATION usage is adjusted accordingly.

In `@DevDashboard/mobile/src/components/themed-text.tsx`:
- Around line 63-67: The style object for linkPrimary currently hard-codes color
"`#3c87f7`" which bypasses theming; update the linkPrimary style in
themed-text.tsx (the linkPrimary style entry) to use a theme token instead
(e.g., theme.colors.linkPrimary or theme.colors.link) by reading the theme
passed into the component or adding a new token to the theme and referencing it
from the component so the color is derived from the theme system rather than a
literal string.

In `@DevDashboard/mobile/src/components/themed-view.tsx`:
- Around line 7-8: ThemedView currently destructures lightColor and darkColor
props but never uses them; either remove these props from the ThemedViewProps
and the destructuring in the ThemedView component, or add a short comment next
to the prop declarations and the destructuring in ThemedView explaining they are
intentionally retained for API compatibility with ThemedText/future use. Update
ThemedViewProps (and the function signature for ThemedView) to reflect the
chosen approach and run type checks to ensure no unused variable warnings
remain.

In `@DevDashboard/PRODUCT-ROADMAP.md`:
- Line 45: Update the phrase in the product roadmap where it currently reads
"keypairs" to use the correct two-word form "key pairs" — specifically in the
sentence starting "Killer feature: Multi-machine fleet view with hard
per-machine isolation." and the portion "separate E2E keypairs per pairing"
should become "separate E2E key pairs per pairing".

---

Duplicate comments:
In `@DevDashboard/cloud/landing/obsidian-terminal/index.html`:
- Line 168: The menu button with id "menuBtn" is missing an explicit type and
can submit surrounding forms unintentionally; update the <button id="menuBtn"
...> element (the hamburger menu) to include type="button" so it does not act as
a submit button.
- Around line 721-724: The email input lacks an accessible label; add one by
giving the input an id (e.g., id="newsletter-email") and either add a
visually-hidden <label for="newsletter-email">Email address</label> or add an
aria-label/aria-labelledby on the input (e.g., aria-label="Email address"),
targeting the input element (input[type="email"]) inside the form with
onsubmit="return false;"; ensure the required attribute remains and the label
text is descriptive such as "Email address".

In `@DevDashboard/cloud/web/.gitignore`:
- Around line 22-24: The .gitignore entry '*.png' is too broad and will ignore
all PNGs under the repo; narrow it to only the Playwright scratch artifacts by
replacing the global '*.png' pattern with a path-scoped pattern that targets the
.playwright-mcp directory (e.g., use '.playwright-mcp/*.png' or
'.playwright-mcp/**/.png' or similar), keeping the existing '.playwright-mcp/'
entry intact so only Playwright verification artifacts are ignored and
legitimate assets (favicons, public images) are not accidentally excluded.

In `@DevDashboard/cloud/web/src/components/auth/AuthCard.tsx`:
- Around line 28-45: The signup flow in AuthCard (signUp.email) does not persist
the selected plan and navigation to dashboard loses it; update the signUp.email
call in AuthCard.tsx to include the plan value (e.g., pass plan as part of the
payload/metadata) so the backend can store tier, and also ensure navigate({ to:
"/dashboard" }) preserves the plan by appending ?plan=... (or include plan in
the post-signup response handling) so downstream getOverview/ensureSubscription
can read and apply the selected tier; adjust the signUp.email payload and the
navigate invocation accordingly (references: signUp.email, navigate in
AuthCard).

In `@DevDashboard/mobile/app.config.ts`:
- Around line 3-8: The header comment incorrectly states both
react-native-zeroconf and expo-camera are config plugins; update it to match the
implementation by removing the claim that react-native-zeroconf is a config
plugin and instead note that react-native-zeroconf must NOT be listed in the
plugins array while expo-camera is applied via a config plugin. Reference the
existing symbols: the header text describing iOS Bonjour/_devdashboard._tcp,
local-network/camera usage strings, and the plugins array (which contains
expo-camera but not react-native-zeroconf) and adjust the wording accordingly.

In `@DevDashboard/mobile/e2e/specs/network-status.spec.ts`:
- Line 18: The test is swallowing errors by using connectPage.isShown().catch(()
=> false), which hides real failures; change this to await the call inside a
try/catch (or remove the catch) so errors propagate or are explicitly
asserted/logged. For example, wrap await connectPage.isShown() in a try { const
shown = await connectPage.isShown(); if (shown) { ... } } catch (err) { throw
err; } or replace the catch handler with one that logs and rethrows—target the
connectPage.isShown() call in network-status.spec.ts (same pattern as in
reminders-todos.spec.ts).

In `@DevDashboard/mobile/e2e/specs/quick-commands.spec.ts`:
- Line 25: The test currently swallows all errors by using .catch(() => false)
on connectPage.isShown(), which hides real failures; update the check around
connectPage.isShown() to stop swallowing errors—either await
connectPage.isShown() directly (letting the test fail on unexpected exceptions)
or catch only expected/known errors and rethrow or log unexpected ones; locate
the use of connectPage.isShown() in quick-commands.spec.ts and replace the
blanket .catch(() => false) with proper error handling that surfaces real
failures.

In `@DevDashboard/mobile/README.md`:
- Around line 26-36: Update the README text so the onboarding paths reflect the
actual project structure: replace references to the top-level "app" and
"app-example" directories with "src/app" and "src/app-example" and clarify that
the npm script "npm run reset-project" will move starter code to src/app-example
and create a blank src/app; edit the paragraph containing the command and the
two directory names to use these updated paths and wording.

In `@DevDashboard/mobile/src/app/`(tabs)/terminals.tsx:
- Around line 205-209: The MobileKeyBar is getting rendererRef.current during
initial render and can be null; instead promote the renderer to React state and
set it from DriverComponent's ref callback so MobileKeyBar always gets a stable
value. Introduce a state variable (e.g., renderer, setRenderer) and change the
DriverComponent ref to a callback that assigns rendererRef.current and also
calls setRenderer(rendererRef.current); then pass the state variable (renderer)
into MobileKeyBar rather than rendererRef.current so it updates when the ref is
first assigned. Ensure any existing uses of rendererRef.current continue to work
by keeping the ref in sync in the callback.

In `@DevDashboard/mobile/src/app/connect.tsx`:
- Line 153: The parsed port assigned to port via Number.parseInt(url.port, 10)
must be validated before use: replace the direct assignment with logic that
parses url.port, checks Number.isInteger and that the value is between 1 and
65535, and falls back to the default 3042 if it is NaN or out of range; then
ensure subsequent uses (setLan and baseUrl) consume this validated port variable
rather than the raw parse result so no NaN or invalid port ever flows into
setLan/baseUrl.
- Around line 192-205: The call to applyPairingUri can throw and leaves
reachability stuck in "probing"; wrap the await applyPairingUri(data, password)
call in a try/catch inside the same block so any exception sets an error and
updates reachability. In the catch, call setError with the caught error message
(e.g. error?.message ?? "Pairing failed.") and ensure you dispatchReach({ type:
"probe-fail", tier, paired: tier !== "managed" }) when tier is present; also log
the caught error for debugging. This keeps probe-start paired with a
corresponding probe-fail on exceptions while reusing the existing dispatchReach,
setError, and tier variables.

In `@DevDashboard/mobile/src/components/connect/QrScanner.tsx`:
- Around line 40-47: The onBarcodeScanned handler currently sets the scannedOnce
flag unconditionally, blocking all further scans; change the contract so
onScanned (the prop passed from connect.tsx, e.g. onQrScanned) returns a boolean
indicating success, then only call setScannedOnce(true) when onScanned(data)
returns true; update the handler in QrScanner.tsx (onBarcodeScanned,
scannedOnce, setScannedOnce) to await/capture the boolean result and
conditionally set the flag, and update the parent onQrScanned implementation in
connect.tsx to return true on successful pairing and false on failure.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: eb277c33-0b8b-4edd-b29e-f5d619572ec7

📥 Commits

Reviewing files that changed from the base of the PR and between ab719f9 and b31a369.

⛔ Files ignored due to path filters (24)
  • DevDashboard/cloud/web/bun.lock is excluded by !**/*.lock
  • DevDashboard/mobile/assets/expo.icon/Assets/expo-symbol 2.svg is excluded by !**/*.svg
  • DevDashboard/mobile/assets/expo.icon/Assets/grid.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/android-icon-background.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/android-icon-foreground.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/android-icon-monochrome.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/expo-badge-white.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/expo-badge.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/expo-logo.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/favicon.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/icon.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/logo-glow.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/react-logo.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/react-logo@2x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/react-logo@3x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/splash-icon.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/explore.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/explore@2x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/explore@3x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/home.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/home@2x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tabIcons/home@3x.png is excluded by !**/*.png
  • DevDashboard/mobile/assets/images/tutorial-web.png is excluded by !**/*.png
  • DevDashboard/mobile/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (276)
  • DevDashboard/DECISIONS.md
  • DevDashboard/PRODUCT-ROADMAP.md
  • DevDashboard/README.md
  • DevDashboard/cloud/landing/daylight/index.html
  • DevDashboard/cloud/landing/field-notes/index.html
  • DevDashboard/cloud/landing/obsidian-terminal/index.html
  • DevDashboard/cloud/shared/account-model.ts
  • DevDashboard/cloud/shared/data-boundary.test.ts
  • DevDashboard/cloud/shared/data-boundary.ts
  • DevDashboard/cloud/shared/tier-policy.test.ts
  • DevDashboard/cloud/shared/tier-policy.ts
  • DevDashboard/cloud/web/.env.example
  • DevDashboard/cloud/web/.gitignore
  • DevDashboard/cloud/web/README.md
  • DevDashboard/cloud/web/biome.json
  • DevDashboard/cloud/web/db/migrations/0000_true_energizer.sql
  • DevDashboard/cloud/web/db/migrations/meta/0000_snapshot.json
  • DevDashboard/cloud/web/db/migrations/meta/_journal.json
  • DevDashboard/cloud/web/docs/getting-started.md
  • DevDashboard/cloud/web/docs/install-the-agent.md
  • DevDashboard/cloud/web/docs/pricing-and-tiers.md
  • DevDashboard/cloud/web/docs/security-and-trust.md
  • DevDashboard/cloud/web/docs/troubleshooting.md
  • DevDashboard/cloud/web/drizzle.config.ts
  • DevDashboard/cloud/web/package.json
  • DevDashboard/cloud/web/playwright.config.ts
  • DevDashboard/cloud/web/src/components/RouteError.tsx
  • DevDashboard/cloud/web/src/components/RouteNotFound.tsx
  • DevDashboard/cloud/web/src/components/auth/AuthCard.tsx
  • DevDashboard/cloud/web/src/components/dashboard/Card.tsx
  • DevDashboard/cloud/web/src/components/landing/Features.tsx
  • DevDashboard/cloud/web/src/components/landing/Footer.tsx
  • DevDashboard/cloud/web/src/components/landing/Hero.tsx
  • DevDashboard/cloud/web/src/components/landing/HowItWorks.tsx
  • DevDashboard/cloud/web/src/components/landing/Nav.tsx
  • DevDashboard/cloud/web/src/components/landing/Pricing.tsx
  • DevDashboard/cloud/web/src/components/landing/TrustStory.tsx
  • DevDashboard/cloud/web/src/components/landing/icons.tsx
  • DevDashboard/cloud/web/src/content/copy.ts
  • DevDashboard/cloud/web/src/integrations/tanstack-query/root-provider.tsx
  • DevDashboard/cloud/web/src/lib/auth/auth-client.ts
  • DevDashboard/cloud/web/src/lib/auth/auth-service.ts
  • DevDashboard/cloud/web/src/lib/auth/auth.functions.ts
  • DevDashboard/cloud/web/src/lib/auth/auth.server.ts
  • DevDashboard/cloud/web/src/lib/auth/auth.test.ts
  • DevDashboard/cloud/web/src/lib/billing/billing.functions.ts
  • DevDashboard/cloud/web/src/lib/billing/env-gating.test.ts
  • DevDashboard/cloud/web/src/lib/billing/stripe.ts
  • DevDashboard/cloud/web/src/lib/dashboard/dashboard.functions.ts
  • DevDashboard/cloud/web/src/lib/db/cloud-store.ts
  • DevDashboard/cloud/web/src/lib/db/index.ts
  • DevDashboard/cloud/web/src/lib/db/migrate.ts
  • DevDashboard/cloud/web/src/lib/db/schema.ts
  • DevDashboard/cloud/web/src/lib/provision/cloudflare.ts
  • DevDashboard/cloud/web/src/lib/server/env.ts
  • DevDashboard/cloud/web/src/router.tsx
  • DevDashboard/cloud/web/src/routes/__root.tsx
  • DevDashboard/cloud/web/src/routes/api.auth.$.ts
  • DevDashboard/cloud/web/src/routes/api.stripe.webhook.ts
  • DevDashboard/cloud/web/src/routes/dashboard.billing.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.devices.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.index.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.settings.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.setup.tsx
  • DevDashboard/cloud/web/src/routes/dashboard.tsx
  • DevDashboard/cloud/web/src/routes/index.tsx
  • DevDashboard/cloud/web/src/routes/signin.tsx
  • DevDashboard/cloud/web/src/routes/signup.tsx
  • DevDashboard/cloud/web/src/start.ts
  • DevDashboard/cloud/web/src/styles/app.css
  • DevDashboard/cloud/web/tests-e2e/api.spec.ts
  • DevDashboard/cloud/web/tests-e2e/auth-flow.spec.ts
  • DevDashboard/cloud/web/tests-e2e/auth-guard.spec.ts
  • DevDashboard/cloud/web/tests-e2e/auth.setup.ts
  • DevDashboard/cloud/web/tests-e2e/constants.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-billing.spec.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-devices.spec.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-overview.spec.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-settings.spec.ts
  • DevDashboard/cloud/web/tests-e2e/dashboard-setup.spec.ts
  • DevDashboard/cloud/web/tests-e2e/helpers.ts
  • DevDashboard/cloud/web/tests-e2e/landing.spec.ts
  • DevDashboard/cloud/web/tests-e2e/prepare-db.ts
  • DevDashboard/cloud/web/tests-e2e/signin.spec.ts
  • DevDashboard/cloud/web/tests-e2e/signup.spec.ts
  • DevDashboard/cloud/web/tsconfig.json
  • DevDashboard/cloud/web/vite.config.ts
  • DevDashboard/mobile/.claude/settings.json
  • DevDashboard/mobile/.gitignore
  • DevDashboard/mobile/.vscode/extensions.json
  • DevDashboard/mobile/.vscode/settings.json
  • DevDashboard/mobile/AGENTS.md
  • DevDashboard/mobile/CLAUDE.md
  • DevDashboard/mobile/README.md
  • DevDashboard/mobile/app.config.ts
  • DevDashboard/mobile/app.json
  • DevDashboard/mobile/assets/expo.icon/icon.json
  • DevDashboard/mobile/babel.config.js
  • DevDashboard/mobile/e2e/README.md
  • DevDashboard/mobile/e2e/pages/ActivityTimelinePage.page.ts
  • DevDashboard/mobile/e2e/pages/BuildLogTailPage.page.ts
  • DevDashboard/mobile/e2e/pages/ClaudeUsagePage.page.ts
  • DevDashboard/mobile/e2e/pages/ConnectPage.page.ts
  • DevDashboard/mobile/e2e/pages/ConnectionsPage.page.ts
  • DevDashboard/mobile/e2e/pages/ContainersPage.page.ts
  • DevDashboard/mobile/e2e/pages/DaemonPage.page.ts
  • DevDashboard/mobile/e2e/pages/DiskJanitorPage.page.ts
  • DevDashboard/mobile/e2e/pages/MoreNav.page.ts
  • DevDashboard/mobile/e2e/pages/NeedsInputInboxPage.page.ts
  • DevDashboard/mobile/e2e/pages/NetworkStatusPage.page.ts
  • DevDashboard/mobile/e2e/pages/ObsidianPage.page.ts
  • DevDashboard/mobile/e2e/pages/PortKillerPage.page.ts
  • DevDashboard/mobile/e2e/pages/ProcessMonitorPage.page.ts
  • DevDashboard/mobile/e2e/pages/PulsePage.page.ts
  • DevDashboard/mobile/e2e/pages/QaPage.page.ts
  • DevDashboard/mobile/e2e/pages/QuickCommandsPage.page.ts
  • DevDashboard/mobile/e2e/pages/RemindersTodosPage.page.ts
  • DevDashboard/mobile/e2e/pages/TerminalsPage.page.ts
  • DevDashboard/mobile/e2e/pages/TmuxPresetsPage.page.ts
  • DevDashboard/mobile/e2e/pages/WeatherPage.page.ts
  • DevDashboard/mobile/e2e/pages/app.page.ts
  • DevDashboard/mobile/e2e/pages/base.page.ts
  • DevDashboard/mobile/e2e/pages/testAgent.ts
  • DevDashboard/mobile/e2e/specs/activity-timeline.spec.ts
  • DevDashboard/mobile/e2e/specs/build-log-tail.spec.ts
  • DevDashboard/mobile/e2e/specs/claude-usage.spec.ts
  • DevDashboard/mobile/e2e/specs/connect.spec.ts
  • DevDashboard/mobile/e2e/specs/connections.spec.ts
  • DevDashboard/mobile/e2e/specs/containers.spec.ts
  • DevDashboard/mobile/e2e/specs/daemon.spec.ts
  • DevDashboard/mobile/e2e/specs/disk-janitor.spec.ts
  • DevDashboard/mobile/e2e/specs/features-rest.smoke.spec.ts
  • DevDashboard/mobile/e2e/specs/more.spec.ts
  • DevDashboard/mobile/e2e/specs/needs-input-inbox.spec.ts
  • DevDashboard/mobile/e2e/specs/network-status.spec.ts
  • DevDashboard/mobile/e2e/specs/obsidian.spec.ts
  • DevDashboard/mobile/e2e/specs/port-killer.spec.ts
  • DevDashboard/mobile/e2e/specs/process-monitor.spec.ts
  • DevDashboard/mobile/e2e/specs/pulse.spec.ts
  • DevDashboard/mobile/e2e/specs/qa.spec.ts
  • DevDashboard/mobile/e2e/specs/quick-commands.spec.ts
  • DevDashboard/mobile/e2e/specs/reminders-todos.spec.ts
  • DevDashboard/mobile/e2e/specs/smoke.spec.ts
  • DevDashboard/mobile/e2e/specs/terminals.spec.ts
  • DevDashboard/mobile/e2e/specs/tmux-presets.spec.ts
  • DevDashboard/mobile/e2e/specs/weather.spec.ts
  • DevDashboard/mobile/e2e/tsconfig.json
  • DevDashboard/mobile/e2e/wdio.conf.ts
  • DevDashboard/mobile/eslint.config.js
  • DevDashboard/mobile/metro.config.js
  • DevDashboard/mobile/nativewind-env.d.ts
  • DevDashboard/mobile/package.json
  • DevDashboard/mobile/patches/react-native-css-interop@0.2.4.patch
  • DevDashboard/mobile/patches/react-native-webview@13.16.0.patch
  • DevDashboard/mobile/scripts/reset-project.js
  • DevDashboard/mobile/src/api/client-provider.tsx
  • DevDashboard/mobile/src/api/mock-client.ts
  • DevDashboard/mobile/src/api/query-keys.ts
  • DevDashboard/mobile/src/app/(more)/_layout.tsx
  • DevDashboard/mobile/src/app/(more)/activity-timeline.tsx
  • DevDashboard/mobile/src/app/(more)/build-log-tail.tsx
  • DevDashboard/mobile/src/app/(more)/claude-usage.tsx
  • DevDashboard/mobile/src/app/(more)/connections.tsx
  • DevDashboard/mobile/src/app/(more)/containers.tsx
  • DevDashboard/mobile/src/app/(more)/daemon.tsx
  • DevDashboard/mobile/src/app/(more)/disk-janitor.tsx
  • DevDashboard/mobile/src/app/(more)/needs-input-inbox.tsx
  • DevDashboard/mobile/src/app/(more)/network-status.tsx
  • DevDashboard/mobile/src/app/(more)/port-killer.tsx
  • DevDashboard/mobile/src/app/(more)/process-monitor.tsx
  • DevDashboard/mobile/src/app/(more)/quick-commands.tsx
  • DevDashboard/mobile/src/app/(more)/reminders-todos.tsx
  • DevDashboard/mobile/src/app/(more)/tmux-presets.tsx
  • DevDashboard/mobile/src/app/(more)/weather.tsx
  • DevDashboard/mobile/src/app/(tabs)/_layout.tsx
  • DevDashboard/mobile/src/app/(tabs)/index.tsx
  • DevDashboard/mobile/src/app/(tabs)/more.tsx
  • DevDashboard/mobile/src/app/(tabs)/obsidian.tsx
  • DevDashboard/mobile/src/app/(tabs)/qa.tsx
  • DevDashboard/mobile/src/app/(tabs)/terminals.tsx
  • DevDashboard/mobile/src/app/_layout.tsx
  • DevDashboard/mobile/src/app/connect.tsx
  • DevDashboard/mobile/src/app/pair.tsx
  • DevDashboard/mobile/src/components/animated-icon.module.css
  • DevDashboard/mobile/src/components/animated-icon.tsx
  • DevDashboard/mobile/src/components/animated-icon.web.tsx
  • DevDashboard/mobile/src/components/connect/QrScanner.tsx
  • DevDashboard/mobile/src/components/connect/ReachabilityBadge.tsx
  • DevDashboard/mobile/src/components/connect/TierPicker.tsx
  • DevDashboard/mobile/src/components/external-link.tsx
  • DevDashboard/mobile/src/components/hint-row.tsx
  • DevDashboard/mobile/src/components/themed-text.tsx
  • DevDashboard/mobile/src/components/themed-view.tsx
  • DevDashboard/mobile/src/components/ui/collapsible.tsx
  • DevDashboard/mobile/src/components/web-badge.tsx
  • DevDashboard/mobile/src/constants/theme.ts
  • DevDashboard/mobile/src/features/activity-timeline/components/EventRow.tsx
  • DevDashboard/mobile/src/features/activity-timeline/components/HourGroup.tsx
  • DevDashboard/mobile/src/features/activity-timeline/components/Timeline.tsx
  • DevDashboard/mobile/src/features/activity-timeline/hooks.ts
  • DevDashboard/mobile/src/features/activity-timeline/queries.test.ts
  • DevDashboard/mobile/src/features/activity-timeline/queries.ts
  • DevDashboard/mobile/src/features/activity-timeline/types.ts
  • DevDashboard/mobile/src/features/activity-timeline/units.test.ts
  • DevDashboard/mobile/src/features/activity-timeline/units.ts
  • DevDashboard/mobile/src/features/build-log-tail/components/LogStream.tsx
  • DevDashboard/mobile/src/features/build-log-tail/components/RunPicker.tsx
  • DevDashboard/mobile/src/features/build-log-tail/hooks.ts
  • DevDashboard/mobile/src/features/build-log-tail/queries.test.ts
  • DevDashboard/mobile/src/features/build-log-tail/queries.ts
  • DevDashboard/mobile/src/features/build-log-tail/subscription.test.ts
  • DevDashboard/mobile/src/features/build-log-tail/subscription.ts
  • DevDashboard/mobile/src/features/build-log-tail/types.ts
  • DevDashboard/mobile/src/features/build-log-tail/units.test.ts
  • DevDashboard/mobile/src/features/build-log-tail/units.ts
  • DevDashboard/mobile/src/features/claude-usage/components/AccountHistoryCharts.tsx
  • DevDashboard/mobile/src/features/claude-usage/components/AccountUsageCard.tsx
  • DevDashboard/mobile/src/features/claude-usage/components/RangeSelector.tsx
  • DevDashboard/mobile/src/features/claude-usage/hooks.ts
  • DevDashboard/mobile/src/features/claude-usage/queries.test.ts
  • DevDashboard/mobile/src/features/claude-usage/queries.ts
  • DevDashboard/mobile/src/features/claude-usage/units.test.ts
  • DevDashboard/mobile/src/features/claude-usage/units.ts
  • DevDashboard/mobile/src/features/connections/ConnectionForm.tsx
  • DevDashboard/mobile/src/features/connections/ConnectionRow.tsx
  • DevDashboard/mobile/src/features/connections/ConnectionsScreen.tsx
  • DevDashboard/mobile/src/features/connections/components.tsx
  • DevDashboard/mobile/src/features/connections/format.ts
  • DevDashboard/mobile/src/features/connections/index.ts
  • DevDashboard/mobile/src/features/connections/store.ts
  • DevDashboard/mobile/src/features/connections/types.ts
  • DevDashboard/mobile/src/features/containers/components/ContainerRow.tsx
  • DevDashboard/mobile/src/features/containers/hooks.ts
  • DevDashboard/mobile/src/features/containers/queries.test.ts
  • DevDashboard/mobile/src/features/containers/queries.ts
  • DevDashboard/mobile/src/features/containers/units.test.ts
  • DevDashboard/mobile/src/features/containers/units.ts
  • DevDashboard/mobile/src/features/daemon/components/DaemonStatusHeader.tsx
  • DevDashboard/mobile/src/features/daemon/components/RunLogSheet.tsx
  • DevDashboard/mobile/src/features/daemon/components/RunRow.tsx
  • DevDashboard/mobile/src/features/daemon/hooks.ts
  • DevDashboard/mobile/src/features/daemon/queries.test.ts
  • DevDashboard/mobile/src/features/daemon/queries.ts
  • DevDashboard/mobile/src/features/daemon/units.test.ts
  • DevDashboard/mobile/src/features/daemon/units.ts
  • DevDashboard/mobile/src/features/disk-janitor/components/UsageBars.tsx
  • DevDashboard/mobile/src/features/disk-janitor/hooks.ts
  • DevDashboard/mobile/src/features/disk-janitor/queries.test.ts
  • DevDashboard/mobile/src/features/disk-janitor/queries.ts
  • DevDashboard/mobile/src/features/disk-janitor/units.test.ts
  • DevDashboard/mobile/src/features/disk-janitor/units.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/attention-target-store.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/components/AttentionItem.tsx
  • DevDashboard/mobile/src/features/needs-input-inbox/components/AttentionList.tsx
  • DevDashboard/mobile/src/features/needs-input-inbox/hooks.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/index.tsx
  • DevDashboard/mobile/src/features/needs-input-inbox/queries.test.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/queries.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/select.test.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/select.ts
  • DevDashboard/mobile/src/features/needs-input-inbox/types.ts
  • DevDashboard/mobile/src/features/network-status/components/RepairButton.tsx
  • DevDashboard/mobile/src/features/network-status/components/StatusCard.tsx
  • DevDashboard/mobile/src/features/network-status/hooks.ts
  • DevDashboard/mobile/src/features/network-status/queries.test.ts
  • DevDashboard/mobile/src/features/network-status/queries.ts
  • DevDashboard/mobile/src/features/network-status/types.ts
  • DevDashboard/mobile/src/features/network-status/units.test.ts
  • DevDashboard/mobile/src/features/network-status/units.ts
  • DevDashboard/mobile/src/features/obsidian/components/NewFolderModal.tsx
  • DevDashboard/mobile/src/features/obsidian/components/NoteReader.tsx
  • DevDashboard/mobile/src/features/obsidian/components/NoteRenderer.tsx
  • DevDashboard/mobile/src/features/obsidian/components/VaultTree.tsx
  • DevDashboard/mobile/src/features/obsidian/components/VaultTreeNode.tsx
  • DevDashboard/mobile/src/features/obsidian/expanded-dirs.test.ts
  • DevDashboard/mobile/src/features/obsidian/expanded-dirs.ts

Comment on lines +78 to +103
/** The exhaustive allow-list of fields the cloud is PERMITTED to persist, per table. */
export const CLOUD_PERSISTABLE_FIELDS = {
accounts: ["id", "email", "name", "createdAt"],
subscriptions: [
"id",
"accountId",
"tier",
"status",
"stripeCustomerId",
"stripeSubscriptionId",
"currentPeriodEnd",
"createdAt",
],
devices: ["id", "accountId", "label", "kind", "publicKey", "pairedAt"],
managed_subdomains: [
"id",
"accountId",
"hostname",
"name",
"routingTarget",
"vendorFronted",
"status",
"createdAt",
],
account_settings: ["accountId", "pushAlertsEnabled", "theme", "updatedAt"],
} as const;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove "accounts" entry — table does not exist in schema.

Line 80 includes accounts: ["id", "email", "name", "createdAt"] in the allow-list, but no accounts table is defined in schema.ts. The schema contains Better-Auth tables (user, session, account for OAuth, verification) and domain tables (subscriptions, devices, managedSubdomains, accountSettings). The cloud-store.ts never writes to an "accounts" table. This creates dead configuration and type confusion: CloudTable (line 105) will include "accounts" as a valid table name, but calling assertNoKeyMaterial("accounts", ...) will guard a non-existent table.

Remove the accounts entry and the unused Account interface (lines 13-18), or clarify the intent if this is forward-looking configuration.

🗑️ Proposed fix
-export interface Account {
-    id: string;
-    email: string;
-    name: string | null;
-    createdAt: string;
-}
-
 export interface Subscription {
 export const CLOUD_PERSISTABLE_FIELDS = {
-    accounts: ["id", "email", "name", "createdAt"],
     subscriptions: [
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/shared/account-model.ts` around lines 78 - 103,
CLOUD_PERSISTABLE_FIELDS contains an obsolete "accounts" entry and a
corresponding unused Account interface which causes CloudTable (and runtime
guards like assertNoKeyMaterial) to include a non-existent table; remove the
"accounts" key from CLOUD_PERSISTABLE_FIELDS and delete the unused Account
interface (or alternatively reconcile names if you intended to map to
schema.ts's account/accountSettings naming), then run TypeScript checks to
ensure CloudTable and any usages in cloud-store.ts and assertNoKeyMaterial are
updated to only reference real tables defined in schema.ts (subscriptions,
devices, managed_subdomains/managedSubdomains,
account_settings/accountSettings).

Comment on lines +56 to +61
- **Setup:** one guided command —
```bash
tools dev-dashboard tunnel setup
```
It installs `cloudflared`, walks you through the Cloudflare login, and prints a pairing QR. No
copy-paste.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | 💤 Low value

Add blank lines around fenced code blocks.

Markdown best practice requires blank lines before and after fenced code blocks for better parser compatibility.

📝 Proposed fix
 - **Setup:** one guided command —
+
   ```bash
   tools dev-dashboard tunnel setup
  • It installs cloudflared, walks you through the Cloudflare login, and prints a pairing QR. No

```diff
 2. To register a device against your cloud account out-of-band, the agent prints a short
    **device code**:
+
    ```bash
    tools dev-dashboard pair
    ```
+
    The device code is proof that *your Mac agent consents* — the cloud never validates or decrypts

Also applies to: 88-92

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 57-57: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


[warning] 59-59: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/web/docs/install-the-agent.md` around lines 56 - 61, The
fenced code blocks containing the commands `tools dev-dashboard tunnel setup`
and `tools dev-dashboard pair` need blank lines before and after the
triple-backtick fences; update the markdown in install-the-agent.md so each
fenced block is preceded and followed by an empty line (i.e., ensure there is a
blank line above the opening ``` and a blank line below the closing ```
surrounding the `tools dev-dashboard tunnel setup` and `tools dev-dashboard
pair` blocks and the surrounding explanatory text).

Comment on lines +40 to +43
```
privateKey secretKey sessionKey sharedSecret derivedSecret
pairingSecret symmetricKey aeadKey nonceSecret
```

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Specify language identifier for fenced code block.

The fenced code block listing forbidden field names should specify a language identifier for proper rendering and accessibility.

📝 Proposed fix
-```
+```text
 privateKey   secretKey     sessionKey    sharedSecret   derivedSecret
 pairingSecret  symmetricKey  aeadKey      nonceSecret
</details>

<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

```suggestion

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 40-40: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/web/docs/security-and-trust.md` around lines 40 - 43, The
fenced code block showing forbidden field names (privateKey, secretKey,
sessionKey, sharedSecret, derivedSecret, pairingSecret, symmetricKey, aeadKey,
nonceSecret) lacks a language identifier; update the markdown fenced block to
include an explicit language tag (e.g., "text") so it renders/accesses correctly
— locate the block containing those symbol names and add the language identifier
immediately after the opening triple backticks.

"better-auth": "^1.2.7",
"better-sqlite3": "^12.6.0",
"drizzle-orm": "^0.45.1",
"h3": "^2.0.1-rc.7",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if a stable h3 2.x version is available
npm view h3 versions --json | jq -r '.[]' | grep '^2\.' | grep -v 'rc\|beta\|alpha' | tail -1

Repository: genesiscz/GenesisTools

Length of output: 72


Consider switching h3 prerelease to a stable version for production

DevDashboard/cloud/web/package.json uses "h3": "^2.0.1-rc.7" (a prerelease). The latest stable 2.x non-prerelease available is 2.0.0, so if a stable dependency is desired, use ^2.0.0/2.0.0; otherwise, pin the exact RC version and document why a prerelease is required (e.g., a specific runtime constraint).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/web/package.json` at line 30, The package.json currently
depends on a prerelease h3 ("h3": "^2.0.1-rc.7"); either replace that dependency
with the latest stable 2.x release ("h3": "^2.0.0") or explicitly pin the RC
(e.g., "h3": "2.0.1-rc.7") and add a short justification comment in your repo
docs explaining why the prerelease is required; update the
DevDashboard/cloud/web/package.json entry for "h3" and commit the reasoning if
you choose to keep the RC.

"drizzle-orm": "^0.45.1",
"h3": "^2.0.1-rc.7",
"lucide-react": "^0.562.0",
"nitro": "latest",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Pin nitro to a specific version instead of "latest".

Using "latest" prevents reproducible builds and can introduce unexpected breaking changes. The lockfile will capture a specific version, but the intent in package.json should be explicit.

📦 Recommended fix

Check the currently installed version:

npm list nitro

Then pin to that version (example, replace X.Y.Z with actual):

-    "nitro": "latest",
+    "nitro": "^X.Y.Z",
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/cloud/web/package.json` at line 32, The package.json currently
pins the "nitro" dependency to "latest", which breaks reproducible builds; run
npm list nitro to determine the installed version and replace the "nitro":
"latest" entry in package.json with that exact version (e.g., "nitro": "X.Y.Z"),
then run npm install to update the lockfile so the change is persisted; target
the "nitro" key in package.json to make this edit.


try {
const [host, portStr] = lanHost.replace(/^https?:\/\//, "").split(":");
const port = portStr ? Number.parseInt(portStr, 10) : 3042;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate the parsed port before using it.

Number.parseInt can return NaN or out-of-range values. The same validation issue exists in connectLan (line 153).

🛡️ Proposed fix
             const [host, portStr] = lanHost.replace(/^https?:\/\//, "").split(":");
-            const port = portStr ? Number.parseInt(portStr, 10) : 3042;
+            const parsedPort = portStr ? Number.parseInt(portStr, 10) : 3042;
+            if (!Number.isInteger(parsedPort) || parsedPort < 1 || parsedPort > 65535) {
+                throw new Error("Invalid port. Use a value between 1 and 65535.");
+            }
+            const port = parsedPort;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const port = portStr ? Number.parseInt(portStr, 10) : 3042;
const [host, portStr] = lanHost.replace(/^https?:\/\//, "").split(":");
const parsedPort = portStr ? Number.parseInt(portStr, 10) : 3042;
if (!Number.isInteger(parsedPort) || parsedPort < 1 || parsedPort > 65535) {
throw new Error("Invalid port. Use a value between 1 and 65535.");
}
const port = parsedPort;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/app/connect.tsx` at line 173, The parsed port value
(const port = portStr ? Number.parseInt(portStr, 10) : 3042;) can be NaN or out
of valid TCP range; update the parsing logic in connect (and the same logic in
connectLan) to validate Number.parseInt output with Number.isInteger and ensure
1 <= port <= 65535, and if validation fails fall back to the default port (3042)
or surface a clear error; apply this to the port variable in the connect
function and the port parsing in connectLan to prevent using invalid ports.

Comment on lines +43 to +56
const glowKeyframe = new Keyframe({
0: {
transform: [{ rotateZ: "-180deg" }, { scale: 0.8 }],
opacity: 0,
},
[DURATION / 1000]: {
transform: [{ rotateZ: "0deg" }, { scale: 1 }],
opacity: 1,
easing: Easing.elastic(0.7),
},
100: {
transform: [{ rotateZ: "7200deg" }],
},
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keyframe step percentage appears incorrect.

Line 48 uses [DURATION / 1000] as a keyframe step, which evaluates to 0.3 (since DURATION = 300). In Reanimated's Keyframe API, keys represent percentages from 0-100, so 0.3 means 0.3% of the animation duration—nearly the start. This is likely unintentional.

🔧 Proposed fix

If the intent was to place this keyframe at 30% of the animation:

 const glowKeyframe = new Keyframe({
         transform: [{ rotateZ: "-180deg" }, { scale: 0.8 }],
         opacity: 0,
     },
-    [DURATION / 1000]: {
+    30: {
         transform: [{ rotateZ: "0deg" }, { scale: 1 }],
         opacity: 1,
         easing: Easing.elastic(0.7),
     },
         transform: [{ rotateZ: "7200deg" }],
     },
 });

Alternatively, verify the intended keyframe timing and use the correct percentage value.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const glowKeyframe = new Keyframe({
0: {
transform: [{ rotateZ: "-180deg" }, { scale: 0.8 }],
opacity: 0,
},
[DURATION / 1000]: {
transform: [{ rotateZ: "0deg" }, { scale: 1 }],
opacity: 1,
easing: Easing.elastic(0.7),
},
100: {
transform: [{ rotateZ: "7200deg" }],
},
});
const glowKeyframe = new Keyframe({
0: {
transform: [{ rotateZ: "-180deg" }, { scale: 0.8 }],
opacity: 0,
},
30: {
transform: [{ rotateZ: "0deg" }, { scale: 1 }],
opacity: 1,
easing: Easing.elastic(0.7),
},
100: {
transform: [{ rotateZ: "7200deg" }],
},
});
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/components/animated-icon.web.tsx` around lines 43 -
56, The keyframe step in glowKeyframe is using [DURATION / 1000] (which
evaluates to 0.3) but Reanimated Keyframe keys are percentages 0–100; replace
that dynamic numeric key with the intended percentage (e.g., 30) or compute
percent correctly (e.g., (DURATION / totalDuration)*100) so the middle keyframe
is at the proper 30% position; update the Keyframe object used by glowKeyframe
and ensure DURATION usage is adjusted accordingly.

Comment on lines +63 to +67
linkPrimary: {
lineHeight: 30,
fontSize: 14,
color: "#3c87f7",
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Hard-coded color bypasses the theme system.

Line 66 sets color: "#3c87f7" for the linkPrimary type, which circumvents the component's theming mechanism. The link type correctly relies on the theme color, but linkPrimary hard-codes a blue value.

Consider using a theme token instead, or document why this color must be fixed.

♻️ Proposed fix to use theme token
 linkPrimary: {
     lineHeight: 30,
     fontSize: 14,
-    color: "`#3c87f7`",
 },

Then in the component:

 <Text
     style={[
         { color: theme[themeColor ?? "text"] },
         // ... other type checks ...
-        type === "linkPrimary" && styles.linkPrimary,
+        type === "linkPrimary" && [styles.linkPrimary, { color: theme.primary }],
         style,
     ]}

Or add a dedicated theme token like linkPrimary to the theme definition.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/components/themed-text.tsx` around lines 63 - 67, The
style object for linkPrimary currently hard-codes color "`#3c87f7`" which bypasses
theming; update the linkPrimary style in themed-text.tsx (the linkPrimary style
entry) to use a theme token instead (e.g., theme.colors.linkPrimary or
theme.colors.link) by reading the theme passed into the component or adding a
new token to the theme and referencing it from the component so the color is
derived from the theme system rather than a literal string.

Comment on lines +7 to +8
lightColor?: string;
darkColor?: string;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Remove unused props or document their purpose.

The lightColor and darkColor props are destructured but never used in the component body. If these are intended for future use or API compatibility with ThemedText, add a comment explaining this; otherwise, remove them.

♻️ Proposed fix to remove unused props
 export type ThemedViewProps = ViewProps & {
-    lightColor?: string;
-    darkColor?: string;
     type?: ThemeColor;
 };

-export function ThemedView({ style, lightColor, darkColor, type, ...otherProps }: ThemedViewProps) {
+export function ThemedView({ style, type, ...otherProps }: ThemedViewProps) {
     const theme = useTheme();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/mobile/src/components/themed-view.tsx` around lines 7 - 8,
ThemedView currently destructures lightColor and darkColor props but never uses
them; either remove these props from the ThemedViewProps and the destructuring
in the ThemedView component, or add a short comment next to the prop
declarations and the destructuring in ThemedView explaining they are
intentionally retained for API compatibility with ThemedText/future use. Update
ThemedViewProps (and the function signature for ThemedView) to reflect the
chosen approach and run type checks to ensure no unused variable warnings
remain.


### 1.7 Agencies / consultants (multi-client machines)
- **JTBD:** "I run work for five clients on five machines/VMs. I need them side by side, clearly separated, and I must never cross client data."
- **Killer feature: Multi-machine fleet view with hard per-machine isolation.** One list of all paired machines, each its own Pulse tile + terminal set + isolated credentials/keys. Switch client context in one tap; nothing bleeds across machines (separate E2E keypairs per pairing).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use two words for "key pairs".

The term should be "key pairs" (two words) rather than "keypairs".

📝 Proposed fix
-...clearly separated, and I must never cross client data." - **Killer feature: Multi-machine fleet view with hard per-machine isolation.** One list of all paired machines, each its own Pulse tile + terminal set + isolated credentials/keys. Switch client context in one tap; nothing bleeds across machines (separate E2E keypairs per pairing).
+...clearly separated, and I must never cross client data." - **Killer feature: Multi-machine fleet view with hard per-machine isolation.** One list of all paired machines, each its own Pulse tile + terminal set + isolated credentials/keys. Switch client context in one tap; nothing bleeds across machines (separate E2E key pairs per pairing).
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- **Killer feature: Multi-machine fleet view with hard per-machine isolation.** One list of all paired machines, each its own Pulse tile + terminal set + isolated credentials/keys. Switch client context in one tap; nothing bleeds across machines (separate E2E keypairs per pairing).
- **Killer feature: Multi-machine fleet view with hard per-machine isolation.** One list of all paired machines, each its own Pulse tile + terminal set + isolated credentials/keys. Switch client context in one tap; nothing bleeds across machines (separate E2E key pairs per pairing).
🧰 Tools
🪛 LanguageTool

[grammar] ~45-~45: Ensure spelling is correct
Context: ...ng bleeds across machines (separate E2E keypairs per pairing). ### 1.8 Content creators...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@DevDashboard/PRODUCT-ROADMAP.md` at line 45, Update the phrase in the product
roadmap where it currently reads "keypairs" to use the correct two-word form
"key pairs" — specifically in the sentence starting "Killer feature:
Multi-machine fleet view with hard per-machine isolation." and the portion
"separate E2E keypairs per pairing" should become "separate E2E key pairs per
pairing".

@genesiscz
genesiscz changed the base branch from feat/tmux-cmux-dev-dashboard to master July 18, 2026 16:14
genesiscz added 23 commits July 18, 2026 18:18
genesiscz added 21 commits July 18, 2026 18:18
@genesiscz
genesiscz force-pushed the feat/dev-dashboard-mobile branch from b31a369 to 9af300c Compare July 18, 2026 16:33
@socket-security

socket-security Bot commented Jul 18, 2026

Copy link
Copy Markdown

Warning

Review the following alerts detected in dependencies.

According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.

Action Severity Alert  (click "▶" to expand/collapse)
Warn High
Obfuscated code: npm @expo/cli is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo@55.0.26npm/@expo/cli@55.0.32

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@expo/cli@55.0.32. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @react-native/debugger-frontend is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/react-native@0.83.6npm/expo@55.0.26npm/@react-native/debugger-frontend@0.83.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@react-native/debugger-frontend@0.83.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm @zip.js/zip.js is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/@wdio/mocha-framework@9.27.2npm/webdriverio@9.27.2npm/@wdio/cli@9.27.2npm/@wdio/local-runner@9.27.2npm/@zip.js/zip.js@2.8.26

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/@zip.js/zip.js@2.8.26. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm barcode-detector is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-camera@55.0.19npm/barcode-detector@3.1.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/barcode-detector@3.1.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm better-sqlite3 is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: DevDashboard/cloud/web/package.jsonnpm/better-sqlite3@12.10.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/better-sqlite3@12.10.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm commander is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/@wdio/mocha-framework@9.27.2npm/webdriverio@9.27.2npm/@wdio/cli@9.27.2npm/@wdio/local-runner@9.27.2npm/commander@9.5.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/commander@9.5.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm edgedriver is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/@wdio/mocha-framework@9.27.2npm/webdriverio@9.27.2npm/@wdio/cli@9.27.2npm/@wdio/local-runner@9.27.2npm/edgedriver@6.3.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/edgedriver@6.3.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm es-abstract is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/eslint-config-expo@55.0.1npm/es-abstract@1.24.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/es-abstract@1.24.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm execa is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/@wdio/cli@9.27.2npm/execa@9.6.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/execa@9.6.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm moment-timezone is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/appium-xcuitest-driver@11.7.3npm/moment-timezone@0.6.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/moment-timezone@0.6.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm node-forge is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo@55.0.26npm/node-forge@1.4.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/node-forge@1.4.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm react-native-web is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: DevDashboard/mobile/package.jsonnpm/react-native-web@0.21.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/react-native-web@0.21.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm react-native is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: DevDashboard/mobile/package.jsonnpm/react-native@0.83.6

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/react-native@0.83.6. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm scheduler is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/@shopify/react-native-skia@2.4.18npm/scheduler@0.25.0

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/scheduler@0.25.0. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm seroval is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/@tanstack/react-router-ssr-query@1.167.0npm/@tanstack/react-router@1.170.8npm/@tanstack/router-plugin@1.168.11npm/@tanstack/react-start@1.168.14npm/seroval@1.5.4

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/seroval@1.5.4. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm spacetrim is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/@wdio/mocha-framework@9.27.2npm/webdriverio@9.27.2npm/@wdio/cli@9.27.2npm/@wdio/local-runner@9.27.2npm/spacetrim@0.11.59

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/spacetrim@0.11.59. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm webdriverio is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: DevDashboard/mobile/package.jsonnpm/webdriverio@9.27.2

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/webdriverio@9.27.2. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm workerpool is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/@wdio/mocha-framework@9.27.2npm/workerpool@6.5.1

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/workerpool@6.5.1. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm zxing-wasm is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-camera@55.0.19npm/zxing-wasm@3.0.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/zxing-wasm@3.0.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm zxing-wasm is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-camera@55.0.19npm/zxing-wasm@3.0.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/zxing-wasm@3.0.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

Warn High
Obfuscated code: npm zxing-wasm is 90.0% likely obfuscated

Confidence: 0.90

Location: Package overview

From: ?npm/expo-camera@55.0.19npm/zxing-wasm@3.0.3

ℹ Read more on: This package | This alert | What is obfuscated code?

Next steps: Take a moment to review the security alert above. Review the linked package source code to understand the potential risk. Ensure the package is not malicious before proceeding. If you're unsure how to proceed, reach out to your security team or ask the Socket team for help at support@socket.dev.

Suggestion: Packages should not obfuscate their code. Consider not using packages with obfuscated code.

Mark the package as acceptable risk. To ignore this alert only in this pull request, reply with the comment @SocketSecurity ignore npm/zxing-wasm@3.0.3. You can also ignore all packages with @SocketSecurity ignore-all. To ignore an alert for all future pull requests, use Socket's Dashboard to change the triage state of this alert.

View full report

@genesiscz
genesiscz force-pushed the feat/dev-dashboard-mobile branch from 9af300c to 2418627 Compare July 18, 2026 16:36
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