@happyvertical/smrt-svelte 0.55.16 → 0.55.17

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 (153) hide show
  1. package/README.md +5 -3
  2. package/agents/cookbook-shell.md +7 -1
  3. package/agents/overview-surfaces.md +5 -0
  4. package/agents/workspace.md +145 -11
  5. package/dist/browser-ai/svelte/components/DownloadProgress.svelte +22 -19
  6. package/dist/browser-ai/svelte/components/ModelStatusControl.svelte +16 -13
  7. package/dist/browser-ai/svelte/components/STTTest.svelte +41 -38
  8. package/dist/browser-ai/svelte/components/VoiceInput.svelte +17 -14
  9. package/dist/components/activity/ActivityList.svelte +1 -1
  10. package/dist/components/app/AppShell.svelte +274 -9
  11. package/dist/components/app/AppShell.svelte.d.ts +63 -2
  12. package/dist/components/app/AppShell.svelte.d.ts.map +1 -1
  13. package/dist/components/app/DockToggles.svelte +27 -1
  14. package/dist/components/app/DockToggles.svelte.d.ts.map +1 -1
  15. package/dist/components/app/OwnerSetupForm.svelte +98 -34
  16. package/dist/components/app/OwnerSetupForm.svelte.d.ts +4 -0
  17. package/dist/components/app/OwnerSetupForm.svelte.d.ts.map +1 -1
  18. package/dist/components/app/PhoneTabBar.svelte +136 -0
  19. package/dist/components/app/PhoneTabBar.svelte.d.ts +22 -0
  20. package/dist/components/app/PhoneTabBar.svelte.d.ts.map +1 -0
  21. package/dist/components/app/ShellSettingsPage.svelte +17 -2
  22. package/dist/components/app/ShellSettingsPage.svelte.d.ts +11 -16
  23. package/dist/components/app/ShellSettingsPage.svelte.d.ts.map +1 -1
  24. package/dist/components/app/dock-toggle.d.ts +3 -2
  25. package/dist/components/app/dock-toggle.d.ts.map +1 -1
  26. package/dist/components/app/index.d.ts +2 -1
  27. package/dist/components/app/index.d.ts.map +1 -1
  28. package/dist/components/app/index.js +1 -0
  29. package/dist/components/app/owner-setup-types.d.ts +5 -0
  30. package/dist/components/app/owner-setup-types.d.ts.map +1 -1
  31. package/dist/components/app/phone-tabs.d.ts +41 -0
  32. package/dist/components/app/phone-tabs.d.ts.map +1 -0
  33. package/dist/components/app/phone-tabs.js +66 -0
  34. package/dist/components/app/slot-item.d.ts +16 -0
  35. package/dist/components/app/slot-item.d.ts.map +1 -1
  36. package/dist/components/app/view-transition.d.ts +43 -0
  37. package/dist/components/app/view-transition.d.ts.map +1 -0
  38. package/dist/components/app/view-transition.js +76 -0
  39. package/dist/components/audit/AuditList.svelte +4 -1
  40. package/dist/components/audit/RecordHistory.svelte +4 -1
  41. package/dist/components/board/Board.svelte +4 -1
  42. package/dist/components/command-palette/CommandPalette.svelte +4 -1
  43. package/dist/components/command-palette/CommandPalette.svelte.d.ts +2 -0
  44. package/dist/components/command-palette/CommandPalette.svelte.d.ts.map +1 -1
  45. package/dist/components/command-palette/CommandPaletteDialog.svelte +3 -0
  46. package/dist/components/command-palette/CommandPaletteTrigger.svelte +3 -0
  47. package/dist/components/cookbook/CookbookApp.svelte +2 -1
  48. package/dist/components/cookbook/CookbookApp.svelte.d.ts.map +1 -1
  49. package/dist/components/cookbook/CookbookSectionPage.svelte +4 -1
  50. package/dist/components/cookbook/brand-theme.d.ts.map +1 -1
  51. package/dist/components/cookbook/brand-theme.js +21 -2
  52. package/dist/components/cookbook/build.js +1 -1
  53. package/dist/components/cookbook/theme.d.ts.map +1 -1
  54. package/dist/components/cookbook/theme.js +9 -5
  55. package/dist/components/cookbook/types.d.ts +5 -1
  56. package/dist/components/cookbook/types.d.ts.map +1 -1
  57. package/dist/components/forms/AddressInput.svelte +25 -22
  58. package/dist/components/forms/CheckboxInput.svelte +8 -5
  59. package/dist/components/forms/DateRangeInput.svelte +41 -38
  60. package/dist/components/forms/DateTimeInput.svelte +29 -26
  61. package/dist/components/forms/FileUpload.svelte +24 -21
  62. package/dist/components/forms/Form.svelte +34 -31
  63. package/dist/components/forms/FormMicButton.svelte +10 -7
  64. package/dist/components/forms/FormScope.svelte +4 -1
  65. package/dist/components/forms/MeasurementInput.svelte +26 -23
  66. package/dist/components/forms/MoneyInput.svelte +18 -15
  67. package/dist/components/forms/NumberInput.svelte +17 -14
  68. package/dist/components/forms/PhoneInput.svelte +37 -34
  69. package/dist/components/forms/SearchInput.svelte +16 -13
  70. package/dist/components/forms/SelectInput.svelte +17 -14
  71. package/dist/components/forms/TextInput.svelte +19 -16
  72. package/dist/components/forms/TextareaInput.svelte +21 -18
  73. package/dist/components/import-export/ImportExport.svelte +4 -1
  74. package/dist/components/module/ModulePanel.svelte +14 -11
  75. package/dist/components/notifications/NotificationBell.svelte +4 -1
  76. package/dist/components/overview/OverviewAssistantUndo.svelte +4 -1
  77. package/dist/components/overview/OverviewGrid.svelte +11 -6
  78. package/dist/components/overview/OverviewGrid.svelte.d.ts.map +1 -1
  79. package/dist/components/overview/WidgetOptionsForm.svelte +3 -0
  80. package/dist/components/overview/widgets/ChartWidget.svelte +7 -4
  81. package/dist/components/overview/widgets/MetricWidget.svelte +5 -2
  82. package/dist/components/overview/widgets/NoteWidget.svelte +5 -2
  83. package/dist/components/overview/widgets/RecordListWidget.svelte +6 -3
  84. package/dist/components/overview/widgets/ShortcutsWidget.svelte +12 -6
  85. package/dist/components/overview/widgets/ShortcutsWidget.svelte.d.ts.map +1 -1
  86. package/dist/components/screens/DetailScreen.svelte +6 -3
  87. package/dist/components/screens/EditForm.svelte +6 -3
  88. package/dist/components/screens/ListScreen.svelte +4 -1
  89. package/dist/components/screens/RecipeScreens.svelte +1 -1
  90. package/dist/components/settings/SettingsCatalog.svelte +4 -1
  91. package/dist/components/sortable/Sortable.svelte +11 -5
  92. package/dist/components/sortable/Sortable.svelte.d.ts.map +1 -1
  93. package/dist/components/workspace/Breadcrumbs.svelte +4 -1
  94. package/dist/components/workspace/NavTree.svelte +4 -1
  95. package/dist/components/workspace/RoleShell.svelte +4 -1
  96. package/dist/components/workspace/WorkspaceShell.svelte +43 -40
  97. package/dist/components/workspace/admin-shell/ActivityItem.svelte +4 -1
  98. package/dist/components/workspace/admin-shell/ActivityList.svelte +5 -2
  99. package/dist/components/workspace/admin-shell/ActivityTicker.svelte +6 -3
  100. package/dist/components/workspace/admin-shell/ActivityToasts.svelte +4 -1
  101. package/dist/components/workspace/admin-shell/AdminShell.svelte +502 -72
  102. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts +7 -1
  103. package/dist/components/workspace/admin-shell/AdminShell.svelte.d.ts.map +1 -1
  104. package/dist/components/workspace/admin-shell/AppScopePanel.svelte +4 -1
  105. package/dist/components/workspace/admin-shell/HotkeyInput.svelte +4 -1
  106. package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte +21 -8
  107. package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts +9 -2
  108. package/dist/components/workspace/admin-shell/PhoneBottomBar.svelte.d.ts.map +1 -1
  109. package/dist/components/workspace/admin-shell/PhoneTopBar.svelte +4 -1
  110. package/dist/components/workspace/admin-shell/ShellBrand.svelte +3 -0
  111. package/dist/components/workspace/admin-shell/ShellCorner.svelte +4 -1
  112. package/dist/components/workspace/admin-shell/ShellIconButton.svelte +4 -1
  113. package/dist/components/workspace/admin-shell/ShellLayoutEditor.svelte +4 -1
  114. package/dist/components/workspace/admin-shell/ShellNavEditor.svelte +13 -7
  115. package/dist/components/workspace/admin-shell/ShellNavEditor.svelte.d.ts +1 -1
  116. package/dist/components/workspace/admin-shell/ShellNavEditor.svelte.d.ts.map +1 -1
  117. package/dist/components/workspace/admin-shell/ShellNavToggle.svelte +4 -1
  118. package/dist/components/workspace/admin-shell/ShellSectionIcon.svelte +4 -1
  119. package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte +15 -10
  120. package/dist/components/workspace/admin-shell/ShellSectionMenu.svelte.d.ts.map +1 -1
  121. package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte +236 -48
  122. package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte.d.ts +11 -1
  123. package/dist/components/workspace/admin-shell/ShellSettingsPanel.svelte.d.ts.map +1 -1
  124. package/dist/components/workspace/admin-shell/ShellTitle.svelte +4 -1
  125. package/dist/components/workspace/admin-shell/ShortcutsOverlay.svelte +4 -1
  126. package/dist/components/workspace/admin-shell/SystemScopePanel.svelte +4 -1
  127. package/dist/components/workspace/admin-shell/SystemStatusChips.svelte +5 -2
  128. package/dist/components/workspace/admin-shell/TenantNav.svelte +81 -36
  129. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts +4 -3
  130. package/dist/components/workspace/admin-shell/TenantNav.svelte.d.ts.map +1 -1
  131. package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte +305 -53
  132. package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts +15 -2
  133. package/dist/components/workspace/admin-shell/WorkspaceAccountMenu.svelte.d.ts.map +1 -1
  134. package/dist/components/workspace/admin-shell/motion.d.ts +19 -0
  135. package/dist/components/workspace/admin-shell/motion.d.ts.map +1 -0
  136. package/dist/components/workspace/admin-shell/motion.js +44 -0
  137. package/dist/components/workspace/admin-shell/settings.d.ts +1 -0
  138. package/dist/components/workspace/admin-shell/settings.d.ts.map +1 -1
  139. package/dist/components/workspace/admin-shell/settings.js +1 -1
  140. package/dist/components/workspace/admin-shell/shell-icons.d.ts +8 -0
  141. package/dist/components/workspace/admin-shell/shell-icons.d.ts.map +1 -1
  142. package/dist/components/workspace/admin-shell/shell-icons.js +8 -0
  143. package/dist/components/workspace/admin-shell/state.svelte.d.ts +6 -0
  144. package/dist/components/workspace/admin-shell/state.svelte.d.ts.map +1 -1
  145. package/dist/components/workspace/admin-shell/state.svelte.js +15 -0
  146. package/dist/components/workspace/admin-shell/types.d.ts +13 -3
  147. package/dist/components/workspace/admin-shell/types.d.ts.map +1 -1
  148. package/dist/components/workspace/tools-dock/ToolsDock.svelte +41 -38
  149. package/dist/i18n/strings.workspace.d.ts +37 -0
  150. package/dist/i18n/strings.workspace.d.ts.map +1 -1
  151. package/dist/i18n/strings.workspace.js +42 -2
  152. package/dist/mcp-apps/McpAppsBridge.svelte +4 -1
  153. package/package.json +14 -14
