Files
pos-system/docs/audit/architect.md

12 KiB

Audit Report — Architecture Design System (Architect Agent)

Date: 2026-03-20 Auditor: Architect (Architecture Design System Specialist) Scope: Design system patterns, architecture consistency, component library Codebase: /Users/velikho/Desktop/WORKING/pos-system


Executive Summary

The GoodGo POS System has a solid foundational design system built on MudBlazor 8.15 + CSS Custom Properties with a well-structured Primitives → Semantics → Components token architecture. The main app (web-client-tpos-net) demonstrates mature theming, bilingual i18n, and responsive layout patterns. However, the design system lives entirely within a single app — there is no shared UI package, no Storybook, and no cross-platform token synchronization. Gaps in accessibility (ARIA), no light/dark mode flexibility, and fragmented component ownership across apps are the primary concerns requiring remediation.


Critical Issues

C-1: No Shared UI Component Package

Impact: Blockers for cross-app reuse and design consistency.

Components in apps/web-client-tpos-net/src/WebClientTpos.Client/Components/ are not exported as a shared package. The enterprise portal (web-client-base-net) and any future Blazor apps must re-implement their own versions of AuthButton, AuthCard, OtpInput, etc.

  • Components folder: apps/web-client-tpos-net/src/WebClientTpos.Client/Components/ (only 2 subdirs: Auth/, Pos/)
  • No packages/@goodgo/ui-kit or equivalent exists
  • TypeScript packages at packages/ do not include any Blazor/UI package

Fix: Extract reusable components into a shared Blazor component library (Razor Class Library), published as a NuGet package or mono-repo project reference.


C-2: ARIA / Accessibility Gaps in Custom Components

Impact: WCAG 2.1 AA violation risk.

The custom component AuthButton.razor renders a <button> without aria-label when ChildContent is absent and only an icon is rendered. The OtpInput.razor renders 6 <input> fields without aria-label or aria-describedby. The PosLayout.razor sidebar toggle buttons use only title= for label — insufficient for screen readers.

Evidence:

  • AuthButton.razor: <button ... @onclick="OnClick"> — no aria-label parameter, no aria-busy for loading state
  • OtpInput.razor: <input id="otp-@index" ...> — no <label>, no aria-label="Digit @(index+1) of 6"
  • PosLayout.razor:30: <button class="pos-mobile-toggle" ... title="Menu"> — only title=, no aria-expanded, no aria-controls
  • Only MainLayout.razor:31 has a single aria-label="Toggle menu" — the only ARIA attribute in all layouts

Fix: Add ARIA attributes to all interactive custom components. Minimum: aria-label on icon-only buttons, aria-label on OTP inputs, aria-expanded/aria-controls on toggles.


C-3: No Design-to-Code Token Synchronization

Impact: Token drift between design files and implementation.

The CSS comment in app.css references pencil-design/src/pages/aPOS/landing/ tokens, but there is no automated pipeline to sync Figma/Pencil variables → CSS custom properties. Design tokens exist only in:

  • app.css (CSS custom properties, 1983 lines)
  • AppTheme.cs (MudBlazor PaletteDark, partially duplicated)

The two token stores are manually maintained and can drift. AppTheme.cs references #1A1A1D for Surface while app.css defines --bg-elevated: #1A1A1D — same value, different names, no automated cross-check.

Fix: Implement Style Dictionary or Tokens Studio pipeline. Single source of truth → generates both CSS vars and C# constants.


Warnings

W-1: Theme Architecture Duplication Between AppTheme.cs and app.css

File: apps/web-client-tpos-net/src/WebClientTpos.Client/AppTheme.cs + wwwroot/css/app.css

