Skip to content

fix(desktop): make fresh installs always populate lessons (v0.2.8)#33

Merged
GigaChadGRC merged 1 commit intomainfrom
fix/lessons-not-populating-v0.2.8
Apr 18, 2026
Merged

fix(desktop): make fresh installs always populate lessons (v0.2.8)#33
GigaChadGRC merged 1 commit intomainfrom
fix/lessons-not-populating-v0.2.8

Conversation

@chadfryer
Copy link
Copy Markdown
Collaborator

Summary

Fixes the long-standing "fresh install shows 'Loading...' with no lessons" problem on the macOS desktop build, plus eight other bugs uncovered by a deep audit. Bumps to v0.2.8.

The core insight from the audit was that fresh installs were failing for multiple compounding reasons, not one — and existing users were being silently wiped on upgrade. This PR addresses every root cause and adds runtime self-healing so users never see an empty library again.

Tier 1 — fixes for "lessons not populating"

Fix Why
src-tauri/Entitlements.plist + correct codesign order in desktop-build.yml macOS Gatekeeper / hardened runtime was silently killing the bundled node because nested executables lacked JIT (V8) and library-validation (Prisma engine) entitlements. Sign inner execs/dylibs first, then deep-sign the .app with the same entitlements.
strip_quarantine_from_self() in src-tauri/src/lib.rs Rescues users who downloaded via Safari without right-click → Open.
Capture stdout in addition to stderr (next-stdout.log) The sidecar was running blind on stdout — instrumentation logs and Prisma engine boot were dropped.
Replace ensure_writable_db size-comparison with copy-only-if-missing + content-versioned reseed via src/instrumentation.ts The old logic either dropped new content (when user DB grew past bundled) or wiped UserStats/TopicProgress/SessionCompletion/ReadingPosition/CapstoneRubricState/QuestionAnalytics when it did copy. New approach: bundle ships seeded DB; runtime reconciles SessionContent against data/release-library/session-content.json keyed by new UserStats.seedGeneratedAt. Preserves all user progress across upgrades.
Prune duplicate dev.db files in prepare-tauri-sidecar.mjs, add >1MB sanity guard, single canonical find_bundled_db path Next standalone trace was dragging in empty prisma/dev.db copies that could win the lookup.

Tier 2 — other audit findings

  • stripHtml in /api/news regressed; reapplied do-while strip + safe entity decode order (CodeQL: incomplete multi-character sanitization + double unescaping).
  • kill_stale_port_holder now identifies the holder of port 1430 via ps/tasklist and refuses to kill -9 anything that is not node — surfaces a clear error instead of nuking unrelated dev servers.
  • firstTryCorrect analytics counter was guarded by && !existing inside the update-only branch, so it never incremented. Fixed.
  • toKeyTerms filter dropped 3-letter acronyms (AI, GRC, ISO, SOC). Changed to length >= 2 + small stopword set; npm run curriculum:validate now asserts "AI Governance" keeps "AI".
  • /api/diagnostics is now platform-aware: derives Prisma engine filename from process.platform/process.arch, surfaces app-data dir, both log files, and seedGeneratedAt.

Schema

Adds nullable UserStats.seedGeneratedAt String?. Backwards-compatible — populated by the instrumentation hook on next launch via prisma db push semantics (no destructive migration).

Test plan

Verified locally on macOS arm64:

  • npm run lint — clean
  • npm run library:verify — 184 modules, all citation rates 100%
  • BASE_URL=http://127.0.0.1:3199 npm run qa — 0/10 pages with issues
  • /api/diagnosticssessionContentCount: 184, prismaStatus: connected, seedGeneratedAt set, both stdout+stderr logs surfaced
  • /api/news → 25 articles, all 5 sources OK
  • Cold-boot path: cleared seedGeneratedAt, deleted one SessionContent row, restarted server → instrumentation upserted 184 rows, restored the missing one, preserved a hand-inserted TopicProgress row
  • Warm-boot path: subsequent restart logs release snapshot ... already applied; skipping reseed
  • npm run tauri:prepare produces canonical 9MB dev.db and bundled data/release-library/session-content.json
  • Direct exec of bundled sidecar (resources/next-standalone/server.js under resources/node-runtime/bin/node) boots, applies snapshot, serves 184 modules
  • cargo check --release clean

