Shell Cohesion
One consistent chrome across every product: a shared page header, shared empty/loading primitives, breadcrumbs, and a trimmed launcher — so the platform reads as one product instead of five editors that each rolled their own toolbar. The Resource Explorer builds on these primitives.
Components
Shell primitives live in app/components/Styled/ (design-system set) and app/components/App/ (app-chrome), one export per file.
Styled/PageHeader.vue— the canonical page header. Av-toolbar(surface,height="auto") laying out two flex rows — a breadcrumb row (defaultAppBreadcrumbs, with a right-alignedactionsslot) and acontrolsrow — never insidev-toolbar-title.Styled/EmptyState.vue— icon + title + description + optional action slot, for "no resources yet" / "no rows" / "not bound to data" states.Styled/Skeleton.vue— thinv-skeleton-loaderwrapper with the project's bordered-card preset, for per-region loading (instead of relying on the single globalAppLoadingIndicator).Styled/KeyboardShortcutsDialog.vue— the shared shortcuts help dialog (category groups +kbdchips over aKeyboardShortcutCategory[]prop); bound to the messaging shortcuts dialog and the resource explorer'sResource/ShortcutsOverlay.App/Breadcrumbs.vue— derives the trail from the current route matched againstProductListLinkItems(route paths are kebab, soprettifywon't do — product titles come from the launcher data); rendered byPageHeader, not the global app bar, so it carries page context and never duplicates.App/MenuLinkListItem.vue— a single launcher leaf (extracted fromMenuLinkListso leaves render identically at top level and inside a group).
Navigation
ListLinkItemcarrieschildren?: readonly ListLinkItem[].MenuLinkListrenders a child-bearing item as av-list-group; leaves go throughMenuLinkListItem.ProductListLinkItems: one Resources entry replaces the old per-editor entries; Clicker + Dungeons live under a "Games" group.ProductList(home/login drawer) reusesMenuLinkListso both surfaces stay in sync.- The app bar's
#appendcluster (App/Bar.vue) orders the app launcher (nine-dotmdi-dots-grid) first, then the theme toggle, the notification bell, and the More/account menu last — the launcher leads so the product grid is the first affordance, and the bell sits directly beside the More menu.
Key files
| File | Role |
|---|---|
app/components/Styled/PageHeader.vue | shared page header (title + breadcrumbs/controls/actions) |
app/components/Styled/EmptyState.vue | shared empty state |
app/components/Styled/Skeleton.vue | shared skeleton loader |
app/components/App/Breadcrumbs.vue | route-derived breadcrumb trail rendered by PageHeader |
app/components/App/MenuLinkListItem.vue | launcher leaf, reused by group + top level |
app/services/app/ProductListLinkItems.ts | launcher data (Resources entry + Games group) |
app/models/shared/ListLinkItem.ts | children field for submenus |
app/components/App/Bar.vue | top app bar; #append button order (launcher · theme · bell · more) |
Notes
- Styling follows the
stylingskill (UnoCSS attributify,flexnotd-flex) andvuetifyskill (typed select items,v-btntooltips). Header layout uses flex rows, not nestedv-toolbar-title. - Additive and incremental: each surface adopts
PageHeaderindependently; nothing breaks if one lags.