Changelog

What's new in nn.os — v2.5.0

← Back to nn.os

nn.os v5 — Release Notes

Latest

Released: May 2026 Codename: Resilience

v5 is a reliability and performance release. We hardened the OS so it stays fast and stable as we keep adding apps and features. Everything below is user-facing.


Cross-tab sync

Settings, theme, and dock changes now sync instantly across all open nn.os tabs. Change your wallpaper in one tab and every other tab updates in real time — no reload needed.

Fewer crashes

Each app now runs inside its own error boundary. If one app hits a bug, the rest of the OS keeps running. You will see a crash screen with a Restart button to relaunch just that app, and an option to report the issue.

Faster launches

The OS now prefetches your most-used apps in the background, so they open almost instantly. This is based on your usage patterns and adapts over time.

Keyboard cheat sheet

Press Cmd+/ (or Ctrl+/ on Windows/Linux) to see a searchable list of every keyboard shortcut in the OS. No more guessing.

Better accessibility

  • All interactive elements have descriptive aria labels.
  • Focus is trapped inside modals and managed predictably.
  • Screen readers announce window opens, closes, and state changes.
  • High Contrast theme passes WCAG AAA contrast ratios.

Tighter performance

  • Smarter memoisation eliminates unnecessary re-renders across the shell.
  • IndexedDB writes are batched so rapid saves (e.g. typing in Notes) don't stall the main thread.
  • Inactive windows use fewer CPU cycles.
  • First-load JS remains under 220 KB gzip with per-route budgets enforced in CI.

Under the hood

If you are curious about the internals:

  • OsProvider decomposition. The 1 700-line god provider is now four focused services (WindowManager, SettingsService, NotificationCenter, PersistenceService) and five hooks (useWindows, useWorkspace, useAppLauncher, useNotifications, useActiveWindow). Each uses useSyncExternalStore for fine-grained subscriptions.
  • Typed storage. Every localStorage read/write now goes through a Zod- validated wrapper (kvStore). Corrupted data degrades to a safe default instead of crashing.
  • Effect audit. We classified all 333 useEffect calls into five categories and eliminated the 70 that were replaceable by derived state or event handlers.
  • Architecture Decision Records. Major technical decisions are documented in docs/architecture/adr/ for future contributors.

Upgrade notes

  • No action required. All settings, files, and data carry over automatically.
  • If you had custom keybindings, they are preserved.
  • The new error boundaries are always on — you will see them only if an app crashes.

Thanks for using nn.os. Feedback is welcome through the in-app widget or the project repository.

nn.os v4 — Release Notes

Released: May 2026 Codename: Cohesion

v4 is the largest single release of nn.os to date. It spans five connected phases that together raise the floor on speed, intelligence, polish, trust, and personality. Everything below is user-facing — no internal jargon.


What's new for users

A short version, in case you only read one paragraph:

  • nn.os now boots faster, scrolls smoother, and stays out of your way more reliably than ever.
  • A reworked assistant and Spotlight make the OS feel like it's working with you, not waiting for you.
  • Every built-in app — Notes, Files, Mail, Terminal, Settings, Calendar, and the Developer Center — got a meaningful upgrade.
  • New trust & security controls (cookie consent, session controls, privacy summary) put you in charge of your data.
  • Three new themes (High Contrast, Sepia, Ultra Dark), tasteful micro-interactions, and a couple of well-hidden easter eggs round out the polish.

Phase 1 — Performance

The whole shell now feels noticeably quicker, especially on lower-end hardware and slower networks.

  • Faster cold start. Boot screen displays sooner, and the desktop is interactive within the first second on most devices.
  • Smarter window rendering. Inactive windows are deferred so the foreground app always gets the most CPU and GPU time.
  • Lighter wallpapers. All built-in wallpapers were re-encoded; live wallpapers gracefully degrade when battery is low or prefers-reduced-motion is set.
  • Smoother animations. Mission Control, Stage Manager, and the dock hover effect run on the compositor wherever possible.
  • Smaller bundles. Several rarely-used apps (Developer Center, AI Assist, Promo Studio) are now lazy-loaded on first open.
  • Less memory pressure. Background services flush caches on idle and retire stale clipboard, screenshot, and assistant entries automatically.

Phase 2 — Smart OS

The system now anticipates what you want instead of just reacting.

  • Spotlight is smarter. Results learn from how you use your apps and files (frecency-ranked) and now include notes, recent paths, assistant threads, and quick actions in one ranked list.
  • Inline AI fallback. If Spotlight has no good match, you can hand the query straight to the assistant with one keystroke.
  • Assistant memory. The assistant remembers context across threads and surfaces related notes, files, and previous chats while you type.
  • Quick actions everywhere. New keyboard shortcuts for "tile left", "tile right", "reopen last closed", "new note", "new terminal", and more — all discoverable from Spotlight.
  • Auto theme schedule. Dark mode can follow sunrise/sunset or a custom schedule, in addition to system preference.
  • Hot corners are configurable: trigger Mission Control, the launcher, "show desktop", lock screen, or the notification center from any corner of the display.

Phase 3 — App Excellence

Every built-in app got a focused upgrade.

  • Notes — Live backlinks panel, a new graph view to navigate your knowledge base, faster search across large libraries.
  • Files — Recents, favorites, and indexed search now share one ranking model with Spotlight; folder browsing is dramatically smoother.
  • Mail — Thread-aware reading pane, smarter compose with assistant drafts, and improved offline behavior.
  • Terminal — New themes, AI Assist for explaining commands and errors, and a much better history search.
  • Settings — Reorganized appearance, accessibility, and privacy tabs; the wallpaper picker shows live previews.
  • Calendar — Faster month/week switching, agenda view, and better all-day event rendering.
  • Developer Center — Release manager, review queue, and bundle budget reports for anyone shipping their own nn.os apps.

Phase 4 — Trust & Security

You should always know what nn.os is doing with your data. v4 makes that explicit.

  • Cookie consent. A clear, dismissible banner the first time you visit, with granular categories.
  • Session controls. See active sessions, sign out remotely, and rotate credentials from Settings → Privacy.
  • Privacy summary. A one-page overview of what is stored locally, what syncs to the cloud, and what is never sent off-device.
  • Lock screen hardening. Optional re-auth after the screensaver, and a visible indicator when the system is recording or sharing the screen.
  • Safer defaults. New installs ship with telemetry off, optional cloud sync off, and the assistant's long-term memory disabled until you opt in.
  • Audit trail. Sensitive settings changes (account, sync, permissions) are now timestamped in an in-app log you can review.

Phase 5 — Polish & Themes

The finishing touches that make daily use feel good.

  • Three new themes:
    • High Contrast — pure black/white with bold borders for maximum legibility and accessibility.
    • Sepia — a warm cream-paper background with deep brown ink for long reading sessions.
    • Ultra Dark — true #000000 everywhere, optimized for OLED displays and battery savings.
  • Refined micro-interactions. Buttons, toggles, and menus have subtle, consistent feedback that respects prefers-reduced-motion.
  • Better empty states. Every app now has a helpful, friendly empty state instead of a blank pane.
  • Improved typography. Tightened heading scales, better line heights, and consistent monospace usage in code, build hashes, and the terminal.
  • Two easter eggs. We won't spoil them — but try the classic ten-key sequence on the desktop, and search for about in Spotlight.

Upgrade notes

  • No action required. Your settings, files, and assistant memory carry over automatically.
  • The new themes are opt-in; your previous theme choice is preserved.
  • If you used a custom accent or wallpaper, both are unchanged.

Thanks for using nn.os. As always, feedback is welcome through the in-app feedback widget or the project repository.

nn.os v2.1.3 — 2026-07-11

A full-codebase audit release. Three parallel review agents (apps, API routes, OS core) swept the tree; the highest-value findings — several of them silent data-loss bugs — are fixed here, each with a regression test. Whole gate green: typecheck, lint, 9040 tests.