@@ -51,6 +51,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
51
51
  import { M } from '../../../i18n/strings.workspace.js';
52
52
  import ActivityBadge from './ActivityBadge.svelte';
53
53
  import ShellBrand from './ShellBrand.svelte';
54
+ import { SHELL_DEFAULT_TOOL_ICON, SHELL_ICON_PATHS } from './shell-icons.js';
54
55
  import { setAdminShell } from './context.js';
55
56
  import {
56
57
  formatHotkeyBinding,
@@ -64,7 +65,12 @@ function buildLayoutStyle(shell: ModuleShellState): string {
64
65
  installShellViewport,
65
66
  watchFormActionBar,
66
67
  } from './mobile-shell-dom.js';
67
- import { clampPanelSize, resolveHotkey } from './settings.js';
68
+ import { tokenDuration } from './motion.js';
69
+ import {
70
+ clampPanelSize,
71
+ DEFAULT_PANEL_CONFIG,
72
+ resolveHotkey,
73
+ } from './settings.js';
68
74
  import { createShellState, type ShellState } from './state.svelte.js';
69
75
  import {
70
76
  resolveSlot,
@@ -168,8 +174,14 @@ function buildLayoutStyle(shell: ModuleShellState): string {
168
174
  * children receive them.
169
175
  */
170
176
  overlays?: Snippet<[{ viewport: ShellViewport; bottomBar: BottomBarMode }]>;
171
- /** Phone behavior switches (scrim, swipe to close, hide on scroll). */
177
+ /** Phone behavior switches (scrim and swipe to close default on; hide on scroll). */
172
178
  phone?: AdminShellPhoneOptions;
179
+ /**
180
+ * Show the built-in "Tools" opener on phones while the right edge has
181
+ * focus tools (default true). `AppShell` turns it off when dock toggles
182
+ * already open the dock from the header.
183
+ */
184
+ phoneDockOpener?: boolean;
173
185
  /** Keep the phone top bar shown (e.g. while a banner is visible). */
174
186
  pinChrome?: boolean;
175
187
  /**
@@ -221,6 +233,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
221
233
  phoneBottomBar,
222
234
  overlays,
223
235
  phone,
236
+ phoneDockOpener = true,
224
237
  pinChrome = false,
225
238
  path,
226
239
  children,
@@ -257,6 +270,8 @@ function buildLayoutStyle(shell: ModuleShellState): string {
257
270
  let formActions = $state(false);
258
271
  let chromeHidden = $state(false);
259
272
  let resizing = $state<SideEdge | null>(null);
273
+ /** Set after hydration and two frames, so first paint never animates. */
274
+ let motionReady = $state(false);
260
275
  const sideElements = $state<Partial<Record<SideEdge, HTMLElement>>>({});
261
276
  let scrollChrome: ReturnType<typeof installScrollChrome> | null = null;
262
277
 
@@ -280,16 +295,20 @@ function buildLayoutStyle(shell: ModuleShellState): string {
280
295
  });
281
296
  return () => { cancelled = true; queueMicrotask(() => { if (opener?.isConnected) opener.focus(); }); };
282
297
  });
