Skip to content

Latest commit

 

History

History
118 lines (110 loc) · 19.6 KB

File metadata and controls

118 lines (110 loc) · 19.6 KB

System theme and design system (Feature Draft)

Status: Draft
Updated: 2026-06-05 Owner: Core
Purpose: Draft for the CMS system theme, admin UI design language, shared layout primitives, and reusable interface components.

Overview

  • Defines the visual and interaction foundation for setup, login, admin, editor, operational workflows, and system pages.
  • Covers design tokens, layouts, reusable components, accessibility, translations, responsive behavior, and theme boundaries.
  • Depends on core architecture, setup automation, error handling, and the public theme engine.
  • Keeps the CMS usable as a product instead of only a backend architecture.

Outline

The CMS needs native system templates that behave like immutable virtual packages with frontend-theme, backend-theme, and system-template scopes. Frontend templates render public project content, user flows, and error pages; backend templates render setup, admin, editor, package management, and operations. Root templates provide shared wrappers and macros. The native package also contains the first public fallback templates, so content remains renderable without an installed package.

The system UI should be quiet, dense, predictable, and work-focused. It should favor clear navigation, readable forms, stable table layouts, explicit status indicators, and low-friction repeated workflows over decorative marketing surfaces. The goal is to make content and configuration work safe, fast, and understandable.

The first design system should be small but real. It should define enough shared primitives that every feature does not invent its own UI, while avoiding a broad custom framework. Twig components, Symfony UX, Stimulus controllers, Tailwind utilities, forms, translations, and AssetMapper should remain the default toolchain.

