@medalsocial/meda 2.7.2 → 2.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (164) hide show
  1. package/README.md +65 -1
  2. package/dist/auth/auth-message.d.ts +3 -3
  3. package/dist/auth/auth-provider-button.d.ts +1 -1
  4. package/dist/auth/auth-provider-list.d.ts +1 -1
  5. package/dist/auth/better-auth.d.ts +3 -3
  6. package/dist/brand/medal-social-mark.d.ts +1 -1
  7. package/dist/calendar/calendar-toolbar.d.ts +1 -1
  8. package/dist/calendar/calendar.d.ts +1 -1
  9. package/dist/calendar/day-view.d.ts +1 -1
  10. package/dist/calendar/internal/day-cell.d.ts +1 -1
  11. package/dist/calendar/internal/event-pill.d.ts +1 -1
  12. package/dist/calendar/internal/time-slot.d.ts +1 -1
  13. package/dist/calendar/month-view.d.ts +1 -1
  14. package/dist/calendar/week-view.d.ts +1 -1
  15. package/dist/chat/latency-badge.d.ts +1 -1
  16. package/dist/chat/latency-breakdown.d.ts +1 -1
  17. package/dist/chat/tool-call-block.d.ts +1 -1
  18. package/dist/chat/transcript-stream.d.ts +1 -1
  19. package/dist/chat/turn-card.d.ts +1 -1
  20. package/dist/components/ui/checkbox.d.ts +1 -1
  21. package/dist/components/ui/collapsible.d.ts +3 -3
  22. package/dist/components/ui/command.d.ts +9 -9
  23. package/dist/components/ui/dialog.d.ts +10 -10
  24. package/dist/components/ui/drawer.d.ts +10 -10
  25. package/dist/components/ui/dropdown-menu.d.ts +15 -15
  26. package/dist/components/ui/tooltip.d.ts +4 -4
  27. package/dist/email-builder/block-palette.d.ts +1 -1
  28. package/dist/email-builder/block-renderer.d.ts +1 -1
  29. package/dist/email-builder/block-renderers/button.d.ts +1 -1
  30. package/dist/email-builder/block-renderers/columns.d.ts +1 -1
  31. package/dist/email-builder/block-renderers/divider.d.ts +1 -1
  32. package/dist/email-builder/block-renderers/footer.d.ts +1 -1
  33. package/dist/email-builder/block-renderers/heading.d.ts +1 -1
  34. package/dist/email-builder/block-renderers/image.d.ts +1 -1
  35. package/dist/email-builder/block-renderers/social.d.ts +1 -1
  36. package/dist/email-builder/block-renderers/spacer.d.ts +1 -1
  37. package/dist/email-builder/block-renderers/text.d.ts +1 -1
  38. package/dist/email-builder/builder-canvas.d.ts +1 -1
  39. package/dist/email-builder/builder-header.d.ts +1 -1
  40. package/dist/email-builder/builder-left-tabs.d.ts +1 -1
  41. package/dist/email-builder/email-builder.d.ts +1 -1
  42. package/dist/email-builder/envelope-card.d.ts +1 -1
  43. package/dist/email-builder/floating-bar.d.ts +1 -1
  44. package/dist/email-builder/internal/dnd-wrapper.d.ts +1 -1
  45. package/dist/email-builder/mobile-drawers.d.ts +1 -1
  46. package/dist/email-builder/mobile-tab-bar.d.ts +1 -1
  47. package/dist/email-builder/property-editors/_shared.d.ts +8 -8
  48. package/dist/email-builder/property-editors/button-editor.d.ts +1 -1
  49. package/dist/email-builder/property-editors/columns-editor.d.ts +1 -1
  50. package/dist/email-builder/property-editors/divider-editor.d.ts +1 -1
  51. package/dist/email-builder/property-editors/footer-editor.d.ts +1 -1
  52. package/dist/email-builder/property-editors/heading-editor.d.ts +1 -1
  53. package/dist/email-builder/property-editors/image-editor.d.ts +1 -1
  54. package/dist/email-builder/property-editors/social-editor.d.ts +1 -1
  55. package/dist/email-builder/property-editors/spacer-editor.d.ts +1 -1
  56. package/dist/email-builder/property-editors/text-editor.d.ts +1 -1
  57. package/dist/email-builder/property-inspector.d.ts +1 -1
  58. package/dist/email-builder/view-controls.d.ts +1 -1
  59. package/dist/kanban/kanban-board.d.ts +1 -1
  60. package/dist/kanban/kanban-card-wrapper.d.ts +1 -1
  61. package/dist/kanban/kanban-column.d.ts +1 -1
  62. package/dist/list/list-row.d.ts +2 -2
  63. package/dist/marketing/marketing-announcement-bar.d.ts +1 -1
  64. package/dist/marketing/marketing-bento-card.d.ts +1 -1
  65. package/dist/marketing/marketing-bento-grid.d.ts +1 -1
  66. package/dist/marketing/marketing-callout.d.ts +1 -1
  67. package/dist/marketing/marketing-contact.d.ts +1 -1
  68. package/dist/marketing/marketing-cta-list.d.ts +1 -1
  69. package/dist/marketing/marketing-cta.d.ts +1 -1
  70. package/dist/marketing/marketing-faq.d.ts +1 -1
  71. package/dist/marketing/marketing-footer.d.ts +1 -1
  72. package/dist/marketing/marketing-header-logo.d.ts +1 -1
  73. package/dist/marketing/marketing-header.d.ts +1 -1
  74. package/dist/marketing/marketing-hero.d.ts +1 -1
  75. package/dist/marketing/marketing-lead-magnet.d.ts +1 -1
  76. package/dist/marketing/marketing-mega-menu.d.ts +1 -1
  77. package/dist/marketing/marketing-nav-item.d.ts +1 -1
  78. package/dist/marketing/marketing-numbered-features.d.ts +1 -1
  79. package/dist/marketing/marketing-shell.d.ts +1 -1
  80. package/dist/marketing/marketing-trust-bar.d.ts +1 -1
  81. package/dist/panel/inspector-field.d.ts +1 -1
  82. package/dist/panel/inspector-json.d.ts +1 -1
  83. package/dist/panel/inspector.d.ts +1 -1
  84. package/dist/post-preview/internal/avatar.d.ts +1 -1
  85. package/dist/post-preview/platforms/bluesky.d.ts +1 -1
  86. package/dist/post-preview/platforms/discord.d.ts +1 -1
  87. package/dist/post-preview/platforms/facebook.d.ts +1 -1
  88. package/dist/post-preview/platforms/generic.d.ts +1 -1
  89. package/dist/post-preview/platforms/google-business.d.ts +1 -1
  90. package/dist/post-preview/platforms/instagram.d.ts +1 -1
  91. package/dist/post-preview/platforms/linkedin.d.ts +1 -1
  92. package/dist/post-preview/platforms/telegram.d.ts +1 -1
  93. package/dist/post-preview/platforms/threads.d.ts +1 -1
  94. package/dist/post-preview/platforms/tiktok.d.ts +1 -1
  95. package/dist/post-preview/platforms/twitter.d.ts +1 -1
  96. package/dist/post-preview/platforms/youtube.d.ts +1 -1
  97. package/dist/primitives/card.d.ts +4 -4
  98. package/dist/primitives/empty-state.d.ts +1 -1
  99. package/dist/primitives/filter-rail.d.ts +2 -2
  100. package/dist/primitives/markdown-view.d.ts +1 -1
  101. package/dist/primitives/skeleton.d.ts +1 -1
  102. package/dist/primitives/status-pill.d.ts +1 -1
  103. package/dist/shell/app-shell-auth.d.ts +1 -1
  104. package/dist/shell/app-shell-chat.d.ts +1 -1
  105. package/dist/shell/app-shell-workspace.d.ts +8 -2
  106. package/dist/shell/app-shell-workspace.js +7 -3
  107. package/dist/shell/app-shell.d.ts +14 -4
  108. package/dist/shell/app-shell.js +1 -1
  109. package/dist/shell/command-palette.d.ts +1 -1
  110. package/dist/shell/context-rail.d.ts +1 -1
  111. package/dist/shell/drag-mode-banner.d.ts +1 -1
  112. package/dist/shell/extras/shell-desktop-panel-dock.d.ts +1 -1
  113. package/dist/shell/extras/shell-scrollable-content.d.ts +1 -1
  114. package/dist/shell/extras/shell-tab-bar.d.ts +1 -1
  115. package/dist/shell/extras/workbench-layout.d.ts +1 -1
  116. package/dist/shell/icon-rail.d.ts +2 -2
  117. package/dist/shell/index.d.ts +2 -2
  118. package/dist/shell/internal/mobile-bottom-nav.d.ts +1 -1
  119. package/dist/shell/internal/mobile-dock.d.ts +7 -1
  120. package/dist/shell/internal/mobile-dock.js +26 -11
  121. package/dist/shell/internal/mobile-drawers.d.ts +1 -1
  122. package/dist/shell/internal/mobile-header.d.ts +1 -1
  123. package/dist/shell/internal/mobile-workspace-sheet.d.ts +15 -1
  124. package/dist/shell/internal/mobile-workspace-sheet.js +70 -3
  125. package/dist/shell/panel-views-provider.d.ts +1 -1
  126. package/dist/shell/rail-drop-slot.d.ts +1 -1
  127. package/dist/shell/rail-drop-zones.d.ts +1 -1
  128. package/dist/shell/resizable-shell.d.ts +3 -3
  129. package/dist/shell/right-panel.d.ts +1 -1
  130. package/dist/shell/shell-header.d.ts +43 -6
  131. package/dist/shell/shell-header.js +29 -5
  132. package/dist/shell/shell-main.d.ts +1 -1
  133. package/dist/shell/shell-provider.d.ts +1 -1
  134. package/dist/shell/theme-next-themes.d.ts +1 -1
  135. package/dist/shell/theme.d.ts +2 -2
  136. package/dist/shell/types.d.ts +38 -0
  137. package/dist/shell/use-shell-viewport.js +3 -1
  138. package/dist/timeline/date-switcher.d.ts +1 -1
  139. package/dist/timeline/event-card.d.ts +1 -1
  140. package/dist/timeline/lane-timeline.d.ts +1 -1
  141. package/dist/timeline/lane.d.ts +1 -1
  142. package/dist/timeline/live-indicator.d.ts +1 -1
  143. package/dist/timeline/scrub-bar.d.ts +1 -1
  144. package/dist/timeline/time-axis.d.ts +1 -1
  145. package/dist/timeline/timeline-rail.d.ts +1 -1
  146. package/dist/timeline/timeline-tape.d.ts +1 -1
  147. package/dist/voice/voice-level.d.ts +2 -1
  148. package/dist/voice/voice-orb-scene.d.ts +2 -1
  149. package/dist/voice/voice-status-pill.d.ts +1 -1
  150. package/dist/workflow-builder/edges/default-edge.d.ts +1 -1
  151. package/dist/workflow-builder/nodes/action-node.d.ts +1 -1
  152. package/dist/workflow-builder/nodes/base-node.d.ts +1 -1
  153. package/dist/workflow-builder/nodes/condition-node.d.ts +1 -1
  154. package/dist/workflow-builder/nodes/delay-node.d.ts +1 -1
  155. package/dist/workflow-builder/nodes/end-node.d.ts +1 -1
  156. package/dist/workflow-builder/nodes/trigger-node.d.ts +1 -1
  157. package/dist/workflow-builder/workflow-builder.d.ts +1 -1
  158. package/dist/workflow-builder/workflow-canvas.d.ts +1 -1
  159. package/dist/workflow-builder/workflow-card-compact.d.ts +1 -1
  160. package/dist/workflow-builder/workflow-card.d.ts +1 -1
  161. package/dist/workflow-builder/workflow-header.d.ts +1 -1
  162. package/dist/workflow-builder/workflow-toolbox.d.ts +1 -1
  163. package/package.json +42 -41
  164. package/skills/shell/SKILL.md +86 -1
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
3
3
  import { ChevronDown, Monitor, Moon, PanelRight, Sun } from 'lucide-react';
