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

229 lines
12 KiB
Markdown

# 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`
```csharp
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`:
```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:
```css
--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-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/` |