Dashboard Builder
Server-synced admin preferences with 4-layer merge engine, 61 customizable settings, FOUC prevention, 409 conflict resolution, and edition-based feature gating.
The Dashboard Builder is SCRIPE's enterprise-grade admin preference system that syncs 61 customizable dashboard settings between the browser and server. It uses a 4-layer merge engine (Platform → Tenant → Admin → Runtime) to resolve settings with tenant-level override control, cross-device persistence via AdminSettingsJson, and 5 edge case protections including FOUC prevention, tab-close keepalive, and field-level 409 conflict resolution.
System Overview
The Dashboard Builder provides a complete admin preference lifecycle — from immediate cache-first rendering to background server reconciliation. Every admin gets 61 customizable settings covering layout, colors, typography, component styles, and accessibility, all persisted to the server and synced across devices.
| Component | Description | Technology |
|---|---|---|
| 4-Layer Merge Engine | Platform → Tenant → Admin → Runtime settings resolution | React Context + localStorage |
| Server Sync Hook | Bidirectional sync of admin preferences to AdminSettingsJson | Custom React Hook + REST API |
| FOUC Prevention | Optimistic render from cache, silent server reconcile | localStorage + CustomEvent |
| Override Control | Tenant admins control which settings admins can customize | Path-level whitelist |
| Preset System | Pre-built and custom theme presets with marketplace | Database + JSON blobs |
| Edition Gating | Feature visibility controlled by subscription tier | FeatureChecker pipeline |
4-Layer Merge Engine
Settings follow a strict 4-layer precedence chain. Each layer can override the previous, with optional path-level access control at the tenant layer. This architecture enables white-label deployments where tenant administrators enforce brand standards while still allowing individual admins to personalize their workspace within allowed boundaries.
| Layer | Source | Persistence | Scope |
|---|---|---|---|
| 1. Platform Defaults | defaultSettings in settings-provider.tsx | Hardcoded | All users |
| 3. Tenant Defaults | DashboardThemeJson on TenantSettings | Database (tenant) | All admins in tenant |
| 4. Admin Overrides | AdminSettingsJson on Admin | Database (per-admin) | Individual admin |
Server Sync Hook
The useAdminSettingsSync hook manages the complete lifecycle of admin settings: initial load from cache, deferred flush of pending changes from previous sessions, background server fetch, silent reconciliation, debounced save on changes, and tab-close protection. The hook is wired in DashboardLayout — the root client component for all authenticated pages.
// In DashboardLayout (authenticated layout root):
const { isSettingsReady } = useAdminSettingsSync();
// Shimmer only on first-ever device login (no cache)
if (!isSettingsReady) {
return <LoadingShimmer />;
}Widget & Grid Configuration Schema
Each widget placed on the dashboard builder canvas is a positioned block on a 12-column CSS Grid. The builder configures their columns, rows, alignment, specific props, zIndex, and visibility, and serializes these into the DashboardThemeJson alongside existing theme tokens.
export interface DashboardWidget {
id: string; // Unique identifier
type: DashboardWidgetType; // 'statsCard' | 'chart' | 'dataTable' | 'quickActions' | ...
gridColumn: string; // CSS Grid span configuration (e.g. "1 / 7")
gridRow: string; // CSS Grid row configuration (e.g. "1 / 3")
alignment: WidgetAlignment; // 'start' | 'center' | 'end' | 'stretch'
props: Record<string, unknown>; // Element specific settings (title, chartType, customUrl)
zIndex: number; // Overlap layer control
visible: boolean; // Hide/show toggle
}
export interface DashboardBuilderCanvas {
enabled: boolean; // True if layout customization is enabled
widgets: DashboardWidget[]; // Collection of positioned dashboard widgets
gridRows: number; // Configured row height (default: 6)
}Edge Case Protections
The sync system handles 5 critical edge cases that are common in real-world enterprise deployments where admins work across multiple devices, close tabs without saving, and occasionally have network interruptions.
| Edge Case | Problem | Solution |
|---|---|---|
| FOUC (Flash of Unstyled Content) | Blocking render for server fetch causes visible flash | Optimistic render from localStorage cache; shimmer only on first-ever device |
| Tab Close Data Loss | 2s debounce means last change may be lost | fetch({ keepalive: true }) with JWT in beforeunload handler |
| 409 Concurrency Conflict | Two sessions editing same admin's settings | Field-level last-write-wins merge using changedFieldsSinceLastSync tracker |
| Payload Size Bomb | Large base64 in logoText could overflow 10KB column | 8KB client-side guard with admin-settings-size-error event |
| JWT Expired at Tab Close | beforeunload fetch fails with 401 | Deferred flush via PENDING_SETTINGS_FLUSH localStorage key, flushed on next login |
Settings Reference (61 Settings)
All 61 settings are organized into 9 sections. Each setting has a defined type, default value, DOM data-attribute for CSS theming, and an optional edition gate that controls which subscription tier can access it.
| Section | Count | Examples | Edition Gate |
|---|---|---|---|
| Layout & Structure | 7 | layoutTemplate, sidebarPosition, headerStyle | Always |
| Colors & Theme | 16 | colorTheme, gradientDirection, customPrimaryColor | Gradients / CustomColors |
| Typography & Spacing | 5 | fontSize, borderRadius, spacingSize | Always |
| Component Styles | 16 | buttonStyle, inputStyle, tableStyle | ComponentStyles.Enabled |
| Logo & Branding | 5 | logoType, logoAnimation, logoSize | LogoCustomization.Enabled |
| Navigation & UX | 9 | navigationStyle, highContrast, showDetailPanel | Always |
| Toast Configuration | 3 | toastStyle, showToastIcons, toastDuration | ComponentStyles.Enabled |
| Hover Effects | 2 | hoverEffectType, hoverEffectIntensity | HoverEffects.Enabled |
Admin Override Control
Tenant administrators can control which settings individual admins are allowed to customize. When AllowAdminThemeOverride is true, the AllowedAdminSettingsJson field contains a JSON whitelist of setting paths that admins can modify. Settings not in the whitelist are rendered as read-only with a lock indicator.
| Scenario | Settings Page Behavior |
|---|---|
| AllowAdminThemeOverride = true, no path filter | Full edit mode (current behavior) |
| AllowAdminThemeOverride = true, with path filter | Mixed mode — editable settings show controls, locked settings show 🔒 badge |
| AllowAdminThemeOverride = false | Full read-only mode — all controls disabled with banner |
Security Model
The Dashboard Builder implements defense-in-depth security to prevent cross-admin data leakage, payload overflow, and unauthorized settings modification.
| Threat | Mitigation |
|---|---|
| Cross-admin settings leak | AUTH_STORAGE_KEYS_TO_CLEAR wipes all settings on logout |
| Raw theme key leak | clearAllLocalStorage() removes theme and scripe_admin_prefs_version explicitly |
| Oversized payload | 8KB client guard + 10KB server column limit |
| Admin modifies locked setting | Server validates keys against AllowedAdminSettingsJson |
| Concurrent 409 conflict | Field-level last-write-wins merge with toast notification |
| Tab-close data loss | fetch({ keepalive: true }) with JWT Authorization header |
| JWT expired at tab close | Deferred flush via PENDING_SETTINGS_FLUSH (survives logout) |
Architecture & File Map
The Dashboard Builder is implemented across 7 files in the core layer, following SCRIPE's provider-based architecture pattern. The sync hook and providers handle all state management, while the layout component serves as the entry point.
src/core/providers/
├── useAdminSettingsSync.ts # Server sync hook (5 edge case protections)
├── settings-provider.tsx # 4-layer merge engine + field tracking
├── tenant-branding-provider.tsx # Layer 3 sync + override metadata
src/core/config/
├── storage-keys.ts # Centralized localStorage key definitions
├── api-endpoints.ts # Dashboard builder API endpoint constants
src/core/ui/layout/
├── dashboard-layout.tsx # FOUC shimmer gate (layout entry point)
src/modules/auth/core/data/
├── repositories/AuthRepository.ts # Logout cleanup (raw key removal)
src/modules/monitoring/dashboard/
├── domain/entities/DashboardWidget.ts # 12-column widget schema
├── presentation/components/dashboard-builder/DashboardBuilderPanel.tsx # DnD wrapper
└── presentation/viewmodels/useDashboardBuilderStore.ts # Zustand layout manager