feat(cards): Phase-1 Spinoff — standalone cards.mana.how + cards-core extraction

Builds out the Cards spinoff end-to-end so the standalone app at
cards.mana.how shares its data layer with the in-mana cards module
through a single pure-utility package.

Why a spinoff and not just a deeper module: per the GUIDELINES, Cards
gets its own brand + URL while reusing mana-auth, mana-sync, and the
mana-credits/billing stack. The in-mana module under mana.how/cards
stays untouched as the integrated experience.

Phase 0 — mana-modul foundation
  • New tables cardReviews + cardStudyBlocks (Dexie v61) + plaintext
    classification in the crypto registry.
  • LocalCard learns a {type, fields} shape; legacy front/back columns
    kept as a back-compat mirror so older builds keep rendering.
  • FSRS v6 scheduler + Cloze parser + Markdown render pipeline.
  • UI in apps/mana/.../routes/(app)/cards/ gets a learn session
    (learn/[deckId]), 4-type card editor, due-counter, markdown lists.

Phase 1 — standalone (apps/cards/apps/web)
  • SvelteKit 2 + Svelte 5 + Tailwind 4, port 5180.
  • Own Dexie 'cards' DB with a slim 5-table schema.
  • Own sync engine: pending-changes hooks, 1 s push / 5 s pull against
    POST /sync/cards, server-apply with suppression to avoid ping-pong.
  • Auth-Gate via @mana/shared-auth-ui (LoginPage / RegisterPage).
  • Encryption hooks at every write/read/apply path, currently no-op
    stubs — flipping to real vault-backed AES-GCM is a single-file
    change in src/lib/data/crypto.ts.

Shared package — @mana/cards-core
  • Pulls types, cloze, card-reviews, FSRS wrapper, and Markdown
    renderer out of the mana module so both frontends import from one
    source. mana-modul keeps thin re-export shims so consumers don't
    need to change imports.
  • 19 vitest tests carried over from the mana module.

Server-side wiring
  • cards.mana.how added to mana-auth PRODUCTION_TRUSTED_ORIGINS and
    its CORS_ORIGINS env (sso-config.spec.ts stays green).
  • New cards-web container in docker-compose.macmini.yml (mirrors
    manavoxel-web pattern, 128m, depends on mana-auth healthy).
  • cloudflared-config.yml repoints cards.mana.how from :5000 (the
    unified mana-web container) to :5180. mana.how/cards is unchanged.

Cleanup
  • Removed an unrelated 2026-03/04 NestJS+Supabase+Expo experiment
    that was lingering under apps/cards/ (apps/landing, supabase/,
    .github/workflows, MANA_CORE_*.md, etc.). It predated this plan
    and would have confused future readers.

Validation
  • svelte-check on mana-web: 0 errors over 7697 files
  • svelte-check on cards-web: 0 errors over 3481 files
  • vitest on cards-core: 19/19 pass
  • pnpm check:crypto: 214 tables classified
  • bun test sso-config.spec.ts: 8/8 pass
  • vite build on cards-web: green

Not done in this commit (deliberate)
  • Real encryption (vault roundtrip) — Phase 2.
  • WebSocket-driven pull (5 s polling for now).
  • Mobile/landing standalone surfaces — Phase 2/3.
  • The actual production cutover on the Mac mini (build, deploy,
    cloudflared sync) — config is staged, deploy is a user action.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-07 01:20:43 +02:00
parent 950b822070
commit 0a544ac410
94 changed files with 4090 additions and 7466 deletions

157
pnpm-lock.yaml generated
View file