Technical Specifications

  • Keep frontend, backend, and root template areas scope-aware, even while all native fallbacks live in the project.
  • Keep templates/base.html.twig as the global wrapper. The template root should otherwise contain only shared directories such as templates/macros/**; area-specific templates belong under templates/frontend/** or templates/backend/**.
  • Use logical Twig namespaces in native templates: @frontend/..., @backend/..., and @root/....
  • Use templates/frontend/frontend.html.twig as the frontend base layout and templates/frontend/layouts/** for optional frontend variants.
  • Use templates/backend/admin.html.twig, templates/backend/editor.html.twig, and templates/backend/setup.html.twig as backend area layouts and templates/backend/layouts/** for optional backend variants.
  • Reserve templates/backend/admin/**, templates/backend/editor/**, templates/backend/setup/**, and templates/backend/operations/** for backend-scoped UI.
  • Use templates/frontend/content/entity.html.twig as the generic public content fallback renderer.
  • Keep user-route templates frontend-scoped for the first release, for example under templates/frontend/user/**, even though the public routes are /user/...; keep the controller/resolver boundary small enough that these templates can later move or become capability-controlled overrides.
  • Place operation UI under templates/backend/operations/**; the shared action-log overlay should live there with small partials for entries, summaries, and progress states.
  • Place error pages under templates/frontend/error-pages/**, including default.html.twig and lightweight standalone candidates for 429 and 503.
  • Place context-specific partials under templates/frontend/partials/**, templates/backend/partials/**, or narrower area directories. Do not place generic page partials at template root.
  • Keep partials intentionally small and override-friendly: layout fragments, brand/navigation fragments, typography headers, feedback states, form labels/help/errors/actions, action buttons/toolbars, and individual field types should be separate templates where practical.
  • Place reusable Twig components under the matching Twig namespace component directory: templates/components/** for root:*, templates/frontend/components/** for frontend:*, templates/backend/components/** for backend:*, and provider/package component templates below namespace-aware components/** paths so active package template ordering can override or extend them through the existing Twig loader.
  • Place reusable Twig macros/functions under namespaced macro files and aggregate provider macro namespaces rather than treating them as replaceable override files.
  • Provide a base admin layout with sidebar or top navigation, content area, page header, action area, status area, and flash/error rendering.
  • Provide a setup/login layout for unauthenticated system workflows.
  • Resolve native backend areas through explicit Symfony routes for /setup, /admin, and /editor before feature pages are added.
  • Keep setup DB-free: setup availability must be decidable without Doctrine/DBAL because database configuration is created during setup.
  • Lock setup after successful installation through a loaded APP_SETUP_COMPLETED environment value that setup writes into Composer's dumped .env.local.php array.
  • Protect backend areas with ACL access levels: setup stays public until locked, editor requires level 3+, and admin requires level 8+.
  • Use the shared navigation builder for backend menus only while it remains simple: backend items may target Symfony routes, carry minimum access metadata, and expose active plus active-ancestor state to templates.
  • Let packages contribute backend pages through static view injections on the admin and editor surfaces. Core backend views keep priority over injected package paths, and setup is not a package-facing surface because it locks after installation.
  • Define design tokens for spacing, typography, color, borders, focus states, elevation, status colors, and density.
  • Use existing local font assets and icon assets where appropriate.
  • Keep unDraw illustration assets compatible with theme colors by preserving var(--primary-svg-color) fills and mapping that variable to var(--color-primary) in the surrounding system UI where inline rendering is used.
  • Prefer Symfony Forms and Twig components for repeatable form layouts.
  • Provide a small renderer-neutral form-definition and submission layer for generated forms before committing to feature-specific form builders. The same layer should drive Admin Settings and package settings now, and later support public forms such as contact forms.
  • Keep optional provider fields, including captcha, represented as field types that render through stable provider namespaces such as @provider/captcha/** instead of hard-coding one provider into native forms.
  • Provide reusable UI primitives:
    • Buttons and icon buttons.
    • Form rows, field errors, help text, and validation summaries.
    • Tables, filters, bulk-action bars, pagination, and empty states.
    • Tabs, segmented controls, toggles, checkboxes, radios, selects, and menus.
    • Cards for repeated items only, not as default page sections.
    • Modals, confirmation dialogs, drawers, toasts, progress indicators, badges, and alerts.
    • Diff/review panels for imports, destructive actions, and generated changes.
    • Action-log panels for long-running or high-impact operations.
  • Provide command palette, contextual help drawer, notification/toast, and background-job status primitives only after the base admin shell exists.
  • Keep user-facing strings translated through deterministic translation keys.
  • Keep translation catalogues modular: messages is reserved for the structured message layer, while UI copy belongs in narrower domains such as ui, admin, editor, setup, operations, demo, or future feature slices.
  • Provide responsive behavior for desktop and tablet first, with safe mobile fallback for urgent administration.
  • Meet baseline accessibility expectations: semantic landmarks, keyboard navigation, visible focus, color contrast, labels, error association, and reduced-motion safety.
  • Keep JavaScript progressive and feature-specific through Stimulus controllers. Alpine and ApexCharts are not baseline dependencies; prefer Symfony UX, native Stimulus controllers, and the project live-polling helper before adding another frontend runtime.
  • Do not let frontend-theme packages override backend templates.
  • Do not let packages override @root shared templates unless they declare system-template.
  • Allow active packages to contribute admin navigation entries, dashboard widgets, form sections, or action buttons only through documented extension points.
  • Keep system UI assets under assets/styles/** and assets/js/** with context-specific subdirectories such as frontend, backend, admin, editor, and setup; keep JavaScript Stimulus-first and treat Alpine as an optional later dependency, not an assumed baseline.
  • Keep global design tokens under assets/styles/tokens/** and shared primitives under assets/styles/system/**. Frontend-only styles belong under assets/styles/frontend/**; backend/admin/editor/setup styles belong under assets/styles/backend/**. This mirrors Twig namespace ownership and keeps future package styles from leaking across shell boundaries.
  • Treat assets/frontend/** and assets/backend/** inside packages as area-specific package asset roots. Package assets outside those folders are shared/global and enter the extension registry only when the package also declares a global runtime scope such as module, captcha-provider, editor-provider, or system-template.
  • Remember that Tailwind currently aggregates all native and package CSS into one app.css. Asset buckets protect rebuild order and ownership, but CSS isolation still depends on area root selectors. Native root selectors should move toward branding-neutral system-frontend and system-backend names, while package selectors stay under their package slug and surface scope; stricter namespace validation can be added later if package CSS leakage becomes a practical risk.
  • Provide temporary shell preview routes under /demo, /demo/backend, and /demo/typography through the optional demo module while the product UI is still forming. These routes are development scaffolding for checking the complete native template set and Markdown typography profiles with placeholder content and can be removed with the package before production readiness. The module declares its routes as a configurable static route set, so the stable parent path defaults to /demo but can move through package settings if the site owner wants that path for content. Demo-only translation keys live in the demo module language files and are aggregated only when the package is active.

Testing & Validation

  • Render representative system pages with php bin/console render:route /<route> once routes exist.
  • Run .codex/compare_translations.php after UI copy changes.
  • Test form error rendering, flash messages, empty states, pagination, and destructive confirmations.
  • Test keyboard navigation and focus states for core controls.
  • Test responsive layout behavior for narrow and wide viewports.
  • Run php bin/console tailwind:build after CSS asset changes; keep asset-map:compile production-only.
  • Use browser screenshots for major UI changes once routes exist.

Implementation Notes

  • Decision recorded: The CMS native templates behave like immutable virtual frontend-theme, backend-theme, and system-template packages.
  • Decision recorded: The canonical Twig namespaces are @frontend, @backend, and @root.
  • Decision recorded: Root templates are restricted to base.html.twig and shared macro/helper directories; area templates live under frontend/** or backend/**.
  • Decision recorded: The native package also includes the fallback public theme so the CMS can render without an installed frontend package.
  • Decision recorded: The first system UI should be functional, dense, and work-focused, with reusable primitives but no broad custom UI framework.
  • Decision recorded: Backend templates are not overridden by frontend-only packages.
  • Decision recorded: User-route templates such as login, registration, profile, password reset, password change, API keys, and invitation acceptance remain native frontend-scoped templates in early releases and should be structured so they can later move or become capability-controlled without broad controller rewrites. The login route uses the native frontend template and is also reused by anonymous 401 responses. Registration remains config-gated and is linked from the login page only when enabled.
  • Decision recorded: Operation templates, including the action-log overlay, live under templates/backend/operations/**.
  • Decision recorded: Error-page templates live under templates/frontend/error-pages/**; 429 and 503 may use lightweight standalone templates.
  • Decision recorded: Packages may contribute system UI elements only through documented extension points.
  • Decision recorded: Long-running or high-impact operations should have a reusable action-log panel component in addition to simple progress indicators.
  • Decision recorded: UI alerts use root-scoped Twig components and a stable targeted Mercure contract. Alert producers should publish explicit UI alerts to private user or session topics instead of broadcasting arbitrary structured message-layer entries.
  • Decision recorded: Alerts are managed through a lightweight notification center with badge counts, auto/hidden/persistent modes, quiet text actions, sessionStorage restoration when available, and close semantics that remove active alerts rather than merely hiding the panel.
  • Decision recorded: Live JSON polling belongs in a reusable Stimulus/helper layer for /api/live/** endpoints. Operation forms should default to notification-center runner alerts with optional ActionLog overlay details, and provider packages can reuse the polling helper for later dynamic mechanisms such as captcha seed refreshes.
  • Decision recorded: Scoped Twig components should mirror the existing Twig namespaces (root:*, frontend:*, backend:*, and provider/package namespaces) so themes and active packages can override or extend reusable UI primitives through the same template path order as partials.
  • Decision recorded: Global tokens and shared UI primitives are native system assets. Frontend and backend theme packages should use their matching area asset roots for area-specific styles, while root/shared package assets stay in the global extension bucket only for packages that declare a global runtime scope.
  • Decision recorded: Tailwind builds a single stylesheet for now. Native area CSS and package CSS should therefore use owner/surface root selectors for practical isolation when a rule is not intentionally global.
  • Decision recorded: Temporary /demo, /demo/backend, and /demo/typography routes may exist before production readiness through the demo module so the native shells, partials, Markdown profiles, status colors, forms, empty states, and operation panels have real render targets without loose Core demo routes. Demo copy belongs to the demo module package language files.
  • Decision recorded: Packages may declare package-setting-backed configurable static route sets when a feature needs a stable route parent but should not permanently reserve a hard-coded public path. Static content and system views still win over injected package routes during route resolution.
  • Decision recorded: Native translation sources are split into language-grouped files so UI areas stay small and scoped while Symfony consumes a generated default-domain messages.*.yaml runtime catalogue.
  • Decision recorded: External SVG files used through CSS background-image cannot reliably inherit CSS custom properties from the host element. Dynamic theme coloring for unDraw assets should therefore use an allowlisted inline SVG renderer or Twig component when real theme-color adaptation is required. Mask-based coloring is acceptable only for single-color icon-like use cases; build-time or cached server-side SVG variants remain fallback options for later public/theme delivery needs.
  • Decision recorded: Backend routing starts with explicit Symfony routes for setup, admin, and editor. Static package view injections can add admin/editor route targets and menu entries, while core backend views keep priority over matching injected paths.
  • Decision recorded: Backend navigation reuses the shared navigation builder for parent/child trees, depth slicing, route URL generation, permission filtering, active state, and active-ancestor state as long as it stays modular and understandable.
  • Decision recorded: Setup completion is written to Composer's dumped .env.local.php array and checked through the already-loaded environment value, so /setup can remain available before database configuration exists and lock itself after successful installation without Doctrine/DBAL.
  • Decision recorded: Before setup completion, main public requests should redirect to /setup through an early DB-free request subscriber so a fresh installation cannot fall through into content, navigation, config, or package queries that require migrated tables.
  • Decision recorded: Generated forms use a neutral App\Form definition/submission layer rather than an admin-only helper, so Admin Settings, package settings, and later public forms can share field definitions, validation attributes, typed value casting, translated validation feedback, and provider-backed captcha fields.
  • Decision recorded: The shared form builder should remain one central renderer-neutral form layer, but it should align as closely as practical with Symfony Forms and Validator so it does not become a parallel framework.
  • Decision recorded: Twig helpers should be split by helper family as they stabilize, for example navigation, package, settings form, diagnostics, and markup/Markdown helpers instead of one broad extension.
  • Decision recorded: Markdown profiles are intentional. The Design profile may allow richer HTML/attributes only for trusted/admin/package content and can later be assigned through ACL or field policy.
  • Decision recorded: Dynamic view injection failures should be visible to administrators or debug contexts while normal production visitors are protected from raw failures. The first implementation keeps rendering graceful and records failed dynamic injection rendering through the message layer with bounded context.
  • Decision recorded: Admin diagnostics may offer detailed exports only in strongly redacted form. Raw secrets, complete environment dumps, cookies, and request payloads are not valid diagnostic output just because the Admin Panel is protected.
  • Decision recorded: Technical API, Twig helper, form, and CSS naming should be stabilized during refactors where practical. Studio/studio is branding, not the default selector or technical API namespace. Native system styles should use system-*, system-frontend-*, system-backend-*, or system-{provider-scope}-*; package styles should use {package-slug}-*, {package-slug}-frontend-*, {package-slug}-backend-*, or {package-slug}-{provider-scope}-*.
  • Decision recorded: Runtime rebranding should stay concentrated in .manifest, branding assets, branding tokens, and later optional branding packages. Hardcoded user-facing or inspectable studio-* identifiers should be treated as migration debt unless they are deliberately retained product branding.
  • Open: Decide the first navigation model, dashboard information architecture, and exact component inventory during the first UI implementation slice.
  • Open: Decide whether command palette and contextual help ship in the first admin release or remain second-slice enhancements.