4
4
  import { createElement, Fragment, isValidElement } from 'react';
5
5
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger, } from '../components/ui/dropdown-menu.js';
6
+ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger, } from '../components/ui/tooltip.js';
6
7
  import { cn } from '../lib/utils.js';
7
8
  import { useMedaShell } from './shell-provider.js';
8
9
  import { useTheme } from './theme.js';
@@ -52,12 +53,27 @@ function renderConfiguredItem(item) {
52
53
  const renderLink = item.href != null ? _jsx("a", { href: item.href }) : undefined;
53
54
  return (_jsxs(DropdownMenuItem, { render: renderLink, "data-variant": item.variant ?? 'default', className: item.variant === 'destructive' ? 'text-destructive' : undefined, onClick: handleSelect, children: [renderShellIcon(item.icon), item.label] }));
54
55
  }
56
+ /** Mark fallback for the tile variant when the workspace carries no icon. */
57
+ function workspaceInitial(name) {
58
+ return name.trim().charAt(0).toUpperCase();
59
+ }
55
60
  /* v8 ignore next — v8 phantom duplicate function record for WorkspaceSwitcher (default params) */
56
- export function WorkspaceSwitcher({ menuItems, menuFooter, workspaceMenuFooter, } = {}) {
61
+ export function WorkspaceSwitcher({ menuItems, menuFooter, workspaceMenuFooter, variant = 'chip', showLabel = true, } = {}) {
57
62
  const { workspace, workspaces } = useMedaShell();
58
63
  const resolvedFooter = menuFooter ?? workspaceMenuFooter;
59
64
  const useConfiguredItems = Array.isArray(menuItems);
60
- return (_jsxs(DropdownMenu, { children: [_jsxs(DropdownMenuTrigger, { render: _jsx("button", { type: "button", className: "flex min-w-0 items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm font-semibold hover:bg-accent" }), children: [workspace.icon != null && (_jsx("span", { className: "inline-flex size-8 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground ring-1 ring-border/70", "aria-hidden": "true", children: workspace.icon })), _jsx("span", { className: "max-w-[13rem] truncate", children: workspace.name }), _jsx(ChevronDown, { size: 16, "aria-hidden": "true" })] }), _jsxs(DropdownMenuContent, { className: "min-w-[240px]", children: [!useConfiguredItems && workspaces.length > 0 && (_jsxs(_Fragment, { children: [workspaces.map((ws) => (_jsxs(DropdownMenuItem, { children: [ws.icon != null && (_jsx("span", { className: "inline-flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted text-foreground ring-1 ring-border/70", "aria-hidden": "true", children: ws.icon })), ws.name] }, ws.id))), _jsx(DropdownMenuSeparator, {})] })), useConfiguredItems ? (menuItems.map((item) => (_jsxs(Fragment, { children: [renderConfiguredItem(item), item.separatorAfter && _jsx(DropdownMenuSeparator, {})] }, item.id)))) : (_jsxs(_Fragment, { children: [_jsx(DropdownMenuItem, { children: "Manage workspaces" }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { children: "Settings" }), _jsx(DropdownMenuItem, { children: "Profile" })] })), _jsx(DropdownMenuSeparator, {}), _jsx(ThemeToggleMenuItem, {}), !useConfiguredItems && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { children: "Sign out" })] })), resolvedFooter] })] }));
65
+ // The tile sits in the header's rail column: the mark is the only
66
+ // flow-level child, so it stays centred on the rail's axis, and the chevron
67
+ // hangs off it absolutely rather than pushing it sideways.
68
+ //
69
+ // Its intrinsic height is deliberately bounded — mark 28 + gap 2 + one 14px
70
+ // label line = 44px, with NO vertical padding — so it fits inside
71
+ // `--shell-header-height` even when a consumer tightens that token (the web
72
+ // app runs a 52px header under its window-tab strip). `max-h-full` plus the
73
+ // rail column's `overflow-hidden` is the belt-and-braces guard.
74
+ const tileTrigger = (_jsxs(DropdownMenuTrigger, { "data-meda-workspace-switcher": "tile", render: _jsx("button", { type: "button", "aria-label": `${workspace.name} workspace menu`, className: "flex max-h-full w-full min-w-0 flex-col items-center justify-center gap-0.5 rounded-lg px-1 hover:bg-accent" }), children: [_jsxs("span", { className: "relative inline-flex shrink-0", "aria-hidden": "true", children: [_jsx("span", { className: "inline-flex size-7 items-center justify-center overflow-hidden rounded-lg bg-muted text-[13px] text-foreground font-semibold ring-1 ring-border/70", children: workspace.icon ?? workspaceInitial(workspace.name) }), _jsx(ChevronDown, { size: 11, "aria-hidden": "true", className: "pointer-events-none absolute -right-1.5 -bottom-1 rounded-full bg-background text-muted-foreground" })] }), showLabel && (_jsx("span", { "data-slot": "icon-rail-label", className: "max-w-full truncate text-center font-medium text-[11px] leading-[14px] [@media(max-height:700px)]:hidden", children: workspace.name }))] }));
75
+ const trigger = variant === 'tile' ? (_jsx(TooltipProvider, { children: _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { render: _jsx("span", { className: "flex max-h-full w-full min-w-0" }), children: tileTrigger }), _jsx(TooltipContent, { side: "bottom", children: workspace.name })] }) })) : (_jsxs(DropdownMenuTrigger, { "data-meda-workspace-switcher": "chip", render: _jsx("button", { type: "button", className: "flex min-w-0 items-center gap-2.5 rounded-lg px-2.5 py-2 text-sm font-semibold hover:bg-accent" }), children: [workspace.icon != null && (_jsx("span", { className: "inline-flex size-8 shrink-0 items-center justify-center overflow-hidden rounded-lg bg-muted text-foreground ring-1 ring-border/70", "aria-hidden": "true", children: workspace.icon })), _jsx("span", { className: "max-w-[13rem] truncate", children: workspace.name }), _jsx(ChevronDown, { size: 16, "aria-hidden": "true" })] }));
76
+ return (_jsxs(DropdownMenu, { children: [trigger, _jsxs(DropdownMenuContent, { className: "min-w-[240px]", children: [!useConfiguredItems && workspaces.length > 0 && (_jsxs(_Fragment, { children: [workspaces.map((ws) => (_jsxs(DropdownMenuItem, { children: [ws.icon != null && (_jsx("span", { className: "inline-flex size-7 shrink-0 items-center justify-center overflow-hidden rounded-md bg-muted text-foreground ring-1 ring-border/70", "aria-hidden": "true", children: ws.icon })), ws.name] }, ws.id))), _jsx(DropdownMenuSeparator, {})] })), useConfiguredItems ? (menuItems.map((item) => (_jsxs(Fragment, { children: [renderConfiguredItem(item), item.separatorAfter && _jsx(DropdownMenuSeparator, {})] }, item.id)))) : (_jsxs(_Fragment, { children: [_jsx(DropdownMenuItem, { children: "Manage workspaces" }), _jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { children: "Settings" }), _jsx(DropdownMenuItem, { children: "Profile" })] })), _jsx(DropdownMenuSeparator, {}), _jsx(ThemeToggleMenuItem, {}), !useConfiguredItems && (_jsxs(_Fragment, { children: [_jsx(DropdownMenuSeparator, {}), _jsx(DropdownMenuItem, { children: "Sign out" })] })), resolvedFooter] })] }));
61
77
  }
