Skip to content

Refactor/UI redesign - #257

Merged
Moritz1708 merged 13 commits into
masterfrom
refactor/ui-redesign
Sep 15, 2026
Merged

Moritz1708 merged 13 commits into
masterfrom
refactor/ui-redesign

Conversation

@Moritz1708

Copy link
Copy Markdown
Owner

Summary

Type of change

  • feat — new feature
  • fix — bug fix
  • refactor — change without behavioural impact
  • docs / style / test / chore / ci / build / perf

Checklist

  • Build green (dotnet build OrderSphere.slnx)
  • Tests green and extended where applicable (dotnet test)
  • No new compiler warnings (TreatWarningsAsErrors enabled)
  • EF migrations included for schema changes (backward-compatible or agreed)
  • Architecture conventions upheld (Result, layer dependencies, no cross-service project refs)
  • Documentation updated (docs/, README) where relevant
  • No secrets/connection strings committed

Breaking changes

References

Moritz1708 and others added 13 commits September 10, 2026 18:07
Replaces the six-brand "Flat & Focused" theming with one light and one dark
theme built from a shared token layer, and lays the CSS, font and motion
groundwork for the redesign. See docs/adr/0013-editorial-design-system.md.

Why now: the brand system had no consumer and blocked any design that assumes
a known palette. Three defects came out of the audit and are fixed here — the
dark-mode CSS hung on `data-mud-theme`, an attribute MudBlazor never emits;
the Google Fonts link was blocked by the production CSP, so production silently
rendered in system fonts; and roughly half of app.css was dead.

- Tokens live twice on purpose (DesignTokens.cs for the MudBlazor theme,
  tokens.css for everything else) with TokensCssSyncTests guarding the pair
  and DesignTokensContrastTests asserting the documented WCAG ratios.
- MudBlazor is imported into the lowest cascade layer, so overrides win on
  layer order without !important or a specificity war.
- theme-boot.js stamps html[data-theme] before first paint; Program.cs reads
  the same value back so MudThemeProvider renders the right palette first time.
- Fonts self-hosted under SIL OFL 1.1, which the CSP allows.
- Interactive outlines get their own token: the 24% hairline reaches only
  1.7:1 on canvas, short of the 3:1 WCAG 1.4.11 requires for control borders.

legacy.css is transitional and holds the classes the not-yet-migrated pages
still reference; it goes away with the component kit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ooter, page transition

Second phase of the redesign (ADR 0013). The app shell now carries the
editorial identity and fixes three navigation defects the audit found.

- Header: serif wordmark, underline-rule nav links, a real search field that
  routes to /search (the page was reachable only by URL before), cart badge
  that pops on change, one LocaleMenu for language and currency instead of
  two icons, and a burger below md.
- MobileNavDrawer: the storefront had no navigation at all on phones — the
  links simply disappeared under the md breakpoint. Serif links, settings and
  sign-in/out live in the drawer now.
- Footer: four editorial columns covering every public route. The old
  footer linked /legal/privacy and /legal/imprint, which never existed.
- PageTransition wraps @Body in both layouts and re-keys on path changes so
  the CSS enter animation replays; query-only changes keep the page mounted.
- First kit components: OsButton and OsIconButton over native elements
  (MudButton needs ~6 selectors per variant to neutralise and has no loading
  state), OsBadge, OsSearchBar, AccountMenu, ThemeToggle with an animated
  sun/moon.
- The brand link is a plain <a>: MudBlazor's colour utilities are declared
  !important, which beats the cascade layer. Documented in ui-conventions.md
  and the frontend agent.
- PageHero and Home used inline 1fr 1fr grids that never collapsed; they
  share a responsive .os-split now.

Verified in Chromium at 1280 and 390px, light and dark, incl. persistence
across reload, the 404 route, and drawer open/close.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Third phase of the redesign (ADR 0013): the design system's component kit,
and the storefront, account and shared components moved onto it.

Kit (Components/Ui): OsCard, OsSection, OsPageHeader, OsBreadcrumbs,
OsDisplay, OsEyebrow, OsPrice, OsStat, OsStatusChip, OsSkeleton with four
composites, OsEmptyState, OsErrorState, OsQuantityStepper, OsProductImage,
OsReveal/OsRevealGroup, ConfirmDialog, AddToCartDialog and AddressForm.
OsDisplay and the reveal wrappers are plain classes because Razor cannot
pick an element tag at runtime; every page now renders a real <h1>.

Behavioural fixes that fell out of the migration:
- Search's add-to-cart button did nothing (an empty callback); it now opens
  the same AddToCartDialog as the shop.