Fixed

  • Contacts and Habits now persist. Both apps had complete, unit-tested storage layers that were never wired into the app — state initialized to demo data and every add/edit/delete/completion was discarded on reload. They now hydrate on open and save on change. The Habits heatmap likewise showed randomly generated activity; it now reflects your real completion history. (Verified on the previous production build that it persisted nothing.)

  • Trashing a folder no longer destroys its contents. Directories were moved to Trash with no snapshot of what was inside, then deleted; "restore" only recreated an empty folder. Trash now serializes a folder's entire subtree and restores it file-for-file.

  • Writing a file can't silently clobber a folder. The virtual filesystem's writeFile replaced any existing node — including a directory and its whole subtree. It now refuses, matching the existing mkdir/rename guards.

  • Weather's hourly forecast is correct. The timezone alignment shipped in 2.1.0 compared bare hour-of-day across a 7-day series, so it could label one slot per day "Now" and repeat hours out of order. It now takes the next 24 consecutive hours from the location's current hour, labeling only the first as "Now".

  • Password reset enforces the real policy. The forgot-password flow accepted any password of 8+ characters, bypassing the 12-character mixed-case-plus-digit policy used by sign-up and change-password. All three now share one policy.

  • Two-factor backup codes are encrypted at rest (they were stored as plaintext despite the field name), and the ⌘J assistant no longer flips back to idle when a quick follow-up message interrupts an in-flight reply.

Method

Reviews were run with cheaper (Sonnet) subagents in parallel, then each finding was verified by hand before fixing. The recurring theme — matching this project's own history — was orphaned wiring: fully built, tested subsystems that nothing in the running app ever calls. The fixes above target the ones with direct user impact; larger unfinished features (calendar recurrence, passkey/TOTP enrollment, the AI-assistant apps) are honestly gated in the UI and left for dedicated work.

nn.os v2.1.2 — 2026-07-11

The second fast-follow to 2.1.0, closing the loop on the ⌘K launcher — again caught by the previous release's live proof pack.

Fixed

  • The ⌘K calculator now works in production. 2.1.1 made the launcher's calculator and conversions reachable, and conversions worked live — but arithmetic (2+2, sqrt(144), 2^10) still returned nothing on production. Root cause: the math evaluator used new Function(), and the production Content-Security-Policy is script-src 'self' 'unsafe-inline' with no unsafe-eval, so it threw on every keystroke. It worked in development only because the dev CSP is relaxed — which is exactly why local verification missed it. Rewrote the evaluator as a tokenizer + recursive-descent parser (no runtime code generation): same operator precedence, functions, constants, percentages, thousands separators, and rejection of code-injection attempts. A regression test now fails if new Function/eval is ever reintroduced.

The pattern

Three real issues on the road through 2.1.x, each surfaced by the previous release's automated browser proof pack rather than by unit tests:

  1. 2.1.0's conversions were wired into an orphaned launcher component → 2.1.1.
  2. 2.1.1's calculator was blocked by the production CSP → 2.1.2.

Unit tests are green throughout; they can't see orphaned wiring or a CSP that only exists in production. Driving the real deployed UI is what caught both. Verified live on production this time: 2+2, sqrt(144), 100 eur to usd, 1 gb to mb, and 90 min to h all evaluate in the launcher.

nn.os v2.1.1 — 2026-07-11

A fast-follow to 2.1.0 that makes its headline feature actually reachable — caught by 2.1.0's own post-deploy proof pack.

Fixed

  • ⌘K calculator & conversions now appear in the real launcher. 2.1.0 shipped live-rate currency conversion plus data-size and duration units, but the code lived in an orphaned launcher component (Spotlight2) that the shell never renders. The launcher users actually open (⌘K / the menu-bar search → Launcher.tsx) had no calculator or conversion at all, so 100 eur to usd, 1 gb to mb, 90 min to h, and even 2+2 returned nothing. The feature is now ported into the real launcher pipeline (useLauncherResults): a Calculator or Conversion row appears above search results and copies to the clipboard on ↵. Verified live in a real browser on the actual launcher before shipping.

Why a patch so soon

2.1.0's automated proof pack drove the live site and reported the conversion queries returned no result — a real regression the unit tests missed because they covered the orphaned component. Re-derived by hand, root-caused to the wiring, and fixed on the launcher users actually use. The other 2.1.0 changes (Esc-to-close dialogs, recording-permission errors, weather timezone) were already correct and live.

nn.os v2.1.0 — 2026-07-11

The 10X-plan-v15 release: truer data, a sharper Spotlight, and the keyboard and error edges finished. Audit-first — two planned phases contracted honestly because the code was already correct.

Added

  • Spotlight learns live money and more units. ⌘K currency conversion used a hardcoded exchange-rate table (eur: 1.08, jpy: 0.0067, …) that drifted daily and silently showed wrong numbers. It now uses live daily reference rates from a keyless ECB source (GET /api/nnos/convert/rates, 12h server cache, rate-limited, serves stale on upstream failure), cached in the browser for offline use, and labelled rates · <date> (or offline rates · approx on the fallback). Two new conversion families join it: decimal data sizes (1 gb to mb) and time durations (90 min to h).

Fixed

  • Keyboard-close dialogs. The Focus settings and day-details dialogs and the Calendar quick-add dialog were role="dialog" overlays a keyboard user could open but not close — no Escape, no focus trap. All three now close with Esc and keep Tab inside them (via the shell's shared useFocusTrap); the settings and day-detail dialogs also gained aria-modal and backdrop-to-cancel.
  • Recording tells you when it fails. Denying camera or screen access left the video recorder looking idle — the failure was swallowed by an empty catch. It now shows a friendly role="alert" message distinguishing a blocked/cancelled prompt, no device found, and a device already in use.
  • Weather hourly forecast respects the location's clock. The forecast is fetched with timezone=auto (location-local times) but the "Now" label and the past-hours filter compared against the server's hour (UTC on the VM), so any location in a different timezone showed the strip starting at the wrong hour. Now anchored to the location's own time via utc_offset_seconds, with day labels correct across calendar-day boundaries too.

Audited — no change needed

  • Client date grouping (notification "Today/Yesterday", relative time): flagged as a timezone bug, but both sides of the comparison are the viewer's local day and self-consistent — this is the correct idiom, not a defect. The one genuine timezone bug (server-side, the weather route) is fixed above.
  • Per-app crash isolation already exists: every app window is wrapped in AppErrorBoundary with retry / restart / close / report affordances, so a crashing app can't take down the shell.

Under the hood

  • New route GET /api/nnos/convert/rates, hook useConversionRates (stale-while-revalidate over IndexedDB), and expanded converter with cross-category guards. Full gate green; new unit + route tests across Spotlight conversion, the rates route, Focus/Calendar dialogs, the video recorder, and the weather timezone path.

nn.os v2.0.0 — 2026-07-11

The road-to-2.0 release (10X plan v14): the beta funnel becomes a real invite loop, sign-in gets measurably faster, the backup story is proven by an actual restore, and the marketing site shows the real current product.

Added

  • Beta invite loop. Signups from /beta now persist to the database (nnos_beta_signup) instead of an in-memory Set that lost every request on deploy. Admins get a Beta tab listing signups with an Invite action: it generates a single-use, 14-day token (nnos_invite), emails it when SMTP is configured, and always shows a copy-link fallback. /register accepts ?invite=<token> and bypasses closed registration for the bound email; registering marks the token used and the signup registered.
  • Faster sign-in. Measured first: production login POST itself is ~830ms (bcrypt cost-12 + network, already at the floor), but click-to-desktop was running 3.4–4.0s. Two dead-time gaps in LoginForm.tsx — a hardcoded 600ms setTimeout before navigating, and no prefetch of the desktop route while typing — accounted for the rest. Fixed both; measured before/after on the same machine: click-to-desktop p50 down 29%, p95 down 33%.
  • First restore drill. Backups have run nightly since 1.4.0 and pulled off-VM since 1.5.0, but nothing had ever restored one. Restored the newest dump into a throwaway local Docker Postgres container — zero contact with the shared Neon DB — and verified real row counts and a parseable os_state payload. Recorded in docs/ops/restore-drills.md; do this quarterly.
  • Refreshed landing page. All 8 hero screenshots on /welcome were shot 2026-07-09, before the icon-spacing fix, new wallpapers, quick-action chips, and the notification bell. Recaptured every one from the live product; copy now calls out assistant web search, per-workspace wallpapers, and offline/URL-launch support. Accessibility stayed 100 on both /welcome and /login.

The 1.1 → 2.0 arc, for anyone catching up

  • 1.1 — Assistant web search, CSRF fix that had silently blocked every mutating request in production, faster first paint, self-identifying deploys (/api/health reports the exact commit).
  • 1.2 — Copilot quick actions, instant-paint sign-in/landing pages, per-space wallpapers, Accounts activity feed error states, mobile onboarding polish, public-page accessibility fixes.
  • 1.3 — Graceful offline page, assistant/wallpaper/export-import Help coverage, a full API ownership-check audit (81 routes, no gaps), the full e2e suite green across three browser engines, login timing first measured (~2s locally).
  • 1.4/?app= URL launch and installed-app shortcuts, mail-account deep links, one-time update notification, nightly automated database backups with alerting.
  • 1.5 — Message-level mail deep links, ⌘K search over Help and Contacts, one-click "What's New" from the update notice, off-VM backup copies on every deploy, a resurrected nightly synthetic production monitor.
  • 2.0 — the invite loop, the login fix, the proven restore, and the refreshed site, above.

Contracted honestly

  • The invite-bypass registration path is fully covered by route-level tests (token validation, expiry, single-use, wrong-email), but not by a browser e2e — that would require flipping the shared production registrationOpen flag during routine CI runs, which risks blocking real signups. The true end-to-end loop is verified live in this release's post-deploy proof pack instead.
  • Login p95 dropped 33%, short of an initial 60%-of-baseline target. The remainder is the unavoidable auth round-trip (bcrypt cost 12, per policy never lowered) and app-boot API calls after sign-in — architecture-level work, not a single fix.

nn.os v1.5.0 — 2026-07-11

Finish-the-threads release (10X plan v13): the loose ends from the linkable-OS work are closed, and a silently-broken production monitor is alive again.

Fixed

  • Nightly synthetic monitor resurrected. The production register → sign-in → heartbeat → delete check had been failing every night with three stacked causes: the runner host has no Node (exit 127 — now runs in node:22-alpine), the failure alert posted to an empty ntfy topic (secret never provisioned — now set), and the VM cannot hairpin to its own public IP (now targets the local container; the public path is verified by every deploy's external health gate). Verified green.

Added

  • Message-level mail deep links. Activity-feed mail items now open the exact message (intent uid) once the account's list loads — completing the v12 account-level link. Component test drives the full chain.
  • ⌘K searches Help articles and Contacts (the v12 recon gaps). Contacts load once per launcher open from local storage; both sources appear only for non-empty queries.
  • Clickable update notice — the "nn.os updated" notification carries a one-click "What's New" action (existing notification command system).
  • Off-VM backup copies — every successful deploy pulls the dump directory to the deploying machine (deploy/pull-backups.sh, keep 30); verified with a live pull. ops-01 remains operator-gated.
  • URL-launch e2e spec/?app= open + query-strip + bogus-id no-op in the authed Playwright project.

Contracted honestly

  • Telegram chat-intent consumption deferred: the app is a single-configured- chat bot integration with no chat navigation surface to deep-link into.

nn.os v1.4.0 — 2026-07-10

The linkable OS (10X plan v12): one new primitive — launch intents — cashed in three ways, plus update awareness and real backups.

Added

  • Launch intents. LaunchOptions/WindowState carry an optional JSON intent delivered at window creation (the filePath pattern generalized). Consumed-once semantics by construction; the workspace-restore allowlist keeps stale intents from re-firing after reloads.
  • URL launch. /?app=<id> (+ optional rooted path) opens that app once the shell hydrates, then cleans the address bar. Unknown/malformed ids are ignored silently.
  • PWA shortcuts. Installed-app long-press/right-click shortcuts for Files, Mail, and Assistant via the new URLs.
  • Activity-feed deep links. Mail items open Mail on the right account (intent carries accountId + uid); telegram items carry chat context.
  • Update awareness. A one-time notification when the OS ships a new version ("open What's New"); first-run stores silently.
  • Nightly DB backups on the production VM: dockerized pg_dump, size gate, 14-day retention, ntfy alert on failure — first dump verified. The backup/restore runbook was rewritten for the self-hosted era (the old one referenced Vercel and a backup cron that never existed).

Fixed / cleaned

  • The two e2e manifest specs tested /manifest.webmanifest, an April-era route that no longer exists; they now validate the real /manifest.json including the maskable icon and shortcuts.
  • /admin/site migrated to the shared requireAdminSession() guard (unauthenticated now 401, matching every peer admin route).

Notes

  • Off-VM backup copies (second host / object storage) remain backlog.
  • Launcher recon recorded: ⌘K already searches apps, files, commands, settings entries, assistant threads, store listings, and notes; Help articles and contacts are known gaps (backlog).

nn.os v1.3.0 — 2026-07-10

Hardening-and-habitability release (10X plan v11): verify what nobody had looked at recently, and make the OS nicer to live in.

Added

  • Offline fallback. A cold offline navigation now shows a branded "You're offline" page (precached with the shell) instead of the browser's network error. Service-worker fallback chain: cached page → cached shell → offline page.
  • Help refresh. New "Assistant & copilot" section (⌘J, web search, memory, tool auditing, copilot quick actions) and updated customization docs (per-space wallpapers via Mission Control, full-setup export/import).

Verified (audits, all clean)

  • Authorization: all 81 /api/nnos routes audited for cross-user access — 79 properly session/ownership-scoped, 2 public by design, 0 gaps (docs/security/2026-07-10-authz-sweep.md).
  • Rate limiting: 100% of mutating routes covered (shared helpers included).
  • Secret scan: clean; CSP posture unchanged and documented.
  • E2E suite: 46/46 Playwright scenarios green across chromium, webkit, and mobile projects (3.3m).
  • Client-error loop: browser error → telemetry endpoint → admin audit page, verified live against production.
  • Boot performance: login-to-interactive-desktop measured at ~2.0s median (5 runs, production build) — recorded as already tight; no speculative changes made.

Notes

  • PWA launch shortcuts deferred: the shell has no URL-launch mechanism yet (backlog).

nn.os v1.2.0 — 2026-07-10

Depth-and-delight release, driven by the v10 plan's audit-first loop: root-cause the last public-page quality gaps, ship the pillar features that audits showed were genuinely missing, and prove it all in production.

Added

  • Copilot quick actions. A fresh per-app copilot chat now offers up to three curated, context-aware prompts as one-tap chips (Notes: summarize / retitle / tighten; Mail: unread summary / draft reply / follow-up; also Files, Terminal, TextEdit, Calendar). Pure helper, fully tested.
  • Signature wallpapers "Silk current" (dark) and "Glass morning" (light), generated with Nano Banana Pro and shipped as 2560px webp. Per-space wallpapers (already supported via Mission Control's picker) gained test coverage.

Performance

  • Public pages paint from the first frame. Lighthouse traces showed LCP was dominated by render delay: hero content was server-rendered with animation-initial opacity: 0, so nothing painted until hydration plus a staggered entrance (up to 1.45s of deliberate delay). Hero text, CTAs, the desktop screenshot, and login branding now use transform-only entrances — identical motion, visible immediately.

Fixed

  • The Accounts activity feed showed "No recent activity" when every connected source had actually failed; it now shows a retryable error, while partial results still render.
  • Accessibility on public pages: footer contrast on /welcome now passes; decorative login stars dropped a prohibited (and inaccurate) aria-label.

Verified

  • RUM truth check: web-vitals beacons wired app-wide and accepted by the production endpoint (validated live).
  • DB index audit: every hot query path (messages by thread, threads/memory by user, audits, jobs) already has a composite index — no migration needed.
  • Full gate green; health-gated deploy; independent post-deploy browser verification.

nn.os v1.1.0 — 2026-07-09

Reliability-and-truth release: the flows a new user touches first now provably work in production, the assistant gained a real capability, and every deploy identifies itself.

Fixed

  • Beta request-access, in-app copilot, and Developer Center asset validation all failed in production with 403 CSRF blocked — the client calls never sent the X-Requested-With header the proxy requires. All mutating internal fetches now go through the shared NNOS_JSON_HEADERS contract, and a repo-wide scan guards against regressions.
  • Desktop icon labels were clipped by the icon below them: auto-arranged rows were pitched at the bare grid cell height (80px) while an icon + label renders ~100px. Rows now space by the full visual height (regression-tested); user-saved layouts are untouched.
  • Notes widget renders newlines in note bodies.

Added

  • Assistant web search that actually works. web.search previously posted to an endpoint that never existed. It is now backed by a keyless DuckDuckGo-based search service (src/lib/web-search.ts) behind an authenticated, rate-limited POST /api/nnos/web/search, with server-side tool execution calling the service directly.
  • Self-identifying deploys. Both deploy paths stamp the Docker image with the git SHA; /api/health reports it as commit.

Performance

  • Ambient background animation on sign-in/public pages (25 infinite composited animations) now starts on browser idle instead of first paint. The resting visual is pixel-identical; the LCP critical path is lighter.

Security & correctness

  • The code.run vm sandbox moved fully into src/lib/server/ (strict server-only import gate green again); all 11 sandbox-escape tests unchanged.
  • New end-to-end regression tests prove one user's assistant memory can never appear in another user's assembled prompt, including under concurrent requests.

Verification

Full gate (lint 0/0, typecheck, 8,900+ tests, build, bundle budget) green; health-gated production deploys with independent post-deploy browser verification (public flows, login, desktop, console-error sweep — all pass).

Release v1.0.0 — Self-Hosted Production

Date: 2026-07-06 Tag: v1.0.0-selfhosted

Highlights

Self-hosted production deployment

  • nn.os is now live at https://nnos.neima.me on dedicated infrastructure
  • Docker standalone build (3-stage, node:22-alpine)
  • Health-gated deploy script with automatic rollback
  • TLS via Traefik + Let's Encrypt
  • HSTS and security headers on all routes

Visual overhaul (10X v2, 20 phases)

  • Premium SVG brand logo and favicon
  • 8 custom desktop icons (Documents, Notes, Mail, etc.)
  • Window open/close spring animations with shadow expansion
  • Dock: accent-colored indicators, glassy reflection
  • Menu bar: clock tooltip, consistent hover highlights
  • Spotlight: section header icons, accent left-border on active items
  • Notifications: time bucket headers, pill action buttons, collapsible groups
  • Login: Remember me, social proof, error slide-down spring
  • Settings: live search highlighting, animated toggles, collapsible sections
  • Mail: hover preview, folder badges, rich compose toolbar
  • Files: breadcrumb nav, tag color chips, hover scale
  • Calendar: density gradients, pulsing today indicator, event color picker
  • Music: album art blur backdrop, glowing progress bar
  • Photos: filter chips, lightbox zoom
  • Terminal: themed scrollbars, vibrant ANSI colors
  • Mobile: drag indicators, tactile feedback
  • Performance: route loading bar, image blur-up, Suspense fallbacks
  • Micro-interactions: button press, card lift, premium spinner

Code quality

  • 8,857 tests across 720 files (0 errors, 0 lint warnings)
  • 8500+ decompositions reducing file sizes
  • Full theme system (5 modes × 5 accents)
  • Accessibility: focus-visible rings, reduced-motion, ARIA

Operations

  • Health watchdog cron (5-min interval, auto-restart)
  • Proxmox weekly VM backup (Sun 03:00, keep-last=2)
  • Production runbook (docs/ops/runbook-nnos-neima-me.md)
  • Bundle budget: all routes within limit
  • Security: all 5 headers verified, dependency audit passed

Phase 15 — Release, Telemetry & GTM (v1.0.0)

Date: 2026-05-13

Summary

First public release of nn.os. Versioned releases, onboarding, telemetry, changelog, and beta invite flow.

What shipped

  • Onboarding flow — Five-step first-run experience: welcome, wallpaper picker, theme selection, sign-in/guest, and assistant tour. Completion persisted in localStorage.
  • Telemetry SDK — Opt-in anonymized event tracking via sendBeacon. Anonymous identity hashing, one-click opt-out. Off by default with first-run prompt.
  • Changelog route/changelog renders all release notes from docs/release-notes/, sorted newest-first, as a server component.
  • Beta sign-up/beta page with email submission for early access and sign-in link for existing users.
  • Changeset release automation.changeset/config.json and scripts/release.mjs for semver bumps, changelog updates, and git tagging.
  • Version bump to 1.0.0package.json, nn-os-version.ts, and test expectations updated.

Acceptance criteria

  • npm run lint passes
  • npm run typecheck passes
  • Onboarding completes in < 60s for a new user
  • Telemetry is documented and opt-out is one click
  • /changelog shows every phase's release notes
  • Version is 1.0.0

Phase 14 — Accessibility, i18n & Mobile/Touch

Summary

WCAG AA+ accessibility, internationalization (EN/ES/AR), RTL support, and touch interactions.

What shipped

  • Keyboard shortcuts doc — Complete reference for all shell surfaces
  • Focus management — trapFocus, restoreFocus, aria-live announcements
  • i18n — EN/ES/AR translations with locale hook and RTL detection
  • RTL support — RTLProvider with automatic dir/lang switching
  • Touch support — useLongPress, useSwipe, useMomentumScroll hooks

Phase 13 — Performance & Offline

Summary

Sub-1.5s cold start optimization, dynamic imports, prefetching, RUM telemetry.

What shipped

  • Lazy app loading — Centralized createLazyApp() with skeleton fallbacks
  • Prefetch hooksusePrefetchOnHover and usePrefetchOnIntent for dock/launcher items
  • AppLoadingSkeleton — Animated skeleton with theme-aware shimmer
  • RUM endpoint — POST/GET for Web Vitals metrics (admin dashboard)
  • RUM client — Collects LCP/CLS/TTFB/INP/FCP via web-vitals, opt-in sendBeacon

Phase 12 — Files, Search & Spotlight 2.0

Summary

Unified search across all data, redesigned Spotlight, file preview pane, tags, and smart folders.

What shipped

  • Unified index — TF-IDF search across 9 scopes (files, apps, mail, contacts, calendar, notes, memories, web)
  • Spotlight 2.0 — Categorized results, keyboard nav, "Ask AI" fallback, recent queries
  • File preview pane — Image/text preview, metadata, inline AI actions
  • Tag service — Color tags, searchable, draggable
  • Smart folders — Rule-based saved searches with 7 fields and 8 operators

Phase 11 — Notifications, Reminders & Inbox

Summary

Unified notification system with DND scheduling, per-source rules, and redesigned notification center.

What shipped

  • Notification store — CRUD with grouping, snooze, DND evaluation (33 tests)
  • Notification center redesign — Grouped by source/day, inline actions, swipe-to-dismiss
  • Notification settings — Per-source rules, DND schedules, critical assistant override

Phase 10 — Assistant Tools & Skills SDK

Summary

Typed tool SDK, skills system, and 3 shipped skills.

What shipped

  • Tool SDKdefineTool() factory with Zod validation, ToolDefinition types
  • Skill loader — Load/toggle/get skills with manifest format
  • 3 shipped skills — Research, Writing Coach, Inbox Triage
  • ToolConfirmCard — Approval UI for destructive tools
  • SkillsPage — Settings UI for managing skills

Phase 09 — Memory, RAG & Knowledge Graph

Summary

Long-term memory, hybrid RAG retrieval, and entity graph for the AI assistant.

What shipped

  • Memory store — In-memory vector store with cosine similarity search (8 tests)
  • Hybrid retriever — BM25 + vector hybrid retrieval across 9 scopes (8 tests)
  • Entity graph — NER extraction (email, @mention, #hashtag, names) + relationship graph (16 tests)
  • Memory settings UI — List, search, pin, delete, export memories

Phase 08 — Background Agent Runtime

Summary

AI tasks that run while the user is away — background research jobs, reminders, and a runtime that persists across sessions.

What shipped

  • Background agent runtime — create/run/cancel background tasks with status tracking
  • Reminder service — time-based reminders with check-and-fire notifications
  • Research jobs — iterative AI research (quick/standard/deep depth)
  • BackgroundTasksPanel — UI with status badges, progress, results viewer
  • ReminderList — Create/manage reminders with countdown timers
  • 21 new tests covering all services

Phase 07 — Persistent AI Session Core

Summary

The assistant is now a persistent, server-backed session per user with encryption, cross-device sync, cold-resume optimization, and thread management.

What shipped

  • Encryption — AES-GCM encrypt/decrypt with per-user key derivation (7 tests)
  • Assistant sessions + attachments tables — Server-backed session state and file attachments
  • Cold-resume — Cached state with optimistic reconciliation for <600ms resume
  • SSE sync — Cross-tab/device real-time sync with auto-reconnect
  • Thread list UI — Sidebar with search, collapse, rename, archive, delete, unread dots
  • Session manager — getSession, updateState, export (JSON+MD), deleteThread
  • Menu-bar status pill — "Working on N tasks" / "Waiting on you" / "Idle" with pulse animation

Phase 06 — Window Manager Pro

Summary

Professional window management with edge-snap tiling, split view, window groups, configurable hot corners, and session persistence.

What shipped

  • Tiling presets — Edge-snap zones (halves, quarters) with 33 tests
  • Tiling overlay — Animated snap preview with Framer Motion
  • Split view — Two-window split with draggable divider and persisted ratio
  • Window groups — Create, save, restore named window groups
  • Hot corners — Configurable (Mission Control, Desktop, Launchpad, Notifications, Lock) with 120ms activation
  • Window persistence — Save/restore window state per-display on reload

Phase 05 — Dock 2.0

Summary

Fully customizable dock with drag-to-reorder, magnification, settings panel, context menus, badge API, and right-side stacks.

What shipped

  • Dock items tablennos_dock_items with position, kind, divider support
  • useDockItems hook — localStorage-backed dock item management
  • Magnification — Pure magnification math with 7 tests
  • Badge APIuseAppBadge(appId) with count/dot/progress
  • DockContextMenu — Keep, Open at Login, Hide, Quit, Remove
  • DockSettingsPanel — Position, size, magnification, auto-hide, recents, indicators
  • DockReorder — Framer Motion Reorder.Group with poof animation on remove
  • DockStacks — Documents, Downloads, Trash with expandable grid

Phase 04 — Folders, Stacks & Smart Stacks

Summary

Desktop now supports folders with mosaic thumbnails, auto-stacking by kind/date/tag, spring-loaded interactions, and a trash system with undo.

What shipped

  • Desktop folders — Create, rename, delete with 2×2 child mosaic thumbnails and count badges
  • Folder service — Pure module with create/rename/move/delete operations (15 tests)
  • Desktop stacks — Collapsed pile with spring fan-out, scrub-to-preview
  • Stack rules engine — Group by kind, date, tag, name pattern
  • Smart Stack builder — Rule UI with live preview and saved rules
  • Desktop trash — Drop target trash icon with restore + empty (10s undo toast)
  • Databasennos_desktop_folders table + extended nnos_desktop_icons with parentFolderId, tags, addedAt

Acceptance criteria

  • quality:full passes
  • Folder service fully unit-tested
  • Stack fan-out uses spring physics from design tokens

Phase 03 — Desktop Physics & Free Icons

Summary

Desktop is now a physical surface with free-positioned icons, snap grid, marquee selection, multi-drag, keyboard nudging, context menu, and view options.

What shipped

  • Desktop layout module — Pure desktop-layout.ts with snap, collision, sort, cleanup, hit-test, marquee selection functions (39 tests)
  • Free-positioned icons — Icons stored with {x,y,z} coordinates, draggable with Framer Motion spring physics
  • Snap grid — Toggleable with S/M/L/XL sizes, adjustable via View Options
  • Marquee selection — Click-drag rectangle selects multiple icons
  • Multi-select drag — Drag one icon moves all selected icons together
  • Context menu — Right-click: New Folder, Clean Up, Sort By, View Options, Get Info
  • Keyboard nudging — Arrow (1px), Shift+Arrow (10px), Cmd+Arrow (snap to grid), Tab focus cycle
  • View Options sheet — Icon size, grid spacing, label position, sort, snap toggle
  • Database tablennos_desktop_icons with migration for per-user/space persistence
  • Screen reader announcements — All movements announced via aria-live

Acceptance criteria

  • quality:full passes (272 test files, 2048 tests)
  • All layout functions unit-tested
  • Keyboard accessible with visible focus

Phase 02 — Design System 2.0

Summary

Expanded nn.os design system with new themes, component primitives, custom icon set, design showcase page, and UI sound service.

What shipped

  • Design tokens directory — Split tokens into src/design/tokens/ with dedicated files for color, typography, spacing, radius, shadow, motion, and sound.
  • Vibrant + Mono themes — Added two new theme modes: Vibrant (high-saturation neon cyberpunk) and Mono (grayscale high-contrast).
  • New UI primitives — Surface, Sheet, Tooltip, Menu, Input, Slider components with full theme integration and tests.
  • Custom SVG icon set — 24 shell chrome icons in src/design/icons/ (window controls, menu bar, dock, system status) with consistent stroke-based style.
  • /design showcase page — Internal route showing every token, component, and theme state. Gated behind enableDesignPage feature flag.
  • UI Sound Service — Web Audio API synthesized sounds (open, close, error, notification, drop, click, menu, success) with localStorage persistence and Settings toggle.
  • Shell sound integration — Dock clicks, window open/close, launcher, and notifications now play contextual sounds.

Acceptance criteria

  • quality:full passes (lint, typecheck, 2009 tests, build, bundle budget)
  • /design renders and shows all components in 4 themes
  • All new routes within bundle budget
  • UI sounds toggle in Settings and persist across reload

Phase 01 — Foundation & Quality Baseline

Summary

Locked the floor for all subsequent phases. Lint, typecheck, tests, build, and bundle budget all pass green. Dead code removed, CI hardened, developer onboarding documented.

What shipped

  • ESLint: zero errors, zero warnings. Fixed 7 React Compiler lint errors (set-state-in-effect, static-components, preserve-manual-memoization). Added underscore-prefix ignore pattern for intentional destructuring discards.
  • Dead code removal. Audited src/os and src/apps with ts-prune. Removed ~2,934 lines across 29 files (15 deleted entirely, 14 surgically edited).
  • Bundle analyzer. Added npm run analyze using @next/bundle-analyzer. Generates client/edge/node treemap HTML reports in .next/analyze/.
  • Lighthouse CI script. Added scripts/lighthouse.mjs that fails CI if Performance < 80 or Accessibility < 90.
  • Playwright smoke test. Rewrote scripts/qa-smoke.mjs from agent-browser to Playwright. Covers boot, dock, launcher, 11 bundled apps, assistant, and sign-in page.
  • CI hardened. Updated ci.yml with quality:full + smoke test. Updated pr.yml to Node 22 + bundle budget step.
  • Lockfile policy. Removed stale pnpm-lock.yaml. Documented npm-only policy in README.
  • CONTRIBUTING.md + PR template. Full developer guide with quality gates, branch workflow, feature flags policy.
  • Dependency bumps. React 19.2.4 → 19.2.6, React DOM 19.2.4 → 19.2.6, eslint-config-next 16.2.3 → 16.2.6.

Acceptance criteria

  • npm run quality:full passes (lint, typecheck, 1992 tests, build, bundle budget)
  • Zero ESLint errors and zero warnings
  • Zero TypeScript errors in source code
  • npm run analyze outputs treemap
  • All routes within bundle budget
  • CONTRIBUTING.md documents the development loop
  • PR template created

Deferred

  • noUncheckedIndexedAccess and exactOptionalPropertyTypes: These flags produce 591+ type errors across the codebase. Adding them in a foundation phase would be counterproductive. Recommend a gradual migration in a future phase.

nn.os 10x Overhaul — Release Notes (April 2026)

This release captures the Phase 1–10 hardening and polish pass across the nn.os shell, app runtime, assistant, persistence, and release tooling.

Summary of work by phase

  1. Phase 1 — Build hygiene and bundle budget. Added npm run typecheck and npm run perf:bundle scripts; introduced scripts/bundle-budget.mjs as an enforceable per-route budget gate.
  2. Phase 2 — React 19 / Next 16 correctness pass. Tightened server vs. client component boundaries across the App Router; consolidated security headers and auth redirects into src/proxy.ts. Documented in docs/architecture/server-client-boundaries.md.
  3. Phase 3 — Account and admin dataflow. Server-gated account/ and admin/ layouts; follow-up for moving initial page data to server components is tracked in the boundaries doc.
  4. Phase 4 — Window manager decomposition. Extracted pure window operations into src/os/windowing/window-reducer.ts with integration + unit tests. OsProvider now funnels every window mutation through applyWindowAction.
  5. Phase 5 — Media + image review. Audited next/image usage and dynamic dimensions (one outstanding PHASE-5-IMG-REVIEW marker in the music library cover art path).
  6. Phase 6 — Accessibility and keyboard. Focus management for shell dialogs, typed command-bus replacing stringly-typed shortcut events, per-window AppErrorBoundary deduplication.
  7. Phase 7 — VFS / persistence. Introduced the saveInFlightRef + saveQueuedRef coalescer with a 750 ms debounce in OsProvider, guaranteeing at most one in-flight and one queued save. FileIndex is kept synchronous with VirtualFileSystem mutations.
  8. Phase 8 — Security and secrets. AUTH_SECRET is now a hard start-up failure in production (src/auth.ts); audit ring buffer formalised in src/lib/observability/audit.ts; mail SSRF guard in src/lib/server/mail-host-validation.ts; iframe sandbox policy for the third-party app runtime.
  9. Phase 9 — Tests and QA. Vitest suite expanded to 825 tests across 141 files, all passing. Agent-browser smoke + a11y review captured in docs/qa/agent-browser-test-report.md.
  10. Phase 10 — Release readiness. This release. scripts/release.sh now runs the real gate set (typecheck, lint, test, build, bundle budget); deployment and architecture docs brought in sync with the code; create-nnos-app template versions verified against root (next@16.2.3, react@19.2.4).

Test status

Test Files  141 passed (141)
     Tests  825 passed (825)

Captured from npx vitest run on the Phase 10 working tree.

Deferred / tracked follow-ups

grep -rn "PHASE-[0-9]\+-" src/ docs/ at release time (6 markers total):

MarkerLocationSummary
PHASE-5-IMG-REVIEWsrc/apps/music/Library.tsx:72✅ Resolved 2026-04-22 — replaced raw <img> with AlbumCover wrapper that reserves a 1:1 aspect ratio, sets explicit width/height, loading="lazy", decoding="async", and renders a 2-letter fallback tile on load error.
PHASE-6-FOLLOWUPsrc/os/shell/MissionControl.tsx:207✅ Resolved 2026-04-22 — useFocusTrap({ onEscape: onClose }) wraps the dialog with full tab/shift-tab trapping and focus-restore parity with other shell overlays.
PHASE-8-FOLLOWUPsrc/lib/observability/audit.ts:11Audit log is an in-memory ring buffer; a durable backend (DB or external sink) is deferred.
PHASE-8-FOLLOWUPsrc/os/shell/LockScreen.tsx:92✅ Resolved 2026-04-22 — unlock now posts to POST /api/nnos/auth/verify-password which re-verifies against the bcrypt hash with CSRF + session + rate-limit (5/15min) checks before permitting an unlock.
PHASE-9-FOLLOWUPdocs/qa/agent-browser-test-report.md:25✅ Resolved 2026-04-22 — .github/workflows/a11y.yml now runs pnpm vitest run src/test/a11y.test.tsx using vitest-axe as the component-level harness.
PHASE-9-FOLLOWUPdocs/qa/agent-browser-test-report.md:42✅ Resolved 2026-04-21 — copilot-session test now uses vi.spyOn(Date, "now") for an exact-equality assertion.

No other phase markers exist in src/ or docs/ at release time.

External-service constraints

  • Stripe — only the stripe.test.ts sandbox helpers exist in tree today (src/lib/billing/stripe.test.ts). Production billing requires provisioning a Stripe account, publishable + secret keys, and webhook signing secret before any billing code path is enabled.
  • Mail providers — IMAP/POP/SMTP credentials are user-supplied and encrypted with NNOS_ENCRYPTION_SECRET. The SSRF guard in src/lib/server/mail-host-validation.ts rejects loopback / RFC1918 / CGNAT / link-local / .internal / .local hosts. There is no static allow-list; any non-internal public host is accepted.
  • AI providers — Hermes and OpenClaw adapters are opt-in via NNOS_ASSISTANT_HERMES_ENABLED / NNOS_ASSISTANT_OPENCLAW_ENABLED plus the corresponding API key and optional endpoint override. With no provider enabled, assistant requests gracefully degrade.
  • AuthAUTH_SECRET is hard-required in production. AUTH_URL is strongly recommended on Vercel for cookie + redirect correctness.
  • Database — Neon Postgres via DATABASE_URL. Schema changes are applied through drizzle-kit by the operator; there is no npm run db:migrate script and no committed drizzle/ folder at the time of this release.

Release risk assessment: Low–Medium

Rationale:

  • Low — No schema migrations ship with this release; no breaking API contract changes; all 825 unit/integration tests green; release script enforces the full gate set; feature flags gate all optional external providers; create-nnos-app template versions match root so new-app scaffolding stays in sync with the host runtime.
  • Medium — The audit ring buffer is still in-memory (PHASE-8-FOLLOWUP); the lock-screen unlock is local-only (PHASE-8-FOLLOWUP); axe-core-based a11y is not yet an enforced gate (PHASE-9-FOLLOWUP). These are known, documented, and do not block release but should be prioritised in the next maintenance window.

Rollback

Follow docs/release-playbook.md:

  • scripts/rollback.sh <previous-tag> for code rollback, or
  • Vercel dashboard → Deployments → Promote to Production on the last known-good deployment.

No data-layer rollback is required because no migrations were shipped.

nn.os Phase 3b → 10 completion report

Date: 2026-04-22 Branch: main (unpushed; 28 commits ahead of origin/main) Session handle: OpenCode / claude-opus-4.7

This document summarises the work performed in the follow-up session driven by docs/superpowers/plans/2026-04-22-nnos-remaining-work-for-opencode.md, which covered the remaining items across all ten overhaul phases.


Scope actually delivered

✅ Phase 6 — Shell polish

  • useGlobalShortcut hook + document-level dispatcher — new module src/os/hooks/useGlobalShortcut.ts. Introduces a capture-phase keydown dispatcher that calls stopImmediatePropagation() when a registered binding matches, so the legacy monolithic DesktopShell keydown handler no longer double-fires. Scope filtering (shell | app:<id>), mac/non-mac mod normalisation, optional allowInInputs override, duplicate-id rejection in dev. 16 vitest cases.
  • Three shell shortcuts migrated as proof: ⌘K launcher toggle, ⌘/ overlay, ⌃⌘Q lock screen. Remaining shortcuts still live in the legacy effect and will migrate incrementally.
  • KeyboardShortcutsOverlay now subscribes to the registry and merges registered entries over its legacy hard-coded table (dedupe by (category, action)). New formatCombo helper translates the canonical mod+shift+f string to the human glyphs (⌘ ⇧ F) with 6 vitest cases.
  • MissionControl focus-trap — verified already-applied useFocusTrap({ onEscape: onClose }); release-notes marker resolved.
  • LockScreen step-up auth — verified POST /api/nnos/auth/verify-password exists with bcrypt verification, CSRF, session check, and 5/15min rate-limit. Release-notes marker resolved.

✅ Phase 7 — Apps product polish (deep)

Pure-helper extraction + vitest coverage for every major built-in app. Each extraction creates a sibling <app>-data.ts / <app>-math.ts / <app>-time.ts module, imports from the .tsx, and re-exports types for backward compatibility. No UI regressions.

Apps covered this session:

AppNew module(s)Tests
Mail— (existing helpers)thread, snooze, search tests + 1 bug fix
Calendar— (existing helpers)rrule, ics, quick-add tests
Assistantentity extraction, summariser, auto-extractor, rate-limit, message-normaliser tests
Weatherweather-data.tsformatters, units, phrase selection
Mapsmaps-math.tsbbox, projection, haversine
Newsnews-data.tssource merge, time-ago, category filters
Musicmusic-format.ts, AlbumCover.tsxduration format, library roll-up, cover fallback
Photosphotos-data.tsalbum roll-up, timeline grouping
Drawingdrawing-math.tsshape hit-testing, bounds
Sportssports-data.tsleague merge, record math
Wellnesswellness-data.tsstreak calc, unit conversions
Videovideo-time.tsseek clamp, ts format
AI Translatortranslator-data.tslanguage list, swap logic
AI Writerwriter-data.tsprompt templates

Total new Phase 7 tests this session: +408 (825 → 1233 entering, now 1275).

✅ Phase 8 — Security, privacy, ops

  • Rate-limit tests added for /api/nnos/auth/reset/request (7 cases including silent-200 oracle prevention) and /api/nnos/mail/send (9 cases covering rate-limit 429, SSRF guard, SMTP failure, schema rejection).
  • LockScreen marker resolved (see Phase 6 above).
  • Durable audit log, admin /admin/audit viewer, and request-meta helper were landed in earlier commits in the same branch and remain green.

✅ Phase 9 — Real CI gates

  • vitest-axe smoke suite at src/test/a11y.test.tsx exercises the login form, signup form, and a sample dialog; color-contrast rule disabled (jsdom cannot compute layout).
  • .github/workflows/a11y.yml updated to run the real vitest-axe step (placeholder removed, PHASE-9-FOLLOWUP marker resolved).
  • copilot-session fake-clock test uses vi.spyOn(Date, "now") for exact-equality assertions.

✅ Phase 10 — Docs breadth

24 new markdown files across:

  • docs/apps/*.md — per-app pages (admin, app-store, assistant, built-in-apps, calendar, files, mail, notes, settings, terminal).
  • docs/assistant/*.md — audit model, job queue, memory format, tool system.
  • docs/ops/*.md — on-call playbook, backup/restore, secret rotation, README.
  • docs/issues/drafts/*.md — cloud-drive adapters, mail provider OAuth, OAuth client IDs, Stripe production, VFS re-encryption script.

✅ Phase 4 / 5 — Image wrapper

  • AlbumCover component in src/apps/music/AlbumCover.tsx resolves the final PHASE-5-IMG-REVIEW marker. Reserves a 1:1 aspect-ratio wrapper (CLS=0), sets explicit width/height, loading="lazy", decoding="async", and renders a two-letter fallback tile on image load error. Native <img> retained because cover URLs are external/dynamic; the wrapper is the obvious swap point for next/image when covers move to first-party storage.

Gates status at session close

GateCommandResult
Typecheckpnpm tsc --noEmit✅ clean
Lintpnpm lint✅ 0 errors, 329 warnings
Testspnpm vitest run✅ 1275 / 1275 in 169 files
Buildpnpm build✅ production build succeeds
Bundle budgetpnpm perf:bundle✅ 22 / 22 routes within budget

Test delta this session: 825 → 1275 (+450).


PHASE-*-FOLLOWUP markers at session close

MarkerStatus
PHASE-5-IMG-REVIEW music/Library.tsx✅ resolved (AlbumCover)
PHASE-6-FOLLOWUP MissionControl✅ resolved (useFocusTrap)
PHASE-8-FOLLOWUP LockScreen✅ resolved (verify-password endpoint)
PHASE-8-FOLLOWUP audit durable backend✅ resolved in earlier commit (nnos_audit_log table)
PHASE-9-FOLLOWUP a11y workflow✅ resolved (vitest-axe)
PHASE-9-FOLLOWUP copilot-session fake clock✅ resolved

All PHASE-*-FOLLOWUP markers in the release-notes index are now resolved. A handful of follow-up work items remain (listed below) but none are blocking and none have in-code FOLLOWUP markers.


Remaining / intentionally deferred

These items were documented in the handoff plan but not executed in this session and remain appropriate for a follow-up iteration:

  1. Phase 3 app splits — seven apps still exceed 700 LOC (Mail 1697, Clock 1703, Terminal 1244, AppStore 1222, Assistant 1111, ReleaseManager 1110, Files 927). Prioritised below Phase 6/7/8 because splitting risks subtle behaviour regressions and the per-app tests added this session de-risk those splits for the next agent.
  2. Remaining shell-shortcut migration — 15+ bindings still in the DesktopShell legacy effect (window management, spaces, widget board, dispatchAppCommand forwarding). Pattern is now proven; migration is mechanical.
  3. Phase 5 src/design/ui/ primitives package — not started.
  4. Phase 7.2 Mail HTML sanitisationisomorphic-dompurify dep not yet added; current render path is plaintext.
  5. Phase 8.3 marketplace iframe sandbox audit — manifest Zod validation exists but sandbox= attribute enforcement was not verified this session.
  6. Phase 8.4 privacy export/delete endpoints — not started.
  7. Phase 9.5 agent-browser evidence set — no new screenshots captured this session (the existing suite from earlier commits remains current).
  8. Phase 5 mobile / motion / visual QA sweep — no new visual evidence captured.

Commits in this session (newest first)

5d8cac7 test(api): phase 8 rate-limit tests for password reset + mail send
2400aca feat(shell): KeyboardShortcutsOverlay reads from useGlobalShortcut registry
a3daa8b feat(music): phase 4/5 — AlbumCover wrapper resolves PHASE-5-IMG-REVIEW
aaf1c3c docs(release-notes): mark 3 stale phase markers resolved
0cf2216 feat(shell): phase 6 — global shortcut registry + useGlobalShortcut hook
2df0d0c Phase 10: per-app, assistant subsystem, ops, and issue-draft docs
376bc37 Phase 9: wire up vitest-axe a11y smoke suite
b42247f Phase 7: extract video + translator + ai-writer pure helpers + tests
434e798 Phase 7: extract sports + wellness pure helpers + tests
c5ef90c Phase 7: extract music/photos/drawing pure helpers + tests
69288ae Phase 7: extract news app data helpers + add tests
97814c9 Phase 7: extract maps math helpers + add tests
c42f185 refactor(weather): extract pure data + helpers into weather-data module
590a5e0 test(assistant/memory): cover entity extraction, summarizer, auto-extractor
b1e4b79 test(calendar): cover rrule + ics pure helpers
384797b test(mail): cover thread/snooze/search helpers; fix header-less thread drop
82ca7ba nn.os Phase 7: pure-helper tests (calendar quick-add, assistant rate limit + message normalizer)
41279f2 nn.os Phase 9: fake-clock copilot-session timestamp test
89c65c1 nn.os Phase 8: admin audit-log viewer + request-meta helper
dce299e nn.os Phase 8: durable audit log with DB persistence + in-memory mirror
ecd49f7 nn.os Phase 6: step-up lock-screen auth + shared focus trap
947c8d3 docs: handoff plan for opencode covering remaining Phase 3-10 work

Every commit carries a Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> trailer and left all gates green.


External-service constraints (unchanged)

None of the items below were surfaced or altered this session; documented here for completeness:

  • OAuth provider client IDs — see docs/issues/drafts/oauth-provider-client-ids.md.
  • Stripe production integration — see docs/issues/drafts/stripe-production-integration.md.
  • Real mail provider OAuth — see docs/issues/drafts/mail-provider-oauth.md.
  • Cloud drive adapters — see docs/issues/drafts/cloud-drive-adapters.md.
  • VFS re-encryption script — see docs/issues/drafts/vfs-reencryption-script.md.

Continuation session — 2026-04-22 (later)

A second session picked up every "Remaining / intentionally deferred" item from above and drove it to completion. Branch main, now 46 commits ahead of origin/main (still unpushed). Session handle: OpenCode / claude-opus-4.7 + subagents.

Scope delivered

✅ Phase 3 — App splits (all remaining apps)

Every app flagged as >700 LOC in the prior handoff has been split into sibling modules with pure helpers extracted and tests added. Root-file LOC reductions:

AppBeforeAfterΔNew tests
Clock1703102−1601+19
Mail16971426−271+16
Terminal12441091−153+28
AppStore12221063−159+6
Assistant1111739−372+49
ReleaseManager1110888−222+26
Files927770−157+29

No UI regressions; every extraction ships a sibling test file.

✅ Phase 5 — UI primitives library

New src/design/ui/ package with 11 primitives: Button, IconButton, Toggle, SegmentedControl, SliderField, TextField, SelectField, Modal, Toolbar, EmptyState, Skeleton. Each:

  • Consumes CSS custom properties (--ui-accent, --ui-text, etc.) with sensible fallbacks so primitives stay provider-free.
  • Passes through aria-* props.
  • Shows a visible focus ring on :focus-visible.
  • Honours prefers-reduced-motion.
  • Expands to a 44×44 touch target on coarse pointers.

src/design/ui/_shared.ts centralises reduce-motion + coarse-pointer detection and the focus-ring style. src/design/ui/index.ts re-exports everything. +33 tests (3 per primitive: role, keyboard activation, disabled state).

Call-site migration is an explicit follow-up — primitives exist now so app-level refactors can adopt them incrementally without churn.

✅ Phase 6 — Shortcut migration (remainder)

All 10 residual DesktopShell legacy-effect shortcuts migrated to useGlobalShortcut: mission control (F3 and Ctrl+ArrowUp), space switch prev/next (Ctrl+ArrowLeft/Right), widget board (Ctrl+W), reopen last closed window (mod+shift+T), window cycle fwd/back (mod+Tab / mod+shift+Tab), focus window N (mod+1..mod+9), new Finder window (mod+shift+N), close/minimize/maximize front window (mod+W, mod+M, mod+shift+F). Per-app dispatchAppCommand forwarding blocks remain in a smaller residual useEffect pending a per-app migration pass.

+3 tests covering window-cycle, space-switch, and mission-control dispatch through the registry.

Known subtle behaviour change documented for QA: registry-gated global shortcuts with a meta/ctrl modifier now fire even while a text input has focus (consistent with macOS convention); the legacy handler suppressed them. No user report raised yet.

✅ Phase 7.2 — Mail HTML sanitisation

src/apps/mail/mail-html-sanitizer.ts upgraded from a minimal stub to a strict DOMPurify-based sanitiser wrapping isomorphic-dompurify (added as a direct dep). Strips <script>, <iframe>, <object>, <embed>, <form>, all on* event handlers, javascript: / vbscript: / non-image data: URLs, and dangerous style props (position, z-index, behavior, -moz-binding). Remote images blocked by default; opt-in allowRemoteImages: true replaces each remote <img src> with a placeholder and returns the count for a future UI toggle. Wired into MessageReaderBody before srcDoc assignment. +21 tests covering every strip rule + passthrough cases.

✅ Phase 8.3 — Iframe sandbox audit

  • src/lib/security/iframe.ts no longer emits allow-popups-to-escape-sandbox; popups now inherit the sandbox.
  • src/apps/app-runtime/AppRuntimeApp.tsx host iframe string updated to match.
  • docs/security/iframe-sandbox-audit-2026-04.md documents every iframe surface in the repo (app-runtime host, mail reader, widget previews), the policy rationale, and follow-up items.
  • +3 tests assert the token is never emitted regardless of option combinations and pin the host iframe sandbox string.

✅ Phase 8.4 — Privacy export / delete

  • GET /api/nnos/account/export — returns profile, settings (redacted), notes, files listing, mail accounts (no credentials), calendar, contacts.
  • POST /api/nnos/account/delete — hard-delete / anonymise; writes audit entry.
  • /account/privacy page wired to the real endpoints (was a stub).
  • +12 integration tests.

✅ Phase 9.5 — agent-browser evidence refresh

New artifact set at artifacts/qa/agent-browser/20260422-204900/: 32 PNGs with matching .snapshot.txt files covering shell (5 desktop

  • 5 mobile), auth (login/register/forgot), account pages, admin pages, and 12 built-in apps. docs/qa/agent-browser-test-report.md updated with a 2026-04-22 section.

The sweep surfaced finding AB-009 (see next item).

✅ Phase 8 — Admin auth-gate regression (AB-009)

The 9.5 evidence sweep caught that /admin sub-pages were unguarded: only /admin itself ran a (broken) client-side check against the non-existent /api/admin/check endpoint, while /admin/users, /admin/flags, /admin/listings, /admin/support, /admin/audit and /admin/diagnostics rendered their (mock) UIs to any visitor.

Fix: src/app/admin/layout.tsx is now a server component that calls auth() + loadUserRoleFromDb() + isAdminRole(). Unauthenticated sessions redirect to /login?next=/admin. Non-admin sessions redirect to /?denied=admin. Visual chrome lifted into a client src/app/admin/_shell.tsx so the sidebar can keep using usePathname. The redundant client check in /admin/page.tsx is removed. +5 tests covering every gate branch.


Gates status at continuation-session close

GateCommandResult
Typecheckpnpm tsc --noEmit✅ clean
Lintpnpm lint✅ 0 errors, 336 warnings (budget 339)
Testspnpm vitest run✅ 1520 / 1520 in 194 files
Buildpnpm build✅ production build succeeds
Bundle budgetpnpm perf:bundle✅ 22 / 22 routes within budget

Test delta this continuation: 1275 → 1520 (+245). Cumulative delta since the overhaul began: 825 → 1520 (+695).

Commits added in this continuation (newest first)

b2d226b phase 8: server-side auth gate for /admin/** (AB-009)
a169f5f phase 9.5: refresh agent-browser evidence set (2026-04-22)
dada874 phase 5: add UI primitives library (src/design/ui/)
1d50604 phase 6: migrate DesktopShell legacy shortcuts to registry
2085828 phase 8.3: tighten iframe sandbox policy (drop popups-to-escape-sandbox)
25a97a3 phase 3: extract Files pure helpers (927 → 770 LOC)
4dd21bc phase 3: extract ReleaseManager pure helpers (1110 → 888 LOC)
777ab8e phase 7.2: mail HTML sanitisation with isomorphic-dompurify
a79070c phase 3: extract Assistant pure helpers (1111 → 739 LOC)
7ed82ea phase 3: extract App Store presentation helpers (1222 → 1063 LOC)
15d1aac phase 3: extract Terminal pure helpers to terminal-utils.tsx
33c18c5 phase 8.4: GDPR privacy endpoints (export + delete) wired to UI
1eb49d5 phase 3: start Mail split (1697 → 1426 LOC, extract reader + helpers)
c5083d0 phase 3: split Clock app (1703 → 102 LOC root, 6 sibling files)
03fdea7 docs(release-notes): phase 3b→10 completion report for 2026-04-22 session

Every commit carries the Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> trailer.

Remaining follow-ups (intentionally deferred)

These surfaced during the session and are explicit non-goals for this round:

  1. Mail follow-up splitMailApp.tsx still 1426 LOC. Compose view + thread list are the next natural extractions.
  2. Call-site migration to src/design/ui/ primitives — the library ships now; apps are not yet retrofitted.
  3. Per-app dispatchAppCommand forwarding still lives in a residual DesktopShell useEffect. Migrating each to per-app scope: "app:<id>" registry bindings is the next mechanical pass.
  4. Phase 5 mobile / motion / visual QA deep sweep — primitives + a fresh evidence set exist, but we did not audit every app for motion hygiene or mobile viewports this round.
  5. Input-guard behaviour of registry shortcuts — registry now allows meta/ctrl combos to fire while typing. Consistent with macOS, but worth QA sign-off before shipping.
  6. AB-009 regression test at the HTTP layer — unit test covers the layout; an E2E (agent-browser) check that /admin/users returns a redirect for a non-admin session would close the loop.

Every other line item from the original handoff plan is resolved.

Deployment release rule

Every future production deployment must do both of these in the same commit before running vercel deploy --prod:

  1. Bump package.json to the release version being shipped.
  2. Prepend a matching top entry in src/lib/nn-os-version.ts with that same version and the deployment date.

NN_OS_VERSION now reads from package.json, so /api/health, the boot/about surfaces, and the What's New app all reflect the same release number. The top changelog entry is the deploy note for that version; if it is stale, the deployment is stale.

nn.os v2.5.0 — Web Desktop