feat: Wordeck-Web-Branding + Manifest-Cutover
Some checks are pending
CI / validate (push) Waiting to run

Cards-zu-Wordeck-Umbennung im Web-Layer komplett:

- app-manifest.json: id, name, homepage, icon, base_url, deep_link_scheme,
  link_patterns alle auf wordeck. tools[].name auch (cards.create →
  wordeck.create — bei mana-mcp wird das beim re-upsert konsumiert).
- 5 i18n-Files (de/en/fr/es/it): App-Beschreibung von Cardecky-zu-Wordeck-
  Wording, USP "text-first" wo es passt.
- 11 Routes mit Page-<title>: Cardecky → Wordeck.
- CSP connect-src ergänzt um api.wordeck.com (cardecky-api bleibt während
  Übergang).
- AASA exposed jetzt BEIDE Bundle-IDs (ev.mana.cardecky alt + ev.mana.wordeck
  neu) — die alte Native bleibt während der Ω-3 Bauphase funktional.
- UI-Slug-Vorschau (PublishDeckModal, +page, me/published): wordeck.com.

DNS für wordeck.com + api.wordeck.com + www.wordeck.com sind als
Cloudflare-Tunnel-CNAMEs angelegt; cloudflared-Ingress auf mana-server
patched + reloaded. wordeck.com antwortet HTTP 200 mit Cards-Container
(Branding-Update hier macht das jetzt zu Wordeck).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-05-17 20:46:10 +02:00
parent 22927fbe59
commit 39fe22a018
21 changed files with 58 additions and 52 deletions

View file

