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-kitor 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">— noaria-labelparameter, noaria-busyfor loading stateOtpInput.razor:<input id="otp-@index" ...>— no<label>, noaria-label="Digit @(index+1) of 6"PosLayout.razor:30:<button class="pos-mobile-toggle" ... title="Menu">— onlytitle=, noaria-expanded, noaria-controls- Only
MainLayout.razor:31has a singlearia-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:
#0A0A0Bappears in bothAppTheme.cs:Backgroundandapp.css:--bg-page - Surface:
#1A1A1Dappears inAppTheme.cs:SurfaceANDapp.css:--bg-elevated - Primary:
#FF5C00inAppTheme.cs:Primaryandapp.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.cssLayout/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.Sharedis app-scoped and not importable byweb-client-base-netWebClientBase.Sharedis 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 systemAuthCard,AuthInput,OtpInput→ auth primitivesResponsiveOrderPanel→ 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 ofapp.css)DesignTokens.cs— C# static class with all token values (replacesAppTheme.cshardcoded 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.
I-5: Add Storybook (or Blazorise Gallery)
Since Storybook does not natively support Blazor, consider:
- Option A: Storybook with
@storybook/web-componentsfor design token showcase (colors, typography, spacing) - Option B: A
/galleryroute within the app itself that renders all component variants (similar to/storybookpattern for Blazor) - Option C: Blazor Story tool (
BlazorStoriesNuGet)
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: Addaria-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-corePlaywright 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
MarketingDarkdeviates fromDefaultDark - 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/ |