283
- const showDefaultPhoneTenantOpener = $derived(
298
+ const phoneScrim = $derived(phone?.scrim !== false);
299
+ const phoneSwipeToClose = $derived(phone?.swipeToClose !== false);
300
+ /** The right edge has tools a phone user must be able to open. */
301
+ const rightOpenerShown = $derived(
302
+ isPhone &&
303
+ phoneDockOpener &&
304
+ shell.isEdgeShown('right') &&
305
+ (shell.focusTools.length > 0 || Boolean(focusRail)),
306
+ );
307
+ const leftOpenerShown = $derived(
284
308
  isPhone &&
285
- !phoneTopBar &&
286
- panelState('top') === 'hidden' &&
287
309
  shell.isEdgeShown('left') &&
288
310
  shell.phonePresentation('left') !== 'hidden',
289
311
  );
290
- const showPhoneTop = $derived(
291
- isPhone && (Boolean(phoneTopBar) || showDefaultPhoneTenantOpener),
292
- );
293
312
  const showHeader = $derived(
294
313
  Boolean(header) && !(isPhone && Boolean(phoneTopBar)),
295
314
  );
@@ -304,6 +323,18 @@ function buildLayoutStyle(shell: ModuleShellState): string {
304
323
  (edge) => panelState(edge) === 'expanded' && shell.isEdgeShown(edge),
305
324
  ),
306
325
  );
