--- name: sage-frontend description: Sage platform frontend — shell architecture, UX design methodology, deployment workflow, theme system, menu/navigation patterns, and bricks-compatible implementation. tags: [sage, frontend, shell, deployment, ux-design, theme, bricks, menu] --- # Sage Frontend: Shell, Design & Deployment ## When to Use - Designing new Sage UI features or redesigning existing ones - Modifying shell layout (index.ui, global_menu.ui, sidebar, topbar) - Deploying UI changes to production - Working with theme CSS/JS (dark/light mode, shell_theme.*) - Adding/modifying navigation menu items with role-based visibility - Building bricks-compatible UI files (.ui, .dspy) ## Architecture Overview **Sage shell files live at the sage level, not in modules:** | File | Location | Purpose | |------|----------|---------| | `index.ui` | `sage/wwwroot/` | Shell layout (header + sidebar + main content area) | | `global_menu.ui` | `sage/wwwroot/` | Global navigation menu (all modules, role-based visibility) | | `shell_theme.js` | `sage/wwwroot/` | Theme toggle, sidebar collapse, SPA router, `sageReloadMenu()` | | `shell_theme.css` | `sage/wwwroot/` | Theme CSS variables, dark/light overrides | **Module responsibility:** Each module only handles its own content when its menu items are clicked. No module owns the shell layout or global menu. **`.sage-main` uses `overflow: hidden`** — Each page is responsible for its own scrolling via `VScrollPanel` with `css: "filler"`. ## Design Methodology (Phased Approval — MANDATORY) ### Phase 1: System Analysis 1. Map all modules: scan `pkgs/*/wwwroot/*.ui`, `pkgs/*/json/*.json` 2. Identify user roles from RBAC module, role-permission mappings, menu templates 3. Classify pages: Dashboard, List/CRUD, Form/Edit, Detail, Settings 4. Audit pain points: layout inconsistencies, color fragmentation, navigation issues ### Phase 2: Design Proposal (PRESENT TO USER — DO NOT IMPLEMENT) Present as terminal text (ASCII diagrams + tables): 1. Design Philosophy + core principles + color system 2. Global Layout (ASCII wireframe) 3. Key Interface Mockups (dashboard, list, form, detail) 4. Bricks Widget Mapping Table 5. File Organization Plan 6. Implementation Phases 7. Open Questions **STOP HERE.** Wait for user feedback before coding. ### Phase 3: Implementation (ONLY AFTER APPROVAL) Follow bricks-framework conventions strictly. Maintain backward compatibility. See `references/design-methodology.md` for full workflow detail and `references/design-system.md` for color system, spacing, and widget mapping. ## Design System (Dark Theme Default) ### Color System ``` Background: #0B1120 (global), #111827 (panel), #1E293B (card), #334155 (hover) Borders: #334155 (strong), #1E293B (subtle) Text: #F1F5F9 (primary), #94A3B8 (secondary), #64748B (tertiary) Brand: #3B82F6 (primary), #22C55E (success), #F59E0B (warning), #EF4444 (danger) ``` ### Spacing Container: 24px | Card: 20px | Between sections: 16px | Between elements: 12px | Tight: 8px ### Card Pattern VBox: `bgcolor:#1E293B`, `borderRadius:12px`, `padding:20px`, `border:1px solid #334155` See `references/design-system.md` for full widget mapping table and responsive layout patterns. ## Deployment Workflow ### Git Strategy **`sage/wwwroot/` is tracked in git** (removed from .gitignore). Only real files go in git. Module symlinks are created by `build.sh` and MUST be gitignored. **Git-tracked:** index.ui, global_menu.ui, imgs/, other real .ui/.dspy files **Gitignored:** ALL module symlinks (created by build.sh), .DS_Store, *.bak, *.swp ### Deploy Steps ```bash # 1. Edit source files vim ~/repos/sage/wwwroot/index.ui # 2. Commit to sage repo cd ~/repos/sage && git add wwwroot/ && git commit -m "feat: update shell" && git push # 3. Deploy to production cd ~/sage && git pull # 4. Restart cd ~/sage && ./stop.sh && ./start.sh ``` ### Shell Theme Files - `shell_theme.css` and `shell_theme.js` live directly in `sage/wwwroot/` (NOT symlinked from dashboard_for_sage — that module is deprecated). - Auto-discovered by ahserver's `cssfiles()`/`jsfiles()` — NO header.tmpl modification needed. - NEVER modify `bricks/dist/header.tmpl` for Sage-specific themes. - **Verify the file is actually served:** `curl -sI https:///shell_theme.js` must return 200. Broken symlinks produce 404 silently — the page still loads but sidebar toggle, theme switch, and menu reload all fail. See `references/deployment-workflow.md` for full git strategy, build.sh details, and deployment pitfalls. ## Menu & Navigation ### global_menu.ui Structure - Items use comma-prefix formatting (not trailing commas) - Each item targets `app.sage_main_content` - Jinja2 conditions for role-based visibility (use with caution) ### Role-Based Menu Pattern ```jinja2 {% set roles = get_user_roles(get_user()) %} {% set role_str = roles|join(',') %} {% set is_customer = 'customer.' in role_str %} {% set is_customer_admin = 'customer.admin' in roles %} ``` ### Sidebar Toggle `sageToggleSidebar()` in shell_theme.js calls both CSS class toggle AND `bricks.getWidgetById('global_nav_menu').toggle_collapse()`. Menu reload (`sageReloadMenu()`) must re-apply collapsed state. **CRITICAL: `sageReloadMenu()` must set `sidebar.el.style.width` for BOTH states.** The original code only set width when collapsed (64px), leaving the expanded state (240px) unset. After destroying and rebuilding the menu (`sidebar.el.innerHTML = ''`), the CSS `transition: width 0.3s ease` on `.sage-sidebar` can be interrupted, leaving the inline width stuck at a narrow value. Menu text widgets use the bricks `filler` CSS class which has `overflow: hidden` — when the sidebar is narrower than expected, text labels get clipped to ~1 character. **Correct pattern after menu rebuild:** ```javascript sidebar.el.style.width = isCollapsed ? '64px' : '240px'; updateSidebarIcon(isCollapsed); if (isCollapsed) { var menu = bricks.getWidgetById('global_nav_menu', bricks.app); if (menu && menu.collapse) menu.collapse(); } ``` See `references/shell-theme-js-deploy.md` for the symlink gotcha. ### Dynamic Menu Reload Event: `user_logined` (NOT `sage_login`). Fires after login to refresh menu with role-based items. See `references/menu-patterns.md` for role hierarchy detection, nested conditions, and customer role examples. ## Theme System & CSS Overrides ### CRITICAL: bricks.css Light Defaults Override Dark Theme When the dark shell loads CRUD pages from modules, bricks.css default light styles apply. `shell_theme.css` must include `[data-theme="dark"]` overrides for ALL bricks.css component classes: - `.tabular`, `.tabular-header-row`, `.tabular-row`, `.tabular-cell` - `.popup`, `.modal`, `.message`, `.titlebar` - `.inputbox`, `.auto-textarea` - `.htoolbar`, `.vtoolbar`, `.toolbar-button` - `.accordion-item`, `.card`, `.tabpanel` - `.llm_msg`, `.user_msg`, `pre` ### CRITICAL: Verify BOTH Light AND Dark Modes After Any CSS Change Common failure: fix dark mode but break light mode (light text on light bg). Checklist: 1. Switch to light → CRUD text must be dark 2. Switch to dark → CRUD text must be light 3. Check popups and forms in both ### Quick Entry Button Styling Buttons linking to other modules: `bgcolor: "#1E293B"`, `border: "1px solid #475569"`, `color: "#FFFFFF"`, `fontWeight: 600`. Avoid `bgcolor: "#334155"`. ## Critical Pitfalls 1. **DO NOT implement during design phase** — present wireframes first, wait for approval 2. **All designs must be bricks-compatible** — no invented properties, no `style` nesting, correct `actiontype` 3. **shell_theme.css/js are now real files in `sage/wwwroot/`** — dashboard_for_sage module is deprecated. Edits and commits go to sage repo. 4. **Don't put shell files in dashboard_for_sage** — shell lives at `sage/wwwroot/` 5. **Module symlinks are created by build.sh** — NEVER add to git 6. **shell.ui no longer exists** — `index.ui` IS the shell layout 7. **Static assets must exist in the repo** — missing assets cause 401 → JS runtime errors cascade 8. **urlwidget containers should NOT have ids** — id belongs in loaded UI file's root widget 9. **Only include Sage modules in global_menu.ui** — external modules cause 401 errors. See `references/module-membership.md` 10. **Shell top bar must include language switcher and window dock** — check historical `top.ui` for required components 11. **Menu navigation must trigger Router hook** — `menu_clicked()` must call `bricks.Router._onReplace()` or F5 refresh won't restore page 12. **user_logined event fires AFTER Router._restore()** — handler must check if Router already loaded a page before overriding with dashboard 13. **RBAC-aware navigation** — menu conditions are for UX (hiding items), not security. Path-level RBAC remains primary access control. 14. **Documentation pages pattern**: Copy markdown to wwwroot → MarkdownViewer UI → add RBAC permissions → menu entry points to .ui page 15. **Dashboard stats need real data sources** — identify existing .dspy endpoints; don't propose hardcoded values 16. **Maintain backward compatibility** — new shell wraps existing module pages, doesn't replace them 17. **Jinja2 comma management** — conditional blocks in .ui files produce JSON. Each `{% if %}` block must independently start with `,` and the preceding widget must end with `}` (no comma). Never produce `},,{` or `},]`. Mentally render every role combination before committing. 18. **Owner and Reseller are equivalent** — always check both: `'owner.*' in roles or 'reseller.*' in roles` 19. **ALL-users menu items go after `{% endif %}`** — not inside either `{% if is_customer %}` or `{% else %}` branch. Items inside a branch are only visible to that role group. 19. **Menu URL trailing slash causes RBAC 403** — if a menu item URL ends with `/` (e.g. `/dapi/downapp/`), the registered permission path must also include the trailing slash variant. RBAC does exact match. See `sage-rbac-auth` skill for details. 20. **Bricks Image widget: CSS targets the `` tag directly** — an `Image` widget renders as a raw ``, NOT a wrapper div. CSS like `.logo img { height: 50px }` silently fails because `.logo` IS the ``. Use `.logo { height: 50px }` instead. Verify: `document.querySelector('.logo').tagName` returns `"IMG"`. Symptom: images render at full native resolution (e.g. 2405×777 logo fills entire viewport). See `sage-platform: references/multi-tenant-routing.md`. 21. **`.filler` CSS class has `overflow: hidden`** — root VBox with `css:"filler"` blocks scrolling when content exceeds viewport. Add `"overflow":"auto"` to VBox options. Inline style takes precedence over the class. Symptom: tall landing pages appear cut off; `scrollHeight == clientHeight` despite content being 3000+px. 21b. **bricks `urlwidget` bind format: `url` goes inside `options`** — `buildUrlwidgetHandler` copies `desc.options` via `objcopy(desc.options||{})`. A bare `{"url": "..."}` on the bind is IGNORED. Correct: `{"actiontype":"urlwidget","target":"root.center","options":{"url":"{{entire_url('/path.ui')}}"}}`. Wrong: `{"actiontype":"urlwidget","target":"root.center","url":"..."}`. See `references/bricks-bind-patterns.md`.\n\n21c. **cross-widget binds need `root.` or `app.`** — bricks `get_by_id` uses `fromw.dom_element.querySelector('#'+id)` — descendant-only search. A widget deep in a dropdown can't find a top-level sibling. Use `target:"root.center"` to start from root. Test with `bricks.getWidgetById('root.center', bricks.app)` in console.\n\n21d. **Language switching: `getlang()` doesn't exist in bricks** — bricks only has `bricks.get_current_language()` (reads `navigator.language`). For multi-language tenant pages, use `localStorage.setItem('hermes-lang',lang)` + `location.href` redirect. The entry `index_carousel.ui` should check localStorage on ready and redirect if non-default lang is set. See `references/tenant-language-switching.md`.\n\n22. **Tenant CSS dual-loading: TWO files, both must match** — bricks auto-loads BOTH `tenant/tenant.css` (global) AND `/tenant.css` (tenant-specific). The global file loads FIRST, the tenant-specific file SECOND. Both share selectors like `.tenant-hero`, `.login-btn`, `.nav-link:hover`. If you only update the domain-specific file, the global file's values may still leak through where `!important` isn't used. Always grep BOTH files and update them together. Verify with `document.querySelectorAll('link[rel="stylesheet"]')` in browser console to see which CSS files are actually loaded. 23. **`height: 100%` on absolute children needs explicit parent `height`, not `min-height`** — when a parent uses `position: absolute` + `min-height`, and children use `position: absolute; height: 100%`, the children's `height: 100%` resolves against the parent's `height` property (which may be unspecified), NOT `min-height`. Children collapse to 0px. Fix: use `height` instead of `min-height` on the absolute-positioned parent. Symptom: card content exists in DOM but all `getBoundingClientRect().height` values are 0. 24. **Browser CSS cache survives across navigations** — the cloud browser (Browserbase) caches CSS aggressively. After deploying CSS changes, the browser may keep serving stale cached copies. Verify the server file first with `curl`, then in the browser force-reload the stylesheet: `var l=document.querySelector('link[href*="tenant.css"]'); l.href=l.href.split('?')[0]+'?t='+Date.now()`. Do not re-navigate expecting fresh CSS — the stylesheet URL has not changed so the browser reuses its cache. 25. **bricks auto-sets `data-theme="dark"` — force light with MutationObserver** — bricks framework detects OS `prefers-color-scheme: dark` and sets `data-theme="dark"` on `` AFTER the ready event fires. An inline `setAttribute` in the same ready handler gets overwritten. Fix: inject a `theme.js` script that (a) sets `data-theme="light"` immediately, (b) uses `MutationObserver` on `` attributes to block bricks from reverting it, and (c) checks `?theme=dark` query param for opt-in dark mode. Inject via `index_carousel.ui` binds BEFORE carousel.js. See `references/tenant-theme-forcing.md` for full script, color palette, and pitfalls. 26. **bricks `vcontainer`/`hcontainer` overrides CSS dimensions — always use `!important`** — bricks wraps every VBox/HBox in a `
` or `
` with flexbox layout. These classes set `width:100%` and/or `display:flex` which override CSS class-based rules. When building widgets that require `position:absolute` (carousel cards, flip inners) or fixed dimensions, use `!important`: `position:absolute!important; width:280px!important; height:360px!important`. For absolute children inside a bricks flex parent, also override the parent track to `display:block!important`. See `references/bricks-carousel-flip.md`. 27. **bricks strips custom `data-*` attrs; `cssText`+`!important` silently fails; urlwidget renders async — carousel.js triple pitfall**. When building carousel/flip-card widgets: (a) `data-pos` set in JSON `options` is NOT rendered by bricks — assign via JS with `c.setAttribute('data-pos', String(i-mid))`. (b) `element.style.cssText = 'position:absolute!important;...'` silently fails — the browser rejects the entire string when `!important` is present. Use individual `element.style.position = 'absolute'` without `!important` (inline styles already have highest specificity). (c) `urlwidget` content loads AFTER the parent page's `ready` event, so `querySelectorAll('.carousel-card')` returns empty when carousel.js first runs. Wrap init in `setTimeout(init, 500)` retry: `var cards=document.querySelectorAll('.carousel-card'); if(!cards.length){setTimeout(init,500);return;}`. See `references/bricks-carousel-flip.md`. 28. **bricks widget development: adding new widget types** — creating a new bricks widget (e.g. FlipCard, Carousel) requires extending `JsWidget`/`Layout`, implementing `create()`, registering via `Factory.register()`, adding to `build.sh` SOURCES, and adding CSS to `bricks.css`. NEVER call async `widgetBuild` in the synchronous constructor — breaks the page silently. See `references/bricks-widget-development.md`. 29. **Menu text clipped to 1 char after toggle: sidebar width stuck narrow** — menu item text widgets use CSS class `filler` (`overflow: hidden`). If the sidebar inline width is stuck at ~64px (collapsed width) while the menu is expanded, text labels get clipped to ~1 character. Root cause: `sageReloadMenu()` not resetting width for expanded state, or CSS `transition: width 0.3s ease` being interrupted by `innerHTML = ''` during menu rebuild. Fix: always set `sidebar.el.style.width = isCollapsed ? '64px' : '240px'` after menu rebuild. Check `shell_theme.js` is actually being served (symlink may be broken — see `references/shell-theme-js-deploy.md`). ## Shell Deployment Checklist (After Any UI Module Update) 1. Verify shell entry point intact: `sage/wwwroot/index.ui` has `sage_sidebar` HBox 2. Rebuild: `cd ~/repos/sage && ./build.sh` 3. Verify global_menu.ui targets `app.sage_main_content` 4. Verify theme files are auto-discovered (in wwwroot root, not blocked by RBAC) 5. Restart Sage: `./stop.sh && ./start.sh` 6. Test: unauthenticated view, login flow, role-based items, logout flow ### Common Failure Modes - Module index.ui overwrites shell index.ui during build.sh - Sidebar CSS class missing (shell_theme.css not loaded) - RBAC blocks shell_theme.js (must be `any` role in load_path.py) ## References - `references/design-methodology.md` — Full phased design workflow and approval process - `references/design-system.md` — Color system, spacing, bricks widget mapping table - `references/deployment-workflow.md` — Full git strategy, build.sh, deployment pitfalls - `references/menu-patterns.md` — Role-based menu, Jinja2 conditions, customer roles - `references/module-membership.md` — Definitive list of Sage vs external modules - `references/customer-role-menu.md` — Customer role menu structure and feature mapping - `references/sms-login-flow.md` — SMS login architecture - `references/shell-architecture.md` — Shell layout structure and key widget IDs - `references/dashboard-optimization.md` — Role-based UI, stat card trends, chart consolidation, Jinja2 comma pitfalls - `references/sage-module-inventory.md` — Module inventory for design analysis - `references/tenant-css-verification.md` — Tenant CSS dual-loading, cache busting, height vs min-height pitfall - `references/tenant-landing-theming.md` — Dual-theme landing page design, aligning to reference SPA, card height fix, color palette - `references/tenant-theme-forcing.md` — MutationObserver pattern to force light theme, bricks dark auto-override, color palette - `references/bricks-carousel-flip.md` — Carousel 3D flip cards, hover-flip case cards, products dropdown — all as bricks CSS-only widgets - `references/bricks-widget-development.md` — How to add new widget types (FlipCard, Carousel) to the bricks framework; constructor pitfalls - `references/product-landing-pages.md` — Product sub-page template, floating chat icon, navigation URL pattern - `references/bricks-bind-patterns.md` — urlwidget options format, target resolution, PopupWindow, direct navigation\n- `references/tenant-language-switching.md` — Multi-language static pages via localStorage + redirect\n\n## Related Skills - `bricks-framework` — Dynamic menu reload, widget properties, actiontype values - `module-development-spec` — Module wwwroot structure and routing - `build-script-modularization` — Build scripts for bricks and other assets - `sage-rbac-auth` — Authentication flow, session management - `sage-cache-sync` — Cross-process cache invalidation patterns