@mana/cards-core wurde nur von apps/cards + services/cards-server +
apps/mana/.../modules/cards genutzt — die ersten zwei sind weg, das
mana-Modul kommt im nächsten Commit.
Rollback: git checkout cards-decommission-base -- packages/cards-core/
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Marketplace-Backend war an apps/cards gekoppelt — beides ist
jetzt im standalone-Repo git.mana.how/till/cards. Live auf
cardecky-api.mana.how (apps/api in cards-Repo).
Rollback: git checkout cards-decommission-base -- services/cards-server/
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cards-App ist seit 2026-05-08 ein eigenständiges Repo
git.mana.how/till/cards (Strategie-B-Greenfield, beschlossen
2026-05-08), live auf cardecky.mana.how + cardecky-api.mana.how.
Alte cards-web-Container wurden gestoppt + entfernt.
Rollback: git checkout cards-decommission-base -- apps/cards/
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two long-uncommitted Mac Mini drifts cleaned up:
1. cloudflared-config.yml — git.mana.how → :3030 (Forgejo). The
route has been live for weeks (HTTP 200), just never committed.
2. .gitignore — exclude secrets/ (private keys: mana-ai mission-grant
RSA keypair lives there; must NEVER be committed) and *.bak-*
files (operator backup workflow on the Mac Mini).
services/mana-auth/drizzle/ on the Mac Mini was Mac-Mini-side
generated state for the (now deleted) mana-monorepo mana-auth
service; cleanup fell out with the Phase 7 deletion.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Stalwart's official Docker image is distroless and has no wget, curl,
nc, ss, or netstat. The compose healthcheck (CMD wget ...) was failing
with "executable file not found" since 2026-05-05; container shows
status=unhealthy 24/7 even though Stalwart itself runs fine on
:25 / :587 / :465 / :993 / :8080.
Disable. Crash detection comes from docker's restart=always plus
mana-monitoring's external SMTP probe (blackbox-exporter), not from
inside the container.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
-1023 / +113 lines: lockfile shrinks dramatically as the workspace
loses 8 service packages plus their transitive devDeps. @mana/media-client
now resolved from Verdaccio (^0.1.0) instead of as a workspace member.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live containers on the Mac Mini build out of `../mana/services/mana-auth/`
since the 8-Doppel-Cutover commit (774852ba2). Smoke test green
2026-05-08 — health endpoints, JWKS, login flow, Stripe-webhook all
reachable from the new build path. Removing the now-stale duplicate.
Was 560K in this repo, gone now. Active code lives in
`Code/mana/services/mana-auth/` (siehe ../mana/CLAUDE.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live containers on the Mac Mini build out of `../mana/services/mana-credits/`
since the 8-Doppel-Cutover commit (774852ba2). Smoke test green
2026-05-08 — health endpoints, JWKS, login flow, Stripe-webhook all
reachable from the new build path. Removing the now-stale duplicate.
Was 156K in this repo, gone now. Active code lives in
`Code/mana/services/mana-credits/` (siehe ../mana/CLAUDE.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live containers on the Mac Mini build out of `../mana/services/mana-media/`
since the 8-Doppel-Cutover commit (774852ba2). Smoke test green
2026-05-08 — health endpoints, JWKS, login flow, Stripe-webhook all
reachable from the new build path. Removing the now-stale duplicate.
Was 17M in this repo, gone now. Active code lives in
`Code/mana/services/mana-media/` (siehe ../mana/CLAUDE.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live containers on the Mac Mini build out of `../mana/services/mana-notify/`
since the 8-Doppel-Cutover commit (774852ba2). Smoke test green
2026-05-08 — health endpoints, JWKS, login flow, Stripe-webhook all
reachable from the new build path. Removing the now-stale duplicate.
Was 43M in this repo, gone now. Active code lives in
`Code/mana/services/mana-notify/` (siehe ../mana/CLAUDE.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live containers on the Mac Mini build out of `../mana/services/mana-llm/`
since the 8-Doppel-Cutover commit (774852ba2). Smoke test green
2026-05-08 — health endpoints, JWKS, login flow, Stripe-webhook all
reachable from the new build path. Removing the now-stale duplicate.
Was 90M in this repo, gone now. Active code lives in
`Code/mana/services/mana-llm/` (siehe ../mana/CLAUDE.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live containers on the Mac Mini build out of `../mana/services/mana-tts/`
since the 8-Doppel-Cutover commit (774852ba2). Smoke test green
2026-05-08 — health endpoints, JWKS, login flow, Stripe-webhook all
reachable from the new build path. Removing the now-stale duplicate.
Was 148K in this repo, gone now. Active code lives in
`Code/mana/services/mana-tts/` (siehe ../mana/CLAUDE.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live containers on the Mac Mini build out of `../mana/services/mana-stt/`
since the 8-Doppel-Cutover commit (774852ba2). Smoke test green
2026-05-08 — health endpoints, JWKS, login flow, Stripe-webhook all
reachable from the new build path. Removing the now-stale duplicate.
Was 132K in this repo, gone now. Active code lives in
`Code/mana/services/mana-stt/` (siehe ../mana/CLAUDE.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Live containers on the Mac Mini build out of `../mana/services/mana-mail/`
since the 8-Doppel-Cutover commit (774852ba2). Smoke test green
2026-05-08 — health endpoints, JWKS, login flow, Stripe-webhook all
reachable from the new build path. Removing the now-stale duplicate.
Was 188K in this repo, gone now. Active code lives in
`Code/mana/services/mana-mail/` (siehe ../mana/CLAUDE.md).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Root package.json devDeps reference @mana/eslint-config (workspace:*).
Even with `--filter @mana/api...`, pnpm resolves the root workspace
manifest and chokes on the missing package. Copy eslint-config into the
builder stage so the root resolves cleanly. Same pattern as the platform
mana-auth Dockerfile already uses.
Discovered while running the 8-Doppel-Cutover smoke test on Mac Mini:
mana-api build failed with ERR_PNPM_WORKSPACE_PKG_NOT_FOUND.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Part of the 8-Doppel-Cutover (2026-05-08, plan
~/.claude/plans/floating-swinging-flurry.md):
- docker-compose.{macmini,dev,test}.yml: build context for
mana-{auth,credits,media,llm,notify} switched to ../mana/services/...
so the Mac Mini stack pulls platform services from the platform repo
(sibling clone), not from services/ in this monorepo.
- .npmrc + apps/api/{Dockerfile,package.json}: @mana/media-client now
resolved from Verdaccio (npm.mana.how, ^0.1.0) instead of as a
workspace COPY from services/mana-media/packages/client. Build-arg
NPM_TOKEN flows through .npmrc for pnpm install auth. Required
before services/mana-media/ can be deleted.
- .github/workflows/{ci,cd-macmini,daily-tests}.yml: removed the
detect-/build-/test-jobs that targeted services/mana-{auth,credits,
notify,media}/. Those services build out of the platform repo now —
CI for them belongs in mana/-repo (open). cd-macmini's
workflow_dispatch can still rebuild any of them on demand;
auto-detect on path-change is gone for these five.
- scripts/{mac-mini/push-schemas.sh,run-integration-tests.sh}:
rewritten to look in ../mana/ for the platform services.
- package.json dev:{auth,credits,notify,media}: paths point at
../mana/services/... so local dev still works post-cutover.
What this commit does NOT do: delete services/mana-{auth,credits,...}
from this repo. That waits for Phase 7 once the Mac Mini stack has
booted cleanly from the new build paths.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the two zitare hostnames to PRODUCTION_TRUSTED_ORIGINS in
sso-origins.ts and to the mana-auth CORS_ORIGINS in
docker-compose.macmini.yml. Pre-condition for the first Zitare
live-cut on the Mac Mini — the running mana-auth container must
be rebuilt for the new TRUSTED_ORIGINS list to take effect (see
zitare/DEPLOY.md Schritt 3).
sso-config.spec.ts asserts symmetry between sso-origins.ts and
the CORS_ORIGINS env in compose. Test runs 8/8 green after this
change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three new ingress rules for the Zitare repo (Code/zitare/, separate
repo, deployed under ~/projects/zitare-deploy/ on the Mac Mini).
Ports follow mana/docs/PORTS.md: 3083 api / 3084 app / 3085 com.
zitare.com is a separate Cloudflare zone — the tunnel route for
that hostname needs a one-time `cloudflared tunnel route dns
1435166a-0e3f-4222-8de6-744f32cea5c9 zitare.com` to point the CNAME
at this tunnel. Same for the two .mana.how subdomains, which sit on
the existing mana.how zone.
Code-only: no Mac Mini deploy in this commit. The actual reload
needs ./scripts/mac-mini/sync-tunnel-config.sh after the matching
mana-auth/CORS_ORIGINS + sso-origins changes are committed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Catches the service-level package.json files that the previous
sweep (4cca25ed0) missed — they don't appear in any dev:*:full
orchestrator but get invoked when someone runs `pnpm --filter
@mana/<service> dev` directly.
Touched: mana-geocoding, mana-mail, mana-subscriptions, mana-mcp,
news-ingester, mana-persona-runner, mana-research, mana-user,
plus apps/memoro (server + audio-server).
mana-ai stays on --watch on purpose: its entry uses an explicit
`Bun.serve({...})` call instead of `export default { port,
fetch }`, plus a SIGTERM/SIGINT handler that calls
`server.stop()`. --hot would replace the module without releasing
the old server reference and produce exactly the EADDRINUSE we're
trying to avoid. If mana-ai gets refactored to the standard
default-export shape, flip its dev script too.
Completes the migration documented in docs/PORT_SCHEMA.md:29-30
("moved from 3065 on 2026-05-06 because the platform mana-media
reserves 3065"). services/mana-events/src/config.ts already
defaults to 3115 — generate-env.mjs was the last file still
emitting the old value, so anyone running pnpm setup:env would
get a .env that pinned the service back to 3065 and collided
with mana-media on the platform.
Fans out the cards-server fix from 08f422340 to every other Bun
service in the monorepo. --hot keeps the port bound across HMR
reloads via globalThis[hmrSymbol]; --watch restarts the process
and races old/new Bun.serve for the port.
Touched: dev:auth, dev:credits, dev:events, dev:analytics,
dev:memoro:server, dev:memoro:audio-server, dev:uload:server in
the root package.json plus the matching `dev` script in each
service's own package.json. All six services already export the
`{ port, fetch }` default that Bun's --hot expects.
Smoke-tested: pnpm dev:cardecky:full boots clean, then touching
auth/credits/cards-server entry files all hot-reload without
dropping their port.
(memoro/apps/audio-server doesn't have a `dev: bun --watch ...`
script in its own package.json, so only the root entry got the
swap there.)
cards-server: switch from `bun run --watch` to `bun run --hot`.
--watch restarts the whole process on file change, racing the
old + new Bun.serve calls for the port (the EADDRINUSE you see
right after `listening on :3072`). --hot does in-process HMR via
the globalThis[hmrSymbol] pattern; the port stays bound across
reloads. apps/api already uses --hot for the same reason.
scripts/setup-databases.sh:
- create_db_if_not_exists "mana_notify" + "mana_credits" so a
fresh-machine `pnpm setup:db` no longer leaves these two DBs
off (mana-notify was crashing on boot with SASL fallback,
mana-credits was less obvious because its drizzle config
defaults to mana_platform — but the runtime config can point
at mana_credits, so safer to have the DB exist).
- Fix the cards branch: was pointing at the non-existent
@mana/cards-database package; now points at @mana/cards-server
where the actual schema lives.
Verified: drop+re-create flow + cardecky:full boot + touch-trigger
hot-reload all clean.
The Go service defaults to postgresql://mana:mana@... but the
local docker-compose postgres uses devpassword (matches every
other service + .env.development). Without the override the
notify worker died on boot with "failed SASL auth".
Inline the URL on the script so dev:cardecky:full + dev:notify
both work out of the box. Long-term the right home for this is
an .env file the Go service reads, but that's a separate
refactor across all Go services in the monorepo.
dev:cardecky:full spins up the six processes Cardecky needs end-
to-end so a fresh `pnpm dev:cardecky:full` boots the full stack
in one terminal: mana-auth (3001) + mana-sync (3050) + mana-
credits (3061) + mana-notify (3040) + cards-server (3072) +
cards-web (5180).
dev:cards-server is the standalone shortcut for just the Hono
backend — useful when iterating on the API alone.
One-time prereqs (not in the script): `pnpm docker:up`,
`pnpm setup:env`, `pnpm setup:db`, plus
`cd services/cards-server && bun run db:push` (the legacy
setup-databases.sh cards branch points at a non-existent
@mana/cards-database package).
- CardFace now renders one card surface that flips on Y-axis when
the user taps it. Both faces share a CSS-grid cell so the parent
height is the max of front/back, no jumpy reflow on flip.
- Tap-anywhere on the surface reveals (only while showBack is
false). The /learn page keeps the keyboard space/enter shortcut
via the existing handler; the standalone "Aufdecken" button is
now type-in-only (where the input field on the front breaks the
flip-mental-model).
- prefers-reduced-motion: reduce collapses the rotateY into an
instant cross-fade — same affordance, no vestibular trigger.
- Added a subtle "Tippe auf die Karte oder drücke Leertaste" hint
on the front face so the new affordance is discoverable.
- Fixed the last Phase-A leftover: focus:border-indigo-400 in the
type-in input → focus:border-app-accent.
Phase A — Cards joins the unified theme system:
- Drop placeholder --color-cards-* palette; app.css imports
@mana/shared-tailwind/themes.css + sources.css.
- Remove hardcoded class="dark" from app.html; body uses
bg-background text-foreground.
- New $lib/stores/theme.ts: createThemeStore({ appId: 'cards' }).
ThemeToggle from @mana/shared-theme-ui in the header next to
the streak chip.
- Sweep all neutral / red / emerald / amber / indigo utilities in
apps/cards/apps/web/src to semantic tokens (560 substitutions
across 19 files): bg-neutral-900 → bg-card, text-neutral-400 →
text-muted-foreground, bg-red-500 → bg-error, etc. Domain
literals kept (FSRS grade colors red/orange/green/blue, GitHub-
violet PR-merged badge, marketplace-amber Buy button, admin-
inbox category palette).
- Cards added to validate-theme-utilities scope so future drift
fails CI.
Phase C — per-app accent token:
- New --color-app-accent in shared-tailwind/themes.css. Theme-
agnostic (registered in validate-theme-parity's THEME_AGNOSTIC
regex), so it stays the same across light/dark/lume/etc. Defaults
to Mana indigo at :root.
- Cards layout writes 258 90% 66% (= #8b5cf6 violet, from
MANA_APPS.cards.color) onto documentElement at boot via
applyCardsAccent(). All Cards CTAs (Lernen, Abonnieren, Senden,
links inside cloze cards) flow through bg-app-accent /
text-app-accent now.
Net effect: Cards gets light/dark + 4 palette variants + a11y
toggles for free, and any future app can drop in by setting its
own --color-app-accent without touching shared-tailwind.
Marketplace-Plan auf den realen Code-Stand gebracht:
- §11 Phasen-Liste: pro Phase ✅ shipped / 🟡 partial / ⏳ pending
Marker mit Kurzbegründung. β/γ/δ/ε sind ✅, ζ und η sind 🟡 mit
ζ.2 / η.2 als nächste Sub-Phasen.
- §13a „Bekannte Limitierungen": neue Einträge für ζ (Refunds,
Reconciler, CSV) und η (Community-Verify-Cron, Mod-Permissions,
Public-Changelog, Rate-Limit). PR-Merge-Stale-Blindness und
Card-Preview-Heuristik bleiben dokumentiert.
- §15 ersetzt: Status-Tabelle pro Phase + Empfehlung für die
nächsten 4 Schritte (ζ.2 Reconciler → η.2 Verify-Cron →
Update-Mail δ.4 → Phase θ Auto-Tags/Summary).
- Stand-Zeile auf 2026-05-07 inkl. aller Migrations-Phasen 2c+2d+2e+2f+2g
+ Verdaccio-Rollback erklärt
- Mem-Budget-Tabelle: news-ingester aus "Andere" raus (auf GPU-Box seit
Phase 2f-2), Standalone-Compose-Zeile für verdaccio dazu, Total
~45 → ~42 Container
- GPU-Hostname-Liste vervollständigt: status, mana-ai, research, photon
Pre-existing Drift; nicht durch Phase 2g/Rollback verursacht, aber bei
der Audit aufgefallen.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Server (cards-server):
- ModerationService: createReport, listOpen, resolveReport,
takedownDeck, banAuthor, setVerifiedMana, restoreDeck. Takedown
also auto-closes any sibling open reports against the deck and
closes any open PRs (so contributors see clean state). Ban
cascades to all of the author's decks.
- routes/moderation.ts: POST /v1/reports (any authed user),
GET /v1/admin/reports (admin only), POST /v1/admin/reports/:id/
resolve, POST /v1/admin/decks/:slug/{takedown,restore}, POST
/v1/admin/authors/:slug/verify. Admin gate is `role === 'admin'`
for now — verified-mana-only mods land later.
- Notify hooks: takedown emails the deck owner, mana-verify status
change emails the author.
Frontend (cards-web):
- <ReportButton> (icon or inline variant) with category picker
+ optional explanation. On /d/<slug> as a discreet 🚩 next to
the published-date stamp; in <CardDiscussions> per non-own
comment.
- /d/<slug> shows a red "wurde von der Moderation entfernt" banner
when isTakedown is true.
- /admin/reports inbox: lists open reports with category badges,
Abweisen / Deck entfernen / Author bannen actions. Renders a
forbidden state if the current user isn't admin.
Phase 2f-1 hatte verdaccio von der Mini auf die GPU-Box verlegt — das
Storage-Volume kam dort aber nie an. Der GPU-Container war leer (keine
htpasswd, keine @mana/*-Pakete), externe `npm install @mana/foo` lief
auf 404. Rollback statt Storage-Migration nachzuholen, weil:
- Mini's Standalone-Verdaccio (~/projects/verdaccio/) hat alle Daten
inklusive claudebot-Service-Account und 9 published Pakete
- npm-Reads sind ohnehin niedrig (CI-builds), Mini-Disk hat Platz
- Vereinfacht den User-/Token-Pflad-Lebenszyklus (eine Quelle, keine
Sync-Choreografie)
Cleanup:
- DNS npm.mana.how zurück auf Mini-Tunnel via Cloudflare-API
- Mini cloudflared-config.yml: npm.mana.how-Ingress wieder eingetragen
- GPU-Box: verdaccio-Container + 3 Volumes entfernt (mana_verdaccio-storage,
mana_verdaccio-plugins, verdaccio-storage)
- infrastructure/docker-compose.gpu-box.yml: verdaccio-Service-Block raus
- infrastructure/verdaccio/config.yaml: gelöscht (war GPU-spezifischer
Bundle, der Code/mana hat die kanonische Kopie für Mini)
- docs/PLAN_OPTION_C.md: Phase 2f markiert als ⚠️ teilweise zurückgerollt
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Server (cards-server):
- lib/credits.ts: thin internal-API client for mana-credits
(reserve / commit / refund-reservation / grant). Service-to-
service via X-Service-Key. Throws InsufficientCreditsError
separately so the buy flow can branch on UX.
- services/purchases.ts: 4-step purchase pipeline: reserve →
insert deck_purchases row → commit reservation → grant
author share + insert author_payouts. Idempotent on
(buyer, deck) so a refresh-spam-click can't double-charge.
Verified-mana authors get the 90/10 split, others 80/20
(already in config). Refunds intentionally out of scope —
see MARKETPLACE_PLAN §13a.
- routes/purchases.ts: POST /v1/decks/:slug/purchase,
GET /v1/me/purchases, GET /v1/authors/me/payouts.
- decks.bySlug now returns hasPurchased (null when anonymous,
bool when authed) so the deck-detail page can pick the right
CTA.
- subscriptions.subscribe now blocks paid decks unless the
caller has a non-refunded purchase row (owner exempt for
testing).
- Notify: author gets a "Verkauf"-Email at grant time, with a
deterministic externalId for dedup.
Frontend (cards-web):
- /d/<slug> shows "Kaufen für N 💎" instead of "Abonnieren"
when paid + not yet bought; flips to subscribe path once
purchased.
- /me/purchases page listing buyer history + (when present)
author-payout history. Linked from the top nav.
Memoro's SvelteKit SPA at memoro-app.mana.how is a separate deploy
under mana e.V. that needs to use the central mana-auth (login,
session, JWT). Without this entry Better-Auth rejects its preflight
silently (no Access-Control-Allow-Origin header) and the SPA can't
even reach POST /api/v1/auth/login.
Updates both SSOTs per the rule in CLAUDE.md / mana-auth/CLAUDE.md:
1. PRODUCTION_TRUSTED_ORIGINS in services/mana-auth/src/auth/sso-origins.ts
2. CORS_ORIGINS for mana-auth in docker-compose.macmini.yml
sso-config.spec.ts will pick up the consistency between the two.
- DiscussionService.countsForDeck: bulk count (visible) comments per
card-content-hash for one deck. Mounted at GET
/v1/decks/:slug/discussion-counts so the public deck page can
render comment badges without N+1 fetches.
- <DeckCardList> on /d/<slug>: lists the latest version's cards,
renders a one-line preview + "💬 N" badge, and expands the
inline <CardDiscussions> on click. Anonymous visitors see counts;
posting requires auth (CardDiscussions already gates that).
- mana-notify integration in cards-server: PR-create notifies the
deck owner, merge/reject notifies the PR author. Fire-and-forget
via lib/notify.ts so a notify-service outage never rolls back a
domain action. ExternalIDs are deterministic (cards.pr.{event}.{id})
so retries dedupe.
- <CardDiscussions> on the learn page: collapsed by default, opens
via "💬 Diskussion" alongside the "✏️ Verbessern" trigger. Resets
whenever the current card changes so the panel doesn't bleed
between flashcards.
- MARKETPLACE_PLAN.md §13a — known limitations: PR-merge is
stale-blind (no rebase yet), diff-preview flat, threading 1-level.
Server (cards-server):
- PullRequestService: create / list / get / merge / close / reject.
Merge applies the PR's {add, modify, remove} diff to the latest
version's cards in a single transaction, writes a new
deck_version + deck_cards, bumps latest_version_id, and stamps
the PR with mergedIntoVersionId.
- DiscussionService: post / listForCard / hide. Threads are keyed
by card_content_hash so they survive version bumps.
- Routes mounted under /v1: POST/GET /decks/:slug/pull-requests,
GET /pull-requests/:id, POST /pull-requests/:id/{merge,close,reject},
GET/POST /cards/:contentHash/discussions, POST /discussions/:id/hide.
Frontend (cards-web):
- cardsApi.pullRequests + cardsApi.discussions client surface.
- <PullRequestsSection> on /d/:slug — lists PRs with diff preview;
owner sees Merge/Reject/Close buttons.
- <SuggestEditModal> + "✏️ Verbessern" button on /learn/:deckId for
cards from a subscribed deck — submits a one-card modify (or
remove) PR using the card's serverContentHash as the previous
hash.
- Deck/Card DTOs gain subscribedFromSlug + serverContentHash so the
learn page can decide whether to show the suggest-edit affordance.
Web-Research-Orchestrator (16+ search-/LLM-providers) auf die GPU-Box
verlagert. Cross-LAN für mana-auth/mana-credits/mana-llm/mana-search/
postgres/redis (192.168.178.131). research.mana.how routet jetzt zum
mana-gpu-server-Tunnel (CF config v29). Mini-Container-Count 42 → 41.
PUBLIC_MANA_RESEARCH_URL in mana-app-web auf https-URL umgestellt —
Mini-Container können 192.168.178.11 nicht direkt erreichen (Colima-NAT),
daher Cross-LAN-Bridge via Cloudflare-Tunnel wie bei mana-ai.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the marketplace subscribe loop. When the author publishes a
new version of a subscribed deck, the user sees a compact banner
with a per-card breakdown and one-click "Update anwenden" — FSRS
learning state survives across the merge.
- lib/services/subscribe.ts:
- previewUpdate(slug): server diff /v1/decks/:slug/diff?from=…
with a small UpdatePreview shape ({added, changed, removed,
unchanged} as counts).
- applyUpdate(slug):
- removed → soft-delete by [deckId+serverContentHash], cascade
reviewStore.softDeleteForCard.
- changed → update local card in place; ensureReviewsForCard
re-runs to pick up cloze-cluster changes. FSRS reviews stay
attached because the card-id never changes.
- added → fresh local card + fresh FSRS reviews.
- unchanged → only re-stamp ord if the diff moved it.
Bumps subscribedAtVersion locally + re-stamps server-side.
- lib/data/database.ts: Dexie v3 adds compound index
[deckId+serverContentHash] for the smart-merge lookup.
- routes/decks/[id]/+page.svelte:
- subscribed decks get a green "📥 Abonniert"-banner with the
version stamp + a deep-link to the public marketplace page.
- if previewUpdate returns a non-null diff, the banner inlines
the counts and an "Update anwenden" button.
- "Veröffentlichen", "Neue Karte", "Aus Text generieren" and
the per-card delete buttons are hidden when the deck is
subscribed — read-only mirror of the author's content.
Validated: svelte-check 0/0, vite build green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-to-end subscribe flow on cards.mana.how. From a public deck page
the user can now pull the deck into their own Cards instance with
one click; subscribed decks live alongside own decks but carry a
`subscribedFromSlug` marker so the editor knows to hide mutate
controls (UI gating in δ.3).
- cards-core types: LocalDeck gains subscribedFromSlug +
subscribedAtVersion. LocalCard gains serverContentHash. Both
optional — own decks/cards are unaffected.
- data/database.ts: Dexie v2 adds index on cardDecks.subscribedFromSlug
so the lookup-by-slug path is O(1).
- lib/api/cards-api.ts: subscriptions.{list,subscribe,unsubscribe,
version,diff} + the SubscriptionInfo / ServerCard / DeckVersionPayload
/ DiffPayload types.
- lib/services/subscribe.ts: subscribeAndPull() sequences server
POST /subscribe → GET /decks/:slug → GET /versions/:semver →
create LocalDeck + LocalCards + ensure FSRS reviews. Re-pull
refreshes in place (Phase δ.3 will swap to real diff-apply that
keeps FSRS state). unsubscribe() soft-deletes the local mirror.
isSubscribedLocally() backs the deck-page state check.
- routes/d/[slug]/+page.svelte: full subscribe UI — Abonnieren →
Abonniert + Lernen-Button (deep-links into the existing learn
session route).
Validated: svelte-check 0/0, vite build green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Server-side plumbing for Phase δ. Frontend hookup follows in δ.2.
- services/subscriptions.ts: subscribe/unsubscribe (idempotent
upsert on (user, deck), stamps the latest_version_id at
subscribe-time so the client knows what it pulled). listForUser
returns each sub with `updateAvailable: currentVersion !== latest`
so the client can render an update indicator without a second
round-trip. Refuses paid decks with 403 — that path comes back
in Phase ζ once the credits Marketplace lands.
- versionWithCards: deterministic ord-ordered card payload for a
specific version. Read-public so anonymous browsers can preview
a deck's content.
- diffSince: smart-merge payload between any two versions. Splits
the latest cards into added/changed/unchanged + lists removed
by content_hash. The 'changed' bucket is heuristic (ord-position
pair where one was removed and one was added) — solid enough
until Phase ε's pull-request pipeline gives us real card
lineage.
- routes/subscriptions.ts mounts: GET /v1/me/subscriptions,
POST/DELETE /v1/decks/:slug/subscribe (auth required),
GET /v1/decks/:slug/versions/:semver (public),
GET /v1/decks/:slug/diff?from=<semver> (public).
cards-web layout fix:
- Marketplace surface (/explore, /u/, /d/) was previously gated
behind the AuthGate — anonymous browsers got pushed to /login
via client-side navigate. PUBLIC_PATHS extended so those routes
SSR + render unauthed.
Validated: tsc clean on cards-server, svelte-check 0/0 on cards-web.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- PLAN_OPTION_C.md: new row covers verdaccio + news-ingester + mana-ai
with the cross-arch + workspace-deps gotchas
- infrastructure/README.md: hostname table catches up to npm.mana.how
(Phase 2f-1) and mana-ai.mana.how (Phase 2f-3); config v26 → v28
- infrastructure/.env.gpu-box.example: MANA_SERVICE_KEY +
MANA_AI_PRIVATE_KEY_PEM block added with note that the values mirror
Mini's .env.macmini (the latter's matching public-half stays on
mana-auth, that's what makes Mission-Grant decryption work)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
mana-ai's /metrics endpoint is no longer exposed on Mini's
192.168.178.131:3067 (service moved to GPU-Box, no public /metrics
tunnel since the endpoint is internal). The blackbox-api job
already probes mana-ai.mana.how/health for liveness, which gives
us up/down without needing the metrics scrape.
Status-page is now 58/58 UP after VM rolled past the stale 3067
samples.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Marketplace discovery surface lights up. Anonymous browsers can
explore + search; signed-in users get the same surface plus star/
follow mutations.
- middleware/optional-auth.ts: opportunistic JWT — sets c.get('user')
if a token validates, otherwise leaves it undefined. Read paths
use this; mutating routes call requireUser() inline.
- services/explore.ts: browse() with q (ilike on title/description),
tag, language, author-slug, sort (recent/popular/trending), pagination.
explore() composes featured + trending for the landing.
tagTree()/curatedTagsOnly() round it out. Subqueries for star/
subscriber counts avoid N+1.
- services/engagement.ts: star/unstar deck, follow/unfollow author.
Idempotent via ON CONFLICT DO NOTHING. Self-follow rejected.
- routes/explore.ts mounts /v1/explore, /v1/decks (browse list),
/v1/tags. routes/engagement.ts mounts /v1/decks/:slug/star
(POST/DELETE) + /v1/authors/:slug/follow (POST/DELETE).
- index.ts replaces the previous strict-jwt-on-everything middleware
with optionalAuth on all of /v1, then individual routers gate
their write paths via local requireUser(). Hono context type
relaxes from `user: AuthUser` to `user?: AuthUser` accordingly.
Validated: tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After Phase 2f-3 mana-ai lives on the GPU-Box, so the
blackbox-internal docker-DNS probe (http://mana-ai:3066/health) is
gone — that target sits in a Docker network the blackbox-exporter
can't reach across LAN. Move the probe into blackbox-api against
the public hostname; gives the same up/down signal plus exercises
the Cloudflare-tunnel hop.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
End-to-end "publish my local deck to the marketplace" surface in
the Cards standalone app. Hooks into cards-server (Phase β) so a
user can take a deck they've been editing locally and put it under
cards.mana.how/d/<slug> with one modal.
Pipeline:
• lib/api/cards-api.ts — typed fetch wrapper around the cards-server
/v1 surface. Reads the JWT from authStore, never from storage
directly. CardsApiError carries `{status, message, details}`
so UI can branch on 401/409/etc.
• lib/stores/author.svelte.ts — lazy-loaded author state. Caches
`cardsApi.authors.me()` on first access; resets cleanly on logout.
• lib/util/slug.ts — best-effort slugify mirror of the server-side
validator (server still has final say).
• lib/components/PublishDeckModal.svelte — three-stage flow:
become-author (slug + displayName + pseudonym), deck-meta (title,
description, language, license picker, semver, changelog), then
publishing → done with moderation-flag surface if AI mod returned
'flag'. Keys off authorStore.isAuthor to skip stage 1 for
returning authors.
• routes/decks/[id]/+page.svelte gets a "🌍 Veröffentlichen" button
next to "Lernen". Disabled until the deck has cards.
Wiring:
• hooks.server.ts injects __PUBLIC_CARDS_API_URL__ on every SSR'd
page so the client knows where cards-server lives.
• compose adds PUBLIC_CARDS_API_URL_CLIENT=https://cards-api.mana.how
to the cards-web container.
Validated: svelte-check 0/0, vite build green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2f-3 (final of the 2f-trio). The background tick-loop runner is
the most coupled of the three: it queries mana-api, mana-llm, and
mana-research, and writes through to the mana_sync DB. Wired up via
cross-LAN host-IPs to those Mini-side services + the existing RSA
key-pair for Mission-Grant decryption (MANA_AI_PRIVATE_KEY_PEM moved
into /srv/mana/.env on the GPU-Box; the matching MANA_AI_PUBLIC_KEY_PEM
stays on mana-auth's env-set as before).
Bonus rationale: AI Mission Runner now sits in the same compose
network as the GPU-Box's gpu-llm/gpu-ollama tasks, so future
"agent talks to local LLM" paths skip the Cloudflare round-trip.
Tunnel: mana-ai.mana.how repointed at the mana-gpu-server tunnel
(config v28). The Mini-side ingress was removed in the same step.
OTEL_EXPORTER_OTLP_ENDPOINT cleared since Tempo was retired in 2c.
Mini-side: container stopped + removed from docker-compose.macmini.yml.
Running count went from 39 → 42 because of unrelated services that
re-appeared on the latest CD pull (cards-server, memoro-web), but the
actual mana-ai service is gone — net move accomplished.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Shared-hono's serviceErrorHandler only translates HTTPException
instances; anything else degrades to 500. Our custom Error subclasses
were silently bypassing the translation layer, so a missing JWT came
back as `500 Internal server error` instead of the expected `401
Unauthorized`. Confirmed in prod logs after the Phase-β deploy.
Switching the error hierarchy to extend HTTPException directly. The
JSON body now carries the right status code + the existing `cause`
object surfaces our `code` discriminator + zod-style `details` for
BadRequest. No call-site changes needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
First user-facing surface on cards-server. Three endpoint groups:
Authors (/v1/authors):
- POST /me — upsert author profile (slug, displayName, bio,
avatarUrl, pseudonym). Slug validated for length, charset, and
against a small reserved-words list (admin, api, me, ...).
- GET /me — read own profile (returns null if not yet an author).
- GET /:slug — public profile (omits banned-reason, etc.)
Decks (/v1/decks):
- POST / — claim a slug + create the metadata-only deck row.
License defaults to Cards-Personal-Use-1.0; paid decks
(priceCredits > 0) must use Cards-Pro-Only-1.0 (CHECK constraint
+ service-side guard).
- GET /:slug — deck + latestVersion.
- POST /:slug/publish — version semver enforced strictly increasing,
AI-mod first-pass via mana-llm (block → 403; flag → publish + log
for human review; pass → publish silently). Per-card and per-
version SHA-256 content hashes computed; cards persisted; deck's
latest_version_id flipped atomically in a single transaction.
Helpers:
- lib/slug.ts — slugify (best-effort) + validateSlug (strict).
- lib/hash.ts — canonical SHA-256 over (type, fields) for cards
and (sorted, ord-stable) for versions.
- lib/ai-moderation.ts — mana-llm /v1/chat/completions wrapper
with system prompt that forces JSON output. Fail-open: if
mana-llm is down or returns malformed JSON, the verdict is
'flag' so a human reviewer catches it. Better slow than silent.
Index-mounting of /v1/authors and /v1/decks is gated behind jwtAuth.
Anonymous public reads (Phase γ optionalAuth middleware) come later.
Validated: tsc --noEmit clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2f-2. RSS/Atom ingester (15-min tick → mana_platform.news.curated_articles)
moved to GPU-Box. Service has zero hot-path coupling, all the writes go
cross-LAN to Mini-postgres analog to the Glitchtip pattern.
Two implementation gotchas worth recording:
1. cross-arch image transfer doesn't work. Saved news-ingester:local
from the Mini (Apple M4 → linux/arm64), tried `docker load` on the
GPU-Box (linux/amd64) and got 'exec format error' on every restart.
Native build on the GPU-Box was the only path forward.
2. The original services/news-ingester/Dockerfile assumes
pnpm-workspace state from prior builds (no COPY for packages/shared-rss
in the build context). Fresh builds error with
ERR_PNPM_WORKSPACE_PKG_NOT_FOUND.
Workaround: a GPU-Box-specific Dockerfile at infrastructure/news-ingester/
that vendors shared-rss into the build via a workspace:* → file:ref
sed swap. Build context is the repo root (sparse-clone provides
packages/shared-rss + services/news-ingester). The Mini-side Dockerfile
stays untouched so existing CD builds aren't disturbed.
Mini-side: container stopped + removed from docker-compose.macmini.yml,
running container count 44 → 39.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>