62
78
  /* v8 ignore next — v8 phantom duplicate function record for AppTabs (default params) */
63
79
  export function AppTabs({ renderLink } = {}) {
@@ -117,7 +133,7 @@ export function PanelToggle({ panelViews = [] } = {}) {
117
133
  })] })] }));
118
134
  }
119
135
  /* v8 ignore next — v8 phantom duplicate function record for ShellHeader (default params) */
120
- export function ShellHeader({ globalActions, headerCenter, headerLeading, appTabsRenderLink, className, workspaceMenuItems, workspaceMenuFooter, showPanelToggle = true, panelViews = [], } = {}) {
136
+ export function ShellHeader({ globalActions, headerCenter, headerLeading, appTabsRenderLink, className, workspaceMenuItems, workspaceMenuFooter, showPanelToggle = true, panelViews = [], headerLayout = 'split', railLabelVisibility = 'tooltip', } = {}) {
121
137
  const band = useShellViewport();
122
138
  if (band === 'mobile')
123
139
  return null;
@@ -125,8 +141,16 @@ export function ShellHeader({ globalActions, headerCenter, headerLeading, appTab
125
141
  // the WorkspaceSwitcher — separated by whitespace only (no divider line),
126
142
  // matching the design prototype's topbar spacing.
127
143
  const leadingRegion = headerLeading != null ? (_jsx("div", { className: "ml-2 flex min-w-0 items-center", children: headerLeading })) : null;
144
+ // Rail layout: column 1 is exactly the icon rail's width, so the switcher
145
+ // tile sits on the rail's axis and `headerLeading` starts precisely where
146
+ // the main region starts below. No left padding, `pr-4` on the right.
147
+ if (headerLayout === 'rail') {
148
+ return (_jsxs("header", { "data-meda-shell-header": "", "data-meda-header-layout": "rail", className: cn('grid h-[var(--shell-header-height)] w-full items-center bg-background pr-4', railLabelVisibility === 'visible'
149
+ ? 'grid-cols-[var(--shell-rail-label-width)_minmax(0,1fr)_auto]'
150
+ : 'grid-cols-[var(--shell-rail-width)_minmax(0,1fr)_auto]', className), children: [_jsx("div", { className: "flex h-full min-w-0 items-center justify-center overflow-hidden px-1", children: _jsx(WorkspaceSwitcher, { menuItems: workspaceMenuItems, menuFooter: workspaceMenuFooter, variant: "tile", showLabel: railLabelVisibility === 'visible' }) }), _jsx("div", { className: "flex min-w-0 items-center", children: headerLeading }), _jsxs("div", { className: "flex shrink-0 items-center gap-2 justify-self-end pl-4", children: [globalActions, showPanelToggle && _jsx(PanelToggle, { panelViews: panelViews })] })] }));
151
+ }
128
152
  if (headerCenter !== undefined) {
129
- return (_jsxs("header", { className: cn('grid h-[var(--shell-header-height)] w-full grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center', 'gap-4 bg-background px-4', className), children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2 justify-self-start", children: [_jsx(WorkspaceSwitcher, { menuItems: workspaceMenuItems, menuFooter: workspaceMenuFooter }), leadingRegion] }), _jsx("div", { className: "flex min-w-0 items-center justify-center justify-self-center", children: headerCenter }), _jsxs("div", { className: "flex min-w-0 items-center justify-end gap-2 justify-self-end", children: [globalActions, showPanelToggle && _jsx(PanelToggle, { panelViews: panelViews })] })] }));
153
+ return (_jsxs("header", { "data-meda-shell-header": "", "data-meda-header-layout": "split", className: cn('grid h-[var(--shell-header-height)] w-full grid-cols-[minmax(0,1fr)_auto_minmax(0,1fr)] items-center', 'gap-4 bg-background px-4', className), children: [_jsxs("div", { className: "flex min-w-0 items-center gap-2 justify-self-start", children: [_jsx(WorkspaceSwitcher, { menuItems: workspaceMenuItems, menuFooter: workspaceMenuFooter }), leadingRegion] }), _jsx("div", { className: "flex min-w-0 items-center justify-center justify-self-center", children: headerCenter }), _jsxs("div", { className: "flex min-w-0 items-center justify-end gap-2 justify-self-end", children: [globalActions, showPanelToggle && _jsx(PanelToggle, { panelViews: panelViews })] })] }));
130
154
  }
131
- return (_jsxs("header", { className: cn('flex h-[var(--shell-header-height)] w-full items-center justify-between', 'gap-4 bg-background px-4', className), children: [_jsxs("div", { className: "flex min-w-0 shrink-0 items-center gap-2", children: [_jsx(WorkspaceSwitcher, { menuItems: workspaceMenuItems, menuFooter: workspaceMenuFooter }), leadingRegion] }), _jsx("div", { className: "flex min-w-0 flex-1 items-center", children: _jsx(AppTabs, { renderLink: appTabsRenderLink }) }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [globalActions, showPanelToggle && _jsx(PanelToggle, { panelViews: panelViews })] })] }));
155
+ return (_jsxs("header", { "data-meda-shell-header": "", "data-meda-header-layout": "split", className: cn('flex h-[var(--shell-header-height)] w-full items-center justify-between', 'gap-4 bg-background px-4', className), children: [_jsxs("div", { className: "flex min-w-0 shrink-0 items-center gap-2", children: [_jsx(WorkspaceSwitcher, { menuItems: workspaceMenuItems, menuFooter: workspaceMenuFooter }), leadingRegion] }), _jsx("div", { className: "flex min-w-0 flex-1 items-center", children: _jsx(AppTabs, { renderLink: appTabsRenderLink }) }), _jsxs("div", { className: "flex shrink-0 items-center gap-2", children: [globalActions, showPanelToggle && _jsx(PanelToggle, { panelViews: panelViews })] })] }));
132
156
  }
@@ -5,4 +5,4 @@ export interface ShellMainProps {
5
5
  className?: string;
6
6
  children: ReactNode;
7
7
  }
8
- export declare function ShellMain({ layout, className, children }: ShellMainProps): import("react/jsx-runtime").JSX.Element;
8
+ export declare function ShellMain({ layout, className, children }: ShellMainProps): import("react").JSX.Element;
@@ -74,5 +74,5 @@ export interface MedaShellProviderProps {
74
74
  themeAdapter?: 'default' | 'next-themes' | ThemeAdapter;
75
75
  children: ReactNode;
76
76
  }
77
- export declare function MedaShellProvider(props: MedaShellProviderProps): import("react/jsx-runtime").JSX.Element;
77
+ export declare function MedaShellProvider(props: MedaShellProviderProps): import("react").JSX.Element;
78
78
  export {};
@@ -1,4 +1,4 @@
1
1
  import { type ReactNode } from 'react';
2
2
  export declare function NextThemesAdapter({ children }: {
3
3
  children: ReactNode;
4
- }): import("react/jsx-runtime").JSX.Element;
4
+ }): import("react").JSX.Element;
@@ -4,6 +4,6 @@ import type { ThemeAdapter } from './types.js';
4
4
  export declare const ThemeCtx: import("react").Context<ThemeAdapter | null>;
5
5
  export declare function DefaultThemeProvider({ children }: {
6
6
  children: ReactNode;
7
- }): import("react/jsx-runtime").JSX.Element;
7
+ }): import("react").JSX.Element;
8
8
  export declare function useTheme(): ThemeAdapter;
9
- export declare function ThemeToggle(): import("react/jsx-runtime").JSX.Element;
9
+ export declare function ThemeToggle(): import("react").JSX.Element;
@@ -49,6 +49,19 @@ export interface PanelView {
49
49
  }
50
50
  export type PanelMode = 'closed' | 'panel' | 'expanded' | 'fullscreen';
51
51
  export type ShellMainLayout = 'workspace' | 'centered' | 'fullbleed';
52
+ /**
53
+ * Desktop header grid.
54
+ *
55
+ * - `split` (default, unchanged) — the workspace switcher and `headerLeading`
56
+ * share the LEFT region; `headerCenter`, when supplied, owns the middle and
57
+ * the leading region is capped at roughly half the window.
58
+ * - `rail` — a three-column grid `[rail column | fill | actions]`. Column 1 is
59
+ * exactly as wide as the icon rail below it and holds the workspace switcher
60
+ * in its `tile` variant; column 2 is `headerLeading` and gets ALL remaining
61
+ * width; column 3 is `globalActions` (plus the panel toggle). `headerCenter`
62
+ * is ignored in this layout.
63
+ */
64
+ export type ShellHeaderLayout = 'split' | 'rail';
52
65
  export type ShellViewport = 'mobile' | 'tablet' | 'desktop' | 'wide' | 'ultrawide';
53
66
  export type ContextRailHeader = 'auto' | 'visible' | 'hidden';
54
67
  export type ContextRailScroll = 'auto' | 'none';
@@ -116,6 +129,23 @@ export interface AppShellMobileNavConfig {
116
129
  tree: MobileNavTree;
117
130
  /** The `to` of the currently-active row, for highlighting. */
118
131
  activeTo?: string;
132
+ /**
133
+ * The `id` of the currently-active APP (a `dock` item id and/or a
134
+ * `tree` row id). When set it wins over `activeTo` for:
135
+ *
136
+ * - the dock — a slot lights when `item.id === activeId`, so the dock stays
137
+ * lit on every route inside that app instead of only on its first tab;
138
+ * - the workspace sheet — that row is the one expanded and scrolled into
139
+ * view when the sheet opens.
140
+ *
141
+ * Omit it to keep the pre-2.8 behaviour (both derive from `activeTo`).
142
+ */
143
+ activeId?: string;
144
+ /**
145
+ * Render the current row (see `activeId` / `activeTo`) first inside its
146
+ * group in the workspace sheet. Defaults to `false` — the tree order.
147
+ */
148
+ currentFirst?: boolean;
119
149
  /** Render dock/sheet targets as router links (else plain `<a>`). */
120
150
  renderLink?: (args: MobileNavLinkArgs) => ReactNode;
121
151
  /**
@@ -183,6 +213,14 @@ export interface AppShellContextRailConfig {
183
213
  export interface AppShellRightPanelConfig {
184
214
  panelViews: PanelView[];
185
215
  defaultView?: string;
216
+ /**
217
+ * Whether the header renders its `PanelToggle`. Defaults to `true`, i.e. the
218
+ * toggle shows whenever at least one panel view is registered (the pre-2.8
219
+ * behaviour). Set `false` to keep the panel views — and every other way of
220
+ * opening them, such as `useMedaShell().panel.focus(id)` or a command — while
221
+ * dropping the header control.
222
+ */
223
+ showToggle?: boolean;
186
224
  }
187
225
  /**
188
226
  * A single configurable item in the WorkspaceSwitcher dropdown.
@@ -34,7 +34,9 @@ function subscribeToViewport(onChange) {
34
34
  const matchMedia = getMatchMedia();
35
35
  if (!matchMedia) {
36
36
  subscribedMqls = null;
37
- return () => { };
37
+ return () => {
38
+ // no matchMedia (SSR/test) - nothing to unsubscribe
39
+ };
38
40
  }
39
41
  const mqls = Object.entries(BREAKPOINTS).map(([band, query]) => [band, matchMedia(query)]);
40
42
  subscribedMqls = mqls;
@@ -8,4 +8,4 @@ export interface DateSwitcherProps {
8
8
  onChange: (next: Date) => void;
9
9
  className?: string;
10
10
  }
11
- export declare function DateSwitcher({ value, now, tz, onChange, className }: DateSwitcherProps): import("react/jsx-runtime").JSX.Element;
11
+ export declare function DateSwitcher({ value, now, tz, onChange, className }: DateSwitcherProps): import("react").JSX.Element;
@@ -6,4 +6,4 @@ export interface EventCardProps {
6
6
  onSelect?: (event: TimelineEvent) => void;
7
7
  className?: string;
8
8
  }
9
- export declare function EventCard({ event, size, onSelect, className }: EventCardProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function EventCard({ event, size, onSelect, className }: EventCardProps): import("react").JSX.Element;
@@ -1,2 +1,2 @@
1
1
  import { type LaneTimelineProps } from './lane-timeline-types.js';
2
- export declare function LaneTimeline({ lanes, now, defaultRange, range: controlledRange, onRangeChange, selectedDate: controlledDate, onDateChange, title, groupChip, activeCount, legend, selectedBarId, onSelectBar, labels, className, }: LaneTimelineProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function LaneTimeline({ lanes, now, defaultRange, range: controlledRange, onRangeChange, selectedDate: controlledDate, onDateChange, title, groupChip, activeCount, legend, selectedBarId, onSelectBar, labels, className, }: LaneTimelineProps): import("react").JSX.Element;
@@ -7,4 +7,4 @@ export interface LaneRowProps {
7
7
  selectedBarId?: string;
8
8
  onSelectBar?: (bar: LaneBar, lane: Lane) => void;
9
9
  }
10
- export declare function LaneRow({ lane, windowStart, windowEnd, labelGutterPx, selectedBarId, onSelectBar, }: LaneRowProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function LaneRow({ lane, windowStart, windowEnd, labelGutterPx, selectedBarId, onSelectBar, }: LaneRowProps): import("react").JSX.Element;
@@ -4,4 +4,4 @@ export interface LiveIndicatorProps {
4
4
  tz?: string;
5
5
  className?: string;
6
6
  }
7
- export declare function LiveIndicator({ now, tz, className }: LiveIndicatorProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function LiveIndicator({ now, tz, className }: LiveIndicatorProps): import("react").JSX.Element;
@@ -11,4 +11,4 @@ export interface ScrubBarProps {
11
11
  onSkipForward?: () => void;
12
12
  className?: string;
13
13
  }
14
- export declare function ScrubBar({ durationMs, positionMs, isLive, marks, onSeek, isPlaying, onPlayPause, onSkipBack, onSkipForward, className, }: ScrubBarProps): import("react/jsx-runtime").JSX.Element;
14
+ export declare function ScrubBar({ durationMs, positionMs, isLive, marks, onSeek, isPlaying, onPlayPause, onSkipBack, onSkipForward, className, }: ScrubBarProps): import("react").JSX.Element;
@@ -15,4 +15,4 @@ export interface TimeAxisProps {
15
15
  /** Width of the lane-label gutter on the left, in px (matches Lane component). */
16
16
  labelGutterPx: number;
17
17
  }
