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 (Feature Flags Module)

Module-specific components built from shared App* primitives (00-shared/03). Reuse over new — only components absent from the shared library are defined here.


A. Flag representation

AppFlagRow

  • Purpose: one flag in the list (switch + identity + menu).
  • Props: key, label?, enabled, module?, toggleEnabled (bool — offline/no-perm), onToggle(bool), pending (bool — write in flight), onEdit, onDelete, selectable, selected.
  • Rendering: AppListTile height 56; leading AppFlagKeyChip-ish icon Icons.toggle_on/off (or Icons.flag); title = label ?? key; subtitle = key mono bodySmall + module AppBadge; trailing AppSwitch (value=enabled, onChanged=onToggle, disabled= !toggleEnabled || pending). Row AppMenu (Edit / Delete) — menu hidden when onEdit/onDelete null.
  • States: default / selected (checkbox overlay, primaryContainer tint) / pending (switch disabled + row subtitle "Saving…") / disabled-perm (switch grey, tooltip "Ask an admin") / offline (switch disabled, tooltip "You're offline").
  • a11y: single semantics node button, toggled:, label: "<label>, key <key>, module <m>".
  • Tokens: surfaceContainerLow row, R-md, El-e-0; selected primaryContainer.
  • Motion: m-fast switch; m-entrance stagger on list insert.
  • Flutter: ListTile + Switch + MenuAnchor; selection via Checkbox overlay in AnimatedContainer.

AppFlagKeyChip

  • Purpose: compact key display with copy.
  • Props: key, copyable (default true).
  • Rendering: mono text in surfaceVariant chip R-sm + Icons.content_copy 16; copy → Clipboard.setData + snackbar "Copied" + selectionClick haptic.
  • a11y: Semantics(button, label: "flag key <key>, copy"); copy result live region.
  • Flutter: InputChip-style Material + InkWell.

AppFlagGroupHeader

  • Purpose: module section header with count.
  • Props: module (label, e.g., "biometric"), count, onToggleEnabled? (module-level "show enabled only" — (proposed)).
  • Rendering: AppSectionHeader (titleMedium) + AppBadge count; Ungrouped bucket for null module values (schema allows absent module, feature-flag.schema.ts:21-22).
  • Flutter: Row + Text + Badge.

B. Editor & bulk

AppFlagEditorForm

  • Purpose: the 5-field flag form (create/edit).
  • Props: initial: FlagModel?, createMode, onSave(UpdateFeatureFlagDto), saving, fieldErrors: Map<String,String>, moduleSuggestions: List<String> (from loaded data).
  • Field spec (exact DTO mirror, update-feature-flag.dto.ts:4-26):
    • keyAppTextField, mono, required @IsString(); editable only in create mode.
    • enabledAppSwitch, required boolean.
    • labelAppTextField, optional string.
    • descriptionAppTextField multiline (2–3 lines), optional.
    • moduleAppTextField + AppChips suggestions, optional.
  • Warning banner (edit mode): description/module not persisted by server upsert (feature-flag.repository.ts:40) — see 06 §3.
  • a11y: labels linked; first invalid focus jump; errors announced.
  • Flutter: Form + TextFormFields + SwitchListTile; Autocomplete for module.

AppBulkUpdateSheet

  • Purpose: apply one state to N flags.
  • Props: selected: List<FlagModel>, onApply(state, label?), applying, results?: Map<key, success|failure+reason>, onRetryFailed.
  • Rendering: header + SegmentedButton (enable/disable) + optional label AppTextField
    • AppButton apply; results list of AppFlagRow-lite rows with ✓/✕.
  • a11y: focus trap; progress live region.
  • Flutter: showModalBottomSheet + StatefulBuilder/cubit.

C. Affected-features map

AppAffectedMapCard

  • Purpose: client-composed "what this flag gates" card ((proposed)).
  • Props: key, entries: List<{screen, icon, state}> (from gating registry), empty.
  • Rendering: AppCard with AppListTile rows — screen name + "hidden when off" AppBadge(outline); empty → "No app screens map to this key yet".
  • a11y: card Semantics(header:); rows plain text.
  • Flutter: Card + ListTiles.

D. Status & feedback

  • AppFlagToggleBanner — post-toggle info banner: "Applies to apps within ~30 seconds" (planned) (shown only once the Redis 30 s cache lands — CACHE_ARCHITECTURE.md:48; today propagation is immediate). Flagged behind a client constant; removed when server adds invalidation events (OQ-7).
  • Reuses AppBanner, AppSnackbar, AppErrorState, AppEmptyState, AppOfflineBanner with no module changes.

E. Only elements the API supports

  • List endpoints return non-paginated arrays — no infinite scroll, no LoadMore (feature-flags.service.ts:10-20; envelope meta absent — response-envelope.interceptor.ts:56-59).
  • No percentage/audience rollout controls (schema has no such fields, feature-flag.schema.ts:9-22) — gradual rollout UI is (planned) (OQ-6).
  • No restore endpoint for soft-deleted flags — the affected map and delete dialog surface the re-create caveat (OQ-4).

F. Flutter widget map (module)

ComponentFlutter widgets
AppFlagRowListTile + Switch + Checkbox + MenuAnchor
AppFlagKeyChipMaterial + InkWell + SelectableText
AppFlagGroupHeaderRow + Text + Badge
AppFlagEditorFormForm + TextFormField + SwitchListTile + Autocomplete
AppBulkUpdateSheetshowModalBottomSheet + SegmentedButton
AppAffectedMapCardCard + ListTiles
AppFlagToggleBannerMaterialBanner

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

Every module component: golden at phone/tablet/desktop + dark mode; state goldens for AppFlagRow (on/off/pending/selected/disabled-perm), AppFlagEditorForm (create/edit/error banner), AppBulkUpdateSheet (idle/applying/results).