@ -1,31 +1,31 @@
{
"$schema": "https://pkg.mana.how/@mana/shared-share-protocol/0.1/manifest.json",
"schema_version": "0.1",
"id": "cards",
"name": "Cardecky",
"id": "wordeck",
"name": "Wordeck",
"version": "1.0.0",
"homepage": "https://cardecky.mana.how",
"icon": "https://cardecky.mana.how/icon-512.png",
"description": "Spaced-repetition flashcards with FSRS scheduling.",
"description_de": "Lernkarten mit Spaced-Repetition (FSRS).",
"homepage": "https://wordeck.com",
"icon": "https://wordeck.com/icon-512.png",
"description": "Spaced-repetition flashcards with FSRS scheduling — text-first, no images, no audio.",
"description_de": "Spaced Repetition, text-first. Nur Worte, keine Bilder, kein Audio.",
"ownership": { "kind": "verein" },
"tier_required": "beta",
"endpoints": {
"base_url": "https://cardecky.mana.how",
"base_url": "https://wordeck.com",
"health": "/healthz",
"dsgvo_export": "/api/v1/dsgvo/export",
"search": "/api/v1/search",
"share_receive": "/api/v1/share/receive",
"tool_invoke": "/api/v1/tools/:name",
"deep_link_scheme": "cards://",
"deep_link_scheme": "wordeck://",
"link_patterns": [
{
"pattern": "card/(?<id>[a-z0-9_-]+)",
"template": "https://cardecky.mana.how/c/{id}"
"template": "https://wordeck.com/c/{id}"
},
{
"pattern": "deck/(?<id>[a-z0-9_-]+)",
"template": "https://cardecky.mana.how/d/{id}"
"template": "https://wordeck.com/d/{id}"
}
]
},
@ -37,14 +37,14 @@
],
"tools": [
{
"name": "cards.create",
"name": "wordeck.create",
"description": "Erzeugt eine Lernkarte für den authentifizierten User.",
"input_schema": { "$ref": "/payload-schemas/create-card-input.json" },
"output_schema": { "$ref": "/payload-schemas/card.json" },
"auth": "user_token"
},
{
"name": "cards.search",
"name": "wordeck.search",
"description": "Sucht in den Lernkarten des authentifizierten Users.",
"input_schema": { "$ref": "/payload-schemas/search-input.json" },
"output_schema": { "$ref": "/payload-schemas/search-output.json" },

View file

@ -22,7 +22,7 @@ const CSP_POLICY = [
"img-src 'self' data: blob:",
"font-src 'self' data:",
// XHR/fetch-Ziele: eigene API + Auth-Portal + LLM/Share/MCP-Plattform.
"connect-src 'self' https://cardecky-api.mana.how https://auth.mana.how https://share.mana.how https://mcp.mana.how",
"connect-src 'self' https://api.wordeck.com https://cardecky-api.mana.how https://auth.mana.how https://share.mana.how https://mcp.mana.how",
"media-src 'self' blob:",
"frame-ancestors 'none'",
"base-uri 'self'",

View file

@ -193,7 +193,7 @@
<div class="field">
<label class="field-label" for="pub-slug">Marketplace-Slug</label>
<div class="slug-row">
<span class="slug-prefix">cardecky.mana.how/d/</span>
<span class="slug-prefix">wordeck.com/d/</span>
<input
id="pub-slug"
type="text"

View file

@ -20,7 +20,7 @@ export const de: TranslationNode = {
landing: {
welcome: 'Lernkarten mit Spaced-Repetition.',
intro:
'Cardecky ist die föderierte Karteikarten-App des Vereins mana e.V. — FSRS-Scheduler, Cloze-Karten, Anki-Import.',
'Wordeck ist die föderierte Karteikarten-App des Vereins mana e.V. — FSRS-Scheduler, Cloze-Karten, Anki-Import.',
cta_login: 'Login (dev)',
dev_user_prompt: 'User-ID (dev):',
},

View file

@ -17,7 +17,7 @@ export const en: TranslationNode = {
landing: {
welcome: 'Spaced-repetition flashcards.',
intro:
'Cardecky is the federated flashcard app of mana e.V. — FSRS scheduler, cloze cards, Anki import.',
'Wordeck is the federated flashcard app of mana e.V. — FSRS scheduler, cloze cards, Anki import.',
cta_login: 'Login (dev)',
dev_user_prompt: 'User ID (dev):',
},

View file

@ -17,7 +17,7 @@ export const es: TranslationNode = {
landing: {
welcome: 'Tarjetas de memoria con repetición espaciada.',
intro:
'Cardecky es la app federada de flashcards de mana e.V. — planificador FSRS, tarjetas cloze, importación Anki.',
'Wordeck es la app federada de flashcards de mana e.V. — planificador FSRS, tarjetas cloze, importación Anki.',
cta_login: 'Iniciar sesión (dev)',
dev_user_prompt: 'ID de usuario (dev):',
},

View file

@ -17,7 +17,7 @@ export const fr: TranslationNode = {
landing: {
welcome: 'Fiches de révision espacée.',
intro:
"Cardecky est l'application de fiches fédérée de mana e.V. — planificateur FSRS, cartes à trous, import Anki.",
"Wordeck est l'application de fiches fédérée de mana e.V. — planificateur FSRS, cartes à trous, import Anki.",
cta_login: 'Connexion (dev)',
dev_user_prompt: 'ID utilisateur (dev) :',
},

View file

@ -17,7 +17,7 @@ export const it: TranslationNode = {
landing: {
welcome: 'Flashcard con ripetizione spaziata.',
intro:
"Cardecky è l'app di flashcard federata di mana e.V. — pianificatore FSRS, carte cloze, importazione Anki.",
"Wordeck è l'app di flashcard federata di mana e.V. — pianificatore FSRS, carte cloze, importazione Anki.",
cta_login: 'Accesso (dev)',
dev_user_prompt: 'ID utente (dev):',
},

View file

@ -11,7 +11,7 @@
function callbackUrl(): string {
const base =
typeof window !== 'undefined' ? window.location.origin : 'https://cardecky.mana.how';
typeof window !== 'undefined' ? window.location.origin : 'https://wordeck.com';
const next = page.url.searchParams.get('next');
const nextParam = next ? `?next=${encodeURIComponent(next)}` : '';
return `${base}/auth/callback${nextParam}`;

View file

@ -2,13 +2,13 @@ import { env } from '$env/dynamic/public';
import type { RequestHandler } from './$types';
/**
* Apple App-Site Association für die cards-native iOS-App.
* Apple App-Site Association für die Wordeck-iOS-App.
*
* Apple holt diese Datei beim ersten App-Launch (und gelegentlich danach)
* von `https://cardecky.mana.how/.well-known/apple-app-site-association`
* und vergleicht sie mit dem `applinks:cardecky.mana.how`-Entitlement
* der App. Bei Match werden Links auf `/d/<slug>` direkt in die App
* geöffnet statt im Browser.
* von `https://wordeck.com/.well-known/apple-app-site-association` (sowie
* von der Legacy-Domain `https://cardecky.mana.how/...`) und vergleicht
* sie mit dem `applinks:`-Entitlement der App. Bei Match werden Links
* auf `/d/<slug>` direkt in die App geöffnet statt im Browser.
*
* **Pflichtbedingungen:**
* 1. Antwort MUSS `Content-Type: application/json` haben (nicht
@ -21,22 +21,28 @@ import type { RequestHandler } from './$types';
* Developer-Accounts gesetzt werden, sonst akzeptiert Apple die AASA
* nicht.
*
* Cross-Ref: `cards-native/docs/RELEASE_CHECKLIST.md` "Server-seitige
* Vorbedingungen".
* Beide Bundle-IDs sind aktiv:
* - `ev.mana.cardecky` (cards-native v0.9.4, End-of-Life)
* - `ev.mana.wordeck` (Wordeck-Native ab Ω-3, neue ASC-Eintragung)
*
* Cross-Ref: `mana/docs/playbooks/WORDECK_REBRAND.md`,
* `cards-native/docs/RELEASE_CHECKLIST.md` "Server-seitige Vorbedingungen".
*/
export const GET: RequestHandler = async () => {
const teamId = env.PUBLIC_APPLE_TEAM_ID ?? 'XXXXXXXXXX';
const bundleId = 'ev.mana.cardecky';
const payload = {
applinks: {
apps: [],
details: [
{
appID: `${teamId}.${bundleId}`,
// `/d/*` Deck-Slugs, `/u/*` User-Profile, `/auth/*` für
// Reset-/Verify-Mail-Klicks von mana-auth — der Mail-Link
// öffnet damit direkt die App via Universal Link.
// Legacy: cards-native v0.9.4 (End-of-Life nach Wordeck-Cutover)
appID: `${teamId}.ev.mana.cardecky`,
paths: ['/d/*', '/u/*', '/auth/*'],
},
{
// Aktuell: Wordeck-Native (neuer Bundle ab Ω-3)
appID: `${teamId}.ev.mana.wordeck`,
paths: ['/d/*', '/u/*', '/auth/*'],
},
],

View file

@ -6,7 +6,7 @@
/**
* Web-Fallback für die Reset-Password-Mail von mana-auth.
*
* Auf iOS-Geräten mit installierter Cardecky-App öffnet Apple den
* Auf iOS-Geräten mit installierter Wordeck-App öffnet Apple den
* Universal Link direkt in der App, diese Page wird nie gerendert.
*
* Für alle anderen User (kein iOS, App nicht installiert, Desktop)
@ -32,7 +32,7 @@
</script>
<svelte:head>
<title>Passwort zurücksetzen — Cardecky</title>
<title>Passwort zurücksetzen — Wordeck</title>
</svelte:head>
<main class="container">

View file

@ -6,7 +6,7 @@
/**
* Web-Fallback für die Verify-Email-Mail von mana-auth.
*
* Auf iOS-Geräten mit installierter Cardecky-App öffnet Apple den
* Auf iOS-Geräten mit installierter Wordeck-App öffnet Apple den
* Universal Link direkt in der App. Für alle anderen User leiten
* wir zum Better-Auth-Verify-Endpoint auf auth.mana.how weiter,
* der den Token konsumiert und auf das Auth-Portal redirected.
@ -32,7 +32,7 @@
</script>
<svelte:head>
<title>Email bestätigen — Cardecky</title>
<title>Email bestätigen — Wordeck</title>
</svelte:head>
<main class="container">

View file

@ -157,7 +157,7 @@
</script>
<svelte:head>
<title>{deck?.title ?? slug} · Cardecky</title>
<title>{deck?.title ?? slug} · Wordeck</title>
</svelte:head>
{#if loading}

View file

@ -163,7 +163,7 @@
</script>
<svelte:head>
<title>Meine Decks · Cardecky</title>
<title>Meine Decks · Wordeck</title>
</svelte:head>
<div class="space-y-10">

View file

@ -46,7 +46,7 @@
</script>
<svelte:head>
<title>{deck ? t('print.title', { deck: deck.name }) : t('print.loading')} · Cardecky</title>
<title>{deck ? t('print.title', { deck: deck.name }) : t('print.loading')} · Wordeck</title>
</svelte:head>
<div class="screen-controls">

View file

@ -78,12 +78,12 @@
</script>
<svelte:head>
<title>Explore · Cardecky</title>
<title>Explore · Wordeck</title>
</svelte:head>
<div class="space-y-10">
<header>
<h1 class="text-3xl font-semibold">Cardecky-Library</h1>
<h1 class="text-3xl font-semibold">Wordeck-Library</h1>
</header>
{#if loadingExplore}

View file

@ -1,13 +1,13 @@
<svelte:head>
<title>Hilfe — Cardecky</title>
<meta name="description" content="Hilfe und Support für Cardecky." />
<title>Hilfe — Wordeck</title>
<meta name="description" content="Hilfe und Support für Wordeck." />
</svelte:head>
<main class="page">
<h1>Hilfe &amp; Support</h1>
<p class="lead">
Cardecky wird vom Verein <strong>mana e.V.</strong> (i.&nbsp;G.) betrieben.
Wordeck wird vom Verein <strong>mana e.V.</strong> (i.&nbsp;G.) betrieben.
Bei Fragen oder Problemen melde dich direkt bei uns — wir antworten meist innerhalb
eines Tages.
</p>
@ -22,7 +22,7 @@
<h3>Wie funktioniert das Lern-Schema?</h3>
<p>
Cardecky nutzt <a href="https://github.com/open-spaced-repetition/fsrs4anki">FSRS</a>
Wordeck nutzt <a href="https://github.com/open-spaced-repetition/fsrs4anki">FSRS</a>
(Free Spaced Repetition Scheduler), den genauesten offenen Algorithmus für
Karteikarten. Karten kommen wieder, wenn deine Erinnerung sie braucht — und nicht
nach starren Intervallen.
@ -41,7 +41,7 @@
und beim nächsten Online-Moment synchronisiert.
</p>
<h3>Was ist mit dem Marketplace (Cardecky)?</h3>
<h3>Was ist mit dem Marketplace (Wordeck)?</h3>
<p>
Der Marketplace unter <a href="/explore">Entdecken</a> ist die offene
Sammlung freigegebener Decks. Subscriben heißt: das Deck wird in deine eigene

View file

@ -57,7 +57,7 @@
</script>
<svelte:head>
<title>Meine Forks · Cardecky</title>
<title>Meine Forks · Wordeck</title>
</svelte:head>
<div class="mx-auto max-w-4xl space-y-6">

View file

@ -96,12 +96,12 @@
const pageSubtitle = $derived(
author
? `@${author.slug} · ${decks.length} Deck${decks.length !== 1 ? 's' : ''} veröffentlicht`
: 'Lege dein Author-Profil an um eigene Decks im Cardecky-Marketplace zu veröffentlichen.'
: 'Lege dein Author-Profil an um eigene Decks im Wordeck-Marketplace zu veröffentlichen.'
);
</script>
<svelte:head>
<title>{pageTitle} · Cardecky</title>
<title>{pageTitle} · Wordeck</title>
</svelte:head>
<div class="page-shell">
@ -135,7 +135,7 @@
<label class="field">
<span class="field-label">
Slug <span class="slug-preview"
>{formSlug ? `cardecky.mana.how/a/${formSlug}` : 'url-safe, z. B. till-hoffmann'}</span
>{formSlug ? `wordeck.com/a/${formSlug}` : 'url-safe, z. B. till-hoffmann'}</span
>
</span>
<input

View file

@ -30,7 +30,7 @@
</script>
<svelte:head>
<title>Meine Abos · Cardecky</title>
<title>Meine Abos · Wordeck</title>
</svelte:head>
<div class="mx-auto max-w-4xl space-y-6">

View file

@ -74,7 +74,7 @@
</script>
<svelte:head>
<title>{author?.display_name ?? slug} · Cardecky</title>
<title>{author?.display_name ?? slug} · Wordeck</title>
</svelte:head>
{#if loading}