18
- export declare function TimeAxis({ range, onRangeChange, selectedDate, onDateChange, now, title, groupChip, activeCount, labels, ticks, labelGutterPx, }: TimeAxisProps): import("react/jsx-runtime").JSX.Element;
18
+ export declare function TimeAxis({ range, onRangeChange, selectedDate, onDateChange, now, title, groupChip, activeCount, labels, ticks, labelGutterPx, }: TimeAxisProps): import("react").JSX.Element;
@@ -16,4 +16,4 @@ export interface TimelineRailProps {
16
16
  onManageRetention?: () => void;
17
17
  className?: string;
18
18
  }
19
- export declare function TimelineRail({ date, now, events, zoom, followLive, tz, onZoomChange, onDateChange, onSelect, onFollowChange, onManageRetention, className, }: TimelineRailProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function TimelineRail({ date, now, events, zoom, followLive, tz, onZoomChange, onDateChange, onSelect, onFollowChange, onManageRetention, className, }: TimelineRailProps): import("react").JSX.Element;
@@ -16,4 +16,4 @@ export interface TimelineTapeProps {
16
16
  onSelect?: (event: TimelineEvent) => void;
17
17
  className?: string;
18
18
  }
19
- export declare function TimelineTape({ now, events, pxPerSec, futurePadSec, pastSpanSec, tz, onSelect, className, }: TimelineTapeProps): import("react/jsx-runtime").JSX.Element;
19
+ export declare function TimelineTape({ now, events, pxPerSec, futurePadSec, pastSpanSec, tz, onSelect, className, }: TimelineTapeProps): import("react").JSX.Element;
@@ -1,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  export interface VoiceLevelProps {
2
3
  level: number;
3
4
  variant?: 'bars' | 'wave' | 'ring';
@@ -5,4 +6,4 @@ export interface VoiceLevelProps {
5
6
  height?: number;
6
7
  className?: string;
7
8
  }
8
- export declare function VoiceLevel({ level, variant, width, height, className, }: VoiceLevelProps): import("react/jsx-runtime").JSX.Element;
9
+ export declare function VoiceLevel({ level, variant, width, height, className, }: VoiceLevelProps): React.JSX.Element;
@@ -1,3 +1,4 @@
1
+ import * as React from 'react';
1
2
  import type { TurnPhase } from './types.js';
2
3
  export type VoiceOrbVariant = 'metal' | 'aurora';
3
4
  export interface SceneProps {
@@ -9,4 +10,4 @@ export interface SceneProps {
9
10
  pressed?: boolean;
10
11
  variant?: VoiceOrbVariant;
11
12
  }
12
- export declare function Scene({ level, outputLevel, phase, colors, reducedMotion, pressed, variant, }: SceneProps): import("react/jsx-runtime").JSX.Element;
13
+ export declare function Scene({ level, outputLevel, phase, colors, reducedMotion, pressed, variant, }: SceneProps): React.JSX.Element;
@@ -4,4 +4,4 @@ export interface VoiceStatusPillProps {
4
4
  thinkingForMs?: number;
5
5
  className?: string;
6
6
  }
7
- export declare function VoiceStatusPill({ phase, thinkingForMs, className }: VoiceStatusPillProps): import("react/jsx-runtime").JSX.Element;
7
+ export declare function VoiceStatusPill({ phase, thinkingForMs, className }: VoiceStatusPillProps): import("react").JSX.Element;
@@ -5,6 +5,6 @@ interface DefaultEdgeData {
5
5
  type DefaultWorkflowEdgeProps = EdgeProps & {
6
6
  data?: DefaultEdgeData;
7
7
  };
8
- declare function DefaultWorkflowEdgeComponent({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, markerEnd, }: DefaultWorkflowEdgeProps): import("react/jsx-runtime").JSX.Element;
8
+ declare function DefaultWorkflowEdgeComponent({ sourceX, sourceY, targetX, targetY, sourcePosition, targetPosition, style, markerEnd, }: DefaultWorkflowEdgeProps): import("react").JSX.Element;
9
9
  export declare const DefaultWorkflowEdge: import("react").MemoExoticComponent<typeof DefaultWorkflowEdgeComponent>;
10
10
  export {};
@@ -1,3 +1,3 @@
1
1
  import type { NodeProps } from '@xyflow/react';
2
2
  import type { WorkflowNode } from '../types.js';
3
- export declare function ActionNode({ data, selected }: NodeProps<WorkflowNode>): import("react/jsx-runtime").JSX.Element;
3
+ export declare function ActionNode({ data, selected }: NodeProps<WorkflowNode>): import("react").JSX.Element;
@@ -17,4 +17,4 @@ export interface BaseWorkflowNodeProps {
17
17
  label?: string;
18
18
  }>;
19
19
  }
20
- export declare function BaseWorkflowNode({ kind, icon, label, description, warning, selected, showSourceHandle, showTargetHandle, sourceHandles, }: BaseWorkflowNodeProps): import("react/jsx-runtime").JSX.Element;
20
+ export declare function BaseWorkflowNode({ kind, icon, label, description, warning, selected, showSourceHandle, showTargetHandle, sourceHandles, }: BaseWorkflowNodeProps): import("react").JSX.Element;
@@ -1,3 +1,3 @@
1
1
  import { type NodeProps } from '@xyflow/react';
2
2
  import type { WorkflowNode } from '../types.js';
3
- export declare function ConditionNode({ data, selected }: NodeProps<WorkflowNode>): import("react/jsx-runtime").JSX.Element;
3
+ export declare function ConditionNode({ data, selected }: NodeProps<WorkflowNode>): import("react").JSX.Element;
@@ -1,3 +1,3 @@
1
1
  import type { NodeProps } from '@xyflow/react';
2
2
  import type { WorkflowNode } from '../types.js';
3
- export declare function DelayNode({ data, selected }: NodeProps<WorkflowNode>): import("react/jsx-runtime").JSX.Element;
3
+ export declare function DelayNode({ data, selected }: NodeProps<WorkflowNode>): import("react").JSX.Element;
@@ -1,3 +1,3 @@
1
1
  import type { NodeProps } from '@xyflow/react';
2
2
  import type { WorkflowNode } from '../types.js';
3
- export declare function EndNode({ data, selected }: NodeProps<WorkflowNode>): import("react/jsx-runtime").JSX.Element;
3
+ export declare function EndNode({ data, selected }: NodeProps<WorkflowNode>): import("react").JSX.Element;
@@ -1,3 +1,3 @@
1
1
  import type { NodeProps } from '@xyflow/react';
2
2
  import type { WorkflowNode } from '../types.js';
3
- export declare function TriggerNode({ data, selected }: NodeProps<WorkflowNode>): import("react/jsx-runtime").JSX.Element;
3
+ export declare function TriggerNode({ data, selected }: NodeProps<WorkflowNode>): import("react").JSX.Element;
@@ -7,4 +7,4 @@ import { type WorkflowBuilderProps } from './types.js';
7
7
  * toolbox collapses behind a tab bar (mobile) and at wider widths it sits to
8
8
  * the left of the canvas.
9
9
  */
10
- export declare function WorkflowBuilder({ nodes, edges, onNodesChange, onEdgesChange, onConnect, onAddNode, onSelectionChange, onNodeClick, showToolbox, readOnly, headerSlot, inspectorSlot, customNodeTypes, customEdgeTypes, labels, className, }: WorkflowBuilderProps): import("react/jsx-runtime").JSX.Element;
10
+ export declare function WorkflowBuilder({ nodes, edges, onNodesChange, onEdgesChange, onConnect, onAddNode, onSelectionChange, onNodeClick, showToolbox, readOnly, headerSlot, inspectorSlot, customNodeTypes, customEdgeTypes, labels, className, }: WorkflowBuilderProps): import("react").JSX.Element;
@@ -22,4 +22,4 @@ export interface WorkflowCanvasProps {
22
22
  * toolbox — compose with `WorkflowBuilder` for the full surface, or use this
23
23
  * alone when embedding in a custom layout.
24
24
  */
25
- export declare function WorkflowCanvas({ nodes, edges, onNodesChange, onEdgesChange, onConnect, onNodeClick, onPaneClick, onSelectionChange, readOnly, customNodeTypes, customEdgeTypes, className, }: WorkflowCanvasProps): import("react/jsx-runtime").JSX.Element;
25
+ export declare function WorkflowCanvas({ nodes, edges, onNodesChange, onEdgesChange, onConnect, onNodeClick, onPaneClick, onSelectionChange, readOnly, customNodeTypes, customEdgeTypes, className, }: WorkflowCanvasProps): import("react").JSX.Element;
@@ -1,2 +1,2 @@
1
1
  import type { WorkflowCardCompactProps } from './types.js';
2
- export declare function WorkflowCardCompact({ workflow, onClick, className }: WorkflowCardCompactProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function WorkflowCardCompact({ workflow, onClick, className }: WorkflowCardCompactProps): import("react").JSX.Element;
@@ -1,2 +1,2 @@
1
1
  import type { WorkflowCardProps } from './types.js';
2
- export declare function WorkflowCard({ workflow, onClick, className }: WorkflowCardProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function WorkflowCard({ workflow, onClick, className }: WorkflowCardProps): import("react").JSX.Element;
@@ -1,2 +1,2 @@
1
1
  import { type WorkflowHeaderProps } from './types.js';
2
- export declare function WorkflowHeader({ name, onNameChange, status, onSave, onPublish, onPause, onBack, isSaving, readOnly, labels, className, }: WorkflowHeaderProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function WorkflowHeader({ name, onNameChange, status, onSave, onPublish, onPause, onBack, isSaving, readOnly, labels, className, }: WorkflowHeaderProps): import("react").JSX.Element;
@@ -1,2 +1,2 @@
1
1
  import { type WorkflowToolboxProps } from './types.js';
2
- export declare function WorkflowToolbox({ items, onAddNode, readOnly, labels, className, }: WorkflowToolboxProps): import("react/jsx-runtime").JSX.Element;
2
+ export declare function WorkflowToolbox({ items, onAddNode, readOnly, labels, className, }: WorkflowToolboxProps): import("react").JSX.Element;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@medalsocial/meda",
3
- "version": "2.7.2",
3
+ "version": "2.8.1",
4
4
  "description": "Shared Meda UI shell and runtime package.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -168,62 +168,63 @@
168
168
  }
169
169
  },
170
170
  "dependencies": {
171
- "@base-ui/react": "1.6.0",
172
- "@fontsource-variable/geist": "5.2.8",
173
- "@fontsource-variable/geist-mono": "5.2.7",
171
+ "@base-ui/react": "1.8.0",
172
+ "@fontsource-variable/geist": "5.3.0",
173
+ "@fontsource-variable/geist-mono": "5.3.0",
174
174
  "class-variance-authority": "0.7.1",
175
175
  "clsx": "2.1.1",
176
176
  "cmdk": "1.1.1",
177
- "react-resizable-panels": "4.11.0",
178
- "tailwind-merge": "3.5.0",
179
- "three": "0.184.0",
177
+ "react-resizable-panels": "4.13.1",
178
+ "tailwind-merge": "3.7.0",
179
+ "three": "0.185.1",
180
180
  "vaul": "1.1.2"
181
181
  },
182
182
  "devDependencies": {
183
- "@biomejs/biome": "2.4.14",
184
- "@changesets/changelog-github": "0.6.0",
185
- "@changesets/cli": "2.31.0",
183
+ "@biomejs/biome": "2.5.14",
184
+ "@changesets/changelog-github": "1.0.1",
185
+ "@changesets/cli": "3.0.3",
186
186
  "@dnd-kit/core": "6.3.1",
187
187
  "@dnd-kit/sortable": "10.0.0",
188
188
  "@dnd-kit/utilities": "3.2.2",
189
- "@react-three/fiber": "9.6.1",
190
- "@size-limit/preset-small-lib": "12.1.0",
191
- "@storybook/addon-a11y": "10.3.6",
192
- "@storybook/addon-docs": "10.3.6",
193
- "@storybook/addon-mcp": "0.6.0",
194
- "@storybook/addon-themes": "10.3.6",
195
- "@storybook/addon-vitest": "10.3.6",
196
- "@storybook/react-vite": "10.3.6",
197
- "@tailwindcss/vite": "4.2.4",
198
- "@tanstack/intent": "0.0.41",
199
- "@testing-library/jest-dom": "6.9.1",
200
- "@testing-library/react": "16.3.2",
201
- "@types/react": "19.2.14",
202
- "@types/react-dom": "19.2.3",
203
- "@types/three": "0.184.0",
204
- "@vitejs/plugin-react": "6.0.1",
205
- "@vitest/browser-playwright": "4.1.5",
206
- "@vitest/coverage-istanbul": "4.1.5",
207
- "@vitest/coverage-v8": "4.1.5",
208
- "@xyflow/react": "12.10.2",
209
- "chromatic": "16.6.3",
189
+ "@react-three/fiber": "9.7.0",
190
+ "@size-limit/preset-small-lib": "13.0.3",
191
+ "@storybook/addon-a11y": "10.6.0",
192
+ "@storybook/addon-docs": "10.6.0",
193
+ "@storybook/addon-mcp": "10.6.0",
194
+ "@storybook/addon-themes": "10.6.0",
195
+ "@storybook/addon-vitest": "10.6.0",
196
+ "@storybook/react-vite": "10.6.0",
197
+ "@tailwindcss/vite": "4.3.3",
198
+ "@tanstack/intent": "0.4.0",
199
+ "@testing-library/jest-dom": "7.0.1",
200
+ "@testing-library/react": "16.3.3",
201
+ "@types/react": "19.3.0",
202
+ "@types/react-dom": "19.3.0",
203
+ "@types/three": "0.185.4",
204
+ "@vitejs/plugin-react": "6.1.1",
205
+ "@vitest/browser-playwright": "4.1.11",
206
+ "@vitest/coverage-istanbul": "4.1.11",
207
+ "@vitest/coverage-v8": "4.1.11",
208
+ "@xyflow/react": "12.11.6",
209
+ "chromatic": "18.9.4",
210
+ "fast-check": "4.10.2",
210
211
  "husky": "9.1.7",
211
- "jsdom": "29.1.1",
212
+ "jsdom": "30.1.0",
212
213
  "next-themes": "0.4.6",
213
- "playwright": "1.59.1",
214
- "react": "19.2.5",
215
- "react-dom": "19.2.5",
214
+ "playwright": "1.63.0",
215
+ "react": "19.2.8",
216
+ "react-dom": "19.2.8",
216
217
  "react-markdown": "10.1.0",
217
218
  "rehype-highlight": "7.0.2",
218
219
  "remark-gfm": "4.0.1",
219
- "size-limit": "12.1.0",
220
- "storybook": "10.3.6",
221
- "tailwindcss": "4.2.4",
220
+ "size-limit": "13.0.3",
221
+ "storybook": "10.6.0",
222
+ "tailwindcss": "4.3.3",
222
223
  "typescript": "~6.0.3",
223
- "vite": "8.0.10",
224
- "vitest": "4.1.5",
224
+ "vite": "8.3.0",
225
+ "vitest": "4.1.11",
225
226
  "vitest-axe": "0.1.0",
226
- "wrangler": "4.87.0"
227
+ "wrangler": "4.135.0"
227
228
  },
228
229
  "keywords": [
229
230
  "tanstack-intent"
@@ -21,11 +21,69 @@ The package exports a single `<AppShell>` component with a discriminated `varian
21
21
  | Variant | When to use | Key config |
22
22
  |---|---|---|
23
23
  | `'auth'` | Sign-in / sign-up / password reset / OAuth callbacks. Lets the form scroll past viewport (dense forms, high zoom). | `auth`, `branding`, optional `preview` (right-side art) + `actions` (top-right) |
24
- | `'workspace'` | Logged-in product shell. Has icon rail + context rail + header + main + optional right panel. | `iconRail`, `contextRail`, `rightPanel`, `workspace` (menu items override), `appTabs` (router integration), `headerCenter`, `banners`, `mainLayout`, `globalActions` |
24
+ | `'workspace'` | Logged-in product shell. Has icon rail + context rail + header + main + optional right panel. | `iconRail`, `contextRail`, `rightPanel`, `workspace` (menu items override), `appTabs` (router integration), `headerCenter`, `headerLeading`, `headerLayout`, `banners`, `mainLayout`, `globalActions`, `mobileNav` |
25
25
  | `'chat'` | Chat-first surfaces (full-bleed messaging UI; no rails). | `globalActions` |
26
26
 
27
27
  `AppShellWorkspace.workspace.menuItems` REPLACES the default workspace dropdown ("Manage workspaces / Settings / Profile / Sign out") when provided. **The theme toggle is preserved automatically** — consumers do not have to re-implement theme cycling.
28
28
 
29
+ ## Header layout — `split` (default) vs `rail`
30
+
31
+ `headerLayout` picks the desktop header grid. `split` is the historic layout and stays the default.
32
+
33
+ | | `split` (default) | `rail` |
34
+ |---|---|---|
35
+ | Grid | `[1fr, auto, 1fr]` when `headerCenter` is set, else flex | `[rail width, minmax(0,1fr), auto]` |
36
+ | Column 1 | workspace switcher (`chip`) + `headerLeading` | workspace switcher (`tile`), centred on the rail's axis |
37
+ | Column 2 | `headerCenter` | `headerLeading`, `min-w-0`, **all** remaining width |
38
+ | Column 3 | `globalActions` + `PanelToggle` | `globalActions` + `PanelToggle` |
39
+ | `headerCenter` | rendered | **ignored** |
40
+ | Padding | `px-4` | none on the left, `pr-4` |
41
+
42
+ Reach for `rail` when the app's section tabs live in the header: in `split` they are capped at
43
+ roughly half the window AND they shift horizontally with the workspace name, because the switcher
44
+ sizes to that name. In `rail` the switcher is boxed into the rail column, so the tabs start at a
45
+ fixed x — the same x the main region starts at below.
46
+
47
+ Column 1's width comes from `iconRail.labelVisibility`: `--shell-rail-label-width` for `visible`,
48
+ `--shell-rail-width` for the default `tooltip`. `AppShell` wires this for you; if you render
49
+ `<ShellHeader>` by hand, mirror it into `railLabelVisibility` yourself or the header and the rail
50
+ will disagree.
51
+
52
+ **Target the header with `data-meda-shell-header`**, present on both layouts (alongside
53
+ `data-meda-header-layout="split" | "rail"`). Do NOT reach for it structurally — a selector like
54
+ `.flex.h-svh > header` breaks silently on any markup change.
55
+
56
+ ## WorkspaceSwitcher variants
57
+
58
+ `variant="chip"` (default) is the horizontal mark · name · chevron button. `variant="tile"` is the
59
+ rail-column shape the `rail` header uses: a full-width button with the mark on the rail axis, the
60
+ name beneath it in `data-slot="icon-rail-label"` type, and a small chevron hung off the mark so
61
+ the mark itself never leaves the axis. Its accessible name is `"<workspace name> workspace menu"`.
62
+ Pass `showLabel={false}` in icon-only rail mode. The dropdown — items, theme toggle, footer,
63
+ keyboard and dismiss behaviour — is identical in both.
64
+
65
+ **The tile's height is a budget, not a suggestion.** It has to fit inside
66
+ `--shell-header-height`, which consumers retune (the web app runs 52px under its desktop
67
+ window-tab strip) and which is a fixed `height` — so anything taller overlaps the chrome around
68
+ it rather than growing the header. The tile spends 28px on the mark + a 2px gap + ONE 14px label
69
+ line = 44px, with **no vertical padding**, plus `max-h-full` and an `overflow-hidden` rail column.
70
+ Change any of those and re-check the sum against 52px, not 64px.
71
+
72
+ That one-line label truncates, and hides entirely below 700px viewport height like every other
73
+ rail label. So the tile always renders a tooltip carrying the full workspace name — the same
74
+ `Tooltip` primitive the icon rail uses, rendered unconditionally rather than on the rail's
75
+ `!showLabel || isShortViewport` rule, because unlike a rail item the tile can also be
76
+ visible-but-truncated. **Any new rail-width control whose label can truncate or hide needs the
77
+ same tooltip:** an accessible name alone is not a substitute for a sighted user.
78
+
79
+ ## Keeping panel views without the header toggle
80
+
81
+ `rightPanel.showToggle: false` drops the header's `PanelToggle` while the views stay registered and
82
+ openable from anywhere else (`useMedaShell().panel.focus(id)`, a command, a route). It cannot do
83
+ the reverse — a toggle with no views is still hidden, because it would be a dead end. Do NOT hide
84
+ the toggle with consumer CSS such as `[data-meda-global-actions] + * { display: none }`; that is
85
+ what this prop replaces.
86
+
29
87
  ## MedaShellProvider — the runtime root
30
88
 
31
89
  Wrap your app once with `<MedaShellProvider>` (typically in the root layout). Props:
@@ -129,6 +187,26 @@ Each hook auto-handles register-on-mount and unregister-on-unmount via `useEffec
129
187
 
130
188
  The default palette hotkey is `'mod+k'` — override via `MedaShellProvider.commandPaletteHotkey`. Hotkey matching is strict modifier-aware: `'mod+k'` does NOT fire on `mod+shift+k`. Use `'mod'` (resolves to ⌘ on macOS, Ctrl on Windows/Linux), not platform-specific keywords.
131
189
 
190
+ ## Mobile dock + workspace sheet — light by app, open where you are
191
+
192
+ `mobileNav.activeTo` is the exact address of the active row. On its own it makes the dock go dark
193
+ as soon as the user leaves an app's first tab, because a dock slot only lights on
194
+ `item.to === activeTo`. Pass `mobileNav.activeId` — the active APP's id — alongside it:
195
+
196
+ - **Dock:** a slot is active iff `item.id === activeId`. Action slots (`open-sheet`, `open-ai`,
197
+ `open-command-palette`) light only through `activeId`, never through the `activeTo` fallback.
198
+ An `emphasis: 'brand'` slot keeps its brand disc when active and gains a ring plus a primary
199
+ label — **active always outranks brand.** A slot that carries `aria-current="page"` must carry a
200
+ visible treatment too; a variant branch that reassigns the tone after the active check (the
201
+ `let toneClass` ladder in `mobile-dock.tsx`) is exactly how that regresses.
202
+ - **Workspace sheet:** that row is expanded and scrolled into view on every open. The user can
203
+ still collapse it, and it re-expands on the next open. With no `activeId` the sheet derives the
204
+ row from `activeTo` — an exact `to` match first, then the row that owns `activeTo` among its
205
+ preset `views`.
206
+ - **`mobileNav.currentFirst`:** renders that row first inside its group.
207
+
208
+ Omit `activeId` and both surfaces behave exactly as they did before it existed.
209
+
132
210
  ## Right panel patterns
133
211
 
134
212
  Use a single `RightPanel` per shell. Don't build a parallel right-side surface — multiple right panels create state and dismiss-behavior conflicts. For a stacked detail experience, register multiple `PanelView`s with the existing `PanelViewsProvider` (`src/shell/panel-views-provider.tsx`).
@@ -149,3 +227,10 @@ Use a single `RightPanel` per shell. Don't build a parallel right-side surface
149
227
  | Multiple `RightPanel`s in one shell | Dismiss/state conflicts | Use `PanelViewsProvider` for stacked detail |
150
228
  | Forking `MedaShellProvider` per app | Loses cross-consumer parity | Compose around it; pass a custom `ThemeAdapter` for theme integration |
151
229
  | Hard-coded modifier in hotkey strings (`'cmd+k'`) | Breaks on Windows/Linux | Use `'mod+k'` — resolves per-platform |
230
+ | Selecting the header structurally (`.flex.h-svh > header`) | Breaks silently on any markup change | Target `[data-meda-shell-header]` |
231
+ | Hiding the panel toggle with consumer CSS | Depends on internal sibling order | `rightPanel={{ showToggle: false }}` |
232
+ | Passing `headerCenter` together with `headerLayout="rail"` | The rail grid has no centre column; the node never renders | Put the content in `headerLeading` |
233
+ | Lighting the mobile dock from `activeTo` alone | Goes dark on every route past an app's first tab | Also pass `mobileNav.activeId` |
234
+ | A rail-width control whose label truncates or hides, with no tooltip | The name becomes unrecoverable for sighted users | Add the `Tooltip` the icon rail and switcher tile use |
235
+ | Letting a variant branch (brand, emphasis) reassign the tone after the active check | `aria-current` with no visible state | Test the active state of every variant, not just the default one |
236
+ | Adding vertical padding or a second label line to the switcher tile | Overflows a 52px `--shell-header-height` | Keep the tile inside its 44px budget |