diff --git a/.gitignore b/.gitignore index dfa1915..7a76b4a 100644 --- a/.gitignore +++ b/.gitignore @@ -120,3 +120,4 @@ sbom*.xml NOTES.local.md TODO.local.md SCRATCH.md +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index bb184b1..4bdddca 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -12,7 +12,21 @@ **Auth-Entscheidung (2026-05-24):** Wir bleiben bei selbst geschriebenem Auth (Argon2id + DB-Sessions in `src/lib/auth/*`) statt `next-auth`. Begründung: passt zum Zero-Knowledge-Modell (ADR 0003), keine OAuth-Provider geplant, kein Beta-Code im Auth-Pfad. `next-auth` wurde aus `package.json` entfernt. -> **Letzte Aktualisierung:** 2026-06-07 — **Post-Backlog-Aufräumrunde:** (1) **Mind-Map-Parent-Links +> **Letzte Aktualisierung:** 2026-07-13 — **Frontend-Redesign: msk-shop Design-System übernommen** +> ([ADR 0025](docs/architecture/0025-adopt-msk-shop-design-system.md), löst ADR 0024 „Modern Minimal" ab). +> Die zuvor angefangene „Modern Minimal"-Identität (Geist-Fonts, mintgrüner `#2ee6a6`-Akzent) wurde +> **komplett verworfen** und durch das Design-System des Schwester-Projekts `msk-shop` ersetzt, für einen +> einheitlichen MSK-Marken-Look über beide Produkte: GitHub-Dark-Flächen (bg `#0d1117`, card `#161b22`, +> border `#30363d`) + MSK-Grün (`--color-primary` `#5eb131` dark / `#4ea426` light), Fonts **Inter Variable +> + JetBrains Mono** (self-hosted via `@fontsource-variable`, Geist entfernt), `next-themes` +> `defaultTheme="dark"`. Die **shadcn/ui + Radix-Primitives** (`src/components/ui/`) bleiben, neu eingefärbt. +> Token-Vokabular + Idiome (`.eyebrow`, `.msk-card`, `.msk-btn-*`, `.container-page`) 1:1 aus msk-shop in +> `src/app/globals.css`, mit **Legacy-Alias-Bridge** für die non-breaking Screen-Migration. Alle ~40 Screens +> auf die Primitives + kanonische Tokens migriert (native Checkboxen/Selects/File-Inputs/dnd-Handles bewusst +> nativ). typecheck + lint + build grün, Em-Dashes aus UI-Copy entfernt. Commit `feat(design): adopt +> msk-shop design system`. +> +> _Vorheriger Stand (2026-06-07):_ **Post-Backlog-Aufräumrunde:** (1) **Mind-Map-Parent-Links > round-trippen jetzt durch Board-Export/Import** (`ExportedCard.parentId` + Remap-Pass im Importer, > #49-Folgeschritt erledigt; `tests/unit/board-import.test.ts`). (2) **CSP-`style-src`-„offen"-Item > geschlossen via [ADR 0022](docs/architecture/0022-csp-style-src-unsafe-inline.md):** `'unsafe-inline'` @@ -336,9 +350,11 @@ Konsistent mit Moritz' bestehendem `msk-shop`-Projekt: - **Next.js 16** (App Router, React Server Components, Server Actions, Turbopack) - **React 19** - **TypeScript 5.x** (strict mode, `noUncheckedIndexedAccess`) -- **TailwindCSS 4** + **shadcn/ui** (Radix-basiert, accessibility-first) +- **TailwindCSS 4** (CSS-first `@theme`) + **shadcn/ui** (Radix-Primitives, copy-in unter `src/components/ui/`, accessibility-first) +- **Design-System = msk-shop** ([ADR 0025](docs/architecture/0025-adopt-msk-shop-design-system.md), löst ADR 0024 „Modern Minimal" ab): GitHub-Dark-Flächen (`#0d1117`/`#161b22`) + MSK-Grün (`--color-primary` `#5eb131` dark / `#4ea426` light). Token-Vokabular + Idiome (`.eyebrow`, `.msk-card`, `.msk-btn-*`, `.container-page`, `.hero-decor-gradient`) 1:1 aus msk-shop in `src/app/globals.css`, plus Legacy-Alias-Bridge für die Migration. +- **Fonts:** Inter Variable + JetBrains Mono, self-hosted via `@fontsource-variable/*` (kein CDN, kein Build-Time-Fetch, `font-src 'self'`). Ersetzt Geist. - **lucide-react** für Icons -- **next-themes** (Dark/Light/System) +- **next-themes** (Dark/Light/System, `defaultTheme="dark"` wie msk-shop) - **next-intl** für i18n (mind. DE + EN) - **react-markdown** + **remark-gfm** + **rehype-sanitize** für sanitisiertes Markdown-Rendering (Card-Beschreibungen + Kommentare, #33) @@ -650,7 +666,7 @@ Cross-Origin-Resource-Policy: same-origin ### 5.8 Import & Export - **Import von ✅ (#50):** MSKanban-JSON, Trello (JSON), GitHub Projects (`gh project item-list --format json`), Jira (CSV), Wekan (JSON), Plain CSV — alle Konverter in `src/lib/import/foreign.ts` münden in `ExportedBoardV1` → `importBoard()` (E2EE unter frischem BoardKey). -- **Export nach:** MSKanban-natives JSON, **Trello-kompatibles JSON ✅** (`downloadTrelloExport`, „Export Trello"-Button), Markdown (pro Board als .md-Datei mit allen Karten). CSV-Export noch offen. +- **Export nach:** MSKanban-natives JSON, **Trello-kompatibles JSON ✅** (`downloadTrelloExport`, „Export Trello"-Button), Markdown (pro Board als .md-Datei mit allen Karten), **CSV ✅** (`toCsv`/`downloadCsvExport`, „Export CSV"-Button — flach, eine Zeile pro Karte, Custom-Fields als Spalten, RFC-4180-Escaping, UTF-8-BOM für Excel; `tests/unit/csv-export.test.ts`). ### 5.9 Public Read-Only Boards @@ -1544,6 +1560,8 @@ mskanban` (+ optional `mskanban-ws`) → `/api/health` Check → deploy-Tag. | 0021 | Offline-first — hand-rolled Precache-SW (kein Workbox) + card-scoped Write-Outbox (Server-Autorität, LWW, nur idempotente Ops) | accepted | | 0022 | `style-src 'unsafe-inline'` beibehalten — CSP-Nonces decken keine inline `style`-Attribute (@dnd-kit-Transform); Script-Sink bleibt nonce-gelockt | accepted | | 0023 | Offline-Create via client-generierte IDs + idempotenter Create (card/comment), Comment-Edit offline-fähig — kein Temp-ID-Reconcile | accepted | +| 0024 | Frontend-Redesign „Modern Minimal" (Geist, `#2ee6a6`) — nur Foundation + Landing gebaut, dann verworfen | superseded by 0025 | +| 0025 | msk-shop Design-System übernehmen (GitHub-Dark + MSK-Grün, Inter/JetBrains Mono, shadcn/ui behalten, kanonische Tokens + Legacy-Alias-Bridge) | accepted | --- diff --git a/docs/architecture/0024-frontend-redesign-modern-minimal.md b/docs/architecture/0024-frontend-redesign-modern-minimal.md index 19d54e7..d0ff5d2 100644 --- a/docs/architecture/0024-frontend-redesign-modern-minimal.md +++ b/docs/architecture/0024-frontend-redesign-modern-minimal.md @@ -1,7 +1,10 @@ # 0024 — Frontend redesign: "Modern Minimal" identity + shadcn/ui + Radix -- **Status:** accepted (rollout in phases — this ADR covers the decision; PR1 - ships the token + font foundation) +- **Status:** superseded by [ADR 0025](0025-adopt-msk-shop-design-system.md) + (2026-07-12). The "Modern Minimal" identity (Geist, `#2ee6a6` accent) was + dropped in favour of adopting the msk-shop design system verbatim. The + shadcn/ui + Radix primitive foundation introduced here (PR2) is retained and + re-skinned. Kept for historical context. - **Date:** 2026-06-07 - **Deciders:** Moritz Kohm - **Supersedes (visual):** the original "MSK design language" (dark + `#00E676` @@ -84,6 +87,7 @@ buttons) are then migrated screen-by-screen onto the new primitives in later PRs ## References -- `docs/design/modern-minimal-mockup.html` — the approved direction preview. -- `src/app/globals.css` (tokens), `src/app/layout.tsx` (Geist wiring). +- `docs/design/modern-minimal-mockup.html` — the direction preview (removed + when this ADR was superseded; see the git history of ADR 0025). +- Superseded by [ADR 0025](0025-adopt-msk-shop-design-system.md). - Memory `design-overhaul-direction`. diff --git a/docs/architecture/0025-adopt-msk-shop-design-system.md b/docs/architecture/0025-adopt-msk-shop-design-system.md new file mode 100644 index 0000000..d0f3ed7 --- /dev/null +++ b/docs/architecture/0025-adopt-msk-shop-design-system.md @@ -0,0 +1,103 @@ +# 0025 — Adopt the msk-shop design system (supersedes ADR 0024) + +- **Status:** accepted +- **Date:** 2026-07-12 +- **Deciders:** Moritz Kohm +- **Supersedes:** [ADR 0024](0024-frontend-redesign-modern-minimal.md) ("Modern + Minimal") — its visual identity (Geist fonts, `#2ee6a6` accent, neutral-gray + surfaces) is dropped. The **shadcn/ui + Radix primitive foundation from ADR + 0024 PR2 is kept** and re-skinned; only the identity is replaced. + +## Context + +ADR 0024 introduced a "Modern Minimal / Linear-esque" identity and installed +shadcn/ui + Radix primitives, but only the landing page was migrated onto it +(the authenticated app still ran on the old hand-rolled idiom). The user then +decided the app should instead look **exactly like the sibling `msk-shop` +project** (`../msk-shop`, www.msk-scripts.de) for a single, consistent MSK brand +across both products, and to keep using shadcn/ui. + +`msk-shop` already ships a mature Tailwind v4 CSS-first design system +(`msk-shop/app/globals.css`): GitHub-Dark surfaces + MSK green, Inter + JetBrains +Mono (self-hosted via `@fontsource-variable`), and a set of signature idioms +(`.eyebrow`, `.msk-card`, `.msk-btn-*`, `.container-page`, `.hero-decor-gradient`). + +## Decision + +### 1. Visual identity → the msk-shop design system, verbatim + +Adopt msk-shop's token **values and vocabulary**: + +- **Surfaces (dark, default-dark app):** bg `#0d1117`, card/panel `#161b22`, + border `#30363d`, secondary `#21262d`. Light mode: bg `#ffffff`, muted + `#f4f4f5`, border `#e4e4e7`. +- **Brand:** MSK green — `--color-primary` `#5eb131` (dark) / `#4ea426` (light), + white foreground. This is the single action colour (`--color-accent` aliases + the same green in mskanban — we do not introduce msk-shop's separate bright + highlight accent, to avoid a contrast regression on existing buttons). +- **Type:** Inter Variable (sans) + JetBrains Mono (mono), self-hosted via + `@fontsource-variable/*` — no CDN, no build-time fetch (satisfies §16.8 / + `font-src 'self'`). Replaces Geist. +- **Idioms:** `.eyebrow` (mono-uppercase green section label), `.msk-card`, + `.msk-btn-*`, `.container-page`, `.hero-decor-gradient`, `.pulse-dot`. + +`next-themes` `defaultTheme` moves from `"system"` to `"dark"` (matches msk-shop +and the "durchgängig dark" MSK brand; `enableSystem` stays so users can still +switch). + +### 2. Token vocabulary → msk-shop canonical names, with legacy aliases + +The canonical tokens are msk-shop's (`--color-background`, `--color-foreground`, +`--color-primary`, `--color-card`, `--color-muted`, `--color-border`, +`--color-ring`, `--color-secondary`, …). To migrate the ~40 existing screens +**without a breaking big-bang**, `globals.css` also defines a **legacy-alias +block** mapping the old ADR-0024 token names (`--color-bg`, `--color-accent`, +`--color-text-primary`, `--color-bg-panel`, …) onto the canonical ones. Every +not-yet-migrated component therefore keeps rendering correctly in the new +palette. The alias block is removed once all screens use canonical names. + +### 3. Component foundation → keep shadcn/ui + Radix, re-skinned + +The `src/components/ui/*` primitives (button, input, card, badge, dialog, +dropdown-menu, tabs, tooltip, switch, avatar, label, separator, textarea) are +kept and re-styled on the msk-shop tokens/idioms. Missing primitives are added +via the shadcn copy-in approach as screens need them. + +### 4. Rollout → foundation first, then per-screen migration + +- **Foundation (one commit):** re-theme `globals.css` (tokens + idioms), swap + fonts (Geist → Inter/JetBrains Mono), `defaultTheme="dark"`, re-skin the + tone-setting primitives (button/input/card/badge). The whole app re-skins to + the msk-shop look immediately via the legacy aliases. +- **Per-screen migration:** each of the ~40 screens is migrated onto the ui/ + primitives + msk-shop idioms + canonical tokens. **Presentation only** — no + logic, data-flow, hook, prop, API or crypto changes; all a11y attributes + preserved. +- **Cleanup:** remove the legacy-alias block once no screen references the old + names. + +## Consequences + +**Positive** + +- One consistent MSK brand across msk-shop and MSKanban; components/tokens are + portable between the two repos. +- Whole-app re-skin lands safely in one foundational commit (alias bridge). +- Keeps the a11y-first Radix primitives; no new runtime/CDN/telemetry beyond the + bundled `@fontsource-variable` packages (Geist is removed, net-neutral). + +**Negative / interim** + +- During the per-screen migration the app runs on a mix of canonical + legacy + token names (both valid, identical output). +- msk-shop's green-on-white / white-on-green button contrast is below WCAG AA + for normal text; we replicate it for brand parity. `color-contrast` stays + ungated in the a11y test suite (§5.10), consistent with the prior decision. + +## References + +- `../msk-shop/app/globals.css`, `../msk-shop/app/layout.tsx` — the source design + system. +- `src/app/globals.css` (tokens + idioms + legacy aliases), `src/app/layout.tsx` + (font wiring), `src/components/theme-provider.tsx` (`defaultTheme="dark"`). +- Memory `design-overhaul-direction`. diff --git a/docs/design/modern-minimal-mockup.html b/docs/design/modern-minimal-mockup.html deleted file mode 100644 index 193af84..0000000 --- a/docs/design/modern-minimal-mockup.html +++ /dev/null @@ -1,639 +0,0 @@ - - -
- - -+
Manage your email, password, sessions and two-factor authentication.
+
Email me on a new sign-in (new device/IP):{" "} {enabled ? "on" : "off"}
- + aria-label="Email me on a new sign-in" + />+
Requires the server operator to have configured a mail server. Account-lock alerts are always sent and cannot be turned off.
@@ -206,50 +210,45 @@ function ApiTokensSection() { return (+
Personal access tokens authenticate the public REST API (
/api/v1). They expose{" "}
- metadata + ciphertext only — the server cannot decrypt your content, so a
+ metadata + ciphertext only. The server cannot decrypt your content, so a
token never grants plaintext access.
{created}
Loading…
+Loading…
) : tokens.length === 0 ? ( -No tokens yet.
+No tokens yet.
) : (
+
Download everything the server stores about you as JSON (DSGVO Art. 15/20). Card content is
- encrypted — decrypt it with your own keys.
+ encrypted, decrypt it with your own keys.
{error}
+
This crypto-shreds your account immediately: your keys are destroyed, so all your encrypted content becomes permanently unrecoverable. The account is then hard-deleted after a 30-day grace period. This cannot be undone.
@@ -474,26 +467,29 @@ function DeviceUnlockSection() {
return (
+
Stay unlocked on this device:{" "}
{enabled === null ? "…" : enabled ? "on" : "off"}
+
When on, reloads and new windows skip the password prompt — your key is kept on this device
behind a non-extractable browser key. Less secure: anyone with access to this device/browser
profile can open your vault without the password. Off by default; cleared on sign-out.
@@ -504,10 +500,8 @@ function DeviceUnlockSection() {
function Card({ title, children }: { title: string; children: React.ReactNode }) {
return (
- {msg}
- {title}
-
+ {title}
{children}
- Your data is re-encrypted locally — the new password derives a fresh key. Your 24-word +
+ Your data is re-encrypted locally, the new password derives a fresh key. Your 24-word recovery phrase from sign-up is unaffected and still works.
+
Two-factor authentication:{" "} {totpEnabled ? "authenticator app enabled" : "off"} {" "} · manage passkeys & TOTP on the 2FA page.
- - Manage 2FA - +- Lost your password? Recovery uses the 24-word phrase you saved at sign-up — see the recovery +
+ Lost your password? Recovery uses the 24-word phrase you saved at sign-up, see the recovery page from the login screen.
Loading…
+Loading…
) : (- Server-wide user management. Metadata only — content stays end-to-end encrypted and is +
+ Server-wide user management. Metadata only, content stays end-to-end encrypted and is never visible here.
| User | Created | Last login | @@ -102,20 +104,20 @@ export function AdminClient({ {u.deletionRequested ?+ | {fmt(u.createdAt)} | -+ | {u.lastLoginAt ? fmt(u.lastLoginAt) : "—"} |
{locked ? (
locked
) : (
- active
+ active
)}
{u.failedLoginAttempts > 0 ? (
-
+
{u.failedLoginAttempts} failed
) : null}
@@ -169,19 +171,8 @@ function fmt(iso: string): string {
}
function Tag({ children, tone }: { children: React.ReactNode; tone?: "accent" | "danger" }) {
- const color =
- tone === "accent"
- ? "text-[color:var(--color-accent)] border-[color:var(--color-border-accent)]"
- : tone === "danger"
- ? "text-[color:var(--color-danger)] border-[color:var(--color-danger)]"
- : "text-[color:var(--color-text-muted)] border-[color:var(--color-border)]";
- return (
-
- {children}
-
- );
+ const variant = tone === "accent" ? "accent" : tone === "danger" ? "danger" : "outline";
+ return
-
+
diff --git a/src/app/(app)/workspaces/[wsId]/boards/[boardId]/activity-panel.tsx b/src/app/(app)/workspaces/[wsId]/boards/[boardId]/activity-panel.tsx
index 6ecd90e..453613a 100644
--- a/src/app/(app)/workspaces/[wsId]/boards/[boardId]/activity-panel.tsx
+++ b/src/app/(app)/workspaces/[wsId]/boards/[boardId]/activity-panel.tsx
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useState } from "react";
+import { Button } from "@/components/ui/button";
import type { LabelView, MemberView } from "./card-drawer";
import type { KanbanCard } from "./kanban-view";
@@ -65,19 +66,18 @@ export function ActivityPanel({
}, [boardId, open, refreshKey]);
return (
- |
|---|