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.
StyledPageHeader— the canonical page header. Av-toolbar(surface,height="auto") laying out flex rows — the breadcrumb trail with a right-alignedstatusslot for a standing readout, then the page title beside a right-alignedactionsslot, then an optionalfiltersrow — never insidev-toolbar-title. The trail row is where a persistent number belongs: the trail rarely fills a line, so anything parked on the title row instead pays a row of its own for width already going spare. The title row renders only when there is a title or actions, so a page named by its own content (resource explorer) costs no empty row.StyledEmptyState— icon + title + description + optional action slot, for "no resources yet" / "no rows" / "not bound to data" states.StyledSkeleton— thinv-skeleton-loaderwrapper with the project's bordered-card preset, for per-region loading (instead of relying on the single globalAppLoadingIndicator).StyledKeyboardShortcutsDialog— the shared shortcuts help dialog (category groups +kbdchips over aKeyboardShortcutCategory[]prop); bound to the messaging shortcuts dialog and the resource explorer'sResource/ShortcutsOverlay.AppBreadcrumbs— renders the area hub followed by the pages the visitor actually came through, never the page they are on, so a direct arrival still has its way out (breadcrumb trail); rendered byStyledPageHeader, not the global app bar, so it carries page context and never duplicates.AppMenuLinkListItem— 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 Resource Explorer entry covers every resource type rather than one entry per editor; Clicker + Dungeons live under a "Games" group.ProductList(home/login drawer) reusesMenuLinkListso both surfaces stay in sync.- The app bar's
#appendcluster (AppBar) 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 (breadcrumbs + status, title + actions, filters) |
app/components/Styled/EmptyState.vue | shared empty state |
app/components/Styled/Skeleton.vue | shared skeleton loader |
app/components/App/Breadcrumbs.vue | navigation-derived breadcrumb trail rendered by PageHeader |
app/components/App/Menu/LinkListItem.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.