326
+ // Starting to edit the layout on a phone puts any open drawer or sheet away,
327
+ // so the editor and a drawer never overlap.
328
+ let wasEditingLayout = false;
329
+ $effect(() => {
330
+ const now = Boolean(layoutEdit?.active);
331
+ untrack(() => {
332
+ if (now && !wasEditingLayout && shell.viewport === 'phone') {
333
+ collapsePhoneSurfaces();
334
+ }
335
+ wasEditingLayout = now;
336
+ });
337
+ });
307
338
  const reserveStyle = $derived(
308
339
  [
309
340
  `--smrt-shell-floating-reserve-inline: ${floatingReserve?.inline ?? '0px'}`,
@@ -472,7 +503,45 @@ function buildLayoutStyle(shell: ModuleShellState): string {
472
503
  onMount(() => {
473
504
  const offViewport = installShellViewport(shell);
474
505
  const offOverlayMedia = installShellOverlayMedia(shell);
475
- void shell.hydrate().then(() => shell.closeOverlaidEdges());
506
+ const hydrated = shell
507
+ .hydrate()
508
+ .then(() => shell.closeOverlaidEdges());
509
+ let disposed = false;
510
+ let frame = 0;
511
+ let themeObserver: MutationObserver | null = null;
512
+ let themeTimer: ReturnType<typeof setTimeout> | null = null;
513
+ // Colour-scheme crossfade: flag the root for one transition when the
514
+ // theme root's scheme changes. The flag is set from the observer
515
+ // microtask, i.e. before the style recalc that applies the new tokens.
516
+ function watchColorScheme(): void {
517
+ const root = rootElement;
518
+ if (!root || typeof MutationObserver === 'undefined') return;
519
+ const host =
520
+ root.closest<HTMLElement>('[data-color-scheme]') ??
521
+ document.documentElement;
522
+ themeObserver = new MutationObserver(() => {
523
+ if (prefersReducedMotion()) return;
524
+ root.setAttribute('data-smrt-theme-changing', '');
525
+ if (themeTimer) clearTimeout(themeTimer);
526
+ themeTimer = setTimeout(
527
+ () => root.removeAttribute('data-smrt-theme-changing'),
528
+ tokenDuration('--smrt-duration-medium1', root) + 100,
529
+ );
530
+ });
531
+ themeObserver.observe(host, {
532
+ attributes: true,
533
+ attributeFilter: ['data-color-scheme', 'class'],
534
+ });
535
+ }
536
+ void hydrated.then(() => {
537
+ frame = requestAnimationFrame(() => {
538
+ frame = requestAnimationFrame(() => {
539
+ if (disposed) return;
540
+ motionReady = true;
541
+ watchColorScheme();
542
+ });
543
+ });
544
+ });
476
545
  const offKeyboard = phoneBottomBar
477
546
  ? installKeyboardWatcher((open) => (keyboardOpen = open))
478
547
  : () => {};
@@ -523,6 +592,10 @@ function buildLayoutStyle(shell: ModuleShellState): string {
523
592
  window.addEventListener('keydown', handleKeydown);
524
593
  return () => {
525
594
  window.removeEventListener('keydown', handleKeydown);
595
+ disposed = true;
596
+ cancelAnimationFrame(frame);
597
+ themeObserver?.disconnect();
598
+ if (themeTimer) clearTimeout(themeTimer);
526
599
  offViewport();
527
600
  offOverlayMedia();
528
601
  if (closingTimer) clearTimeout(closingTimer);
@@ -709,9 +782,69 @@ function buildLayoutStyle(shell: ModuleShellState): string {
709
782
  const stripRegions = $derived(
710
783
  layoutEdit?.active && layoutEdit.strip ? (layoutEdit.hiddenRegions ?? []) : [],
711
784
  );
785
+ function slotHas(...names: ShellSlot[]): boolean {
786
+ return names.some((name) => (slotContent[name]?.length ?? 0) > 0);
787
+ }
788
+ const layoutEditing = $derived(Boolean(layoutEdit?.active));
789
+ const menuOpenerCounts = $derived(
790
+ !(layoutEditing && !isPhone) &&
791
+ shell.config.panels.left.initial !== 'hidden' &&
792
+ shell.layoutPanels.left?.visible !== false &&
793
+ (!isPhone || shell.phonePresentation('left') !== 'hidden') &&
794
+ (isPhone || panelState('left') === 'hidden'),
795
+ );
796
+ // A band or edge with nothing to show is not rendered and its grid track
797
+ // collapses to 0 (the five-row grid itself never changes).
798
+ const topRendered = $derived(
799
+ panelState('top') !== 'hidden' &&
800
+ (layoutEditing ||
801
+ Boolean(appBar) ||
802
+ !brandInSlot ||
803
+ showToggle('top') ||
804
+ menuOpenerCounts ||
805
+ rightOpenerShown ||
806
+ Boolean(account) ||
807
+ footerInHeader ||
808
+ Boolean(topLeftCorner) ||
809
+ Boolean(topRightCorner) ||
810
+ slotHas('header.start', 'header.center', 'header.end') ||
811
+ panelMounted('top')),
812
+ );
813
+ const bottomRendered = $derived(
814
+ panelState('bottom') !== 'hidden' &&
815
+ (layoutEditing ||
816
+ Boolean(systemBar) ||
817
+ Boolean(bottomLeftCorner) ||
818
+ Boolean(bottomRightCorner) ||
819
+ (showToggle('bottom') && Boolean(systemPanel || !systemBar)) ||
820
+ slotHas('footer.start', 'footer.center', 'footer.end') ||
821
+ panelMounted('bottom')),
822
+ );
823
+ const rightRendered = $derived(
824
+ shell.isEdgeShown('right') &&
825
+ (layoutEditing ||
826
+ shell.focusTools.length > 0 ||
827
+ Boolean(focusRail) ||
828
+ Boolean(focusPanel) ||
829
+ showToggle('right') ||
830
+ slotHas('rightSidebar.header', 'rightSidebar.footer') ||
831
+ panelMounted('right')),
832
+ );
833
+ const showDefaultPhoneTenantOpener = $derived(
834
+ isPhone &&
835
+ !phoneTopBar &&
836
+ !topRendered &&
837
+ (leftOpenerShown || rightOpenerShown),
838
+ );
839
+ const showPhoneTop = $derived(
840
+ isPhone && (Boolean(phoneTopBar) || showDefaultPhoneTenantOpener),
841
+ );
712
842
  const layoutStyle = $derived(
713
843
  [
714
844
  buildLayoutStyle(shell),
845
+ ...(topRendered ? [] : ['--smrt-admin-shell-top-track: 0rem']),
846
+ ...(bottomRendered ? [] : ['--smrt-admin-shell-bottom-track: 0rem']),
847
+ ...(rightRendered ? [] : ['--smrt-admin-shell-right-track: 0rem']),
715
848
  ...stripRegions.map(
716
849
  (region) => `--smrt-admin-shell-${REGION_TRACK[region]}-track: ${STRIP_TRACK}`,
717
850
  ),
@@ -788,6 +921,39 @@ function buildLayoutStyle(shell: ModuleShellState): string {
788
921
  return shell.config.panels[edge].label;
789
922
  }
790
923
 
924
+ /**
925
+ * Accessible name of the left chevron. A host-configured panel label
926
+ * ("Packages") names the panel; the default region name ("Left sidebar")
927
+ * is a developer term, so the name is then just "Collapse sidebar".
928
+ */
929
+ function leftEdgeToggleName(): string {
930
+ const label = labelFor('left');
931
+ const expanded = edgeExpanded('left');
932
+ if (label && label !== DEFAULT_PANEL_CONFIG.left.label) {
933
+ return t(
934
+ M[expanded ? 'ui.admin_shell.collapse_panel' : 'ui.admin_shell.expand_panel'],
935
+ { panel: label },
936
+ );
937
+ }
938
+ return t(
939
+ M[expanded ? 'ui.admin_shell.collapse_sidebar' : 'ui.admin_shell.expand_sidebar'],
940
+ );
941
+ }
942
+
943
+ function toggleMenu(): void {
944
+ shell.setPanelState('left', edgeExpanded('left') ? 'collapsed' : 'expanded');
945
+ }
946
+
947
+ function toggleTools(): void {
948
+ if (edgeExpanded('right')) {
949
+ shell.collapsePanel('right');
950
+ return;
951
+ }
952
+ const tool = resolveActiveFocusTool();
953
+ if (tool) shell.openFocusTool(tool.id);
954
+ else shell.expandPanel('right');
955
+ }
956
+
791
957
  function hotkeyFor(edge: PanelEdge): string {
792
958
  return formatHotkeyBinding(
793
959
  resolveHotkey(edge, shell.config.panels[edge], shell.settings),
@@ -840,6 +1006,12 @@ function buildLayoutStyle(shell: ModuleShellState): string {
840
1006
  return toggleOn(edge) || showToggle(edge);
841
1007
  }
842
1008
 
1009
+ $effect(() => {
1010
+ shell.hotkeysAvailable = (['top', 'left', 'right', 'bottom'] as const).some(
1011
+ hotkeyActive,
1012
+ );
1013
+ });
1014
+
843
1015
  function showsHotkeyFor(edge: PanelEdge): boolean {
844
1016
  return (
845
1017
  hotkeyActive(edge) &&
@@ -894,21 +1066,80 @@ function buildLayoutStyle(shell: ModuleShellState): string {
894
1066
  </script>
895
1067
 
896
1068
  {#snippet edgeToggle(edge: PanelEdge)}
897
- <Button
898
- variant="ghost"
899
- size="sm"
900
- class="smrt-admin-shell__edge-toggle"
901
- aria-label={edge === 'left' ? t(M[edgeExpanded(edge) ? 'ui.admin_shell.collapse_panel' : 'ui.admin_shell.expand_panel'], { panel: labelFor(edge) }) : undefined}
902
- aria-expanded={edgeExpanded(edge)}
903
- aria-controls={`smrt-admin-shell-${edge}-panel`}
904
- onclick={() => shell.togglePanel(edge)}
1069
+ {#if edge === 'left'}
1070
+ <!-- The left toggle is an icon-only chevron: it points the way the panel
1071
+ will move (left to collapse, right to expand). Its accessible name
1072
+ carries the action and the hotkey rides in the tooltip. -->
1073
+ {@const name = leftEdgeToggleName()}
1074
+ <Button
1075
+ variant="ghost"
1076
+ size="sm"
1077
+ class="smrt-admin-shell__edge-toggle smrt-admin-shell__edge-toggle--left"
1078
+ aria-label={name}
1079
+ title={showsHotkeyFor('left') ? `${name} (${hotkeyFor('left')})` : name}
1080
+ aria-expanded={edgeExpanded('left')}
1081
+ aria-controls="smrt-admin-shell-left-panel"
1082
+ onclick={() => shell.togglePanel('left')}
1083
+ >
1084
+ <svg
1085
+ class="smrt-admin-shell__edge-toggle-chevron"
1086
+ viewBox="0 0 24 24"
1087
+ width="18"
1088
+ height="18"
1089
+ fill="currentColor"
1090
+ aria-hidden="true"
1091
+ focusable="false"
1092
+ ><path d={SHELL_ICON_PATHS.chevronRight} /></svg>
1093
+ <ActivityBadge edge="left" />
1094
+ </Button>
1095
+ {:else}
1096
+ <Button
1097
+ variant="ghost"
1098
+ size="sm"
1099
+ class="smrt-admin-shell__edge-toggle"
1100
+ aria-expanded={edgeExpanded(edge)}
1101
+ aria-controls={`smrt-admin-shell-${edge}-panel`}
1102
+ onclick={() => shell.togglePanel(edge)}
1103
+ >
1104
+ <span>{labelFor(edge)}</span>
1105
+ {#if showsHotkeyFor(edge)}
1106
+ <kbd class="smrt-admin-shell__edge-toggle-kbd">{hotkeyFor(edge)}</kbd>
1107
+ {/if}
1108
+ <ActivityBadge {edge} />
1109
+ </Button>
1110
+ {/if}
1111
+ {/snippet}
1112
+
1113
+ {#snippet menuOpener()}
1114
+ <!-- raw-primitive-allow: shell chrome control, not a content button -->
1115
+ <button
1116
+ type="button"
1117
+ class="smrt-admin-shell__chrome-button"
1118
+ data-smrt-part="trigger"
1119
+ aria-label={t(M['ui.admin_shell.menu'])}
1120
+ title={t(M['ui.admin_shell.menu'])}
1121
+ aria-expanded={edgeExpanded('left')}
1122
+ aria-controls="smrt-admin-shell-left-panel"
1123
+ onclick={toggleMenu}
905
1124
  >
906
- {#if edge === 'left' && !edgeExpanded(edge)}<span aria-hidden="true">›</span>{:else}<span>{labelFor(edge)}</span>{/if}
907
- {#if showsHotkeyFor(edge) && (edge !== 'left' || edgeExpanded(edge))}
908
- <kbd class="smrt-admin-shell__edge-toggle-kbd">{hotkeyFor(edge)}</kbd>
909
- {/if}
910
- <ActivityBadge {edge} />
911
- </Button>
1125
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" aria-hidden="true" focusable="false"><path d={SHELL_ICON_PATHS.menu} /></svg>
1126
+ </button>
1127
+ {/snippet}
1128
+
1129
+ {#snippet toolsOpener()}
1130
+ <!-- raw-primitive-allow: shell chrome control, not a content button -->
1131
+ <button
1132
+ type="button"
1133
+ class="smrt-admin-shell__chrome-button smrt-admin-shell__tools-opener"
1134
+ data-smrt-part="trigger"
1135
+ aria-label={t(M['ui.admin_shell.tools'])}
1136
+ title={t(M['ui.admin_shell.tools'])}
1137
+ aria-expanded={edgeExpanded('right')}
1138
+ aria-controls="smrt-admin-shell-right-panel"
1139
+ onclick={toggleTools}
1140
+ >
1141
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" aria-hidden="true" focusable="false"><path d={SHELL_ICON_PATHS[SHELL_DEFAULT_TOOL_ICON]} /></svg>
1142
+ </button>
912
1143
  {/snippet}
913
1144
 
914
1145
  {#snippet resizer(edge: SideEdge)}
@@ -1007,7 +1238,8 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1007
1238
  data-phone-top={showPhoneTop ? '' : undefined}
1008
1239
  data-chrome-hidden={chromeHidden ? '' : undefined}
1009
1240
  data-resizing={resizing ?? undefined}
1010
- style={`${layoutStyle}; ${chromeStyle}; ${reserveStyle}`}
1241
+ data-motion={motionReady ? '' : undefined}
1242
+ style={`${layoutStyle}; ${chromeStyle}; ${reserveStyle}`} data-smrt-component="AdminShell"
1011
1243
  >
1012
1244
  {#if showHeader}
1013
1245
  <div
@@ -1020,13 +1252,13 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1020
1252
  {/if}
1021
1253
 
1022
1254
  {@render regionStrip('header')}
1023
- {#if panelState('top') !== 'hidden'}
1255
+ {#if topRendered}
1024
1256
  <header
1025
1257
  id="smrt-admin-shell-top-panel"
1026
1258
  class="smrt-admin-shell__edge smrt-admin-shell__edge--top"
1027
1259
  data-state={panelState('top')}
1028
1260
  data-presentation={shell.config.panels.top.presentation}
1029
- style:--edge-columns={topEdgeColumns}
1261
+ style:--smrt-admin-shell-edge-columns={topEdgeColumns}
1030
1262
  >
1031
1263
  {#if topLeftCorner}
1032
1264
  <div class="smrt-admin-shell__corner smrt-admin-shell__corner--top-left">
@@ -1035,7 +1267,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1035
1267
  {/if}
1036
1268
  <div
1037
1269
  class="smrt-admin-shell__band smrt-admin-shell__band--top"
1038
- style:--band-column={2}
1270
+ style:--smrt-admin-shell-band-column={2}
1039
1271
  >
1040
1272
  {#if appBar}
1041
1273
  {@render appBar()}
@@ -1045,7 +1277,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1045
1277
  {/if}
1046
1278
  {#if !(layoutEdit?.active && !isPhone) && shell.config.panels.left.initial !== 'hidden' && shell.layoutPanels.left?.visible !== false && (!isPhone || shell.phonePresentation('left') !== 'hidden')}
1047
1279
  <div class="smrt-admin-shell__tenant-opener" class:restore-hidden={panelState('left') === 'hidden'}>
1048
- <Button variant="ghost" size="sm" aria-label={t(M['ui.admin_shell.menu'])} aria-expanded={edgeExpanded('left')} aria-controls="smrt-admin-shell-left-panel" onclick={() => shell.setPanelState('left', edgeExpanded('left') ? 'collapsed' : 'expanded')}>{t(M['ui.admin_shell.menu'])}</Button>
1280
+ {@render menuOpener()}
1049
1281
  </div>
1050
1282
  {/if}
1051
1283
  {@render slotGroup('header.start')}
@@ -1056,6 +1288,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1056
1288
  </div>
1057
1289
  {/if}
1058
1290
  {@render slotGroup('header.end')}
1291
+ {#if rightOpenerShown}{@render toolsOpener()}{/if}
1059
1292
  {@render regionEdit('header')}
1060
1293
  </div>
1061
1294
  {#if topRightCorner}
@@ -1101,14 +1334,29 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1101
1334
  use:swipeDismiss={{
1102
1335
  direction: 'left',
1103
1336
  onDismiss: () => shell.collapsePanel('left'),
1104
- enabled: isPhone && Boolean(phone?.swipeToClose) && edgeExpanded('left'),
1337
+ enabled: isPhone && phoneSwipeToClose && edgeExpanded('left'),
1105
1338
  }}
1106
1339
  >
1107
1340
  {@render resizer('left')}
1108
1341
  <div class="smrt-admin-shell__rail">
1109
1342
  {@render regionEdit('leftSidebar')}
1110
- {#if (panelState('top') === 'hidden' || !edgeExpanded('left')) && (homeHref || logoSrc || brand)}{@render shellBrand(!edgeExpanded('left'))}{/if}
1111
- {#if showToggle('left') || isPhone}{@render edgeToggle('left')}{/if}
1343
+ {#if isPhone}
1344
+ <!-- Phone drawer: the app title and an icon-only close control. -->
1345
+ <div class="smrt-admin-shell__drawer-head" data-smrt-part="header">
1346
+ <div class="smrt-admin-shell__drawer-title">{@render shellBrand()}</div>
1347
+ <!-- raw-primitive-allow: shell chrome control, not a content button -->
1348
+ <button
1349
+ type="button"
1350
+ class="smrt-admin-shell__chrome-button"
1351
+ data-smrt-part="close"
1352
+ aria-label={t(M['ui.admin_shell.close_menu'])}
1353
+ title={t(M['ui.admin_shell.close_menu'])}
1354
+ onclick={() => shell.collapsePanel('left')}
1355
+ >
1356
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" aria-hidden="true" focusable="false"><path d={SHELL_ICON_PATHS.close} /></svg>
1357
+ </button>
1358
+ </div>
1359
+ {:else if !topRendered && (homeHref || logoSrc || brand)}{@render shellBrand(!edgeExpanded('left'))}{/if}
1112
1360
  {#if shownOpen('left') || (tenantPanel && keepsContent('left'))}
1113
1361
  <div
1114
1362
  class="smrt-admin-shell__tenant-stack"
@@ -1122,6 +1370,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1122
1370
  {@render tenantRail()}
1123
1371
  {/if}
1124
1372
  </div>
1373
+ {#if showToggle('left') && !isPhone && shownOpen('left')}{@render edgeToggle('left')}{/if}
1125
1374
  {#if tenantFooter && !footerInHeader}
1126
1375
  <div class="smrt-admin-shell__tenant-footer">
1127
1376
  {@render tenantFooter()}
@@ -1132,11 +1381,10 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1132
1381
  {/if}
1133
1382
  {#if !shownOpen('left') && (tenantRail || tenantRailFooter)}
1134
1383
  <div class="smrt-admin-shell__tenant-rail-stack">
1135
- {#if tenantRail}
1136
- <div class="smrt-admin-shell__tenant-rail-content">
1137
- {@render tenantRail()}
1138
- </div>
1139
- {/if}
1384
+ <div class="smrt-admin-shell__tenant-rail-content">
1385
+ {#if tenantRail}{@render tenantRail()}{/if}
1386
+ </div>
1387
+ {#if showToggle('left') && !isPhone}{@render edgeToggle('left')}{/if}
1140
1388
  {#if tenantRailFooter}
1141
1389
  <div class="smrt-admin-shell__tenant-rail-footer">
1142
1390
  {@render tenantRailFooter()}
@@ -1144,6 +1392,10 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1144
1392
  {/if}
1145
1393
  </div>
1146
1394
  {/if}
1395
+ <!-- The chevron closes the nav region, above the footer slot: inside
1396
+ the expanded stack or the collapsed rail stack. Only when neither
1397
+ is rendered does it stand alone at the bottom of the rail. -->
1398
+ {#if showToggle('left') && !isPhone && !shownOpen('left') && !(tenantRail || tenantRailFooter)}{@render edgeToggle('left')}{/if}
1147
1399
  </div>
1148
1400
  </aside>
1149
1401
  {/if}
@@ -1163,32 +1415,28 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1163
1415
  {@render phoneTopBar({ hidden: chromeHidden })}
1164
1416
  {:else if showDefaultPhoneTenantOpener}
1165
1417
  <div class="smrt-admin-shell__phone-default-top">
1166
- <Button
1167
- variant="ghost"
1168
- size="sm"
1169
- aria-label={t(M['ui.admin_shell.menu'])}
1170
- aria-expanded={edgeExpanded('left')}
1171
- aria-controls="smrt-admin-shell-left-panel"
1172
- onclick={() =>
1173
- shell.setPanelState(
1174
- 'left',
1175
- edgeExpanded('left') ? 'collapsed' : 'expanded',
1176
- )}
1177
- >{t(M['ui.admin_shell.menu'])}</Button>
1418
+ {#if leftOpenerShown}{@render menuOpener()}{/if}
1419
+ {#if rightOpenerShown}
1420
+ <span class="smrt-admin-shell__phone-default-end">{@render toolsOpener()}</span>
1421
+ {/if}
1178
1422
  </div>
1179
1423
  {/if}
1180
1424
  </div>
1181
1425
  {/if}
1182
1426
 
1183
- {#if isPhone && phone?.scrim && phoneSurfaceOpen}
1427
+ {#if isPhone && phoneScrim && phoneSurfaceOpen}
1184
1428
  <!-- raw-primitive-allow: click-catching backdrop behind a phone drawer -->
1185
1429
  <button
1186
1430
  type="button"
1187
1431
  class="smrt-admin-shell__scrim"
1188
1432
  tabindex="-1"
1189
- aria-label={t(M['ui.admin_shell.close_panel'], {
1190
- label: labelFor(edgeExpanded('left') && shell.isEdgeShown('left') ? 'left' : 'right'),
1191
- })}
1433
+ aria-label={t(
1434
+ M[
1435
+ edgeExpanded('left') && shell.isEdgeShown('left')
1436
+ ? 'ui.admin_shell.close_menu'
1437
+ : 'ui.admin_shell.close_tools'
1438
+ ],
1439
+ )}
1192
1440
  data-testid="admin-shell-scrim"
1193
1441
  onclick={collapsePhoneSurfaces}
1194
1442
  ></button>
@@ -1201,7 +1449,13 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1201
1449
  type="button"
1202
1450
  class="smrt-admin-shell__overlay-scrim"
1203
1451
  tabindex="-1"
1204
- aria-label={t(M['ui.admin_shell.close_panel'], { label: labelFor(scrimEdge) })}
1452
+ aria-label={t(
1453
+ M[
1454
+ scrimEdge === 'left'
1455
+ ? 'ui.admin_shell.close_sidebar'
1456
+ : 'ui.admin_shell.close_tools'
1457
+ ],
1458
+ )}
1205
1459
  data-testid="admin-shell-overlay-scrim"
1206
1460
  data-closing={overlayEdge ? undefined : ''}
1207
1461
  onclick={() => {
@@ -1211,7 +1465,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1211
1465
  {/if}
1212
1466
 
1213
1467
  {@render regionStrip('rightSidebar')}
1214
- {#if shell.isEdgeShown('right')}
1468
+ {#if rightRendered}
1215
1469
  {@const rightPhone = isPhone ? shell.phonePresentation('right') : undefined}
1216
1470
  <!-- tabindex -1 only while overlaid, so the panel can take focus on open -->
1217
1471
  <!-- svelte-ignore a11y_no_noninteractive_tabindex -->
@@ -1234,7 +1488,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1234
1488
  onDismiss: () => shell.collapsePanel('right'),
1235
1489
  enabled:
1236
1490
  rightPhone === 'drawer' &&
1237
- Boolean(phone?.swipeToClose) &&
1491
+ phoneSwipeToClose &&
1238
1492
  edgeExpanded('right'),
1239
1493
  }}
1240
1494
  >
@@ -1254,6 +1508,19 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1254
1508
  {/if}
1255
1509
  <div class="smrt-admin-shell__rail">
1256
1510
  {@render regionEdit('rightSidebar')}
1511
+ {#if rightPhone === 'drawer'}
1512
+ <!-- raw-primitive-allow: shell chrome control, not a content button -->
1513
+ <button
1514
+ type="button"
1515
+ class="smrt-admin-shell__chrome-button smrt-admin-shell__rail-close"
1516
+ data-smrt-part="close"
1517
+ aria-label={t(M['ui.admin_shell.close_tools'])}
1518
+ title={t(M['ui.admin_shell.close_tools'])}
1519
+ onclick={() => shell.collapsePanel('right')}
1520
+ >
1521
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" aria-hidden="true" focusable="false"><path d={SHELL_ICON_PATHS.close} /></svg>
1522
+ </button>
1523
+ {/if}
1257
1524
  {#if shell.config.panels.right.rail === false}
1258
1525
  <!-- Railless: no tool buttons; the host's toggle opens and closes it. -->
1259
1526
  {:else if focusRail}
@@ -1276,7 +1543,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1276
1543
  {#if tool.icon}
1277
1544
  <tool.icon />
1278
1545
  {:else}
1279
- <span aria-hidden="true">{tool.label.charAt(0)}</span>
1546
+ <svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor" aria-hidden="true" focusable="false"><path d={SHELL_ICON_PATHS[SHELL_DEFAULT_TOOL_ICON]} /></svg>
1280
1547
  {/if}
1281
1548
  {#if tool.badge != null}
1282
1549
  <span class="smrt-admin-shell__focus-tool-badge">
@@ -1330,13 +1597,13 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1330
1597
  {/if}
1331
1598
 
1332
1599
  {@render regionStrip('footer')}
1333
- {#if panelState('bottom') !== 'hidden'}
1600
+ {#if bottomRendered}
1334
1601
  <footer
1335
1602
  id="smrt-admin-shell-bottom-panel"
1336
1603
  class="smrt-admin-shell__edge smrt-admin-shell__edge--bottom"
1337
1604
  data-state={panelState('bottom')}
1338
1605
  data-presentation={shell.config.panels.bottom.presentation}
1339
- style:--edge-columns={bottomEdgeColumns}
1606
+ style:--smrt-admin-shell-edge-columns={bottomEdgeColumns}
1340
1607
  >
1341
1608
  {#if bottomLeftCorner}
1342
1609
  <div class="smrt-admin-shell__corner smrt-admin-shell__corner--bottom-left">
@@ -1345,7 +1612,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1345
1612
  {/if}
1346
1613
  <div
1347
1614
  class="smrt-admin-shell__band smrt-admin-shell__band--bottom"
1348
- style:--band-column={2}
1615
+ style:--smrt-admin-shell-band-column={2}
1349
1616
  >
1350
1617
  {@render slotGroup('footer.start')}
1351
1618
  {@render slotGroup('footer.center')}
@@ -1432,6 +1699,8 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1432
1699
  </div>
1433
1700
 
1434
1701
  <style>
1702
+ @layer smrt.reset, smrt.structure, smrt.theme;
1703
+ @layer smrt.theme {
1435
1704
  .smrt-admin-shell {
1436
1705
  --smrt-shell-floating-reserve-inline: 0px;
1437
1706
  --smrt-shell-floating-reserve-block: 0px;
@@ -1497,7 +1766,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1497
1766
  background-color: var(--smrt-admin-shell-scrollbar-thumb, color-mix(in srgb, var(--smrt-color-on-surface) 26%, transparent));
1498
1767
  background-clip: padding-box;
1499
1768
  border: 2px solid transparent;
1500
- border-radius: var(--smrt-radius-full, 999px);
1769
+ border-radius: var(--smrt-radius-full);
1501
1770
  }
1502
1771
 
1503
1772
  .smrt-admin-shell :global(*)::-webkit-scrollbar-thumb:hover {
@@ -1542,7 +1811,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1542
1811
  grid-row: 2;
1543
1812
  display: grid;
1544
1813
  grid-template-columns: var(
1545
- --edge-columns,
1814
+ --smrt-admin-shell-edge-columns,
1546
1815
  var(--smrt-admin-shell-left-collapsed) minmax(0, 1fr)
1547
1816
  var(--smrt-admin-shell-right-collapsed)
1548
1817
  );
@@ -1578,7 +1847,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1578
1847
  grid-row: 4;
1579
1848
  display: grid;
1580
1849
  grid-template-columns: var(
1581
- --edge-columns,
1850
+ --smrt-admin-shell-edge-columns,
1582
1851
  var(--smrt-admin-shell-left-collapsed) minmax(0, 1fr)
1583
1852
  var(--smrt-admin-shell-right-collapsed)
1584
1853
  );
@@ -1608,7 +1877,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1608
1877
 
1609
1878
  .smrt-admin-shell__band--top,
1610
1879
  .smrt-admin-shell__band--bottom {
1611
- grid-column: var(--band-column, 2);
1880
+ grid-column: var(--smrt-admin-shell-band-column, 2);
1612
1881
  }
1613
1882
 
1614
1883
  /* The header's slots can render in the top band too: keep the floating
@@ -1655,7 +1924,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1655
1924
  width: 2.35rem;
1656
1925
  height: 2.35rem;
1657
1926
  border: 1px solid transparent;
1658
- border-radius: var(--smrt-radius-md, 8px);
1927
+ border-radius: var(--smrt-radius-md);
1659
1928
  background: transparent;
1660
1929
  color: var(--smrt-color-on-surface-variant);
1661
1930
  cursor: pointer;
@@ -1683,8 +1952,8 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1683
1952
  padding: var(--smrt-spacing-2, 0.5rem) var(--smrt-spacing-2, 0.5rem);
1684
1953
  border: 1px solid var(--smrt-color-outline-variant);
1685
1954
  border-inline-end: 0;
1686
- border-start-start-radius: var(--smrt-radius-md, 8px);
1687
- border-end-start-radius: var(--smrt-radius-md, 8px);
1955
+ border-start-start-radius: var(--smrt-radius-md);
1956
+ border-end-start-radius: var(--smrt-radius-md);
1688
1957
  background: var(--smrt-color-surface-container-high);
1689
1958
  color: var(--smrt-color-on-surface-variant);
1690
1959
  font: inherit;
@@ -1707,7 +1976,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1707
1976
  inset-block-start: -0.25rem;
1708
1977
  inset-inline-end: -0.25rem;
1709
1978
  min-width: 1rem;
1710
- border-radius: var(--smrt-radius-full, 9999px);
1979
+ border-radius: var(--smrt-radius-full);
1711
1980
  background: var(--smrt-color-error);
1712
1981
  color: var(--smrt-color-on-error);
1713
1982
  font-size: var(--smrt-typography-label-small-size, 0.625rem);
@@ -1725,12 +1994,49 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1725
1994
  max-inline-size: min(20rem, 45vw);
1726
1995
  flex: 1 1 12rem;
1727
1996
  }
1728
- .smrt-admin-shell__account :global(.smrt-workspace-account-menu .dropdown__menu) {
1997
+ .smrt-admin-shell__account :global(.smrt-workspace-account-menu__popover) {
1729
1998
  top: 100%; bottom: auto; right: 0; left: auto;
1730
1999
  inline-size: max-content;
1731
2000
  max-inline-size: calc(100vw - 2 * var(--smrt-spacing-4));
1732
2001
  }
1733
2002
 
2003
+ .smrt-admin-shell__chrome-button {
2004
+ flex: 0 0 auto;
2005
+ display: inline-grid;
2006
+ place-items: center;
2007
+ box-sizing: border-box;
2008
+ inline-size: 2.5rem;
2009
+ block-size: 2.5rem;
2010
+ padding: 0;
2011
+ border: 0;
2012
+ border-radius: var(--smrt-radius-md);
2013
+ background: transparent;
2014
+ color: var(--smrt-color-on-surface-variant);
2015
+ cursor: pointer;
2016
+ }
2017
+ .smrt-admin-shell__chrome-button:hover {
2018
+ background: var(--smrt-color-surface-container-high);
2019
+ color: var(--smrt-color-on-surface);
2020
+ }
2021
+ .smrt-admin-shell__chrome-button:focus-visible {
2022
+ outline: 0.125rem solid var(--smrt-color-primary);
2023
+ outline-offset: 0.125rem;
2024
+ }
2025
+
2026
+ .smrt-admin-shell__drawer-head {
2027
+ display: flex;
2028
+ align-items: center;
2029
+ justify-content: space-between;
2030
+ gap: var(--smrt-spacing-2);
2031
+ min-inline-size: 0;
2032
+ }
2033
+ .smrt-admin-shell__drawer-title {
2034
+ min-inline-size: 0;
2035
+ overflow: hidden;
2036
+ text-overflow: ellipsis;
2037
+ }
2038
+ .smrt-admin-shell__rail-close { justify-self: center; }
2039
+
1734
2040
  .smrt-admin-shell__tenant-opener { display: none; flex-shrink: 0; }
1735
2041
  .smrt-admin-shell__tenant-opener.restore-hidden { display: block; }
1736
2042
  .smrt-admin-shell__edge--left[data-state='collapsed'] .smrt-admin-shell__rail { padding-inline: var(--smrt-spacing-2); }
@@ -1748,7 +2054,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1748
2054
  .smrt-admin-shell__tenant-stack[hidden],
1749
2055
  .smrt-admin-shell__panel[hidden],
1750
2056
  .smrt-admin-shell__drawer[hidden] {
1751
- display: none !important;
2057
+ display: none;
1752
2058
  }
1753
2059
 
1754
2060
  .smrt-admin-shell__tenant-stack {
@@ -1873,7 +2179,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1873
2179
 
1874
2180
  .smrt-admin-shell__tenant-rail-stack {
1875
2181
  display: grid;
1876
- grid-template-rows: minmax(0, 1fr) auto;
2182
+ grid-template-rows: minmax(0, 1fr) auto auto;
1877
2183
  flex: 1;
1878
2184
  min-width: 0;
1879
2185
  min-height: 0;
@@ -1888,7 +2194,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1888
2194
  .smrt-admin-shell__tenant-rail-footer {
1889
2195
  position: relative;
1890
2196
  z-index: 1;
1891
- grid-row: 2;
2197
+ grid-row: 3;
1892
2198
  min-width: 0;
1893
2199
  padding-block-start: var(--smrt-spacing-2);
1894
2200
  border-block-start: 1px solid var(--smrt-color-outline-variant);
@@ -1916,6 +2222,30 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1916
2222
  padding-block-start: var(--smrt-shell-floating-reserve-block);
1917
2223
  }
1918
2224
 
2225
+ /* The left chevron closes the navigation region: after the navigation and
2226
+ above the sidebar footer slot (the account menu), never below it.
2227
+ `margin-block-start: auto` pins it to the end of the nav region even when
2228
+ nothing above it grows. Expanded it hugs the inner edge and points left;
2229
+ collapsed it is centred in the narrow rail and points right. */
2230
+ .smrt-admin-shell__edge--left :global(.smrt-admin-shell__edge-toggle--left) {
2231
+ flex: 0 0 auto;
2232
+ margin-block-start: auto;
2233
+ align-self: flex-end;
2234
+ inline-size: max(2rem, var(--smrt-control-target-min, 0px));
2235
+ block-size: max(2rem, var(--smrt-control-target-min, 0px));
2236
+ padding: 0;
2237
+ color: var(--smrt-color-on-surface-variant);
2238
+ }
2239
+ .smrt-admin-shell__edge--left[data-state='collapsed'] :global(.smrt-admin-shell__edge-toggle--left) {
2240
+ align-self: center;
2241
+ justify-self: center;
2242
+ }
2243
+ .smrt-admin-shell__tenant-rail-stack > :global(.smrt-admin-shell__edge-toggle--left) {
2244
+ grid-row: 2;
2245
+ }
2246
+ .smrt-admin-shell__edge--left[data-state='expanded'] :global(.smrt-admin-shell__edge-toggle-chevron) {
2247
+ transform: scaleX(-1);
2248
+ }
1919
2249
  .smrt-admin-shell__edge-toggle-kbd {
1920
2250
  padding: 0 var(--smrt-spacing-1);
1921
2251
  border-radius: var(--smrt-radius-small);
@@ -1924,6 +2254,19 @@ function buildLayoutStyle(shell: ModuleShellState): string {
1924
2254
  font-size: var(--smrt-typography-body-small-size);
1925
2255
  }
1926
2256
 
2257
+ :global(.smrt-admin-shell__edge-toggle--left.button.ghost) {
2258
+ flex: 0 0 auto;
2259
+ inline-size: max(2.75rem, var(--smrt-control-target-min, 0px));
2260
+ block-size: max(2.75rem, var(--smrt-control-target-min, 0px));
2261
+ padding: 0;
2262
+ color: var(--smrt-color-on-surface-variant);
2263
+ }
2264
+
2265
+ :global(.smrt-admin-shell__edge-toggle--left.button.ghost:hover) {
2266
+ background: var(--smrt-color-surface-container-high);
2267
+ color: var(--smrt-color-on-surface);
2268
+ }
2269
+
1927
2270
  .smrt-admin-shell__drawer {
1928
2271
  position: absolute;
1929
2272
  z-index: 40;
@@ -2083,6 +2426,8 @@ function buildLayoutStyle(shell: ModuleShellState): string {
2083
2426
  var(--smrt-easing-standard, ease);
2084
2427
  }
2085
2428
 
2429
+ .smrt-admin-shell__phone-default-end { margin-inline-start: auto; }
2430
+
2086
2431
  .smrt-admin-shell__phone-default-top {
2087
2432
  display: flex;
2088
2433
  align-items: center;
@@ -2140,7 +2485,7 @@ function buildLayoutStyle(shell: ModuleShellState): string {
2140
2485
  z-index: 25;
2141
2486
  box-shadow: var(
2142
2487
  --smrt-elevation-3,
2143
- 0 0.5rem 1.5rem color-mix(in srgb, var(--smrt-color-shadow, #000) 24%, transparent)
2488
+ 0 0.5rem 1.5rem color-mix(in srgb, var(--smrt-color-shadow) 24%, transparent)
2144
2489
  );
2145
2490
  outline: none;
2146
2491
  }
@@ -2351,6 +2696,13 @@ function buildLayoutStyle(shell: ModuleShellState): string {
2351
2696
  }
2352
2697
 
2353
2698
  .smrt-admin-shell__tenant-opener { display: block; }
2699
+
2700
+ /* Touch rows: every phone chrome control meets the touch target. */
2701
+ .smrt-admin-shell__chrome-button,
2702
+ .smrt-admin-shell__focus-tool {
2703
+ min-inline-size: var(--smrt-touch-target-min);
2704
+ min-block-size: var(--smrt-touch-target-min);
2705
+ }
2354
2706
  .smrt-admin-shell__edge--left:not([data-state='expanded']),
2355
2707
  .smrt-admin-shell__edge--right:not([data-state='expanded']) { visibility: hidden; }
2356
2708
  .smrt-admin-shell__edge--left,
@@ -2459,9 +2811,86 @@ function buildLayoutStyle(shell: ModuleShellState): string {
2459
2811
  }
2460
2812
  }
2461
2813
 
2814
+ /* ---- Motion --------------------------------------------------------- */
2815
+ /* `data-motion` is set after hydration and two frames, so first paint,
2816
+ SSR and a persisted layout restoring never animate. The pointer-drag
2817
+ resize stays immediate (`data-resizing`). Phone drawers keep their own
2818
+ animation handshake (onEdgeAnimationEnd / waitForPanelAnimations).
2819
+ Every rule below sits in `no-preference`, so reduced motion is off by
2820
+ construction, matching the reduced-motion block at the end. */
2821
+
2822
+ @media (prefers-reduced-motion: no-preference) {
2823
+ .smrt-admin-shell[data-motion]:not([data-viewport='phone']) {
2824
+ transition: grid-template-columns var(--smrt-duration-short4)
2825
+ var(--smrt-easing-emphasized);
2826
+ }
2827
+
2828
+ .smrt-admin-shell[data-motion][data-resizing]:not([data-viewport='phone']) {
2829
+ transition: none;
2830
+ }
2831
+
2832
+ /* One colour-scheme crossfade, flagged by the scheme observer. */
2833
+ .smrt-admin-shell[data-smrt-theme-changing]:not([data-viewport='phone']) {
2834
+ transition:
2835
+ grid-template-columns var(--smrt-duration-short4)
2836
+ var(--smrt-easing-emphasized),
2837
+ background-color var(--smrt-duration-medium1) var(--smrt-easing-standard),
2838
+ color var(--smrt-duration-medium1) var(--smrt-easing-standard);
2839
+ }
2840
+
2841
+ .smrt-admin-shell[data-smrt-theme-changing]:not([data-viewport='phone']) .smrt-admin-shell__edge,
2842
+ .smrt-admin-shell[data-smrt-theme-changing]:not([data-viewport='phone']) .smrt-admin-shell__header,
2843
+ .smrt-admin-shell[data-smrt-theme-changing]:not([data-viewport='phone']) .smrt-admin-shell__main {
2844
+ transition:
2845
+ background-color var(--smrt-duration-medium1) var(--smrt-easing-standard),
2846
+ border-color var(--smrt-duration-medium1) var(--smrt-easing-standard),
2847
+ color var(--smrt-duration-medium1) var(--smrt-easing-standard);
2848
+ }
2849
+
2850
+ /* Newly revealed sidebar / dock content fades in. */
2851
+ .smrt-admin-shell[data-motion]:not([data-viewport='phone']) .smrt-admin-shell__edge[data-presentation='push'] .smrt-admin-shell__tenant-stack:not([hidden]),
2852
+ .smrt-admin-shell[data-motion]:not([data-viewport='phone']) .smrt-admin-shell__edge[data-presentation='push'] .smrt-admin-shell__panel--right:not([hidden]) {
2853
+ animation: smrt-admin-shell-content-in var(--smrt-duration-short2)
2854
+ var(--smrt-easing-standard) backwards;
2855
+ }
2856
+
2857
+ /* Layout edit mode: zones, strips and region controls fade / scale in. */
2858
+ .smrt-admin-shell__slot--editing,
2859
+ .smrt-admin-shell__strip,
2860
+ .smrt-admin-shell__region-edit {
2861
+ animation: smrt-admin-shell-edit-in var(--smrt-duration-short2)
2862
+ var(--smrt-easing-standard) backwards;
2863
+ }
2864
+ }
2865
+
2866
+ @keyframes smrt-admin-shell-content-in {
2867
+ from { opacity: 0; }
2868
+ }
2869
+
2870
+ @keyframes smrt-admin-shell-edit-in {
2871
+ from { opacity: 0; transform: scale(0.96); }
2872
+ }
2873
+
2874
+ /* View transitions: the names exist only while runViewTransition runs
2875
+ (html[data-smrt-view-transition]); they must be unique per document, so
2876
+ hosts must not reuse them. */
2877
+ :global([data-smrt-view-transition]) .smrt-admin-shell:not([data-viewport='phone']) :global(.smrt-admin-shell__brand) {
2878
+ view-transition-name: smrt-shell-brand;
2879
+ }
2880
+
2881
+ :global([data-smrt-view-transition]) .smrt-admin-shell:not([data-viewport='phone']) .smrt-admin-shell__tenant-content,
2882
+ :global([data-smrt-view-transition]) .smrt-admin-shell:not([data-viewport='phone']) .smrt-admin-shell__tenant-rail-content {
2883
+ view-transition-name: smrt-shell-nav;
2884
+ }
2885
+
2886
+ :global([data-smrt-view-transition]) .smrt-admin-shell:not([data-viewport='phone']) .smrt-admin-shell__edge--right {
2887
+ view-transition-name: smrt-shell-dock;
2888
+ }
2889
+
2462
2890
  @media (prefers-reduced-motion: reduce) {
2463
2891
  .smrt-admin-shell {
2464
2892
  --smrt-admin-shell-chrome-duration: 0ms;
2893
+
2465
2894
  }
2466
2895
 
2467
2896
  .smrt-admin-shell__edge--left,
@@ -2475,4 +2904,5 @@ function buildLayoutStyle(shell: ModuleShellState): string {
2475
2904
  animation: none;
2476
2905
  }
2477
2906
  }
2907
+ }
2478
2908
  </style>