# Unified Site Configuration Contracts # # Type contracts for site.ncl — validates the complete site configuration # including site metadata, paths, themes, content types, and routes with # embedded multilingual menu data. # # JSON output of site.ncl is deserialized by build_config::site_config::UnifiedSiteConfig. { SiteMetadata = { name | String | doc "Site display name", languages | Array String | doc "Supported language codes (ISO 639-1, e.g. ['en', 'es'])", default_language | String | doc "Default language code when none is specified in URL", }, SitePaths = { i18n | String | doc "Path to i18n/FTL locales directory", content | String | doc "Path to content directory", themes | String | doc "Path to themes configuration directory", assets | String | doc "Path to public assets directory", migrations | String | doc "Path to SQL migration files directory (server-only)" | optional, email_templates | String | doc "Path to email template files directory (server-only)" | optional, work | String | doc "Path to work-in-progress / staging assets directory (implementation-specific)" | optional, }, ThemeConfig = { default | String | doc "Default theme name", available | Array String | doc "List of available theme names", }, ContentTypeFeatures = { style_mode | String | doc "Display style: 'row' or 'grid'", use_feature | Bool | doc "Enable featured content at top of index", use_categories | Bool | default = true, use_tags | Bool | default = true, use_emojis | Bool | default = false, cta_view | String | doc "CTA component name (e.g. 'BlogCTAView')", default_page_size | Number | default = 10, # Allow additional feature fields without breaking the contract .. }, ContentType = { name | String | doc "Content type identifier (e.g. 'blog', 'recipes')", directory | String | doc "Directory name under content path", enabled | Bool | default = true, features | ContentTypeFeatures | doc "Content display and pagination settings", }, # Database connection configuration. # Structural settings (type, path, pool sizing) belong here. # Credentials and full overrides go in .env via DATABASE_URL. DatabaseConfig = { url | String | doc "Database connection URL (e.g. 'sqlite:data/dev.db' or 'postgres://...')", create_if_missing | Bool | doc "Create the SQLite database file if it does not exist" | default = true, max_connections | Number | doc "Maximum number of connections in the pool" | default = 5, min_connections | Number | doc "Minimum number of idle connections in the pool" | default = 1, connect_timeout | Number | doc "Connection acquisition timeout in seconds" | default = 30, idle_timeout | Number | doc "Maximum idle connection duration in seconds" | default = 600, max_lifetime | Number | doc "Maximum connection lifetime in seconds" | default = 1800, }, # Footer metadata — absorbed inline into SiteConfig. FooterMetadata = { title | String | doc "Footer title (language-neutral, e.g. author name)", company_desc | { _ : String } | doc "Company description keyed by language code", copyright_text | { _ : String } | doc "Copyright notice keyed by language code", social_links | Array { name | String, url | String, icon | String, } | default = [], }, # Menu configuration embedded directly in Route. # At least one of label_key or labels must be present (validated by Nickel contracts # plus runtime assertion in Rust loader). MenuEntry = { enabled | Bool | doc "Whether this route appears in navigation" | default = false, order | Number | doc "Display order (lower = first)" | optional, icon | String | doc "Icon identifier (e.g. 'home', 'fas fa-user')" | optional, label_key | String | doc "Primary: FTL i18n key for label (e.g. 'nav-home')" | optional, labels | { _ : String } | doc "Fallback inline labels keyed by language code" | optional, visible_in | Array String | doc "Language codes this item is visible in. Empty = all languages." | default = [], use_in | Array String | doc "Navigation zones: ['header'], ['footer'], or ['header','footer']" | default = ["header"], }, # A unified route entry covering all languages in a single record. # paths maps language code → URL path (e.g. { en = '/', es = '/inicio' }). Route = { component | String | doc "Page component name (e.g. 'HomePage', 'ContentIndex')", paths | { _ : String } | doc "URL paths keyed by language code", priority | Number | doc "SEO/sitemap priority (0.0–1.0)" | default = 0.8, content_type | String | doc "Content type for dynamic routes (e.g. 'blog')" | optional, menu | MenuEntry | doc "Navigation menu configuration for this route", props | { _ : Dyn } | doc "Additional route-specific properties passed to component" | optional, }, LogoConfig = { light_src | String | doc "Path to light-mode logo image", dark_src | String | doc "Path to dark-mode logo image (empty string disables dark variant)", alt | String | doc "Alt text for the logo image", show_in_nav | Bool | doc "Whether to show the logo in the navigation bar", show_in_footer | Bool | doc "Whether to show the logo in the footer", width | optional | doc "Optional logo width (CSS value or null)", height | optional | doc "Optional logo height (CSS value or null)", }, SiteConfig = { site | SiteMetadata, paths | SitePaths, themes | ThemeConfig, logo | LogoConfig, content_types | Array ContentType, database | DatabaseConfig | doc "Database connection and pool configuration", footer | FooterMetadata, routes | Array Route, }, }