@ -183,36 +183,89 @@ importers:
specifier: ~5.9.2
version: 5.9.3
apps/cards/apps/landing:
apps/cards: {}
apps/cards/apps/web:
dependencies:
'@astrojs/check':
specifier: ^0.9.0
version: 0.9.8(prettier-plugin-astro@0.14.1)(prettier@3.8.1)(typescript@5.9.3)
'@astrojs/sitemap':
specifier: ^3.2.1
version: 3.7.2
'@mana/shared-landing-ui':
'@mana/cards-core':
specifier: workspace:*
version: link:../../../../packages/shared-landing-ui
astro:
specifier: ^5.16.0
version: 5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3)
astro-icon:
specifier: ^1.1.5
version: 1.1.5
typescript:
specifier: ^5.0.0
version: 5.9.3
version: link:../../../../packages/cards-core
'@mana/local-store':
specifier: workspace:*
version: link:../../../../packages/local-store
'@mana/shared-auth':
specifier: workspace:*
version: link:../../../../packages/shared-auth
'@mana/shared-auth-ui':
specifier: workspace:*
version: link:../../../../packages/shared-auth-ui
'@mana/shared-branding':
specifier: workspace:*
version: link:../../../../packages/shared-branding
'@mana/shared-crypto':
specifier: workspace:*
version: link:../../../../packages/shared-crypto
'@mana/shared-icons':
specifier: workspace:*
version: link:../../../../packages/shared-icons
'@mana/shared-privacy':
specifier: workspace:*
version: link:../../../../packages/shared-privacy
'@mana/shared-pwa':
specifier: workspace:*
version: link:../../../../packages/shared-pwa
'@mana/shared-stores':
specifier: workspace:*
version: link:../../../../packages/shared-stores
'@mana/shared-tailwind':
specifier: workspace:*
version: link:../../../../packages/shared-tailwind
'@mana/shared-theme':
specifier: workspace:*
version: link:../../../../packages/shared-theme
'@mana/shared-types':
specifier: workspace:*
version: link:../../../../packages/shared-types
'@mana/shared-utils':
specifier: workspace:*
version: link:../../../../packages/shared-utils
dexie:
specifier: ^4.4.1
version: 4.4.2
devDependencies:
'@astrojs/tailwind':
specifier: ^6.0.0
version: 6.0.2(astro@5.18.1(@azure/storage-blob@12.31.0)(@types/node@24.12.2)(ioredis@5.10.1)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)(terser@5.46.1)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.3))(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
'@tailwindcss/typography':
specifier: ^0.5.16
version: 0.5.19(tailwindcss@3.4.19(tsx@4.21.0)(yaml@2.8.3))
'@mana/shared-vite-config':
specifier: workspace:*
version: link:../../../../packages/shared-vite-config
'@sveltejs/adapter-node':
specifier: ^5.0.0
version: 5.5.4(@sveltejs/kit@2.56.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.9.3)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))
'@sveltejs/kit':
specifier: ^2.47.1
version: 2.56.1(@opentelemetry/api@1.9.1)(@sveltejs/vite-plugin-svelte@5.1.1(svelte@5.55.1)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)))(svelte@5.55.1)(typescript@5.9.3)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
'@sveltejs/vite-plugin-svelte':
specifier: ^5.0.4
version: 5.1.1(svelte@5.55.1)(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
'@tailwindcss/vite':
specifier: ^4.1.7
version: 4.2.2(vite@6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
'@types/node':
specifier: ^22.10.5
version: 22.19.17
svelte:
specifier: ^5.41.0
version: 5.55.1
svelte-check:
specifier: ^4.3.3
version: 4.4.6(picomatch@4.0.4)(svelte@5.55.1)(typescript@5.9.3)
tailwindcss:
specifier: ^3.4.17
version: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
specifier: ^4.1.17
version: 4.2.2
typescript:
specifier: ^5.7.2
version: 5.9.3
vite:
specifier: ^6.0.7
version: 6.4.2(@types/node@22.19.17)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3)
apps/chat: {}
@ -319,8 +372,6 @@ importers:
specifier: ^3.4.0
version: 3.4.19(tsx@4.21.0)(yaml@2.8.3)
apps/context: {}
apps/docs:
dependencies:
'@astrojs/check':
@ -495,6 +546,9 @@ importers:
'@huggingface/transformers':
specifier: ^4.0.0
version: 4.0.1
'@mana/cards-core':
specifier: workspace:*
version: link:../../../../packages/cards-core
'@mana/credits':
specifier: workspace:^
version: link:../../../../packages/credits
@ -615,6 +669,9 @@ importers:
dexie:
specifier: ^4.0.11
version: 4.4.2
isomorphic-dompurify:
specifier: ^3.7.1
version: 3.7.1(@noble/hashes@2.0.1)
marked:
specifier: ^17.0.5
version: 17.0.6
@ -642,6 +699,9 @@ importers:
swissqrbill:
specifier: ^4.3.0
version: 4.3.0(typescript@5.9.3)
ts-fsrs:
specifier: ^5.3.2
version: 5.3.2
zod:
specifier: ^3.25.76
version: 3.25.76
@ -1670,6 +1730,34 @@ importers:
specifier: ^5.0.0
version: 5.9.3
packages/cards-core:
dependencies:
'@mana/local-store':
specifier: workspace:*
version: link:../local-store
'@mana/shared-privacy':
specifier: workspace:*
version: link:../shared-privacy
isomorphic-dompurify:
specifier: ^3.7.1
version: 3.7.1(@noble/hashes@2.0.1)
marked:
specifier: ^17.0.5
version: 17.0.6
ts-fsrs:
specifier: ^5.3.2
version: 5.3.2
devDependencies:
'@types/node':
specifier: ^24.10.1
version: 24.12.2
typescript:
specifier: ^5.9.3
version: 5.9.3
vitest:
specifier: ^4.1.3
version: 4.1.3(@opentelemetry/api@1.9.1)(@types/node@24.12.2)(@vitest/coverage-v8@4.1.3)(@vitest/ui@4.1.3)(jsdom@29.0.2(@noble/hashes@2.0.1))(vite@6.4.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.1)(tsx@4.21.0)(yaml@2.8.3))
packages/credits:
devDependencies:
svelte:
@ -1737,7 +1825,7 @@ importers:
version: 4.0.3
isomorphic-dompurify:
specifier: ^3.7.1
version: 3.7.1
version: 3.7.1(@noble/hashes@2.0.1)
marked:
specifier: ^17.0.0
version: 17.0.6
@ -8410,6 +8498,7 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
deprecated: Potential CWE-502 - Update to 1.3.1 or higher
'@urql/core@5.2.0':
resolution: {integrity: sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==}
@ -8599,6 +8688,7 @@ packages:
'@xmldom/xmldom@0.8.12':
resolution: {integrity: sha512-9k/gHF6n/pAi/9tqr3m3aqkuiNosYTurLLUtc7xQ9sxB/wm7WPygCv8GYa6mS0fLJEHhqMC1ATYhz++U/lRHqg==}
engines: {node: '>=10.0.0'}
deprecated: this version has critical issues, please update to the latest version
'@xtuc/ieee754@1.2.0':
resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==}
@ -15654,6 +15744,10 @@ packages:
peerDependencies:
typescript: '>=4.0.0'
ts-fsrs@5.3.2:
resolution: {integrity: sha512-moJJfYAeG9ynyyGCNaQPUloi0sspTMHFtgCvsx2wDchELu3O2c513/7fp+6PxsGth0ztxNjEtG8d85gX4ce0og==}
engines: {node: '>=20.0.0'}
ts-interface-checker@0.1.13:
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
@ -16045,6 +16139,7 @@ packages:
uuid@7.0.3:
resolution: {integrity: sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==}
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
hasBin: true
v8-to-istanbul@9.3.0:
@ -28730,7 +28825,7 @@ snapshots:
iso-639-1@2.1.15: {}
isomorphic-dompurify@3.7.1:
isomorphic-dompurify@3.7.1(@noble/hashes@2.0.1):
dependencies:
dompurify: 3.3.3
jsdom: 29.0.2(@noble/hashes@2.0.1)
@ -33349,6 +33444,8 @@ snapshots:
picomatch: 4.0.4
typescript: 5.9.3
ts-fsrs@5.3.2: {}
ts-interface-checker@0.1.13: {}
tsconfck@3.1.6(typescript@5.9.3):