From c086a226e778ea91eb7ffc888fc2f5af686c49f2 Mon Sep 17 00:00:00 2001 From: Musiker15 Date: Mon, 13 Jul 2026 08:12:10 +0200 Subject: [PATCH 1/5] feat(design): adopt msk-shop design system (ADR 0025, supersede ADR 0024) Replace the abandoned "Modern Minimal" identity with the msk-shop design system for one consistent MSK brand across both products: GitHub-Dark surfaces + MSK green, Inter Variable + JetBrains Mono (self-hosted, no CDN/telemetry), next-themes default dark. The shadcn/ui + Radix primitive foundation is kept and re-skinned. - globals.css: msk-shop tokens + idioms (.eyebrow, .msk-card, .msk-btn-*, .container-page, .hero-decor-gradient) plus a legacy-alias bridge so the screen migration stays non-breaking - fonts: remove geist, add @fontsource-variable/inter + jetbrains-mono - theme-provider: defaultTheme dark (matches msk-shop) - re-skin ui/ primitives and canonicalise every token across the tree - migrate the screens onto the primitives (auth, board, card-drawer, views, panels, workspaces, account, admin, landing, public); native checkboxes, selects, file inputs and dnd handles kept as-is - remove em-dashes from user-facing copy typecheck + lint + build green. Signed-off-by: Musiker15 --- .../0024-frontend-redesign-modern-minimal.md | 7 +- .../0025-adopt-msk-shop-design-system.md | 103 +++ package.json | 3 +- pnpm-lock.yaml | 28 +- src/app/(app)/account/account-client.tsx | 236 ++++--- src/app/(app)/admin/admin-client.tsx | 48 +- src/app/(app)/layout.tsx | 41 +- .../boards/[boardId]/activity-panel.tsx | 22 +- .../boards/[boardId]/analytics-view.tsx | 82 +-- .../boards/[boardId]/attachments-section.tsx | 35 +- .../boards/[boardId]/automation-panel.tsx | 145 ++--- .../[wsId]/boards/[boardId]/board-client.tsx | 60 +- .../[wsId]/boards/[boardId]/calendar-view.tsx | 47 +- .../[wsId]/boards/[boardId]/card-drawer.tsx | 310 ++++----- .../boards/[boardId]/description-editor.tsx | 12 +- .../[wsId]/boards/[boardId]/kanban-view.tsx | 67 +- .../boards/[boardId]/milestone-manager.tsx | 130 ++-- .../[wsId]/boards/[boardId]/mind-map-view.tsx | 8 +- .../[wsId]/boards/[boardId]/share-panel.tsx | 41 +- .../[wsId]/boards/[boardId]/table-view.tsx | 33 +- .../[wsId]/boards/[boardId]/timeline-view.tsx | 64 +- .../boards/[boardId]/webhooks-panel.tsx | 113 ++-- .../(app)/workspaces/[wsId]/members-panel.tsx | 40 +- .../workspaces/[wsId]/workspace-client.tsx | 74 +-- .../(app)/workspaces/workspaces-client.tsx | 52 +- src/app/(auth)/2fa/page.tsx | 5 +- src/app/(auth)/2fa/passkey-manager.tsx | 32 +- src/app/(auth)/2fa/two-factor-enroll.tsx | 81 ++- src/app/(auth)/layout.tsx | 6 +- src/app/(auth)/login/login-form.tsx | 49 +- src/app/(auth)/login/page.tsx | 11 +- src/app/(auth)/recover/page.tsx | 11 +- src/app/(auth)/recover/recover-form.tsx | 50 +- src/app/(auth)/register/page.tsx | 11 +- src/app/(auth)/register/register-form.tsx | 52 +- src/app/error.tsx | 10 +- src/app/globals.css | 598 +++++++++++++----- src/app/layout.tsx | 12 +- src/app/not-found.tsx | 12 +- src/app/p/[slug]/page.tsx | 2 +- src/app/p/[slug]/public-board-view.tsx | 20 +- src/app/page.tsx | 66 +- src/components/landing/demo-board.tsx | 40 +- src/components/language-switcher.tsx | 2 +- src/components/notifications-bell.tsx | 18 +- src/components/offline-sync.tsx | 10 +- src/components/online-status.tsx | 4 +- src/components/theme-provider.tsx | 2 +- src/components/ui/avatar.tsx | 2 +- src/components/ui/badge.tsx | 11 +- src/components/ui/button.tsx | 25 +- src/components/ui/card.tsx | 8 +- src/components/ui/dialog.tsx | 8 +- src/components/ui/dropdown-menu.tsx | 8 +- src/components/ui/input.tsx | 8 +- src/components/ui/label.tsx | 2 +- src/components/ui/switch.tsx | 2 +- src/components/ui/tabs.tsx | 6 +- src/components/ui/textarea.tsx | 6 +- src/components/ui/tooltip.tsx | 2 +- src/components/unlock-gate.tsx | 37 +- src/types/fontsource.d.ts | 5 + 62 files changed, 1662 insertions(+), 1373 deletions(-) create mode 100644 docs/architecture/0025-adopt-msk-shop-design-system.md create mode 100644 src/types/fontsource.d.ts diff --git a/docs/architecture/0024-frontend-redesign-modern-minimal.md b/docs/architecture/0024-frontend-redesign-modern-minimal.md index 19d54e7..251aab5 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` 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/package.json b/package.json index 014c535..7bc1a32 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,8 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "3.2.2", + "@fontsource-variable/inter": "^5.2.8", + "@fontsource-variable/jetbrains-mono": "^5.2.8", "@hookform/resolvers": "^5.4.0", "@node-rs/argon2": "^2.0.2", "@prisma/adapter-mariadb": "^7.8.0", @@ -69,7 +71,6 @@ "@simplewebauthn/server": "^13.3.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "geist": "^1.7.2", "idb": "^8.0.0", "libsodium-wrappers-sumo": "0.8.4", "lucide-react": "^1.17.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 248b5a2..549980b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -35,6 +35,12 @@ importers: '@dnd-kit/utilities': specifier: 3.2.2 version: 3.2.2(react@19.2.6) + '@fontsource-variable/inter': + specifier: ^5.2.8 + version: 5.2.8 + '@fontsource-variable/jetbrains-mono': + specifier: ^5.2.8 + version: 5.2.8 '@hookform/resolvers': specifier: ^5.4.0 version: 5.4.0(react-hook-form@7.77.0(react@19.2.6)) @@ -89,9 +95,6 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 - geist: - specifier: ^1.7.2 - version: 1.7.2(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)) idb: specifier: ^8.0.0 version: 8.0.3 @@ -884,6 +887,12 @@ packages: '@floating-ui/utils@0.2.11': resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + '@fontsource-variable/inter@5.2.8': + resolution: {integrity: sha512-kOfP2D+ykbcX/P3IFnokOhVRNoTozo5/JxhAIVYLpea/UBmCQ/YWPBfWIDuBImXX/15KH+eKh4xpEUyS2sQQGQ==} + + '@fontsource-variable/jetbrains-mono@5.2.8': + resolution: {integrity: sha512-WBA9elru6Jdp5df2mES55wuOO0WIrn3kpXnI4+W2ek5u3ZgLS9XS4gmIlcQhiZOWEKl95meYdvK7xI+ETLCq/Q==} + '@formatjs/fast-memoize@3.1.5': resolution: {integrity: sha512-KLi3fan6WnCHmigd9pmEEN8Hid0v4wiFBW576M/d07KMWYecf1CvyMI3n34vCmHT4AoVqG2n702kiHbXjzZX2A==} @@ -3381,11 +3390,6 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - geist@1.7.2: - resolution: {integrity: sha512-Gu5lDFa3pLRyoBlBPf0QIFHVdWAnpco7fS1bJm41jyLPFoguBgiubseUN2oLXMgqZ7uxAxDoXcHMhCY/fOTTgg==} - peerDependencies: - next: '>=13.2.0' - generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} @@ -5829,6 +5833,10 @@ snapshots: '@floating-ui/utils@0.2.11': {} + '@fontsource-variable/inter@5.2.8': {} + + '@fontsource-variable/jetbrains-mono@5.2.8': {} + '@formatjs/fast-memoize@3.1.5': {} '@formatjs/icu-messageformat-parser@3.5.10': @@ -8320,10 +8328,6 @@ snapshots: functions-have-names@1.2.3: {} - geist@1.7.2(next@16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)): - dependencies: - next: 16.2.6(@babel/core@7.29.0)(@playwright/test@1.60.0)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) - generate-function@2.3.1: dependencies: is-property: 1.0.2 diff --git a/src/app/(app)/account/account-client.tsx b/src/app/(app)/account/account-client.tsx index 962f972..043f695 100644 --- a/src/app/(app)/account/account-client.tsx +++ b/src/app/(app)/account/account-client.tsx @@ -7,6 +7,10 @@ import { changePassword, startLogin } from "@/lib/auth/client-prelude"; import { HIBP_ENABLED, pwnedPasswordCount } from "@/lib/auth/pwned"; import * as deviceUnlock from "@/lib/idb/device-unlock"; import { useKeyBundle } from "@/stores/key-bundle"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Switch } from "@/components/ui/switch"; +import { Badge } from "@/components/ui/badge"; interface UnlockMaterial { email: string; @@ -44,8 +48,8 @@ export function AccountClient({ return (
-

Account settings

-

+

Account settings

+

Manage your email, password, sessions and two-factor authentication.

@@ -94,26 +98,26 @@ function NotificationsSection({ initial }: { initial: boolean }) { return (
-

+

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 ? ( -
- - Copy now — shown only once +
+ + Copy now, shown only once {created}
) : null}
- -