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

View file

@ -22,7 +22,7 @@ const CSP_POLICY = [
"img-src 'self' data: blob:", "img-src 'self' data: blob:",
"font-src 'self' data:", "font-src 'self' data:",
// XHR/fetch-Ziele: eigene API + Auth-Portal + LLM/Share/MCP-Plattform. // 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:", "media-src 'self' blob:",
"frame-ancestors 'none'", "frame-ancestors 'none'",
"base-uri 'self'", "base-uri 'self'",

View file

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

View file

@ -20,7 +20,7 @@ export const de: TranslationNode = {
landing: { landing: {
welcome: 'Lernkarten mit Spaced-Repetition.', welcome: 'Lernkarten mit Spaced-Repetition.',
intro: 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)', cta_login: 'Login (dev)',
dev_user_prompt: 'User-ID (dev):', dev_user_prompt: 'User-ID (dev):',
}, },

View file

@ -17,7 +17,7 @@ export const en: TranslationNode = {
landing: { landing: {
welcome: 'Spaced-repetition flashcards.', welcome: 'Spaced-repetition flashcards.',
intro: 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)', cta_login: 'Login (dev)',
dev_user_prompt: 'User ID (dev):', dev_user_prompt: 'User ID (dev):',
}, },

View file

@ -17,7 +17,7 @@ export const es: TranslationNode = {
landing: { landing: {
welcome: 'Tarjetas de memoria con repetición espaciada.', welcome: 'Tarjetas de memoria con repetición espaciada.',
intro: 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)', cta_login: 'Iniciar sesión (dev)',
dev_user_prompt: 'ID de usuario (dev):', dev_user_prompt: 'ID de usuario (dev):',
}, },

View file

@ -17,7 +17,7 @@ export const fr: TranslationNode = {
landing: { landing: {
welcome: 'Fiches de révision espacée.', welcome: 'Fiches de révision espacée.',
intro: 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)', cta_login: 'Connexion (dev)',
dev_user_prompt: 'ID utilisateur (dev) :', dev_user_prompt: 'ID utilisateur (dev) :',
}, },

View file

@ -17,7 +17,7 @@ export const it: TranslationNode = {
landing: { landing: {
welcome: 'Flashcard con ripetizione spaziata.', welcome: 'Flashcard con ripetizione spaziata.',
intro: 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)', cta_login: 'Accesso (dev)',
dev_user_prompt: 'ID utente (dev):', dev_user_prompt: 'ID utente (dev):',
}, },

View file

@ -11,7 +11,7 @@
function callbackUrl(): string { function callbackUrl(): string {
const base = 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 next = page.url.searchParams.get('next');
const nextParam = next ? `?next=${encodeURIComponent(next)}` : ''; const nextParam = next ? `?next=${encodeURIComponent(next)}` : '';
return `${base}/auth/callback${nextParam}`; return `${base}/auth/callback${nextParam}`;

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -46,7 +46,7 @@
</script> </script>
<svelte:head> <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> </svelte:head>
<div class="screen-controls"> <div class="screen-controls">

View file

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

View file

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

View file

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

View file

@ -96,12 +96,12 @@
const pageSubtitle = $derived( const pageSubtitle = $derived(
author author
? `@${author.slug} · ${decks.length} Deck${decks.length !== 1 ? 's' : ''} veröffentlicht` ? `@${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> </script>
<svelte:head> <svelte:head>
<title>{pageTitle} · Cardecky</title> <title>{pageTitle} · Wordeck</title>
</svelte:head> </svelte:head>
<div class="page-shell"> <div class="page-shell">
@ -135,7 +135,7 @@
<label class="field"> <label class="field">
<span class="field-label"> <span class="field-label">
Slug <span class="slug-preview" 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> </span>
<input <input

View file

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

View file

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