- Three divergent address forms (checkout, profile, onboarding) become one
  MudForm-validated AddressForm over AddressFormModel; checkout validates
  fields in place instead of a warning toast.
- Address deletion asks for confirmation.
- Product detail gets a /p/{slug} alias so advisor citations can navigate
  without a category, and review dates use Formatting.Date instead of a
  hard-coded culture.
- Every status switch collapses into StatusPresentation; every
  Snackbar.Add into SnackbarExtensions (51 call sites in admin rewritten
  mechanically, their layout redesign follows in the admin phase).
- Contrast: the vivid accent stays a fill colour; text uses --os-accent-ink.

Deleted: CheckoutAddressForm, QuantityStepper, StockBadge, LoadingWrapper,
ProductDetailSkeleton. PageHero, EmptyStateCard, ErrorStateWithRetry and
IconBadge remain until the admin, advisor and legal pages migrate.
legacy.css stays for the same reason and is removed in the polish phase.

A development-only gallery at /dev/kit renders every component in both
themes for review without signing in. New tests cover the kit, the status
mapping, the snackbar helpers, the address form and the page transition;
the suite is at 195.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Shop, product, category and search pages no longer require sign-in. Only
the BFF was blocking anonymous reads — the gateway and the Catalog service
already left their read groups open — so the change is mostly routing:

- BFF: four GET/HEAD routes (products, categories, brands, reviews) with
  AuthorizationPolicy "anonymous", ordered before the authenticated
  catch-all. Writes on those paths still fall through to BffUserPolicy and
  the CSRF middleware. Both appsettings files carry them, because the
  Development file replaces the Routes section wholesale.
