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

06 — Screen Specifications (Audit Module)

Production-grade specs: chrome, states, components, micro-interactions, a11y, responsive, dark/light, keyboard, motion. Tokens: Sp = 00-shared/02 §3, Ty = typography §2, El = elevation §5, R = radius §4, Mo = motion §6. All components reference 00-shared/03. Data contract in 12_API_Mapping.md.


1. Audit Log List /settings/audit

Chrome: AppBar (title "Audit log", subtitle count from total when loaded); body = filter bar + result area; horizontal padding 16 (phone) / 24 (tablet+). No FAB (read-only).

Layout (phone portrait):

AppBar  "Audit log"  [count badge]
AuditFilterBar                     [Action ▼] [Entity ▼] [Actor] [Clear]
List (AuditEntryRow cards):
  Card: ActionChip "StudentUpdated"  · 10:32
         Actor: A. Kumar (u_8f2a…) · entity: student_9f1
         context: correlationId 9a1… (mono bodySmall)
  ⋮
Footer: "Showing 50 of 1,234"  [Load more]

Layout (desktop ≥ 1200 px) — AuditTable (primary target):

TimeActionActorEntityContext
02 Aug 2026, 10:32:14StudentUpdatedA. Kumarstudent_9f1…corr 9a1…
02 Aug 2026, 10:30:02UserLoggedInA. Kumarcorr 8c2…
  • Sticky header row (e-2); hover row highlight (m-fast); row tap → detail.
  • Column widths: Time 180, Action 200, Actor 200, Entity 200, Context flexible (mono).
  • Desktop shows both local-time and UTC in the Time column tooltip (server sends ISO UTC occurredAt, audit-log.schema.ts:42-43; display per client locale — no server tz field).

States:

  • loading: AppSkeleton(list) rows (≤ 200 ms target); count hidden.
  • success+empty (total === 0): AppEmptyState icon receipt_long, "No activity recorded", subtitle "Actions that happen in StudyLyon will appear here."
  • success+filtered-empty: "No activity matches these filters" + TextButton "Clear filters".
  • error: AppErrorState.code — 401 → session-expiry flow (00-shared/06 §3.6); 429 → countdown + retry button (api tier, rate-limit.constants.ts:6); 5xx → generic + requestId.
  • offline: AppOfflineBanner + last-good cache; Load more disabled with tooltip.
  • permission: route hidden without audit.read (OQ-5); direct link → 403 screen.

Pagination (server contract — 12_API_Mapping):

  • Default limit=50 (audit.controller.ts:20); client "Load more" increments page by 1.
  • Total from response data.total (not meta.totalItems — shape deviation, OQ-2).
  • Reached end (items.length === total) → footer "End of audit trail" (bodySmall).
  • No infinite scroll auto-trigger on desktop (table semantics); phone may auto-load (C2 — per-design decision).

Realtime (WS, 03 §5): on WsBridge event with matching topic, cubit prepends the entry (dedupe by _id), shows AppSnackbar "1 new entry" (tap → scroll to top + flash row primaryContainer); if filters active and entry doesn't match → snackbar "New activity — refresh filters". WS payloads never rendered verbatim (OQ-8).

Motion: rows m-entrance stagger 40 ms on first load; appends slide-in m-base; filter changes cross-fade m-slow (no pop).

A11y: table exposes column headers (00-shared/09 §7); row semantics "StudentUpdated, by A. Kumar, 10:32 — button"; live region on count + realtime banner; focus to filter bar on /; focus ring primary.

Dark/light: token-only (surface, surfaceContainerLow cards, outlineVariant dividers); both pass contrast.


2. Audit Filter Bar

Chrome: horizontal Wrap of controls (or bottom sheet on phone — shared 05 §5):

ControlComponentBehaviour
ActionAppDropdown searchablevalues = distinct action strings from loaded + cached pages (enum grows with new events — free list)
Entity typeAppDropdown searchablevalues = distinct entityType; helper note when set: "Entity type appears only on entries that carried it (currently rare)" (OQ-1)
ActorAppDropdown searchable OR AppTextField (id)exact string match (audit.controller.ts:24); names (proposed) resolved from users module
ClearTextButtonresets all → unfiltered page-1 reload

State: active filters rendered as AppChips above results (removable individually); any change → changeFilter → page=1 → reload. Esc clears all.

(planned): date-range presets (Today / 7d / 30d / Custom) + free-text search — need backend from/to/q params (OQ; AUDITING.md:86).


3. Audit Entry Detail /settings/audit/:id

Chrome: AppBar "Audit entry" + menu (Copy JSON, Export (planned)); body in SingleChildScrollView.

Header block:

[AuditActionChip: StudentUpdated]   actorType chip: user
Actor:  A. Kumar                    (u_8f2a1c…)        [mono, id copyable]
Time:   02 Aug 2026, 10:32:14       (2026-08-02T05:02:14.000Z · UTC)  [tooltip]
Entity: student_9f1…  (when present; mono)
Trace:  correlationId 9a1c…         [copy icon]

Body — AuditDiffView (the diff):

  • Input: before + after maps (audit-log.schema.ts:45-49; masked server-side, audit.service.ts:20).
  • Client computes union of keys: changed (in both, different value), added (after only), removed (before only).
  • Rows, one per key: key (mono labelMedium) | badge (changed tertiary, added success secondary, removed error) | before value (struck through, error-tint) → after value (success-tint). Scalar values inline; nested maps/arrays collapse to "Object (3 keys)" / "Array (5 items)" → tap expands JsonTreeView (indent 16, mono).
  • Empty map → AppEmptyState compact "No snapshot captured for this action."
  • after fallback: when before absent and after === payload (audit.handler.ts:42-43), header note "Snapshot after action" and render as flat JSON tree — most entries today (emitters send payload without before, e.g. users.service.ts:70-75).
  • No values rendered if neither: single line + note.
  • All values are display-only: SelectableText (desktop Ctrl+C); no editing affordances.

Immutability communication: footer bodySmall onSurfaceVariant: "This record is append-only and cannot be edited or deleted." (derived from audit-log.repository.ts:6 + PLAN 19.3, PLAN.md:191).

Not-found state (deep link, no :id endpoint, OQ-4): AppEmptyState "Entry not found — it may be beyond the loaded pages." + "Back to list". Client retries match by loading next pages up to a cap (proposed).

States: none loading (in-memory); motion m-base route; expand/collapse m-fast.


4. Export Sheet /settings/audit/export (planned)

AppBottomSheet (2/3 height): format radio (CSV, PDF), scope card (active filters + "all pages"), timezone dropdown (client-side date formatting), CTA Exportdisabled with AppBanner info "Export is planned — the API does not offer it yet" (AUDITING.md:87 has no endpoint). Enabled when backend lands; per blueprint the export itself becomes an audited action.


5. Entry Timeline (proposed)

Not bound to a screen today; would be a tab on detail: reverse-chronology of entries sharing entityId/correlationId. Requires backend filter support (OQ-1) — speced as (proposed), no code.


Global micro-interaction & motion notes (all screens)

  • Mo-m-base route transitions; cards El-e-1; sticky table header El-e-2.
  • Snackbar: success < 3 s, error ≥ 4 s (00-shared/03 A).
  • Keyboard: / filter focus, Esc clears filters/closes sheets, arrows+Enter table nav (00-shared/08 §2).
  • Safe areas: bottom nav-height respected in authed shell; table scrolls horizontally on < 1200 px (min-width 720 px, Scrollbar visible).
  • Dynamic type 200%: table degrades to stacked cards automatically at any text scale (no fixed-height rows).