Docs

  • README.md — replaced "lessons missing" troubleshooting with a self-healing note + log paths + xattr workaround.
  • CHANGELOG.md — new top-level changelog file with full v0.2.8 entry.
  • desktop-build.yml release-notes body — mentions hardened-runtime entitlements + self-healing.

The headline goal: a fresh download of the macOS / Windows desktop
build must reliably show all 184 lesson modules on first launch, and
content updates on later releases must not wipe user progress.

Root causes addressed:

- macOS Gatekeeper / hardened runtime silently killed the bundled Node
  sidecar because nested executables were not signed with the JIT and
  library-validation entitlements V8 + Prisma's native query engine
  require. Adds src-tauri/Entitlements.plist and rewrites the unsigned
  fallback codesign step in .github/workflows/desktop-build.yml to sign
  inner executables and dylibs first, then deep-sign the .app with the
  same entitlements. The Rust launcher also strips the
  com.apple.quarantine xattr from its own .app on startup.

- Sidecar was blind on stdout (only stderr captured) so silent failures
  were invisible. Now writes both next-stderr.log and next-stdout.log
  alongside sidecar.log; /api/diagnostics surfaces them with platform-
  aware Prisma-engine and app-data-dir resolution (no more darwin-arm64
  hardcoding).

- ensure_writable_db's size-only update logic either dropped new
  content or wiped user progress on launch. Replaced with: copy bundled
  seed only when no user DB exists, and reconcile content via a Next.js
  instrumentation hook that compares the bundled
  data/release-library/session-content.json generatedAt against a new
  UserStats.seedGeneratedAt column. Existing UserStats, TopicProgress,
  SessionCompletion, ReadingPosition, CapstoneRubricState, and
  QuestionAnalytics rows are preserved across releases. Seed logic
  shared with scripts/seed-release-library.ts via a new
  src/lib/seed-release-library.ts module.

- Stale duplicate dev.db files that Next standalone trace pulled in
  could win find_bundled_db's lookup. prepare-tauri-sidecar.mjs now
  prunes them, enforces a >1MB sanity guard on the canonical seed, and
  the Rust find_bundled_db only consults a single canonical path.

Other fixes uncovered by the audit:

- Reapply do-while strip and safe entity decode order in
  src/app/api/news/route.ts (CodeQL: incomplete multi-character
  sanitization + double unescaping had regressed).
- kill_stale_port_holder now identifies the holder of port 1430 via
  ps/tasklist and refuses to kill -9 anything that isn't node.
- firstTryCorrect counter in /api/session/complete actually increments
  for repeat questions (was guarded by `&& !existing` inside the
  update-only branch, so it never fired).
- toKeyTerms keeps short acronyms (AI, GRC, ISO, SOC) by changing the
  filter from length > 2 to length >= 2 plus a stopword set;
  curriculum:validate now asserts AI Governance keeps "AI".

Verified locally:
- npm run lint, library:verify, qa Playwright (0/10 pages with issues)
- /api/diagnostics returns sessionContentCount=184, prismaStatus=
  connected, seedGeneratedAt set on cold AND warm boot
- Direct exec of the bundled Tauri sidecar (resources/next-standalone/
  server.js under resources/node-runtime/bin/node) boots, applies the
  release snapshot, preserves a hand-inserted TopicProgress row across
  a NULL seedGeneratedAt restart
- cargo check --release on src-tauri compiles cleanly
Copy link
Copy Markdown
Contributor

@GigaChadGRC GigaChadGRC left a comment

Choose a reason for hiding this comment

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

LGTM

@GigaChadGRC GigaChadGRC merged commit 7a017c0 into main Apr 18, 2026
3 of 4 checks passed
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.

2 participants