- Gateway: the public catalog routes are now GET/HEAD only, with separate
  authenticated routes for writes; reviews, brands and admin reviews get
  routes at all — /api/v1/reviews/** had none, so product reviews have been
  404ing since the endpoint was built.
- Catalog: the stock decrement/restore POSTs were reachable anonymously
  through the public route. They now require CatalogAdminPolicy (nothing
  in the codebase calls them over HTTP; Basket uses the authenticated gRPC
  service). The product-by-slug cache key is tenant-scoped, so an anonymous
  miss on the default tenant cannot populate an organisation's entry.
- Rate limits, retuned for browsing that costs 2-3 calls per page: the
  gateway's anonymous per-IP quota goes from 30 to 120/min, the Catalog
  public bucket (one service-wide ceiling) from 100 to 600/min.
- Web: [Authorize] removed from the four pages; add-to-cart, the buy box
  and the review form redirect to or ask for sign-in. Sign-in controls
  navigate with a full load through LoginRedirect — Blazor's router would
  otherwise intercept an <a href="/bff/login"> and show the 404 page.

Tests: BFF route-policy tests (anonymous reads pass, other reads and all
writes stay 401/403), catalog integration test updated to expect 401 /
403 / 204 for anonymous / customer / admin stock adjustments, handler
test carries the tenant context. Verified in Chromium against the running
stack: 170 products browsable signed out, buy and review actions lead to
Auth0, reviews load empty instead of failing.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Home, Shop, Categories and the product page get their real layouts:

- Home now loads live data — a category bento fed by the five largest
  categories, a scroll-snapping rail of the newest products, and a masthead
  that reports the actual catalogue size. The four hard-coded category tiles
  and their resource keys are gone.
- Shop gains a sticky filter rail from md up and a bottom-sheet filter below
  it, both rendered from one definition. Category links carry their product
  counts and mark the active one; the category list scrolls on its own so
  price, sorting and the buttons stay in view.
- Categories becomes a bento with the largest category leading full width.
- The product page lists SKU, category and availability instead of SKU alone.

Three defects surfaced during the browser pass and are fixed here:

- Placeholder hues came from the first four bytes of the id, so every seeded
  category rendered the same colour. Hashing all sixteen bytes separates them.
- The revealed flag moved from a CSS class to `data-revealed`. Blazor owns the
  class attribute of the elements it renders and rewrote it on re-render,
  which dropped the flag and left already-revealed tiles invisible for good.
- `.os-page` filled its enter animation forwards, which kept it a containing
  block for fixed descendants and pushed the shop's filter sheet out of the
  viewport. It now fills backwards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Checkout becomes three numbered editorial steps — address, payment,
  review — with the summary sticky beside them. A failed validation now
  moves focus to the first invalid field instead of only marking it, via a
  new focusFirstInvalid helper in motion.js and IMotionService.
- The success page says what it is waiting for: the order number is written
  by a Service Bus worker, so the page shows a confirming state with the
  attempt count and, after six tries, a still-processing note with a retry.
- The order detail page replaces MudTimeline with a hairline rail: one dot
  per status, mono timestamps, the newest entry emphasised.
- The cart drawer gains a quantity stepper, so a line can be corrected
  without opening the cart page.
- MudGrid, MudItem and MudStack are gone from cart, checkout, order detail,
  profile and onboarding; those pages now use their own grids and .os-stack.
- Onboarding is two numbered steps with an action bar that sticks on a phone.

The kit gallery at /dev/kit gains the numbered-step and timeline patterns,
because every page using them sits behind a sign-in and could otherwise not
be reviewed without a session.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Twelve admin pages, the admin layout and a new AdminListShell, moved onto the
Os* kit: MudGrid/MudPaper cards become OsSection + OsCard, the copied spinner
blocks become OsTableSkeleton, and every list now branches skeleton / error /
empty / content in one place instead of once per page.

Behaviour that changed with the markup:
- destructive actions (order cancel, coupon deactivation, review rejection)
  ask through ConfirmDialog; approving a review still does not
- the coupon dialog and both forms are MudForm-validated, Save stays disabled
  until the form is valid, and a failed submit focuses the first bad field
- lists sort and page client-side (MudTableSortLabel, 25 rows, MudTablePager)
- admin order and invoice statuses resolve through StatusPresentation, so the
  duplicate Admin.Orders.Status* resources are gone

PageHero, EmptyStateCard and ErrorStateWithRetry have no callers left and are
deleted. /dev/kit gains a data-table section: the admin routes sit behind a
login, so the gallery is where the table styling can actually be reviewed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The drawer moves onto the kit: serif greeting with the conversation id in mono,
an empty state with three starter prompts, an inverted ink bubble for the user
against a hairline card for the assistant, and a composer on a hairline footer.
Messages animate in through os-msg.

Two fixes came with it:
- citations linked to /products/{slug}, which is not a route — every citation
  was a 404. They now point at the /p/{slug} alias.
- the drawer carried ~14 hard-coded German strings; they are Advisor.* resources
  now, including the confirm and cancel replies sent back to the model.

The FAB is a native .os-fab button instead of MudFab, so it gets the ink circle
and the same hover lift as the rest of the UI.

/dev/kit gains an advisor-thread section — like the admin tables, the drawer is
only reachable when signed in and cannot otherwise be reviewed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wwwroot/css/*.css, wwwroot/js/*.js and index.html are served under stable
URLs. UseStaticFiles emitted no Cache-Control for them, so a browser was
free to reuse them heuristically and paired the previous build's
stylesheets with the current markup — the UI then rendered half-styled
until a hard reload. ETag revalidation costs one 304 per file; the
self-hosted fonts, whose names change when their contents do, stay
immutable.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…trings

The sticky rail and the category list inside it could both overflow, so
two scrollbars stood side by side and the outer one travelled the ~15px
the list was over. The rail is now clipped and lays its children out as a
column; the category list, the only part that grows, takes the remaining
height. Price and sorting stay visible at every viewport height, which
was the point of capping the list in the first place.

Five German strings still addressed the reader with "Sie" while the rest
of the app uses "du".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- ArticleLayout: page header with the h1, numbered sections and a sticky mono
  table of contents from three sections up. The seven legal and support pages
  are rebuilt on it; their text moved into AppStrings (de/en).
- Imprint cites § 5 DDG and § 18 Abs. 2 MStV, which replaced § 5 TMG and
  § 55 Abs. 2 RStV.
- ApiResult default messages resolve from AppStrings in the current UI culture.
- 404 page on the Os kit; framework error bar restyled.
- legacy.css and IconBadge removed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
MudBlazor 9 no longer wires a custom ActivatorContent to the menu; the
activator has to call MenuContext.ToggleAsync itself. Both header menus
rendered a plain OsIconButton without a handler, so clicks did nothing.
The activators now toggle the menu and expose aria-haspopup/aria-expanded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The invoice now follows the storefront design system: Instrument Serif
display type, Manrope text, Geist Mono figures, hairline tables and a
single accent. Position numbers, a meta block (number, date, order) and
a totals panel replace the unstyled table; totals never split across a
page break.

The fonts ship as embedded resources (SIL OFL 1.1, licences alongside)
instead of relying on Arial being installed, which is not the case in
Linux containers.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@Moritz1708
Moritz1708 merged commit cd0824f into master Sep 15, 2026
9 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.

1 participant