Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

07 — Component Library (Audit Module)

Module-specific components built from shared App* primitives (00-shared/03). Props, states, animation, a11y, tokens, Flutter mapping. Reuse over new — only components that don't exist in the shared library are defined here. Data fields are exact from audit-log.schema.ts:13-56 and the response contract in 12_API_Mapping.md.


A. Entry representations

AuditEntryRow (phone card)

  • Purpose: one audit entry in the mobile list.
  • Props: entry (AuditEntry model), onTap, highlighted (realtime-append flash).
  • Rendering: AppCard e-0: line 1 AuditActionChip(action) + bodyMedium local time (DateTime.toLocal()); line 2 actor (bodyMedium name if resolved (proposed), else actorId mono) + entityType/entityId mono when present; line 3 correlationId mono bodySmall onSurfaceVariant (truncated, tooltip full).
  • States: default / highlighted (primaryContainer flash m-base) / pressed.
  • a11y: card button semantics "StudentUpdated by at
  • Flutter: Card + InkWell + Column.

AuditTable (desktop ≥ 1200 px)

  • Purpose: dense read-only log table (primary desktop surface).
  • Props: rows (AuditEntry), onRowTap(id), loadingMore, total.
  • Columns: Time (local, UTC tooltip) · Action (AuditActionChip) · Actor (name/id) · Entity (entityType + id mono) · Context (correlationId mono, copy icon).
  • Behaviour: sticky header (El-e-2); hover row tint m-fast; row tap → detail; Semantics column headers; footer row "Showing X of Y — End of audit trail".
  • Flutter: custom DataTable-free implementation — Table/TableRow inside horizontal SingleChildScrollView, ListView.builder for rows (virtualized); or DataTable2-style if licensed — wrap in AuditTable so it is swappable.

AuditActionChip

  • Purpose: compact action token with category color.
  • Props: action (string), outlined?.
  • Category mapping (proposed): prefixes/contains — create|created|registersuccess; delete|deleted|revoke|logouterror; update|updated|change|edit|enable|disabletertiary; login|loginprimary; else neutral surfaceVariant. Text = raw action (server vocabulary is free-form PascalCase eventType, audit.handler.ts:36 — never translated by mapping).
  • a11y: Semantics(label: action); color never sole signal — text always present.
  • Flutter: AppBadge/Chip with MaterialStateProperty background.

B. Filtering

AuditFilterBar

  • Purpose: AND-combined filters → GET /audit-logs params (audit.controller.ts:22-24).
  • Props: actions[], entityTypes[], actorId, onChanged(filters), onClear, busy.
  • Rendering: Wrap of AppDropdowns (searchable) + actor AppTextField + Clear; active filters mirrored as removable AppChips.
  • Behaviour: any change → page=1 reload (03 §2); Esc clears.
  • a11y: each control labeled; results count announced (live region).
  • Flutter: Wrap + shared dropdown widget.

C. Diff & JSON rendering (the heart of the detail view)

AuditDiffView

  • Purpose: render before vs after maps (audit-log.schema.ts:45-49) as changed/added/ removed rows.
  • Props: before (Map<String,dynamic>?), after (Map<String,dynamic>?), onExpand(key, value).
  • Computation (client-side): union of keys → changed (value differs), added (after-only), removed (before-only).
  • Rendering per key: key mono labelMedium · status badge (changedtertiary, addedsuccess, removederror) · before (struck through, error tint) → after (success tint). Nested values → "Object (3 keys)" / "Array (5 items)" row → tap expands JsonTreeView. Empty maps → AppEmptyState compact "No snapshot captured".
  • a11y: each row semantics "field changed"; live region on expand.
  • Flutter: Column of custom DiffRow widgets; AnimatedSize expand.

JsonTreeView

  • Purpose: inspect arbitrary payload values (after = payload fallback, audit.handler.ts:43; metadata, audit-log.schema.ts:54-55).
  • Props: value, label?, depth, copyable.
  • Behaviour: recursive expand/collapse; SelectableText leaves; "Copy JSON" action (snackbar "Copied"); collapsed arrays/objects show counts; maxDepth guard with overflow marker.
  • a11y: tree exposes Semantics expand buttons + value labels; announce "3 keys".
  • Flutter: recursive Widget builder; ExpansionTile-free custom (dense).

D. Timeline (proposed)

EntryTimeline

  • Purpose: reverse-chronology of related entries (same entityId/correlationId).
  • Props: entries[], onEntryTap.
  • Rendering: vertical rail + dots per entry (time, AuditActionChip, actor).
  • Status: (proposed) — needs backend entityId filter (OQ-1) or client grouping.
  • Flutter: custom CustomPaint rail + list.

E. Status & feedback

  • Reuses AppSkeleton, AppEmptyState, AppErrorState, AppOfflineBanner, AppSnackbar from 00-shared/03 unchanged.
  • AuditNewEntryBanner: inline AppBanner info "1 new entry — tap to view" (realtime append), auto-dismiss 6 s; a11y live region.

F. Only elements the API supports

  • No create/edit/delete/export actions — server exposes a single GET (audit.controller.ts:17); anything else is (planned) (AUDITING.md:87) and speced disabled.
  • No actor-name field in the response — name resolution (proposed) via users module.

G. Flutter widget map (module)

ComponentFlutter widgets
AuditEntryRowCard + ListTile-style Row
AuditTableTable/custom rows in SingleChildScrollView
AuditActionChipChip + Badge
AuditFilterBarWrap + shared AppDropdown
AuditDiffViewcustom DiffRow column
JsonTreeViewrecursive ExpansionTile-like builder
EntryTimeline (proposed)CustomPaint rail

H. Golden tests (base 00-shared/03 G)

Every module component: golden at phone/tablet/desktop + dark mode; AuditDiffView with changed/added/removed/nested/empty permutations; JsonTreeView deep + long-string cases.