Skip to content

feat(ui): EYT-80 Werkbank-Foundation — domaenenfreier AppShell und DateRangeControl auf /planung - #95

Merged
DYAI2025 merged 15 commits into
masterfrom
feat/eyt-80-werkbank-foundation
Aug 27, 2026
Merged

feat(ui): EYT-80 Werkbank-Foundation — domaenenfreier AppShell und DateRangeControl auf /planung#95
DYAI2025 merged 15 commits into
masterfrom
feat/eyt-80-werkbank-foundation

Conversation

@DYAI2025

@DYAI2025 DYAI2025 commented Aug 27, 2026

Copy link
Copy Markdown
Owner

EYT-80 Increment 1 — die Werkbank-Grundlage aus Basisdesign v2.0 als domaenenfreie Bausteine in @easytree/ui, bewiesen auf der echten Route /planung, ohne eine einzige Verhaltensaenderung.

Basis: 9bebb1186ecc7e6c8338887f5831c1f31d79b1a9 (Merge von #94), beim Abzweigen und erneut vor dem Push geprueft. Keine konkurrierende offene PR auf derselben Flaeche.

Was entsteht

Zwei Primitives in packages/ui, beide ohne Sitzung, Rechte, Router oder Fachbegriff:

  • AppShell — Sprunganker, Kopfleiste, Hauptbereich, optionale Fusszeile. apps/web/components/app-shell.tsx komponiert ihn jetzt und behaelt jede Politik: useSession, die Rechtefilterung ueber hatRecht, Organisationswahl, Abmelden, next/link, next/navigation.
  • DateRangeControl — Bereichswechsel mit injiziertem Link-Element. apps/web/components/wochen-navigation.tsx komponiert ihn und uebersetzt nur noch das Wochenmodell in Beschriftungen, Adressen und data-testid-Werte.

Die Wochenrechnung bleibt vollstaendig in apps/web/lib/wochennavigation.ts (Europe/Berlin, @easytree/domain + @easytree/contracts). Das Primitive rechnet nichts — der Grund ist nicht Geschmack: eine zweite Wochenregel liefe genau an den Jahresgrenzen auseinander, an denen isoWeek ± 1 falsch ist.

apps/web/test/wochen-navigation.test.tsx und wochennavigation.test.tsx sind seit master byte-identisch. Sie sind der Abnahmevertrag; haette der Umbau eine Zeile darin gebraucht, waere der Umbau falsch gewesen.

Die Grenze ist ausfuehrbar, nicht behauptet

Vor diesem Slice stand „domaenenfrei" als Zusage in einem Dateikopf und sonst nirgends. Neu: die Regel ui-dependency-allowlist im Pflichtjob unit-tests laesst unter packages/ui/src/ ausschliesslich react und paketinterne relative Pfade zu — als Allowlist, weil ein universelles Verbot sich nicht durch eine endliche Sperrliste ausdruecken laesst.

Dazu eine Zusicherung, die den Geltungsbereich festnagelt. Ohne sie liess sich der Waechter lautlos abruesten: eine Verengung auf packages/ui/src/b bewachte 1 von 11 Dateien, und beide Suiten blieben vollstaendig gruen. Jetzt wird der Geltungsbereich bei jedem Lauf gegen die tatsaechlich eingesammelten Dateien verglichen.

Ausgefuehrte Gegenmutationen

Jede wurde eingespielt, gemessen und zurueckgenommen — keine ist ausgedacht.

Mutation Rot
@easytree/domain in packages/ui/src/button.tsx violations=1, benennt Datei und Zeile
next/link in date-range-control.tsx violations=1 mit der vollen Abhilfe im Text
Geltungsbereich auf packages/ui/src/b verengt beide Suiten rot (1 statt 11 Dateien)
Sprunganker fest verdrahtet statt aus mainId expected '#hauptinhalt' to be '#inhalt-feld'
Slotwaechter auf === undefined verengt 4 rot — die null/false-Faelle
Slotwaechter auf Wahrheitswert geweitet 2 rot — die Leerstring-Faelle
.eyt-app-shell__skip-link-Positionierung entfernt Sprunganker steht ohne Fokus im Bild (bottom=40)
ueberschreibender Sprunganker-Block angehaengt Bloecke ohne Pseudoklasse: 2
beide hatRecht-Riegel entfernt 2 rot in der Navigationsfilterung
Klassennamen im Primitive umbenannt Emitterseite rot, Stylesheetseite gruen
derselbe Name in globals.css umbenannt Stylesheetseite rot, Emitterseite gruen
Komponente baut die Vorwaertsadresse selbst expected '/planung?weekKey=SENTINEL-SCHLUESSEL' to be '/x?k=SENTINEL-NACH'
min-height der Wochenschaltflaechen entfernt Nächste Woche ist 24 px hoch
min-height des Sitzungszugangs entfernt Anmelden ist 38 px hoch
.app-nav-list { min-width: 1200px } Kopfleiste umbricht: Marke=31, Anmelden=122, Kopfhoehe=159

Die beiden Slotwaechter-Mutationen klammern die Regel von beiden Seiten ein: zu eng faellt an den Abwesenheitsformen, zu weit am Leerstring. Nur die Drei-Formen-Fassung besteht beides.

Der Waechter fuer Klassennamen ist zweiseitig. Gemessen: eine Umbenennung von eyt-app-shell__footer im Primitive liess apps/web bei 30 Dateien / 376 Tests und packages/ui bei 3 / 31 — identisch zum ungemuteten Lauf — waehrend die Fusszeile still Polsterung und Oberkante verlor. Jede Haelfte hat jetzt ihre eigene Gegenmutation, und jede roetet genau eine Haelfte.

Barrierefreiheit

Fuenf Aussagen ueber Lage und Groesse, die es im Repository vorher gar nicht gab — die einzigen Geometriesonden waren vier Fokuspruefungen. Neu gemessen im echten Browser: Sprunganker ausserhalb des Bildes bis zum Fokus, Kopfleiste als eine Zeile mit rechtsstehendem Sitzungsbereich, begrenzte und mittige Hauptspalte, 40-px-Beruehrziele. Dazu unveraendert: axe auf vier Routen mit aktiviertem Kontrast, genau eine main-Landmark, vollstaendiger Tab-Zyklus in DOM-Reihenfolge.

Gates (lokal, Cached: 0 durchgehend)

format (getrackte Menge)   exit 0
lint --force               Tasks: 6 successful   Cached: 0 cached
typecheck --force          Tasks: 10 successful  Cached: 0 cached
test --force               Tasks: 10 successful  Cached: 0 cached
build --force              Tasks: 6 successful   Cached: 0 cached
playwright (2 Specs)       24 passed
[architecture] files=339 imports=1353 modules=5 rules=10 violations=0
api 774 bestanden | 95 uebersprungen · web 380 · ui 45 · contracts 201 · domain 246 · config 36

Die 95 Uebersprungenen sind die Tenant-/DB-Suiten im Modus local ohne Supabase-Stack. db-gates in CI faehrt sie required — das bleibt die hoehere Evidenzstufe. Keine Migration, kein Vertrag, keine RLS-, Rechte- oder Domainaenderung; package.json und Lockfile sind unveraendert.

Was hier bewusst NICHT behauptet wird

Bewiesen ist: gewoehnliche Quelldateien unter packages/ui/src duerfen keine Domain, keine Vertraege und keinen Router importieren. Nicht bewiesen ist „packages/ui ist nachweislich domaenenfrei". Sechs vorbestehende Luecken in apps/api/test/architecture/scan.ts unterlaufen alle zehn Regeln, nicht nur die neue — unter anderem wird /// <reference path=…> nie zu einem ImportRef, ein praeparierter Kommentar verschluckt den naechsten Import, SKIP_DIRS greift auf jeder Tiefe, und Symlinks unter src/ werden vom Walker verworfen, von tsc aber mitgebaut. Alle acht Befunde sind gemessen und im Planungsdokument aufgelistet; keiner ist in dieser PR mitbehoben — sie brauchen je eine eigene Gegenmutation pro Regel.

Offen bleibt ausserdem: .app-logout wird von keinem Job vermessen (web-smoke laeuft abgemeldet), und die drei Wochenschaltflaechen teilen sich eine Regel, von der nur „Nächste Woche" gemessen wird. Beides steht als Kommentar an der jeweiligen Zusicherung, nicht nur hier.

Evidenzstufe

CI auf genau diesem Head ist gemessen. Lauf #361 (ID 33046937186), Head 15de359939a27eee40fa793f5e55cbc4bbced7d5, status=completed, conclusion=success, 11 von 11 Jobs gruendb-gates und auth-journey eingeschlossen, ebenso read-through.

Die Zahlen im Abschnitt Gates daneben bleiben ein lokaler Lauf und damit die niedrigere Evidenzstufe.

Nicht Teil dieses Inkrements: BottomNavigation, die Feld-Shell, EYT-113/81/82/147/148. Sie bekommen ihre Bausteine, wenn es eine echte konsumierende Flaeche gibt.

Summary by Sourcery

Extract reusable, domain-free application-shell and date-range controls into the shared UI package while preserving planning-route behavior and enforcing their architectural boundaries.

New Features:

  • Add domain-independent AppShell and DateRangeControl primitives to the shared UI package and use them on the planning route.

Bug Fixes:

  • Preserve existing planning navigation, session behavior, accessibility semantics, and week-calculation behavior while extracting shared UI structure.

Enhancements:

  • Enforce the shared UI package's dependency boundary with an architecture allowlist and scope coverage checks.
  • Strengthen accessibility and visual regression coverage for shell layout, navigation controls, landmarks, focus behavior, and touch-target sizing.
  • Separate application-specific routing, authorization, session, and week-model translation from reusable UI primitives.

Tests:

  • Add unit, architecture, and browser tests covering the new primitives, dependency restrictions, styling contracts, and layout behavior.

BenPerro and others added 15 commits August 27, 2026 04:11
…Luecken

Acht Befunde aus zwei Reviewrunden, jeder mit ausgefuehrter Gegenmutation.

F1 Emitterseite: `.eyt-app-shell__footer` war von nichts gehalten — sie
umzubenennen liess packages/ui bei 3/31 und apps/web bei 30/376. Ein Fall in
`packages/ui/test/app-shell.test.tsx` prueft jetzt alle SECHS Klassennamen.

F2 Stylesheetseite: `toContain(".eyt-app-shell__brand")` war von
`.eyt-app-shell__brandX` erfuellt; die Sprunganker-Pruefung las ueber
`.exec(...)` nur den ERSTEN Block und uebersah einen angehaengten
Ueberschreiber. Jetzt grenzgebundene Regelsuche auf kommentarfreiem CSS und
Auswertung ALLER Bloecke ueber die zuletzt gewinnende Deklaration.

F3 Die Messangabe im shell-smoke-Kommentar war um eins daneben (vier
Fokussonden, nicht drei) und das Kommando ohne `-E` unbrauchbar.

F4 Der Kommentar zu `css.length > 1000` nannte einen unmoeglichen Fehlerfall —
ein falscher Pfad wirft ENOENT auf Modulebene. Auf das echte Risiko gefasst.

F5 „Kopfleiste ist EINE Zeile" pruefte nur ein Links/Rechts-Verhaeltnis und
blieb bei dreizeiligem Umbruch gruen. Zusicherung auf gleiche vertikale Mitte
ergaenzt (Mutation: Marke=31, Anmelden=122, Kopfhoehe=159).

F6 Der Beruehrziel-Fall misst nur `.app-login-link`; `.app-logout` wird in
`web-smoke` nie gerendert. Kommentar darauf eingegrenzt, Fall umbenannt.

F7 Ein Fall verglich zwei Werte, die das Primitive aus EINER Variablen
rendert — bauartbedingt wahr, ersatzlos entfallen. Die beiden verbleibenden
tragen jetzt ihre benannte Gegenmutation.

F8 `sessionArea` als benannter Wert vor dem `return` statt als 38-zeiliger
Ausdruck an der Prop; Verhalten unveraendert.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…-Element

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Die Darstellung der Bedienleiste liegt jetzt im domaenenfreien Primitive;
was hier bleibt, ist die Uebersetzung des Wochenmodells in dessen Props —
Beschriftungen, die sechs `data-testid`-Werte und der Link-Adapter. Es kommt
keine Rechnung dazu und es faellt keine weg: jeder Wert ist ein Literal oder
ein direkter Feldzugriff auf `modell`.

Der Adapter `WochenLink` statt `linkComponent={Link}` direkt, weil
`next/link` mit `href: Url` weiter ist als der Vertrag `DateRangeLinkProps`
mit `href: string` — die direkte Zuweisung waere eine Wette auf die Varianz
der Prop-Typen, die sich erst im typecheck zeigt.

Ein Boolean speist zwei Knoepfe: `modell.istAktuelleWoche` setzt sowohl
`currentMarker` als auch `reset.current`, im Primitive zwei getrennte
Eingaenge. Beide Haelften sichert der unveraenderte Abnahmevertrag in
derselben Zusicherung.

CSS: fuenf Selektoren `.wochennavigation*` -> `.eyt-date-range*`, inklusive
`min-height: 2.5rem` auf `.eyt-date-range__action`. Danach 0 Treffer fuer
`wochennavigation` in globals.css.

Beide Haelften der Namenskopplung sind bewacht, jede mit ausgefuehrter
Gegenmutation (Umbenennung `__marker` -> `__markerX`):

- Stylesheetseite, KLASSEN in `app-shell-styles.test.ts` von sechs auf elf:
  Mutation in globals.css macht apps/web rot (1 failed | 12 passed) und
  packages/ui bleibt gruen (13 passed).
- Emitterseite, `date-range-control.test.tsx` nannte bereits alle sieben
  Klassen und blieb unveraendert: Mutation im Primitive plus Neubau macht
  packages/ui rot (1 failed | 12 passed) und apps/web bleibt gruen
  (13 passed).

Neu vermessen wird auch die Geometrie: `min-height: 2.5rem` auf den
Wochenwegen hielt bisher nichts fest — die vier Geometriefaelle in
shell-smoke besuchen alle `/`, wo es keine Wochenleiste gibt. Ein Fall auf
`/planung?weekKey=2026-W32` prueft die Hoehe von "Naechste Woche"; die
ausgefuehrte Gegenmutation (Zeile entfernen, neu bauen) misst 24 px.

Zahlen nach der Aenderung: apps/web 30 Dateien / 380 Tests (+5 durch die
fuenf neuen KLASSEN-Eintraege), packages/ui 4 / 45 unveraendert,
architecture `files=339 imports=1353 modules=5 rules=10 violations=0` bei 29
Tests, shell-smoke 20 passed (vorher 19), planungswerkbank 4 passed.

`apps/web/test/wochen-navigation.test.tsx`, `test/wochennavigation.test.tsx`,
`lib/wochennavigation.ts` und `packages/ui/src` sind unveraendert (leerer
`git diff`).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Aug 27, 2026

Copy link
Copy Markdown

Reviewer's Guide

Extracts a reusable, domain-free AppShell and DateRangeControl into @easytree/ui, adapts the real /planung route without changing week-navigation behavior or application policy, and backs the architectural boundary with allowlist enforcement plus focused unit, accessibility, stylesheet, and browser-layout tests.

Sequence diagram for adapting /planung to DateRangeControl

sequenceDiagram
  participant Route as /planung
  participant Navigation as WochenNavigation
  participant Model as wochennavigation.ts
  participant Control as DateRangeControl
  participant NextLinkParticipant as NextLink

  Route->>Model: build Wochenmodell
  Model-->>Navigation: return URLs and labels
  Navigation->>Control: render DateRangeControl
  Navigation->>NextLinkParticipant: provide WochenLink adapter
  Control->>NextLinkParticipant: render previous/reset/next hrefs
  NextLinkParticipant-->>Route: navigate to supplied URL
Loading

File-Level Changes

Change Details Files
Introduced reusable, domain-free UI primitives for the application frame and date-range navigation, while keeping application policy and week calculations in the web app.
  • Added slot-based AppShell structure with skip link, landmarks, optional session area, and footer.
  • Added DateRangeControl with injected link component and pass-through labels, keys, URLs, test IDs, and current-state semantics.
  • Refactored web consumers to provide session/navigation policy and translate the week model into primitive props.
  • Exported both primitives and their public prop types from the UI package.
packages/ui/src/app-shell.tsx
packages/ui/src/date-range-control.tsx
packages/ui/src/index.ts
apps/web/components/app-shell.tsx
apps/web/components/wochen-navigation.tsx
Enforced the UI package dependency boundary with an executable allowlist and tests that verify both violations and complete scope coverage.
  • Allowed only React and package-internal relative imports under packages/ui/src.
  • Added red-case coverage for domain, contracts, and Next router imports, plus valid React and relative imports.
  • Added a scope assertion ensuring every collected UI source file is covered by the rule.
apps/api/test/architecture/rules.ts
apps/api/test/architecture-red-case.test.ts
apps/api/test/architecture.test.ts
Updated application styling and added bidirectional class-name coupling checks for the extracted primitives.
  • Renamed AppShell and date-range selectors to eyt-prefixed primitive class names.
  • Added stylesheet checks for required selectors and key skip-link declarations.
  • Added component tests ensuring the primitives emit all expected class names.
apps/web/app/globals.css
apps/web/test/app-shell-styles.test.ts
packages/ui/test/app-shell.test.tsx
packages/ui/test/date-range-control.test.tsx
Expanded behavioral and browser-level coverage for accessibility, layout, touch targets, and preservation of application behavior.
  • Added primitive tests for landmarks, optional-slot semantics, main ID coupling, pass-through URLs, hidden range keys, current markers, and injected links.
  • Added app-level checks that navigation and footer remain composed by the web application.
  • Added browser geometry assertions for skip-link visibility, single-row header alignment, centered constrained main content, and 40px controls.
  • Retained the existing week-navigation and domain calculation contract unchanged.
packages/ui/test/app-shell.test.tsx
packages/ui/test/date-range-control.test.tsx
apps/web/test/a11y.test.tsx
apps/web/e2e/shell-smoke.spec.ts
apps/web/test/wochen-navigation.test.tsx
apps/web/test/wochennavigation.test.tsx

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've reviewed your changes and they look great!

Sourcery assessment

Approved.


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@DYAI2025
DYAI2025 merged commit 3f98d76 into master Aug 27, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants