@nextclaw/ui 0.26.2 → 0.26.4

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 (218) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/.vite/manifest.json +366 -365
  3. package/dist/assets/{account-panel-ZM2qzKF4.js → account-panel-DVi_jhkG.js} +1 -1
  4. package/dist/assets/{api-Cjq-o1VN.js → api-BZvNpFOS.js} +2 -2
  5. package/dist/assets/app-presenter-provider-BcYL-VNe.js +124 -0
  6. package/dist/assets/{appearance-settings-page-B4zouKzg.js → appearance-settings-page-AZNBzLmT.js} +1 -1
  7. package/dist/assets/{book-open-AteA89HX.js → book-open-CmRdT3N1.js} +1 -1
  8. package/dist/assets/card-CdRBL1E0.js +1 -0
  9. package/dist/assets/{channels-list-page-fj8kMo-T.js → channels-list-page-eZN74FN3.js} +2 -2
  10. package/dist/assets/{circle-dashed-LSYZTzM7.js → circle-dashed-HOgt3gIJ.js} +1 -1
  11. package/dist/assets/{client-DudCRJw4.js → client-DKKbdJ6Q.js} +1 -1
  12. package/dist/assets/config-split-page-BA3V6b3Q.js +1 -0
  13. package/dist/assets/{confirm-dialog-DShT64F6.js → confirm-dialog-8vqYZ0wy.js} +1 -1
  14. package/dist/assets/context-menu-DHVVdHzN.js +1 -0
  15. package/dist/assets/{createLucideIcon-D7hvIHV-.js → createLucideIcon-DE0sB9Nu.js} +1 -1
  16. package/dist/assets/{desktop-capabilities-page-CzqzFZUY.js → desktop-capabilities-page-B9-B9k1A.js} +1 -1
  17. package/dist/assets/{desktop-update-config-CgeQ0Ffo.js → desktop-update-config-Cfr3IRUe.js} +1 -1
  18. package/dist/assets/{dialog-C7glmAQJ.js → dialog-BUfWO8hr.js} +2 -2
  19. package/dist/assets/{doc-browser-context-BlhBAOLH.js → doc-browser-context-ClLgMbxd.js} +1 -1
  20. package/dist/assets/doc-browser-w8q1j0jL.js +1 -0
  21. package/dist/assets/{download-BYgECE0n.js → download-CKqS1Ksj.js} +1 -1
  22. package/dist/assets/{es2015-CsjAAQ6X.js → es2015-BiB05Sav.js} +1 -1
  23. package/dist/assets/{extensions-qYL-NpL3.js → extensions-vPD4dGqt.js} +1 -1
  24. package/dist/assets/{eye-BBuTzJ53.js → eye-bKzylu3p.js} +1 -1
  25. package/dist/assets/{form-actions-CLGWO52v.js → form-actions-CCoxz33Q.js} +1 -1
  26. package/dist/assets/{i18n-qBGEbcW6.js → i18n-OGmF0Ws_.js} +2 -2
  27. package/dist/assets/{keyboard-shortcuts-settings-page-B-wK_6zG.js → keyboard-shortcuts-settings-page-D8rosOVh.js} +1 -1
  28. package/dist/assets/loader-circle-Clq1Gtb1.js +1 -0
  29. package/dist/assets/{login-page-Cd84lpVV.js → login-page-BT3GV3Sb.js} +1 -1
  30. package/dist/assets/login-page-DKId3By8.js +1 -0
  31. package/dist/assets/main-bEWCGDHr.js +2 -0
  32. package/dist/assets/main-i18n-catalog.config-Bpu5GcwF.js +1 -0
  33. package/dist/assets/main-kqMRTwOs.css +1 -0
  34. package/dist/assets/mcp-marketplace-page-CZIP6j3L.js +1 -0
  35. package/dist/assets/{model-config-page-D0DQlSDa.js → model-config-page-D21a-BO0.js} +1 -1
  36. package/dist/assets/{navigation-link-32iM1-e6.js → navigation-link-DC-Pku0Y.js} +1 -1
  37. package/dist/assets/panel-app-BYfQu_TS.js +1 -0
  38. package/dist/assets/panel-app-i18n-catalog.config-D44qmtfk.js +1 -0
  39. package/dist/assets/panelStandalone-BRg7B1EI.css +1 -0
  40. package/dist/assets/{panelStandalone-C9EKaApN.js → panelStandalone-CX8X2hjh.js} +2 -2
  41. package/dist/assets/{privacy-settings-page-Bxxx12OO.js → privacy-settings-page-e0LFHZJm.js} +1 -1
  42. package/dist/assets/projects-Rki7Fmth.js +1 -0
  43. package/dist/assets/provider-models-DFQgF4hS.js +1 -0
  44. package/dist/assets/provider-scoped-model-input-DaONL0j_.js +1 -0
  45. package/dist/assets/providers-config-page-CEOhQlMe.js +1 -0
  46. package/dist/assets/{puzzle-CWHfFyYN.js → puzzle-D_TiFXOv.js} +1 -1
  47. package/dist/assets/{pwa-CiGUjpcd.js → pwa-C9ac_1dg.js} +1 -1
  48. package/dist/assets/{refresh-cw-y3L6PyLt.js → refresh-cw-BG8VfcdA.js} +1 -1
  49. package/dist/assets/remote-DkSx5x4R.js +1 -0
  50. package/dist/assets/{rotate-cw-CNE9qSHi.js → rotate-cw-DPEegUU2.js} +1 -1
  51. package/dist/assets/{runtime-config-page-5o98T-TD.js → runtime-config-page-DDT1dVLt.js} +1 -1
  52. package/dist/assets/{save-B0p5a0ET.js → save-CPlctBjT.js} +1 -1
  53. package/dist/assets/{search-config-page-CpGcnIzi.js → search-config-page-CDwmd6eX.js} +1 -1
  54. package/dist/assets/{secrets-config-page-C4VTiW3r.js → secrets-config-page-1OvgmwPv.js} +1 -1
  55. package/dist/assets/security-config-Pv4CM-kw.js +1 -0
  56. package/dist/assets/{select-CctTN205.js → select-Y9IEbx9x.js} +1 -1
  57. package/dist/assets/service-apps-panel-DPrLeF3N.js +1 -0
  58. package/dist/assets/{settings-2-EPflrNk6.js → settings-2-CrGx9_De.js} +1 -1
  59. package/dist/assets/{status-dot-CensgJZC.js → status-dot-Cb8ewlUr.js} +1 -1
  60. package/dist/assets/{tag-chip-Bjzb4-qW.js → tag-chip-D-6aPuuh.js} +1 -1
  61. package/dist/assets/{theme-provider-hlle7Skk.js → theme-provider-BjF1Eowc.js} +1 -1
  62. package/dist/assets/{trash-2-C5KH5Rw9.js → trash-2-DER2EJKK.js} +1 -1
  63. package/dist/assets/{use-config-CPl_F79t.js → use-config-BM5jC65L.js} +1 -1
  64. package/dist/assets/{use-desktop-capabilities-CEg9-DrQ.js → use-desktop-capabilities-ezKJwKqg.js} +1 -1
  65. package/dist/assets/{use-viewport-layout-CPZUmTPi.js → use-viewport-layout-vmbQUNT6.js} +1 -1
  66. package/dist/assets/x-DSz9cGhy.js +1 -0
  67. package/dist/index.html +34 -34
  68. package/dist/panel-standalone.html +11 -11
  69. package/package.json +3 -3
  70. package/src/app/components/layout/__tests__/sidebar.layout.test.tsx +2 -2
  71. package/src/app/components/layout/page-layout.tsx +5 -2
  72. package/src/app/components/layout/sidebar-rail.styles.ts +3 -3
  73. package/src/app/configs/__tests__/app-navigation.config.test.ts +10 -0
  74. package/src/app/configs/app-navigation.config.ts +6 -2
  75. package/src/app/styles/charcoal-theme.css +0 -3
  76. package/src/app/styles/design-system.css +18 -6
  77. package/src/app/styles/island-theme.css +2 -15
  78. package/src/features/agents/components/agent-details-dialog.tsx +1 -1
  79. package/src/features/agents/components/agents-page.tsx +5 -3
  80. package/src/features/app-data/components/app-data-removal-choice.tsx +1 -1
  81. package/src/features/apps/components/app-package-card.tsx +7 -6
  82. package/src/features/channels/pages/channels-list-page.tsx +1 -1
  83. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +1 -1
  84. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +28 -16
  85. package/src/features/chat/components/conversation/chat-conversation-content.tsx +8 -2
  86. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +3 -3
  87. package/src/features/chat/components/conversation/chat-conversation-header.tsx +4 -4
  88. package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +6 -4
  89. package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +22 -1
  90. package/src/features/chat/components/layout/chat-page-shell.tsx +1 -1
  91. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +10 -14
  92. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +50 -12
  93. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +4 -4
  94. package/src/features/chat/components/layout/chat-sidebar.tsx +2 -2
  95. package/src/features/chat/features/conversation/components/session-queued-input-rows.tsx +1 -1
  96. package/src/features/chat/features/message/components/chat-message-list.container.tsx +12 -1
  97. package/src/features/chat/features/message/components/chat-tool-call-stress-harness.tsx +32 -2
  98. package/src/features/chat/features/message/hooks/__tests__/use-chat-message-virtualizer.test.tsx +7 -5
  99. package/src/features/chat/features/message/hooks/use-chat-message-virtualizer.ts +7 -3
  100. package/src/features/chat/features/session/components/__tests__/chat-sidebar-project-groups.test.tsx +1 -1
  101. package/src/features/chat/features/session/components/chat-mobile-session-row.tsx +2 -2
  102. package/src/features/chat/features/session/components/chat-sidebar-context-card.tsx +1 -1
  103. package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +1 -1
  104. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +7 -5
  105. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +45 -10
  106. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +1 -1
  107. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +3 -5
  108. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +4 -3
  109. package/src/features/chat/features/session/components/session-header/chat-session-more-actions-menu.tsx +3 -0
  110. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +6 -6
  111. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +6 -5
  112. package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +1 -1
  113. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +0 -1
  114. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +1 -1
  115. package/src/features/chat/features/welcome/components/chat-welcome-prompt-suggestions.tsx +1 -1
  116. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
  117. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumb-browser.tsx +2 -2
  118. package/src/features/chat/features/workspace/components/chat-session-workspace-file-breadcrumbs.tsx +2 -2
  119. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +1 -1
  120. package/src/features/chat/features/workspace/components/child-sessions/chat-session-child-sessions.tsx +2 -2
  121. package/src/features/chat/features/workspace/components/file-content-preview/workspace-spreadsheet-preview.tsx +2 -2
  122. package/src/features/chat/features/workspace/components/overview/chat-session-continuous-attention.tsx +3 -3
  123. package/src/features/chat/features/workspace/components/overview/chat-session-token-usage.tsx +1 -1
  124. package/src/features/chat/features/workspace/components/project-files/workspace-directory-tree.tsx +4 -3
  125. package/src/features/chat/features/workspace/components/project-files/workspace-new-folder-tree-item.tsx +2 -1
  126. package/src/features/chat/features/workspace/components/project-files/workspace-project-files-toolbar.tsx +3 -1
  127. package/src/features/chat/features/workspace/components/session-cron-job-content.tsx +3 -3
  128. package/src/features/chat/features/workspace/components/workspace-file-content-preview.tsx +2 -2
  129. package/src/features/chat/features/workspace/components/workspace-markdown-outline.tsx +1 -1
  130. package/src/features/cron/components/cron-config.tsx +3 -2
  131. package/src/features/cron/components/cron-job-detail-dialog.tsx +1 -1
  132. package/src/features/cron/components/cron-job-row.tsx +7 -7
  133. package/src/features/cron/components/cron-task-discovery.tsx +1 -1
  134. package/src/features/inbox/components/inbox-delivery-content.tsx +1 -1
  135. package/src/features/inbox/pages/inbox-page.tsx +25 -29
  136. package/src/features/marketplace/components/curated-shelves/marketplace-curated-shelves.tsx +2 -2
  137. package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +2 -2
  138. package/src/features/marketplace/components/marketplace-list-card.tsx +4 -4
  139. package/src/features/marketplace/components/marketplace-page-parts.tsx +4 -3
  140. package/src/features/marketplace/components/marketplace-page.tsx +4 -4
  141. package/src/features/marketplace/components/mcp/mcp-marketplace-card.tsx +2 -2
  142. package/src/features/marketplace/components/mcp/mcp-marketplace-page.tsx +3 -3
  143. package/src/features/panel-apps/components/panel-app-list-item.tsx +3 -3
  144. package/src/features/panel-apps/components/panel-app-open-standalone-menu-item.tsx +3 -1
  145. package/src/features/projects/components/project-overview.tsx +1 -1
  146. package/src/features/projects/pages/project-home-page.tsx +11 -2
  147. package/src/features/projects/pages/projects-page.test.tsx +4 -2
  148. package/src/features/pwa/components/pwa-install-entry.tsx +1 -1
  149. package/src/features/right-panel-resources/components/__tests__/page-resource-sidebar-nav.test.tsx +9 -1
  150. package/src/features/right-panel-resources/components/page-resource-sidebar-nav.tsx +1 -1
  151. package/src/features/right-panel-resources/components/right-panel-resource-home-page.tsx +1 -1
  152. package/src/features/service-apps/components/service-action-row.tsx +3 -3
  153. package/src/features/service-apps/components/service-app-list-item.tsx +4 -4
  154. package/src/features/service-apps/components/service-apps-panel.tsx +1 -1
  155. package/src/features/settings/components/config/provider-model-list.tsx +5 -5
  156. package/src/features/settings/components/config/provider-model-suggestions-panel.tsx +4 -4
  157. package/src/features/settings/components/config/provider-pill-selector.tsx +1 -1
  158. package/src/features/settings/pages/appearance-settings-page.tsx +2 -2
  159. package/src/features/settings/pages/providers-config-page.tsx +6 -6
  160. package/src/features/side-dock/components/side-dock.tsx +4 -4
  161. package/src/index.css +4 -0
  162. package/src/platforms/desktop/components/desktop-window-chrome.tsx +1 -1
  163. package/src/platforms/mobile/components/mobile-app-shell.tsx +1 -1
  164. package/src/platforms/mobile/components/mobile-bottom-nav.tsx +10 -9
  165. package/src/platforms/mobile/components/mobile-settings-shell.tsx +24 -23
  166. package/src/platforms/mobile/components/mobile-topbar.tsx +10 -11
  167. package/src/shared/components/common/brand-header.tsx +1 -1
  168. package/src/shared/components/common/searchable-model-input.tsx +2 -2
  169. package/src/shared/components/config-split-page.tsx +3 -3
  170. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +34 -2
  171. package/src/shared/components/doc-browser/doc-browser-tab-strip.tsx +38 -7
  172. package/src/shared/components/doc-browser/doc-browser.tsx +5 -1
  173. package/src/shared/components/path-picker/server-path-picker-locations.tsx +1 -1
  174. package/src/shared/components/settings/settings-page.tsx +1 -1
  175. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +5 -5
  176. package/src/shared/components/ui/actions/action-feedback.ts +8 -0
  177. package/src/shared/components/ui/actions/icon-action-button.tsx +13 -12
  178. package/src/shared/components/ui/button.tsx +3 -3
  179. package/src/shared/components/ui/card.tsx +1 -1
  180. package/src/shared/components/ui/context-menu/context-menu-items.tsx +6 -5
  181. package/src/shared/components/ui/dialog.tsx +4 -3
  182. package/src/shared/components/ui/input.tsx +7 -2
  183. package/src/shared/components/ui/overlays/sheet.tsx +1 -1
  184. package/src/shared/components/ui/popover.tsx +1 -1
  185. package/src/shared/components/ui/select.tsx +3 -2
  186. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +8 -6
  187. package/src/shared/components/ui/tabs.tsx +3 -3
  188. package/src/shared/components/ui/textarea.tsx +1 -0
  189. package/src/shared/components/workbench/workbench-surface-toolbar.tsx +3 -4
  190. package/src/shared/components/workbench/workbench-surface.tsx +2 -2
  191. package/src/shared/lib/i18n/locales/en-US/agents.json +2 -0
  192. package/src/shared/lib/i18n/locales/en-US/core.json +1 -0
  193. package/src/shared/lib/i18n/locales/en-US/desktop-update.json +1 -1
  194. package/src/shared/lib/i18n/locales/zh-CN/agents.json +2 -0
  195. package/src/shared/lib/i18n/locales/zh-CN/core.json +1 -0
  196. package/src/shared/lib/i18n/locales/zh-CN/desktop-update.json +1 -1
  197. package/dist/assets/app-presenter-provider-C02YPoMr.js +0 -124
  198. package/dist/assets/card-BLXvBstA.js +0 -1
  199. package/dist/assets/config-split-page-G7Kt2BnV.js +0 -1
  200. package/dist/assets/context-menu-rd7b_kVD.js +0 -1
  201. package/dist/assets/doc-browser-dBiR9Qi0.js +0 -1
  202. package/dist/assets/loader-circle-BA7EMkih.js +0 -1
  203. package/dist/assets/login-page-VdMyNkFf.js +0 -1
  204. package/dist/assets/main-CR0SrTBf.js +0 -2
  205. package/dist/assets/main-DXH6EdXI.css +0 -1
  206. package/dist/assets/main-i18n-catalog.config-Btp_OowF.js +0 -1
  207. package/dist/assets/mcp-marketplace-page-jacRwiI-.js +0 -1
  208. package/dist/assets/panel-app-CC0bVSu4.js +0 -1
  209. package/dist/assets/panel-app-i18n-catalog.config-LUSBp5Ce.js +0 -1
  210. package/dist/assets/panelStandalone-Y91OJkgV.css +0 -1
  211. package/dist/assets/projects-D56yAPNT.js +0 -1
  212. package/dist/assets/provider-models-C3frdXeW.js +0 -1
  213. package/dist/assets/provider-scoped-model-input-CYetrPYZ.js +0 -1
  214. package/dist/assets/providers-config-page-DESB1h8g.js +0 -1
  215. package/dist/assets/remote-DiGFV8nP.js +0 -1
  216. package/dist/assets/security-config-C5-RQVpS.js +0 -1
  217. package/dist/assets/service-apps-panel-CKE5HKNY.js +0 -1
  218. package/dist/assets/x--xxUvHKW.js +0 -1
@@ -69,7 +69,7 @@ export function ProviderModelSuggestionsPanel(props: ProviderModelSuggestionsPan
69
69
  <button
70
70
  type='button'
71
71
  aria-expanded={expanded}
72
- className='flex min-w-0 flex-1 items-center gap-1.5 rounded-lg px-2 py-1.5 text-left text-xs text-muted-foreground transition-colors hover:bg-muted/70 hover:text-foreground'
72
+ className='flex min-w-0 flex-1 items-center gap-1.5 rounded-lg px-2 py-1.5 text-left text-xs text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-foreground'
73
73
  onClick={() => onExpandedChange(!expanded)}
74
74
  >
75
75
  <Sparkles className='h-3.5 w-3.5 shrink-0 text-primary' />
@@ -81,7 +81,7 @@ export function ProviderModelSuggestionsPanel(props: ProviderModelSuggestionsPan
81
81
  {models.length <= BULK_ADD_LIMIT ? (
82
82
  <button
83
83
  type='button'
84
- className='shrink-0 rounded-lg px-2 py-1.5 text-xs font-medium text-primary transition-colors hover:bg-primary/10'
84
+ className='shrink-0 rounded-lg px-2 py-1.5 text-xs font-medium text-primary transition-colors hover:bg-[var(--interaction-hover)]'
85
85
  onClick={() => addModels(models)}
86
86
  >
87
87
  {t('providerModelsSuggestionsAddAll')}
@@ -105,7 +105,7 @@ export function ProviderModelSuggestionsPanel(props: ProviderModelSuggestionsPan
105
105
  {visibleModels.map((modelName) => (
106
106
  <label
107
107
  key={modelName}
108
- className='grid min-w-0 cursor-pointer grid-cols-[auto_minmax(0,1fr)] items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-muted/70'
108
+ className='grid min-w-0 cursor-pointer grid-cols-[auto_minmax(0,1fr)] items-center gap-2 rounded-lg px-2 py-1.5 hover:bg-[var(--interaction-hover)]'
109
109
  >
110
110
  <input
111
111
  type='checkbox'
@@ -137,7 +137,7 @@ export function ProviderModelSuggestionsPanel(props: ProviderModelSuggestionsPan
137
137
  <button
138
138
  type='button'
139
139
  disabled={selectedModels.length === 0}
140
- className='shrink-0 rounded-md bg-primary/10 px-2 py-1 text-[11px] font-medium text-primary transition-colors hover:bg-primary/15 disabled:cursor-not-allowed disabled:opacity-45'
140
+ className='shrink-0 rounded-md bg-primary/10 px-2 py-1 text-[11px] font-medium text-primary transition-colors hover:bg-[var(--interaction-hover)] disabled:cursor-not-allowed disabled:opacity-45'
141
141
  onClick={() => addModels(selectedModels)}
142
142
  >
143
143
  {t('providerModelsSuggestionsAddSelected')}
@@ -16,7 +16,7 @@ export function ProviderPillSelector(props: ProviderPillSelectorProps) {
16
16
  type="button"
17
17
  onClick={() => onChange(option.value)}
18
18
  aria-pressed={selected}
19
- className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${selected ? 'border-foreground/15 bg-foreground text-background' : 'border-border/55 bg-muted/50 text-muted-foreground hover:border-border hover:bg-muted hover:text-foreground'}`}
19
+ className={`rounded-full border px-3 py-1.5 text-xs font-medium transition-colors ${selected ? 'border-[var(--interaction-selection-border)] bg-[var(--interaction-selection)] text-foreground' : 'border-border/55 bg-muted/50 text-muted-foreground hover:border-border hover:bg-[var(--interaction-hover)] hover:text-foreground'}`}
20
20
  >
21
21
  {option.label}
22
22
  </button>
@@ -100,8 +100,8 @@ export function AppearanceSettingsPage() {
100
100
  className={cn(
101
101
  'flex min-w-0 items-start gap-3 rounded-lg px-3 py-2.5 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',
102
102
  selected
103
- ? 'bg-card text-foreground shadow-sm'
104
- : 'text-muted-foreground hover:bg-card/60 hover:text-foreground'
103
+ ? 'bg-[var(--interaction-selection)] text-foreground'
104
+ : 'text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-foreground'
105
105
  )}
106
106
  >
107
107
  <span
@@ -156,7 +156,7 @@ export function ProvidersConfigPage() {
156
156
  <div className='grid max-h-[24rem] grid-cols-2 gap-2 overflow-y-auto sm:grid-cols-3'>
157
157
  <button
158
158
  type='button'
159
- className='flex min-h-20 w-full flex-col items-start gap-2 rounded-lg border border-dashed border-border/70 bg-muted/20 px-3 py-2 text-left transition-colors hover:border-border hover:bg-muted/45'
159
+ className='flex min-h-20 w-full flex-col items-start gap-2 rounded-lg border border-dashed border-border/70 bg-muted/20 px-3 py-2 text-left transition-colors hover:border-border hover:bg-[var(--interaction-hover)]'
160
160
  onClick={async () => {
161
161
  try {
162
162
  const result = await createProvider.mutateAsync({
@@ -183,7 +183,7 @@ export function ProvidersConfigPage() {
183
183
  <button
184
184
  key={template.providerType}
185
185
  type='button'
186
- className='flex min-h-20 w-full flex-col items-start gap-2 rounded-lg border border-border/55 bg-muted/15 px-3 py-2 text-left transition-colors hover:border-border/80 hover:bg-muted/40'
186
+ className='flex min-h-20 w-full flex-col items-start gap-2 rounded-lg border border-border/55 bg-muted/15 px-3 py-2 text-left transition-colors hover:border-border/80 hover:bg-[var(--interaction-hover)]'
187
187
  onClick={async () => {
188
188
  try {
189
189
  const result = await createProvider.mutateAsync({
@@ -254,8 +254,8 @@ export function ProvidersConfigPage() {
254
254
  className={cn(
255
255
  'group w-full rounded-xl p-2.5 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',
256
256
  resolvedSelectedProvider === provider.providerId
257
- ? 'bg-background/95 text-foreground shadow-sm'
258
- : 'bg-transparent text-muted-foreground hover:bg-background/65 hover:text-foreground'
257
+ ? 'bg-[var(--interaction-selection)] text-foreground'
258
+ : 'bg-transparent text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-foreground'
259
259
  )}
260
260
  >
261
261
  <div className='relative min-h-10'>
@@ -300,7 +300,7 @@ export function ProvidersConfigPage() {
300
300
  <PopoverTrigger asChild>
301
301
  <button
302
302
  type='button'
303
- className='inline-flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground/70 hover:bg-muted hover:text-foreground'
303
+ className='inline-flex h-7 w-7 items-center justify-center rounded-lg text-muted-foreground/70 hover:bg-[var(--interaction-hover)] hover:text-foreground'
304
304
  onClick={(event) => event.stopPropagation()}
305
305
  title={t('more')}
306
306
  >
@@ -310,7 +310,7 @@ export function ProvidersConfigPage() {
310
310
  <PopoverContent className='w-40 p-1' align='end' onClick={(event) => event.stopPropagation()}>
311
311
  <button
312
312
  type='button'
313
- className='flex w-full items-center gap-2 rounded-md px-2 py-2 text-sm text-red-600 hover:bg-red-50'
313
+ className='flex w-full items-center gap-2 rounded-md px-2 py-2 text-sm text-red-600 hover:bg-destructive/10'
314
314
  onClick={() => setProviderToDelete(provider.providerId)}
315
315
  >
316
316
  <Trash2 className='h-4 w-4' />
@@ -60,9 +60,9 @@ function SideDockButton({
60
60
  onClick={() => onOpen(item)}
61
61
  className={cn(
62
62
  'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors',
63
- 'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
63
+ 'hover:bg-[var(--interaction-hover)] hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
64
64
  active
65
- ? 'bg-primary/10 text-primary'
65
+ ? 'bg-[var(--interaction-selection)] text-foreground hover:bg-[var(--interaction-selection)]'
66
66
  : 'bg-transparent',
67
67
  )}
68
68
  >
@@ -78,7 +78,7 @@ function SideDockButton({
78
78
  event.stopPropagation();
79
79
  onUnpin(item);
80
80
  }}
81
- className="absolute -right-1 -top-1 hidden h-4 w-4 items-center justify-center rounded-full bg-background text-muted-foreground shadow-sm ring-1 ring-border/70 transition-colors hover:bg-muted hover:text-foreground group-hover:flex focus-visible:flex focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
81
+ className="absolute -right-1 -top-1 hidden h-4 w-4 items-center justify-center rounded-full bg-background text-muted-foreground shadow-sm ring-1 ring-border/70 transition-colors hover:bg-[var(--interaction-hover)] hover:text-foreground group-hover:flex focus-visible:flex focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
82
82
  >
83
83
  <X className="h-3 w-3" aria-hidden="true" />
84
84
  </button>
@@ -109,7 +109,7 @@ function SideDockActionButton({
109
109
  onClick={onClick}
110
110
  className={cn(
111
111
  'flex h-10 w-10 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors',
112
- 'hover:bg-primary/10 hover:text-primary focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
112
+ 'hover:bg-[var(--interaction-hover)] hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
113
113
  )}
114
114
  >
115
115
  <Icon className="h-5 w-5" aria-hidden="true" />
package/src/index.css CHANGED
@@ -140,6 +140,10 @@
140
140
  box-shadow: var(--shadow-card);
141
141
  }
142
142
 
143
+ .shadow-floating {
144
+ box-shadow: var(--shadow-popover);
145
+ }
146
+
143
147
  .shadow-card-hover {
144
148
  box-shadow: var(--shadow-card-hover);
145
149
  }
@@ -90,7 +90,7 @@ function DesktopWindowControlButton({
90
90
  className={
91
91
  control.variant === "danger"
92
92
  ? "desktop-window-no-drag flex h-10 w-[46px] items-center justify-center text-gray-700 transition-colors hover:bg-red-500 hover:text-white"
93
- : "desktop-window-no-drag flex h-10 w-[46px] items-center justify-center text-gray-700 transition-colors hover:bg-black/10"
93
+ : "desktop-window-no-drag flex h-10 w-[46px] items-center justify-center text-gray-700 transition-colors hover:bg-[var(--interaction-hover)]"
94
94
  }
95
95
  onClick={() => {
96
96
  void window.nextclawDesktop?.controlWindow?.(control.action);
@@ -42,7 +42,7 @@ export function MobileAppShell({
42
42
  {isMainRoute ? (
43
43
  <div className="h-full min-h-0 overflow-hidden">{children}</div>
44
44
  ) : (
45
- <main className="h-full overflow-auto px-4 py-4 custom-scrollbar">
45
+ <main className="h-full overflow-auto overscroll-contain px-4 py-4 custom-scrollbar">
46
46
  <div className="mx-auto max-w-3xl animate-fade-in">{children}</div>
47
47
  </main>
48
48
  )}
@@ -19,13 +19,13 @@ export function MobileBottomNav() {
19
19
  return (
20
20
  <nav
21
21
  data-testid="mobile-bottom-nav"
22
- aria-label={t("settings")}
22
+ aria-label={t("mobileNavigation")}
23
23
  className="shrink-0 border-t border-border/60 bg-background text-foreground"
24
24
  style={{
25
25
  paddingBottom: "calc(env(safe-area-inset-bottom, 0px) + 0.25rem)",
26
26
  }}
27
27
  >
28
- <ul className="grid grid-cols-6 gap-1 px-2 pt-1">
28
+ <ul className="grid grid-cols-6 gap-0.5 px-1 pt-1">
29
29
  {getMobileBottomNavItems(t).map((item) => {
30
30
  const active = item.target === "/settings"
31
31
  ? isSettingsRoute(pathname)
@@ -34,19 +34,20 @@ export function MobileBottomNav() {
34
34
  <li key={item.target}>
35
35
  <Link
36
36
  to={item.target}
37
+ aria-label={item.label}
37
38
  aria-current={active ? "page" : undefined}
38
39
  data-testid={active ? "mobile-nav-active-indicator" : undefined}
39
40
  className={cn(
40
- "group flex min-h-[2.875rem] flex-col items-center justify-center gap-0.5 rounded-xl px-2 py-1 text-[10px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
41
+ "group flex min-h-12 flex-col items-center justify-center gap-1 rounded-xl px-0.5 py-1 text-[11px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
41
42
  active
42
- ? "bg-muted text-foreground"
43
- : "text-muted-foreground hover:bg-muted hover:text-foreground",
43
+ ? "bg-[var(--interaction-selection)] text-foreground"
44
+ : "text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-foreground",
44
45
  )}
45
46
  >
46
47
  <span className="relative">
47
48
  <item.icon
48
49
  className={cn(
49
- "h-3.5 w-3.5",
50
+ "h-5 w-5",
50
51
  active ? "text-foreground" : "text-muted-foreground",
51
52
  )}
52
53
  />
@@ -54,7 +55,7 @@ export function MobileBottomNav() {
54
55
  <span className="absolute -right-1 -top-1 h-1.5 w-1.5 rounded-full bg-primary ring-1 ring-background" />
55
56
  ) : null}
56
57
  </span>
57
- <span className="max-w-full truncate">{item.label}</span>
58
+ <span className="max-w-full truncate">{item.target === "/agents" ? t("mobileAgents") : item.label}</span>
58
59
  </Link>
59
60
  </li>
60
61
  );
@@ -63,9 +64,9 @@ export function MobileBottomNav() {
63
64
  <button
64
65
  type="button"
65
66
  onClick={() => openApps(docBrowser)}
66
- className="group flex min-h-[2.875rem] w-full flex-col items-center justify-center gap-0.5 rounded-xl px-2 py-1 text-[10px] font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
67
+ className="group flex min-h-12 w-full flex-col items-center justify-center gap-1 rounded-xl px-0.5 py-1 text-[11px] font-medium text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border"
67
68
  >
68
- <Boxes className="h-3.5 w-3.5 text-muted-foreground" />
69
+ <Boxes className="h-5 w-5 text-muted-foreground" />
69
70
  <span className="max-w-full truncate">{t("appsTitle")}</span>
70
71
  </button>
71
72
  </li>
@@ -1,8 +1,10 @@
1
1
  import { NavLink } from "react-router-dom";
2
+ import { ChevronRight } from "lucide-react";
2
3
  import { getSettingsNavSections } from "@/app/configs/app-navigation.config";
3
4
  import { t } from "@/shared/lib/i18n";
4
5
  import { cn } from "@/shared/lib/utils";
5
6
  import { useDesktopCapabilityAvailability } from "@/features/desktop-capabilities";
7
+ import { SettingsGroup } from "@/shared/components/settings/setting-row";
6
8
 
7
9
  export function MobileSettingsShell() {
8
10
  const desktopCapabilityAvailable = useDesktopCapabilityAvailability();
@@ -19,29 +21,28 @@ export function MobileSettingsShell() {
19
21
  {settingsNavSections.map((section) => (
20
22
  <section key={section.label} className="space-y-2">
21
23
  <h2 className="px-1 pt-2 text-xs font-medium text-muted-foreground">{section.label}</h2>
22
- {section.items.map((item) => (
23
- <NavLink
24
- key={item.target}
25
- to={item.target}
26
- className={({ isActive }) =>
27
- cn(
28
- "flex items-center gap-3 rounded-2xl border border-gray-200/80 bg-white px-4 py-4 shadow-card transition-colors",
29
- isActive
30
- ? "border-gray-300 bg-gray-50"
31
- : "hover:border-gray-300 hover:bg-gray-50/70",
32
- )
33
- }
34
- >
35
- <div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-2xl bg-gray-100 text-gray-600">
36
- <item.icon className="h-4 w-4" />
37
- </div>
38
- <div className="min-w-0">
39
- <p className="truncate text-sm font-semibold text-gray-900">
40
- {item.label}
41
- </p>
42
- </div>
43
- </NavLink>
44
- ))}
24
+ <SettingsGroup>
25
+ {section.items.map((item) => (
26
+ <NavLink
27
+ key={item.target}
28
+ to={item.target}
29
+ className={({ isActive }) => cn(
30
+ "flex min-h-14 items-center gap-3 px-4 py-3 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-border",
31
+ isActive
32
+ ? "bg-[var(--interaction-selection)]"
33
+ : "hover:bg-[var(--interaction-hover)] active:bg-[var(--interaction-hover)]",
34
+ )}
35
+ >
36
+ <div className="flex h-8 w-8 shrink-0 items-center justify-center text-muted-foreground">
37
+ <item.icon className="h-5 w-5" />
38
+ </div>
39
+ <div className="min-w-0 flex-1">
40
+ <p className="text-[15px] font-medium text-foreground">{item.label}</p>
41
+ </div>
42
+ <ChevronRight className="h-4 w-4 shrink-0 text-muted-foreground/60" aria-hidden="true" />
43
+ </NavLink>
44
+ ))}
45
+ </SettingsGroup>
45
46
  </section>
46
47
  ))}
47
48
  </div>
@@ -2,6 +2,7 @@ import { ArrowLeft } from "lucide-react";
2
2
  import { useNavigate, useLocation } from "react-router-dom";
3
3
  import { resolveMobileRouteMeta } from "@/app/configs/app-navigation.config";
4
4
  import { t } from "@/shared/lib/i18n";
5
+ import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
5
6
 
6
7
  export function MobileTopbar({ leadingInset }: { leadingInset?: string }) {
7
8
  const navigate = useNavigate();
@@ -11,31 +12,29 @@ export function MobileTopbar({ leadingInset }: { leadingInset?: string }) {
11
12
  return (
12
13
  <header
13
14
  data-testid="mobile-topbar"
14
- className="shrink-0 border-b border-border/60 bg-secondary text-foreground"
15
+ className="shrink-0 border-b border-border/60 bg-background text-foreground"
15
16
  style={{
16
- paddingTop: "calc(env(safe-area-inset-top, 0px) + 0.25rem)",
17
+ paddingTop: "env(safe-area-inset-top, 0px)",
17
18
  }}
18
19
  >
19
20
  <div
20
- className="flex min-h-[2.75rem] items-center gap-2 py-1.5 pl-3 pr-3"
21
+ className="flex h-[var(--mobile-header-height)] items-center gap-2 pl-3 pr-3"
21
22
  style={{ paddingLeft: leadingInset ? `calc(${leadingInset} + 0.75rem)` : undefined }}
22
23
  >
23
24
  {routeMeta.backTarget ? (
24
- <button
25
- type="button"
25
+ <IconActionButton
26
+ size="lg"
26
27
  onClick={() => navigate(routeMeta.backTarget as string)}
27
- className="inline-flex h-11 w-11 shrink-0 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground"
28
- aria-label={routeMeta.backLabel ?? t("backToMain")}
29
- >
30
- <ArrowLeft className="h-4 w-4" />
31
- </button>
28
+ label={routeMeta.backLabel ?? t("backToMain")}
29
+ icon={<ArrowLeft className="h-4 w-4" />}
30
+ />
32
31
  ) : null}
33
32
  <div className="min-w-0 flex-1">
34
33
  <h1 className="truncate text-center text-[17px] font-semibold text-foreground">
35
34
  {routeMeta.title}
36
35
  </h1>
37
36
  </div>
38
- {routeMeta.backTarget ? <div className="h-11 w-11 shrink-0" aria-hidden="true" /> : null}
37
+ {routeMeta.backTarget ? <div className="w-[var(--icon-control-size)] shrink-0" aria-hidden="true" /> : null}
39
38
  </div>
40
39
  </header>
41
40
  );
@@ -181,7 +181,7 @@ function RuntimeUpdateReleaseNotesHover({
181
181
  external
182
182
  icon={null}
183
183
  aria-label={label}
184
- className="whitespace-nowrap rounded-md border bg-popover px-3 py-1.5 text-left text-xs font-medium text-popover-foreground shadow-md outline-none transition-colors hover:bg-gray-100 focus-visible:ring-2 focus-visible:ring-gray-300"
184
+ className="whitespace-nowrap rounded-md border bg-popover px-3 py-1.5 text-left text-xs font-medium text-popover-foreground shadow-md outline-none transition-colors hover:bg-[var(--interaction-hover)] focus-visible:ring-2 focus-visible:ring-gray-300"
185
185
  >
186
186
  {label}
187
187
  </NavigationLink>
@@ -152,7 +152,7 @@ export function SearchableModelInput({
152
152
  onChange(value.trim());
153
153
  closeMenu();
154
154
  }}
155
- className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-gray-50"
155
+ className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-[var(--interaction-hover)]"
156
156
  >
157
157
  <Check className="h-4 w-4 text-transparent" />
158
158
  <span className="truncate text-gray-700">
@@ -170,7 +170,7 @@ export function SearchableModelInput({
170
170
  onChange(option);
171
171
  closeMenu();
172
172
  }}
173
- className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-gray-50"
173
+ className="flex w-full items-center gap-2 px-3 py-2 text-left text-sm hover:bg-[var(--interaction-hover)]"
174
174
  >
175
175
  <Check className={cn('h-4 w-4', option === value.trim() ? 'text-primary' : 'text-transparent')} />
176
176
  <span className="truncate text-gray-700">{option}</span>
@@ -47,7 +47,7 @@ export function ConfigSplitPage({
47
47
  <button
48
48
  type='button'
49
49
  onClick={onMobileBack}
50
- className='inline-flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40'
50
+ className='inline-flex items-center gap-2 rounded-lg px-2 py-1.5 text-sm font-medium text-muted-foreground transition-colors hover:bg-[var(--interaction-hover)] hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40'
51
51
  >
52
52
  <ArrowLeft className='h-4 w-4' />
53
53
  <span>{mobileListLabel ?? t('backToMain')}</span>
@@ -122,8 +122,8 @@ export function ConfigSelectionCard({
122
122
  className={cn(
123
123
  'w-full rounded-xl p-2.5 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring/40',
124
124
  active
125
- ? 'bg-background/95 text-foreground shadow-sm'
126
- : 'bg-transparent text-muted-foreground hover:bg-background/65 hover:text-foreground',
125
+ ? 'bg-[var(--interaction-selection)] text-foreground'
126
+ : 'bg-transparent text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-foreground',
127
127
  className
128
128
  )}
129
129
  {...props}
@@ -545,7 +545,8 @@ describe("DocBrowser panel app navigation", () => {
545
545
 
546
546
  expect(screen.queryByRole("button", { name: "Pin to left sidebar" })).toBeNull();
547
547
  await user.click(screen.getByRole("button", { name: "More panel app actions" }));
548
- await user.click(screen.getByRole("button", { name: "Pin to left sidebar" }));
548
+ await user.click(screen.getByRole("menuitem", { name: "Layout and position" }));
549
+ await user.click(screen.getByRole("menuitem", { name: "Pin to left sidebar" }));
549
550
  expect(panelAppHooks.mutate).toHaveBeenCalledWith({
550
551
  id: "piano",
551
552
  preferences: { mainSidebar: true },
@@ -571,7 +572,8 @@ describe("DocBrowser panel app navigation", () => {
571
572
  render(<DocBrowser customTabRenderers={PANEL_APPS_DOC_BROWSER_RENDERERS} />);
572
573
 
573
574
  await user.click(screen.getByRole("button", { name: "More panel app actions" }));
574
- await user.click(screen.getByRole("button", { name: "Pin to left sidebar" }));
575
+ await user.click(screen.getByRole("menuitem", { name: "Layout and position" }));
576
+ await user.click(screen.getByRole("menuitem", { name: "Pin to left sidebar" }));
575
577
  expect(panelAppHooks.mutate).toHaveBeenCalledWith({
576
578
  id: "piano",
577
579
  preferences: { mainSidebar: true },
@@ -651,6 +653,36 @@ describe("DocBrowser floating interactions", () => {
651
653
  installDocBrowserPointerTestEnvironment();
652
654
  });
653
655
 
656
+ it("keeps compact navigation, tab selection, resource and app actions reachable", () => {
657
+ const onBack = vi.fn();
658
+ const onSelect = vi.fn();
659
+ const onClose = vi.fn();
660
+ const onAdd = vi.fn();
661
+ const onRefresh = vi.fn();
662
+ render(<DocBrowserTabStrip compact tabs={docBrowserState.tabs} activeTabId="docs"
663
+ canGoBack canGoForward={false} onGoBack={onBack} onGoForward={vi.fn()}
664
+ onOpenNewTab={vi.fn()} onSetActiveTab={onSelect} onCloseTab={onClose}
665
+ mobileToolbar={<button onClick={onRefresh}>Refresh app</button>}
666
+ getTabMenuGroups={() => [{ key: 'chat', items: [{ key: 'add', label: 'Add to Chat', onSelect: onAdd }] }]}
667
+ />);
668
+ const open = () => fireEvent.click(screen.getByRole('button', { name: 'More tab actions' }));
669
+ expect(screen.queryByRole('button', { name: 'Back' })).toBeNull();
670
+ open();
671
+ fireEvent.click(screen.getByRole('menuitem', { name: 'Back' }));
672
+ expect(onBack).toHaveBeenCalledOnce();
673
+ open();
674
+ fireEvent.click(screen.getByRole('menuitem', { name: 'Docs' }));
675
+ expect(onSelect).toHaveBeenCalledWith('docs');
676
+ open();
677
+ fireEvent.click(screen.getByRole('menuitem', { name: 'Add to Chat' }));
678
+ expect(onAdd).toHaveBeenCalledOnce();
679
+ open();
680
+ fireEvent.click(screen.getByRole('button', { name: 'Refresh app' }));
681
+ expect(onRefresh).toHaveBeenCalledOnce();
682
+ fireEvent.click(screen.getByRole('menuitem', { name: 'Close tab' }));
683
+ expect(onClose).toHaveBeenCalledWith('docs');
684
+ });
685
+
654
686
  it("drags the shared header without stealing tab actions", () => {
655
687
  new WorkbenchSurfaceManager().place('global-resources', 'floating');
656
688
  render(<DocBrowser />);
@@ -1,4 +1,8 @@
1
1
  import { PageResourceIcon } from '@/features/right-panel-resources';
2
+ import { useState, type ReactNode } from 'react';
3
+ import { ChevronDown } from 'lucide-react';
4
+ import { Popover, PopoverContent, PopoverTrigger } from '@/shared/components/ui/popover';
5
+ import { ContextMenuItems } from '@/shared/components/ui/context-menu/context-menu';
2
6
  import {
3
7
  ArrowLeft,
4
8
  ArrowRight,
@@ -16,6 +20,8 @@ import {
16
20
  import { t } from "@/shared/lib/i18n";
17
21
 
18
22
  type DocBrowserTabStripProps = {
23
+ compact?: boolean;
24
+ mobileToolbar?: ReactNode;
19
25
  tabs: DocBrowserTab[];
20
26
  activeTabId: string;
21
27
  canGoBack: boolean;
@@ -47,7 +53,10 @@ export function DocBrowserTabStrip({
47
53
  onSetActiveTab,
48
54
  onCloseTab,
49
55
  getTabMenuGroups,
56
+ mobileToolbar,
57
+ compact = false,
50
58
  }: DocBrowserTabStripProps) {
59
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
51
60
  const backLabel = t("docBrowserBack");
52
61
  const closeTabLabel = t("workbenchCloseTab");
53
62
  const dockLabel = dockState?.isDocked
@@ -87,20 +96,42 @@ export function DocBrowserTabStrip({
87
96
  : []),
88
97
  ];
89
98
 
99
+ const current = tabs.find(tab => tab.id === activeTabId);
100
+ const mobileGroups: ContextMenuGroup[] = [
101
+ { key: 'tabs', items: compactTabs.map(tab => ({ key: tab.key, label: tab.label, icon: tab.leadingIcon, pressed: tab.active, onSelect: tab.onSelect })) },
102
+ { key: 'navigation', items: actions.map(action => ({ key: action.key, label: action.label, icon: action.icon, disabled: action.disabled, onSelect: action.onClick })) },
103
+ ...(current ? getTabMenuGroups?.(current) ?? [] : []),
104
+ ...(current ? [{ key: 'close-tab', items: [{ key: 'close-tab', label: closeTabLabel, onSelect: () => onCloseTab(current.id) }] }] : []),
105
+ ];
106
+ if (compact) return (
107
+ <div className="flex h-12 min-w-0 flex-1 items-center">
108
+ <Popover open={mobileMenuOpen} onOpenChange={setMobileMenuOpen}>
109
+ <PopoverTrigger asChild>
110
+ <button type="button" className="flex h-8 min-w-0 items-center gap-2 rounded-lg px-2 text-sm font-medium hover:bg-[var(--interaction-hover)]" aria-label={t('docBrowserTabMoreActions')}>
111
+ {current ? <DocBrowserTabIcon tab={current} /> : null}
112
+ <span className="truncate">{current?.title || t('docBrowserTabUntitled')}</span><ChevronDown className="h-4 w-4 shrink-0" />
113
+ </button>
114
+ </PopoverTrigger>
115
+ <PopoverContent align="start" className="max-h-[70dvh] w-72 overflow-y-auto p-1">
116
+ <ContextMenuItems groups={mobileGroups} onClose={() => setMobileMenuOpen(false)} />
117
+ {mobileToolbar}
118
+ </PopoverContent>
119
+ </Popover>
120
+ </div>
121
+ );
90
122
  return (
91
123
  <CompactTabStrip
92
124
  testId="doc-browser-tab-strip"
93
125
  actionsTestId="doc-browser-tab-actions"
94
126
  tabs={compactTabs}
95
127
  actions={actions}
96
- className="h-10 min-w-0 flex-1 gap-1 border-0 bg-transparent px-0 select-none"
97
- scrollClassName="doc-browser-tab-scrollbar flex h-full items-center gap-0.5"
128
+ className="flex h-10 min-w-0 flex-1 gap-1 border-0 bg-transparent px-0 select-none"
129
+ scrollClassName="doc-browser-tab-scrollbar flex h-full max-md:h-11 max-md:basis-full items-center gap-0.5"
98
130
  tabsClassName="items-center gap-0.5"
99
- actionsClassName="h-full items-center gap-0.5"
100
- actionButtonClassName="rounded-md p-1.5 text-muted-foreground hover:bg-muted/60 hover:text-foreground disabled:opacity-50"
101
- tabBaseClassName="inline-flex cursor-pointer items-center gap-0.5 h-7 px-2 rounded-md text-xs max-w-[220px] shrink-0 transition-colors"
102
- activeTabClassName="bg-muted/80 text-foreground"
103
- inactiveTabClassName="text-muted-foreground hover:bg-muted/45 hover:text-foreground"
131
+ actionsClassName="h-full max-md:h-11 max-md:w-full items-center gap-0.5"
132
+ tabBaseClassName="inline-flex cursor-pointer items-center gap-0.5 h-7 max-md:min-h-11 px-2 rounded-md text-xs max-w-[220px] shrink-0 transition-colors"
133
+ activeTabClassName="bg-[var(--interaction-selection)] text-foreground"
134
+ inactiveTabClassName="text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-foreground"
104
135
  labelClassName="px-0.5 text-xs font-normal"
105
136
 
106
137
  />
@@ -1,4 +1,5 @@
1
1
  import { useState, useRef, useCallback, useEffect } from 'react';
2
+ import { useViewportLayout } from '@/app/hooks/use-viewport-layout';
2
3
  import {
3
4
  DOC_BROWSER_HOME_TAB_KIND,
4
5
  getDefaultDocsUrl,
@@ -258,6 +259,7 @@ export function DocBrowser({
258
259
 
259
260
 
260
261
  const isFullscreen = displayMode === 'fullscreen';
262
+ const { isMobile } = useViewportLayout();
261
263
  const customRenderParams = currentTab ? {
262
264
  currentUrl,
263
265
  open,
@@ -284,7 +286,7 @@ export function DocBrowser({
284
286
  urlInput={urlInput}
285
287
  />
286
288
 
287
- {customToolbar}
289
+ {!isMobile ? customToolbar : null}
288
290
 
289
291
  <DocBrowserFrameContent
290
292
  currentTab={currentTab}
@@ -310,6 +312,8 @@ export function DocBrowser({
310
312
  onWidthCommit={setDockedWidth} onClose={close} closeLabel={t('workbenchHideGroup')}
311
313
  hidden={!isOpen} fullscreen={isFullscreen} navigation={
312
314
  <DocBrowserTabStrip
315
+ mobileToolbar={customToolbar}
316
+ compact={isMobile}
313
317
  tabs={tabs}
314
318
  activeTabId={activeTabId}
315
319
  canGoBack={canGoBack}
@@ -112,7 +112,7 @@ export function ServerPathPickerLocations({
112
112
  type="button"
113
113
  className={cn(
114
114
  'flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border',
115
- active ? 'bg-primary/12 text-primary' : 'text-foreground hover:bg-[var(--interaction-hover)]',
115
+ active ? 'bg-[var(--interaction-selection)] text-foreground' : 'text-foreground hover:bg-[var(--interaction-hover)]',
116
116
  )}
117
117
  aria-current={active ? 'location' : undefined}
118
118
  onClick={() => onNavigate(item.path)}
@@ -27,7 +27,7 @@ export function SettingsPage({
27
27
  className
28
28
  )}
29
29
  >
30
- <PageHeader title={title} description={description} actions={actions} />
30
+ <PageHeader title={title} description={description} actions={actions} mobileTitleInShell />
31
31
  {children}
32
32
  </PageLayout>
33
33
  );
@@ -17,7 +17,7 @@ describe('IconActionButton', () => {
17
17
  expect(button.className).toContain('h-6');
18
18
  expect(button.className).toContain('w-6');
19
19
  expect(button.className).toContain('p-1');
20
- expect(button.className).toContain('hover:bg-black/10');
20
+ expect(button.className).toContain('hover:before:bg-[var(--interaction-hover)]');
21
21
  });
22
22
 
23
23
  it('defaults to medium size and soft accent hover', () => {
@@ -31,7 +31,7 @@ describe('IconActionButton', () => {
31
31
  const button = screen.getByRole('button', { name: 'More' });
32
32
  expect(button.className).toContain('h-7');
33
33
  expect(button.className).toContain('w-7');
34
- expect(button.className).toContain('hover:bg-[var(--interaction-hover)]');
34
+ expect(button.className).toContain('hover:before:bg-[var(--interaction-hover)]');
35
35
  });
36
36
 
37
37
  it('uses surface-relative feedback without introducing a new surface', () => {
@@ -44,9 +44,9 @@ describe('IconActionButton', () => {
44
44
  );
45
45
 
46
46
  const button = screen.getByRole('button', { name: 'Add project' });
47
- expect(button.className).toContain('hover:bg-gray-200/60');
48
- expect(button.className).toContain('hover:text-gray-900');
49
- expect(button.className).toContain('active:bg-gray-200/80');
47
+ expect(button.className).toContain('hover:before:bg-[var(--interaction-hover)]');
48
+ expect(button.className).toContain('hover:text-accent-foreground');
49
+ expect(button.className).toContain('active:before:bg-[var(--interaction-hover)]');
50
50
  });
51
51
 
52
52
  it('ignores restored pointer focus while preserving keyboard tooltip focus', () => {
@@ -0,0 +1,8 @@
1
+ /** Neutral actions share one theme color across icon and menu surfaces. */
2
+ export const ACTION_MENU_ITEM_CLASS = 'flex min-h-8 w-full items-center gap-2 rounded-lg px-2.5 py-1 text-left text-[13px] leading-5 font-medium outline-none transition-colors disabled:pointer-events-none disabled:opacity-45';
3
+
4
+ export const ACTION_FEEDBACK = {
5
+ icon: 'hover:before:bg-[var(--interaction-hover)] active:before:bg-[var(--interaction-hover)] disabled:hover:before:bg-transparent disabled:active:before:bg-transparent',
6
+ item: 'hover:bg-[var(--interaction-hover)] focus-visible:bg-[var(--interaction-hover)] active:bg-[var(--interaction-hover)] disabled:hover:bg-transparent disabled:active:bg-transparent',
7
+ destructive: 'text-destructive hover:bg-destructive/10 focus-visible:bg-destructive/10 disabled:hover:bg-transparent',
8
+ } as const;