The same color values are defined twice: once in PaletteDark for MudBlazor and once as CSS custom properties. When a brand color changes (e.g., accent from #FF5C00 to a new value), it must be updated in two places manually.

Count of duplicated values:

  • Background: #0A0A0B appears in both AppTheme.cs:Background and app.css:--bg-page
  • Surface: #1A1A1D appears in AppTheme.cs:Surface AND app.css:--bg-elevated
  • Primary: #FF5C00 in AppTheme.cs:Primary and app.css:--accent-primary

W-2: eval() Usage in OtpInput for DOM Focus Management

File: apps/web-client-tpos-net/src/WebClientTpos.Client/Components/Auth/OtpInput.razor

await JS.InvokeVoidAsync("eval", $"document.getElementById('otp-{index + 1}')?.focus()");

Using eval() for focus management is a security smell (Content Security Policy violation risk) and bad practice. Should use a dedicated JS interop function in a *.js or *.ts file.


W-3: No Responsive Breakpoint Tokens

File: apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/app.css

The design token system in app.css defines spacing, colors, typography, and border radius — but no breakpoint variables. Responsive behavior in pos.css, admin.css, etc. uses hardcoded @media (max-width: 768px) values, not token references.

This makes it impossible to update the tablet breakpoint globally without a grep-and-replace across all CSS files.


W-4: Component Library Scope Is Too Narrow

Current: Only 8 custom components across 2 subdirectories (Auth/, Pos/).

The 23+ pages in Pages/Admin/Shop/ (e.g., ShopMenu.razor, ShopTables.razor, ShopStaff.razor) rely entirely on raw MudBlazor primitives with no intermediate abstraction layer. There are no shared:

  • Data table with pagination pattern
  • Confirmation dialog component
  • Status badge component
  • Empty state component
  • Loading skeleton component

This leads to duplicated MudBlazor boilerplate across all admin pages.


W-5: Scoped CSS Is Underused

Evidence: Only 2 scoped CSS files exist:

  • Layout/MainLayout.razor.css
  • Layout/PosLayout.razor.css

All other components use global CSS class names in app.css, admin.css, pos.css, auth.css, creating a fragile global namespace. Class naming follows BEM-like patterns (auth-btn, auth-btn--@Variant) but without .razor.css enforcement, naming conflicts are a maintenance risk.


W-6: Marketing Module Uses a Separate Theme — Risk of Visual Drift

File: apps/web-client-tpos-net/src/WebClientTpos.Client/AppTheme.cs

MarketingDark uses a completely different primary color (#FACC15 yellow) and slightly different background (#18181B). The marketing module is visually disconnected from the rest of aPOS. This may be intentional branding, but there is no documented rationale or design system ADR explaining this split.

The --bg-page CSS variable is #0A0A0B (shared across all layouts), while MarketingDark.Background = "#18181B" — MudBlazor components under MarketingLayout will use #18181B, but any custom CSS components using var(--bg-page) will still render #0A0A0B.


W-7: No TypeScript Shared Packages for Blazor Apps

File: packages/ directory

The 6 shared TypeScript packages (@goodgo/types, @goodgo/http-client, @goodgo/auth-sdk, etc.) serve the MCP server and Node.js ecosystem. There is no equivalent shared infrastructure for Blazor:

  • No shared NuGet package for DTOs
  • WebClientTpos.Shared is app-scoped and not importable by web-client-base-net
  • WebClientBase.Shared is a separate, parallel DTOs structure

Both apps duplicate UserDto, ApiResponse<T>, etc. independently.


Improvements

I-1: Establish @goodgo/blazor-ui Razor Class Library

Extract into a shared Razor Class Library (RCL):

  • AuthButton → parameterized variant system
  • AuthCard, AuthInput, OtpInput → auth primitives
  • ResponsiveOrderPanel → already abstracted
  • New: DataTable<T>, ConfirmDialog, StatusBadge, EmptySate, LoadingSkeleton

This enables web-client-base-net and future apps to import the same atoms.

I-2: Style Dictionary Token Pipeline

Implement Style Dictionary with two outputs:

  • wwwroot/css/tokens.css — CSS custom properties (replaces the token section of app.css)
  • DesignTokens.cs — C# static class with all token values (replaces AppTheme.cs hardcoded strings)

This eliminates duplication between CSS vars and MudBlazor palette config.

I-3: Replace eval() Focus Management with JS Module

Create wwwroot/js/otp-helpers.js:

export function focusOtpInput(index) {
  document.getElementById(`otp-${index}`)?.focus();
}

Then use JS.InvokeVoidAsync("focusOtpInput", index + 1) via JSImport or standard Blazor JS interop.

I-4: Add Responsive Breakpoint Tokens

Extend app.css :root {} with:

--breakpoint-sm: 640px;
--breakpoint-md: 768px;
--breakpoint-lg: 1024px;
--breakpoint-xl: 1280px;

Replace all hardcoded @media (max-width: 768px) occurrences with references.

Since Storybook does not natively support Blazor, consider:

  • Option A: Storybook with @storybook/web-components for design token showcase (colors, typography, spacing)
  • Option B: A /gallery route within the app itself that renders all component variants (similar to /storybook pattern for Blazor)
  • Option C: Blazor Story tool (BlazorStories NuGet)

This documents variants, states (loading, disabled, error) and serves as living style guide.

I-6: WCAG 2.1 AA Accessibility Pass

Systematic pass over all custom components:

  • AuthButton: Add [Parameter] string? AriaLabel, aria-busy="@Loading"
  • OtpInput: Add aria-label="Mã OTP, chữ số @(index+1) trên @DigitCount" to each input
  • All layout toggle buttons: Add aria-expanded, aria-controls, aria-label
  • Add axe-core Playwright accessibility assertions to E2E test suite

I-7: Document the Marketing Dual-Theme Decision

Create ADR (Architecture Decision Record) at docs/adr/001-marketing-dual-theme.md explaining:

  • Why MarketingDark deviates from DefaultDark
  • How CSS variable conflicts between layouts should be resolved
  • When/if a light mode should be considered

Action Items

# Priority Task Owner File
1 P0 Fix eval() in OtpInput.razor — replace with proper JS interop module Frontend Dev Components/Auth/OtpInput.razor
2 P0 Add ARIA attributes to AuthButton, OtpInput, layout toggle buttons Frontend Dev Components/Auth/*.razor, Layout/PosLayout.razor
3 P1 Extract AuthButton, AuthCard, AuthInput, OtpInput into shared RCL Architect + Frontend Dev New: packages/blazor-ui/
4 P1 Add responsive breakpoint tokens to app.css and refactor media queries Frontend Dev wwwroot/css/app.css, *.css files
5 P1 Unify DTO sharing: Create GoodGo.Shared NuGet accessible to both web apps Backend Dev New: packages/dotnet-shared/
6 P2 Implement Style Dictionary pipeline for token single source of truth Architect New: packages/design-tokens/
7 P2 Create shared admin atom components (DataTable, StatusBadge, EmptyState) Frontend Dev Components/Admin/ (new)
8 P2 Add axe-core a11y assertions to Playwright E2E suite QA tests/WebClientTpos.E2ETests/
9 P2 Write ADR for Marketing dual-theme decision Architect docs/adr/001-marketing-dual-theme.md
10 P3 Set up Blazor component gallery page at /gallery for living style guide Frontend Dev New: Pages/Gallery.razor

Appendix: Key File Inventory

Category File Path
Design Tokens (CSS) apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/css/app.css
MudBlazor Theme Config apps/web-client-tpos-net/src/WebClientTpos.Client/AppTheme.cs
Auth Components apps/web-client-tpos-net/src/WebClientTpos.Client/Components/Auth/
POS Components apps/web-client-tpos-net/src/WebClientTpos.Client/Components/Pos/
Layout System apps/web-client-tpos-net/src/WebClientTpos.Client/Layout/
Admin Pages apps/web-client-tpos-net/src/WebClientTpos.Client/Pages/Admin/
Localization apps/web-client-tpos-net/src/WebClientTpos.Client/wwwroot/locales/
i18n Implementation apps/web-client-tpos-net/src/WebClientTpos.Client/Localization/
Shared TypeScript Packages packages/ (types, http-client, auth-sdk, logger, tracing, config)
E2E Tests apps/web-client-tpos-net/tests/WebClientTpos.E2ETests/
VitePress Docs apps/web-docs/