@dxos/plugin-deck 0.9.1-main.c7dcc2e112 → 0.9.1-staging.ee54ba693a

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 (258) hide show
  1. package/PLUGIN.mdl +3 -1
  2. package/dist/lib/neutral/{DeckLayout-VX2BP4Q2.mjs → DeckLayout-B3RUUV5P.mjs} +4 -3
  3. package/dist/lib/neutral/DeckLayout-B3RUUV5P.mjs.map +7 -0
  4. package/dist/lib/neutral/DeckPlugin.mjs +2 -6
  5. package/dist/lib/neutral/DeckPlugin.mjs.map +3 -3
  6. package/dist/lib/neutral/DeckSettings-Z7GMB7XJ.mjs +32 -0
  7. package/dist/lib/neutral/DeckSettings-Z7GMB7XJ.mjs.map +7 -0
  8. package/dist/lib/neutral/{add-toast-APKTCLIA.mjs → add-toast-BHNEH5UY.mjs} +2 -2
  9. package/dist/lib/neutral/{adjust-7CZI4GK3.mjs → adjust-MJB6UMRQ.mjs} +16 -10
  10. package/dist/lib/neutral/adjust-MJB6UMRQ.mjs.map +7 -0
  11. package/dist/lib/neutral/{app-graph-builder-6HTXRTU2.mjs → app-graph-builder-2F7P4IFQ.mjs} +4 -3
  12. package/dist/lib/neutral/app-graph-builder-2F7P4IFQ.mjs.map +7 -0
  13. package/dist/lib/neutral/capabilities/index.mjs +5 -5
  14. package/dist/lib/neutral/chunk-FKGYHBUB.mjs +8 -0
  15. package/dist/lib/neutral/{chunk-CL7JIOI5.mjs → chunk-ICOJ4GMB.mjs} +10 -6
  16. package/dist/lib/neutral/chunk-ICOJ4GMB.mjs.map +7 -0
  17. package/dist/lib/neutral/{chunk-NGX6RXNV.mjs → chunk-PP32NEVY.mjs} +2 -2
  18. package/dist/lib/neutral/chunk-PP32NEVY.mjs.map +7 -0
  19. package/dist/lib/neutral/chunk-SONATB35.mjs +20 -0
  20. package/dist/lib/neutral/chunk-SONATB35.mjs.map +7 -0
  21. package/dist/lib/neutral/chunk-Z2B5RSVI.mjs +1096 -0
  22. package/dist/lib/neutral/chunk-Z2B5RSVI.mjs.map +7 -0
  23. package/dist/lib/neutral/{close-KJUCFIJ5.mjs → close-2YGB5IIP.mjs} +4 -3
  24. package/dist/lib/neutral/{close-KJUCFIJ5.mjs.map → close-2YGB5IIP.mjs.map} +1 -1
  25. package/dist/lib/neutral/components/index.mjs +201 -16
  26. package/dist/lib/neutral/components/index.mjs.map +4 -4
  27. package/dist/lib/neutral/containers/index.mjs +6 -11
  28. package/dist/lib/neutral/containers/index.mjs.map +3 -3
  29. package/dist/lib/neutral/hooks/index.mjs +101 -17
  30. package/dist/lib/neutral/hooks/index.mjs.map +4 -4
  31. package/dist/lib/neutral/index.mjs +7 -1
  32. package/dist/lib/neutral/meta.json +1 -1
  33. package/dist/lib/neutral/{notification-tracker-CUDFRDCF.mjs → notification-tracker-Y5CRTWJM.mjs} +4 -2
  34. package/dist/lib/neutral/notification-tracker-Y5CRTWJM.mjs.map +7 -0
  35. package/dist/lib/neutral/{open-VU7YS3HB.mjs → open-P6XQ56M7.mjs} +4 -3
  36. package/dist/lib/neutral/{open-VU7YS3HB.mjs.map → open-P6XQ56M7.mjs.map} +1 -1
  37. package/dist/lib/neutral/operations/index.mjs +1 -1
  38. package/dist/lib/neutral/plugin.mjs +1 -1
  39. package/dist/lib/neutral/{react-surface-O4POQWYL.mjs → react-surface-H63FXUK3.mjs} +2 -2
  40. package/dist/lib/neutral/{react-surface-O4POQWYL.mjs.map → react-surface-H63FXUK3.mjs.map} +1 -1
  41. package/dist/lib/neutral/{revert-workspace-24TKG3I7.mjs → revert-workspace-RXWP2TEE.mjs} +2 -2
  42. package/dist/lib/neutral/{scroll-into-view-3VXT6TWC.mjs → scroll-into-view-HFV5EGYZ.mjs} +2 -2
  43. package/dist/lib/neutral/{set-RFOLTI57.mjs → set-HOOVB7E4.mjs} +4 -3
  44. package/dist/lib/neutral/{set-RFOLTI57.mjs.map → set-HOOVB7E4.mjs.map} +1 -1
  45. package/dist/lib/neutral/{set-layout-mode-AZ73W52Z.mjs → set-layout-mode-XAGSZZGB.mjs} +2 -2
  46. package/dist/lib/neutral/{state-D3YXB5NP.mjs → state-F4X5GPGZ.mjs} +3 -2
  47. package/dist/lib/neutral/{state-D3YXB5NP.mjs.map → state-F4X5GPGZ.mjs.map} +1 -1
  48. package/dist/lib/neutral/{switch-workspace-TBPT23CZ.mjs → switch-workspace-PA7N6EUX.mjs} +2 -2
  49. package/dist/lib/neutral/translations.mjs +2 -2
  50. package/dist/lib/neutral/translations.mjs.map +2 -2
  51. package/dist/lib/neutral/types/index.mjs +1 -1
  52. package/dist/lib/neutral/{update-companion-5LCY6QME.mjs → update-companion-6A2SKQAI.mjs} +14 -6
  53. package/dist/lib/neutral/update-companion-6A2SKQAI.mjs.map +7 -0
  54. package/dist/lib/neutral/{update-complementary-HZ7QXZYE.mjs → update-complementary-K55I5EV5.mjs} +2 -2
  55. package/dist/lib/neutral/{update-dialog-A7W3R5EI.mjs → update-dialog-Z3GEXMVV.mjs} +2 -2
  56. package/dist/lib/neutral/{update-plank-size-XHTYBLSD.mjs → update-plank-size-BTESVV7N.mjs} +2 -2
  57. package/dist/lib/neutral/{update-popover-RDIHG73B.mjs → update-popover-QEVDSZSH.mjs} +2 -2
  58. package/dist/lib/neutral/{update-sidebar-GEI7USA5.mjs → update-sidebar-VSRCN47U.mjs} +2 -2
  59. package/dist/lib/neutral/{url-handler-PUS4X33T.mjs → url-handler-ERZXXEYR.mjs} +3 -2
  60. package/dist/lib/neutral/{url-handler-PUS4X33T.mjs.map → url-handler-ERZXXEYR.mjs.map} +1 -1
  61. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  62. package/dist/types/src/capabilities/app-graph-builder.d.ts +1 -2
  63. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  64. package/dist/types/src/capabilities/notification-tracker.d.ts +1 -1
  65. package/dist/types/src/capabilities/notification-tracker.d.ts.map +1 -1
  66. package/dist/types/src/capabilities/react-root.d.ts +1 -5
  67. package/dist/types/src/capabilities/react-root.d.ts.map +1 -1
  68. package/dist/types/src/capabilities/react-surface.d.ts +1 -1
  69. package/dist/types/src/capabilities/settings.d.ts +1 -3
  70. package/dist/types/src/capabilities/settings.d.ts.map +1 -1
  71. package/dist/types/src/capabilities/state.d.ts +1 -187
  72. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  73. package/dist/types/src/capabilities/tools.d.ts +1 -1
  74. package/dist/types/src/capabilities/url-handler.d.ts +1 -1
  75. package/dist/types/src/components/Companion/Companion.d.ts +17 -0
  76. package/dist/types/src/components/Companion/Companion.d.ts.map +1 -0
  77. package/dist/types/src/components/Companion/index.d.ts +2 -0
  78. package/dist/types/src/components/Companion/index.d.ts.map +1 -0
  79. package/dist/types/src/components/Matrix/Matrix.d.ts +1 -1
  80. package/dist/types/src/components/Matrix/Matrix.d.ts.map +1 -1
  81. package/dist/types/src/components/Matrix/Matrix.stories.d.ts +2 -2
  82. package/dist/types/src/components/Matrix/Matrix.stories.d.ts.map +1 -1
  83. package/dist/types/src/components/Matrix/index.d.ts +1 -1
  84. package/dist/types/src/components/Matrix/index.d.ts.map +1 -1
  85. package/dist/types/src/components/Pane/Pane.d.ts +45 -0
  86. package/dist/types/src/components/Pane/Pane.d.ts.map +1 -0
  87. package/dist/types/src/components/Pane/Pane.stories.d.ts +7 -0
  88. package/dist/types/src/components/Pane/Pane.stories.d.ts.map +1 -0
  89. package/dist/types/src/components/Pane/index.d.ts +2 -0
  90. package/dist/types/src/components/Pane/index.d.ts.map +1 -0
  91. package/dist/types/src/components/Plank/Plank.d.ts +74 -0
  92. package/dist/types/src/components/Plank/Plank.d.ts.map +1 -0
  93. package/dist/types/src/components/Plank/Plank.stories.d.ts +6 -0
  94. package/dist/types/src/components/Plank/Plank.stories.d.ts.map +1 -0
  95. package/dist/types/src/components/Plank/index.d.ts +2 -0
  96. package/dist/types/src/components/Plank/index.d.ts.map +1 -0
  97. package/dist/types/src/components/index.d.ts +3 -2
  98. package/dist/types/src/components/index.d.ts.map +1 -1
  99. package/dist/types/src/containers/Deck/Deck.d.ts +1 -1
  100. package/dist/types/src/containers/Deck/Deck.d.ts.map +1 -1
  101. package/dist/types/src/containers/Deck/Deck.stories.d.ts +2 -2
  102. package/dist/types/src/containers/Deck/DeckPlank.d.ts +21 -0
  103. package/dist/types/src/containers/Deck/DeckPlank.d.ts.map +1 -0
  104. package/dist/types/src/containers/Deck/DeckViewport.d.ts +3 -1
  105. package/dist/types/src/containers/Deck/DeckViewport.d.ts.map +1 -1
  106. package/dist/types/src/containers/{Plank → Deck}/PlankControls.d.ts +3 -10
  107. package/dist/types/src/containers/Deck/PlankControls.d.ts.map +1 -0
  108. package/dist/types/src/containers/Deck/PlankFallback.d.ts +6 -0
  109. package/dist/types/src/containers/Deck/PlankFallback.d.ts.map +1 -0
  110. package/dist/types/src/containers/Deck/useDeckPlank.d.ts +49 -0
  111. package/dist/types/src/containers/Deck/useDeckPlank.d.ts.map +1 -0
  112. package/dist/types/src/containers/DeckLayout/DeckLayout.stories.d.ts +2 -2
  113. package/dist/types/src/containers/DeckLayout/DeckLayout.stories.d.ts.map +1 -1
  114. package/dist/types/src/{components → containers}/DeckSettings/DeckSettings.d.ts +1 -1
  115. package/dist/types/src/containers/DeckSettings/DeckSettings.d.ts.map +1 -0
  116. package/dist/types/src/{components → containers}/DeckSettings/DeckSettings.stories.d.ts +2 -2
  117. package/dist/types/src/{components → containers}/DeckSettings/DeckSettings.stories.d.ts.map +1 -1
  118. package/dist/types/src/containers/DeckSettings/index.d.ts.map +1 -0
  119. package/dist/types/src/containers/index.d.ts +1 -1
  120. package/dist/types/src/containers/index.d.ts.map +1 -1
  121. package/dist/types/src/hooks/index.d.ts +2 -0
  122. package/dist/types/src/hooks/index.d.ts.map +1 -1
  123. package/dist/types/src/hooks/useCompanionSplit.d.ts +28 -0
  124. package/dist/types/src/hooks/useCompanionSplit.d.ts.map +1 -0
  125. package/dist/types/src/hooks/useCompanions.d.ts +11 -7
  126. package/dist/types/src/hooks/useCompanions.d.ts.map +1 -1
  127. package/dist/types/src/hooks/useDeckCompanions.d.ts +2 -2
  128. package/dist/types/src/hooks/useDeckCompanions.d.ts.map +1 -1
  129. package/dist/types/src/hooks/useDeckState.d.ts +1 -1
  130. package/dist/types/src/hooks/useDeckState.d.ts.map +1 -1
  131. package/dist/types/src/hooks/useSelectedCompanionVariant.d.ts +6 -0
  132. package/dist/types/src/hooks/useSelectedCompanionVariant.d.ts.map +1 -0
  133. package/dist/types/src/index.d.ts +1 -0
  134. package/dist/types/src/index.d.ts.map +1 -1
  135. package/dist/types/src/meta.d.ts +4 -0
  136. package/dist/types/src/meta.d.ts.map +1 -1
  137. package/dist/types/src/operations/adjust.d.ts.map +1 -1
  138. package/dist/types/src/operations/update-companion.d.ts.map +1 -1
  139. package/dist/types/src/translations.d.ts +2 -2
  140. package/dist/types/src/translations.d.ts.map +1 -1
  141. package/dist/types/src/types/DeckCapabilities.d.ts +2 -2
  142. package/dist/types/src/types/DeckCapabilities.d.ts.map +1 -1
  143. package/dist/types/src/types/DeckOperation.d.ts +2 -2
  144. package/dist/types/src/types/DeckOperation.d.ts.map +1 -1
  145. package/dist/types/src/types/schema.d.ts +7 -7
  146. package/dist/types/src/types/schema.d.ts.map +1 -1
  147. package/dist/types/src/util/companion-view-state.d.ts +16 -0
  148. package/dist/types/src/util/companion-view-state.d.ts.map +1 -0
  149. package/dist/types/src/util/index.d.ts +1 -0
  150. package/dist/types/src/util/index.d.ts.map +1 -1
  151. package/dist/types/src/util/plank-url-params.d.ts +2 -0
  152. package/dist/types/src/util/plank-url-params.d.ts.map +1 -1
  153. package/dist/types/tsconfig.tsbuildinfo +1 -1
  154. package/package.json +49 -47
  155. package/src/DeckPlugin.ts +1 -2
  156. package/src/capabilities/app-graph-builder.ts +3 -2
  157. package/src/capabilities/notification-tracker.ts +3 -1
  158. package/src/capabilities/react-surface.tsx +1 -1
  159. package/src/components/Companion/Companion.tsx +91 -0
  160. package/src/components/Companion/index.ts +5 -0
  161. package/src/components/Matrix/Matrix.stories.tsx +13 -26
  162. package/src/components/Matrix/Matrix.tsx +3 -3
  163. package/src/components/Matrix/SPEC.md +1 -1
  164. package/src/components/Matrix/index.ts +1 -1
  165. package/src/components/Pane/Pane.stories.tsx +92 -0
  166. package/src/components/Pane/Pane.tsx +178 -0
  167. package/src/components/Pane/index.ts +5 -0
  168. package/src/components/Plank/Plank.stories.tsx +103 -0
  169. package/src/components/Plank/Plank.tsx +149 -0
  170. package/src/components/Plank/index.ts +5 -0
  171. package/src/components/index.ts +3 -4
  172. package/src/containers/Deck/Deck.tsx +3 -3
  173. package/src/containers/Deck/DeckPlank.tsx +187 -0
  174. package/src/containers/Deck/DeckViewport.tsx +116 -243
  175. package/src/containers/{Plank → Deck}/PlankControls.tsx +19 -17
  176. package/src/containers/Deck/PlankFallback.tsx +42 -0
  177. package/src/containers/Deck/useDeckPlank.ts +219 -0
  178. package/src/containers/DeckLayout/DeckLayout.stories.tsx +38 -51
  179. package/src/containers/DeckLayout/Dialog.tsx +1 -1
  180. package/src/{components → containers}/DeckSettings/DeckSettings.stories.tsx +1 -1
  181. package/src/containers/DeckSettings/DeckSettings.tsx +42 -0
  182. package/src/containers/Sidebar/ComplementarySidebar.tsx +2 -2
  183. package/src/containers/index.ts +1 -1
  184. package/src/hooks/index.ts +2 -0
  185. package/src/hooks/useCompanionSplit.ts +78 -0
  186. package/src/hooks/useCompanions.ts +39 -7
  187. package/src/hooks/useDeckCompanions.ts +3 -3
  188. package/src/hooks/useDeckState.ts +1 -1
  189. package/src/hooks/useSelectedCompanionVariant.ts +14 -0
  190. package/src/index.ts +7 -0
  191. package/src/operations/adjust.ts +23 -12
  192. package/src/operations/update-companion.ts +10 -6
  193. package/src/translations.ts +2 -2
  194. package/src/types/DeckCapabilities.ts +1 -1
  195. package/src/types/DeckOperation.ts +2 -1
  196. package/src/types/schema.ts +5 -4
  197. package/src/util/companion-view-state.ts +28 -0
  198. package/src/util/index.ts +1 -0
  199. package/src/util/plank-url-params.test.ts +5 -0
  200. package/src/util/plank-url-params.ts +3 -1
  201. package/src/util/sanitize-persisted-state.test.ts +1 -1
  202. package/src/util/set-active.test.ts +1 -1
  203. package/dist/lib/neutral/DeckLayout-VX2BP4Q2.mjs.map +0 -7
  204. package/dist/lib/neutral/DeckSettings-CQQZJ2YS.mjs +0 -27
  205. package/dist/lib/neutral/DeckSettings-CQQZJ2YS.mjs.map +0 -7
  206. package/dist/lib/neutral/adjust-7CZI4GK3.mjs.map +0 -7
  207. package/dist/lib/neutral/app-graph-builder-6HTXRTU2.mjs.map +0 -7
  208. package/dist/lib/neutral/chunk-CL7JIOI5.mjs.map +0 -7
  209. package/dist/lib/neutral/chunk-DNG3L5QN.mjs +0 -1304
  210. package/dist/lib/neutral/chunk-DNG3L5QN.mjs.map +0 -7
  211. package/dist/lib/neutral/chunk-NGX6RXNV.mjs.map +0 -7
  212. package/dist/lib/neutral/chunk-UZLAR4DR.mjs +0 -8
  213. package/dist/lib/neutral/notification-tracker-CUDFRDCF.mjs.map +0 -7
  214. package/dist/lib/neutral/update-companion-5LCY6QME.mjs.map +0 -7
  215. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +0 -1
  216. package/dist/types/src/components/DeckSettings/index.d.ts.map +0 -1
  217. package/dist/types/src/containers/Plank/Plank.d.ts +0 -17
  218. package/dist/types/src/containers/Plank/Plank.d.ts.map +0 -1
  219. package/dist/types/src/containers/Plank/Plank.stories.d.ts +0 -67
  220. package/dist/types/src/containers/Plank/Plank.stories.d.ts.map +0 -1
  221. package/dist/types/src/containers/Plank/PlankComponent.d.ts +0 -17
  222. package/dist/types/src/containers/Plank/PlankComponent.d.ts.map +0 -1
  223. package/dist/types/src/containers/Plank/PlankContent.d.ts +0 -10
  224. package/dist/types/src/containers/Plank/PlankContent.d.ts.map +0 -1
  225. package/dist/types/src/containers/Plank/PlankControls.d.ts.map +0 -1
  226. package/dist/types/src/containers/Plank/PlankError.d.ts +0 -15
  227. package/dist/types/src/containers/Plank/PlankError.d.ts.map +0 -1
  228. package/dist/types/src/containers/Plank/PlankHeading.d.ts +0 -22
  229. package/dist/types/src/containers/Plank/PlankHeading.d.ts.map +0 -1
  230. package/dist/types/src/containers/Plank/PlankLoading.d.ts +0 -3
  231. package/dist/types/src/containers/Plank/PlankLoading.d.ts.map +0 -1
  232. package/dist/types/src/containers/Plank/PlankRoot.d.ts +0 -37
  233. package/dist/types/src/containers/Plank/PlankRoot.d.ts.map +0 -1
  234. package/dist/types/src/containers/Plank/index.d.ts +0 -4
  235. package/dist/types/src/containers/Plank/index.d.ts.map +0 -1
  236. package/src/components/DeckSettings/DeckSettings.tsx +0 -34
  237. package/src/containers/Plank/Plank.stories.tsx +0 -108
  238. package/src/containers/Plank/Plank.tsx +0 -22
  239. package/src/containers/Plank/PlankComponent.tsx +0 -223
  240. package/src/containers/Plank/PlankContent.tsx +0 -45
  241. package/src/containers/Plank/PlankError.tsx +0 -82
  242. package/src/containers/Plank/PlankHeading.tsx +0 -236
  243. package/src/containers/Plank/PlankLoading.tsx +0 -10
  244. package/src/containers/Plank/PlankRoot.tsx +0 -49
  245. package/src/containers/Plank/index.ts +0 -7
  246. /package/dist/lib/neutral/{add-toast-APKTCLIA.mjs.map → add-toast-BHNEH5UY.mjs.map} +0 -0
  247. /package/dist/lib/neutral/{chunk-UZLAR4DR.mjs.map → chunk-FKGYHBUB.mjs.map} +0 -0
  248. /package/dist/lib/neutral/{revert-workspace-24TKG3I7.mjs.map → revert-workspace-RXWP2TEE.mjs.map} +0 -0
  249. /package/dist/lib/neutral/{scroll-into-view-3VXT6TWC.mjs.map → scroll-into-view-HFV5EGYZ.mjs.map} +0 -0
  250. /package/dist/lib/neutral/{set-layout-mode-AZ73W52Z.mjs.map → set-layout-mode-XAGSZZGB.mjs.map} +0 -0
  251. /package/dist/lib/neutral/{switch-workspace-TBPT23CZ.mjs.map → switch-workspace-PA7N6EUX.mjs.map} +0 -0
  252. /package/dist/lib/neutral/{update-complementary-HZ7QXZYE.mjs.map → update-complementary-K55I5EV5.mjs.map} +0 -0
  253. /package/dist/lib/neutral/{update-dialog-A7W3R5EI.mjs.map → update-dialog-Z3GEXMVV.mjs.map} +0 -0
  254. /package/dist/lib/neutral/{update-plank-size-XHTYBLSD.mjs.map → update-plank-size-BTESVV7N.mjs.map} +0 -0
  255. /package/dist/lib/neutral/{update-popover-RDIHG73B.mjs.map → update-popover-QEVDSZSH.mjs.map} +0 -0
  256. /package/dist/lib/neutral/{update-sidebar-GEI7USA5.mjs.map → update-sidebar-VSRCN47U.mjs.map} +0 -0
  257. /package/dist/types/src/{components → containers}/DeckSettings/index.d.ts +0 -0
  258. /package/src/{components → containers}/DeckSettings/index.ts +0 -0
@@ -0,0 +1,178 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { Slot } from '@radix-ui/react-slot';
6
+ import React, { type ComponentPropsWithRef, forwardRef } from 'react';
7
+
8
+ import { AttentionSigilButton } from '@dxos/app-toolkit/ui';
9
+ import { DensityProvider, IconButton, type ThemedClassName, composableProps, slottable } from '@dxos/react-ui';
10
+ import { type AttendableId, type Related, useAttention } from '@dxos/react-ui-attention';
11
+ import { iconSize, mx } from '@dxos/ui-theme';
12
+ import type { Merge } from '@dxos/util';
13
+
14
+ //
15
+ // Pane
16
+ //
17
+ // A presentational, radix-style component for a single deck pane: a toolbar rail above a content body.
18
+ // A plank always has a toolbar, so it implements that layout directly rather than wrapping the
19
+ // generalized `Panel`. A companion pane is just a Pane whose toolbar holds `Pane.Tabs` instead of a
20
+ // sigil/title. Intentionally free of app-framework concepts (capabilities, operations, Surface) —
21
+ // containers compose the sigil, controls, tabs and content surface into the slots.
22
+ //
23
+
24
+ //
25
+ // Root
26
+ //
27
+
28
+ // Root accepts arbitrary div attributes (the container wires tabIndex, data-* and key handlers), so it
29
+ // is a plain forwarding div rather than the narrowly-typed `slottable` used by the toolbar/content slots.
30
+ type PaneRootProps = ThemedClassName<ComponentPropsWithRef<'div'>>;
31
+
32
+ const PaneRoot = forwardRef<HTMLDivElement, PaneRootProps>(({ children, ...props }, forwardedRef) => (
33
+ <div
34
+ {...composableProps(props, {
35
+ role: 'article',
36
+ classNames:
37
+ 'dx-container flex flex-col dx-attention-surface relative dx-focus-ring-inset-over-all dx-density-lg min-w-0',
38
+ })}
39
+ ref={forwardedRef}
40
+ >
41
+ {children}
42
+ </div>
43
+ ));
44
+
45
+ PaneRoot.displayName = 'Pane.Root';
46
+
47
+ //
48
+ // Toolbar
49
+ //
50
+
51
+ const PaneToolbar = slottable<HTMLDivElement>(({ children, asChild, ...props }, forwardedRef) => {
52
+ const Comp = asChild ? Slot : 'div';
53
+ return (
54
+ <Comp
55
+ {...composableProps(props, {
56
+ style: iconSize(5),
57
+ classNames: 'flex items-center gap-1 px-1 shrink-0 h-(--dx-rail-content) bg-header-surface',
58
+ })}
59
+ ref={forwardedRef}
60
+ >
61
+ <DensityProvider density='lg'>{children}</DensityProvider>
62
+ </Comp>
63
+ );
64
+ });
65
+
66
+ PaneToolbar.displayName = 'Pane.Toolbar';
67
+
68
+ //
69
+ // Content
70
+ //
71
+
72
+ const PaneContent = slottable<HTMLDivElement>(({ children, asChild, ...props }, forwardedRef) => {
73
+ const Comp = asChild ? Slot : 'div';
74
+ return (
75
+ <Comp {...composableProps(props, { classNames: 'flex-1 min-h-0' })} ref={forwardedRef}>
76
+ {children}
77
+ </Comp>
78
+ );
79
+ });
80
+
81
+ PaneContent.displayName = 'Pane.Content';
82
+
83
+ //
84
+ // Title
85
+ //
86
+
87
+ type PaneTitleProps = ThemedClassName<ComponentPropsWithRef<'h1'>> & AttendableId & Related;
88
+
89
+ /** Attention-aware plank title; colors to the accent when the plank (or a related companion) is attended. */
90
+ const PaneTitle = forwardRef<HTMLHeadingElement, PaneTitleProps>(
91
+ ({ attendableId, related, classNames, ...props }, forwardedRef) => {
92
+ const { hasAttention, isAncestor, isRelated } = useAttention(attendableId);
93
+ return (
94
+ <h1
95
+ {...props}
96
+ data-attention={((related && isRelated) || hasAttention || isAncestor).toString()}
97
+ className={mx(
98
+ 'px-1 min-w-0 w-0 grow truncate font-medium text-base-fg data-[attention=true]:text-accent-text self-center',
99
+ classNames,
100
+ )}
101
+ ref={forwardedRef}
102
+ />
103
+ );
104
+ },
105
+ );
106
+
107
+ PaneTitle.displayName = 'Pane.Title';
108
+
109
+ //
110
+ // Tabs
111
+ //
112
+
113
+ export type PaneTab = {
114
+ id: string;
115
+ icon: string;
116
+ /** Already-localized label. */
117
+ label: string;
118
+ };
119
+
120
+ type PaneTabsProps = Merge<
121
+ ThemedClassName<{
122
+ tabs: ReadonlyArray<PaneTab>;
123
+ value?: string;
124
+ onValueChange?: (id: string) => void;
125
+ /** Collapse inactive tabs to icon-only once the tab count exceeds this. */
126
+ maxTabs?: number;
127
+ }>,
128
+ AttendableId,
129
+ Related
130
+ >;
131
+
132
+ /** Full-height tab strip for a companion plank's toolbar; selects among available companions. */
133
+ const PaneTabs = forwardRef<HTMLDivElement, PaneTabsProps>(
134
+ ({ tabs, value, onValueChange, maxTabs = 5, attendableId, related, classNames }, forwardedRef) => {
135
+ // The active tab only reads as primary when the plank (shared with its companion) is attended.
136
+ const { hasAttention, isAncestor, isRelated } = useAttention(attendableId);
137
+ const attended = (related && isRelated) || hasAttention || isAncestor;
138
+ return (
139
+ <div
140
+ role='tablist'
141
+ className={mx('flex-1 min-w-0 overflow-x-auto scrollbar-none flex items-center gap-1', classNames)}
142
+ ref={forwardedRef}
143
+ >
144
+ {tabs.map(({ id, icon, label }) => (
145
+ <IconButton
146
+ key={id}
147
+ role='tab'
148
+ aria-selected={value === id}
149
+ tabIndex={value === id ? 0 : -1}
150
+ data-id={id}
151
+ icon={icon}
152
+ iconOnly={tabs.length > maxTabs && value !== id}
153
+ label={label}
154
+ variant={value === id && attended ? 'primary' : 'ghost'}
155
+ onClick={() => onValueChange?.(id)}
156
+ />
157
+ ))}
158
+ </div>
159
+ );
160
+ },
161
+ );
162
+
163
+ PaneTabs.displayName = 'Pane.Tabs';
164
+
165
+ //
166
+ // Pane
167
+ //
168
+
169
+ export const Pane = {
170
+ Root: PaneRoot,
171
+ Toolbar: PaneToolbar,
172
+ Sigil: AttentionSigilButton,
173
+ Title: PaneTitle,
174
+ Tabs: PaneTabs,
175
+ Content: PaneContent,
176
+ };
177
+
178
+ export type { PaneRootProps, PaneTabsProps, PaneTitleProps };
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './Pane';
@@ -0,0 +1,103 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+ import React, { useMemo } from 'react';
7
+
8
+ import { Capabilities, Capability, Plugin } from '@dxos/app-framework';
9
+ import { withPluginManager } from '@dxos/app-framework/testing';
10
+ import { Surface } from '@dxos/app-framework/ui';
11
+ import { AppActivationEvents, AppPlugin } from '@dxos/app-toolkit';
12
+ import { AppSurface } from '@dxos/app-toolkit/ui';
13
+ import { Obj } from '@dxos/echo';
14
+ import { type Node } from '@dxos/plugin-graph';
15
+ import { corePlugins } from '@dxos/plugin-testing';
16
+ import { random } from '@dxos/random';
17
+ import { withAttention } from '@dxos/react-ui-attention/testing';
18
+ import { Syntax } from '@dxos/react-ui-syntax-highlighter';
19
+ import { Loading, withLayout, withTheme } from '@dxos/react-ui/testing';
20
+ import { Organization, Person } from '@dxos/types';
21
+
22
+ import { DeckState, OperationHandler } from '#capabilities';
23
+ import { meta as pluginMeta } from '#meta';
24
+ import { translations } from '#translations';
25
+
26
+ import { Plank } from './Plank';
27
+
28
+ random.seed(99);
29
+
30
+ const TestPlugin = Plugin.define(pluginMeta).pipe(
31
+ Plugin.addModule({
32
+ id: Capability.getModuleTag(DeckState),
33
+ activatesOn: AppActivationEvents.AppGraphReady,
34
+ activate: () => DeckState(),
35
+ }),
36
+ AppPlugin.addOperationHandlerModule({
37
+ activate: OperationHandler,
38
+ }),
39
+ Plugin.make,
40
+ );
41
+
42
+ const TestExtension = Capability.contributes(
43
+ Capabilities.ReactSurface,
44
+ Surface.create({
45
+ id: 'storyArticle',
46
+ filter: Surface.makeFilter(AppSurface.Article),
47
+ component: ({ data: { subject } }) =>
48
+ subject ? (
49
+ <Syntax.Root data={subject}>
50
+ <Syntax.Content>
51
+ <Syntax.Viewport>
52
+ <Syntax.Code />
53
+ </Syntax.Viewport>
54
+ </Syntax.Content>
55
+ </Syntax.Root>
56
+ ) : (
57
+ <Loading />
58
+ ),
59
+ }),
60
+ );
61
+
62
+ // A border makes each plank's bounds obvious against the deck surface.
63
+ const PLANK_CLASSNAMES = 'border border-separator';
64
+
65
+ const useNode = (data: Obj.Any, icon: string): Node.Node =>
66
+ useMemo(
67
+ () => ({ id: data.id, type: 'test', data, properties: { label: Obj.getLabel(data) ?? 'Untitled', icon } }),
68
+ [data, icon],
69
+ );
70
+
71
+ // Two planks side by side; focus either to move attention (sigil/title take the accent color).
72
+ const DefaultStory = () => {
73
+ const [organization, person] = useMemo(
74
+ () => [Organization.make({ name: random.company.name() }), Person.make({ fullName: random.person.fullName() })],
75
+ [],
76
+ );
77
+ const organizationNode = useNode(organization, 'ph--building-office--regular');
78
+ const personNode = useNode(person, 'ph--user--regular');
79
+
80
+ return (
81
+ <div className='flex h-full gap-3 p-3 bg-deck-surface'>
82
+ <Plank node={organizationNode} classNames={PLANK_CLASSNAMES} />
83
+ <Plank node={personNode} classNames={PLANK_CLASSNAMES} />
84
+ </div>
85
+ );
86
+ };
87
+
88
+ const meta: Meta = {
89
+ title: 'plugins/plugin-deck/components/Plank',
90
+ decorators: [
91
+ withPluginManager({ plugins: [...corePlugins(), TestPlugin()], capabilities: [TestExtension] }),
92
+ withAttention(),
93
+ withTheme(),
94
+ withLayout({ layout: 'fullscreen' }),
95
+ ],
96
+ parameters: { layout: 'fullscreen', translations },
97
+ };
98
+
99
+ export default meta;
100
+
101
+ type Story = StoryObj;
102
+
103
+ export const Default: Story = { render: () => <DefaultStory /> };
@@ -0,0 +1,149 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import React, {
6
+ type ComponentProps,
7
+ Fragment,
8
+ type KeyboardEventHandler,
9
+ type ReactNode,
10
+ forwardRef,
11
+ useMemo,
12
+ } from 'react';
13
+
14
+ import { Surface } from '@dxos/app-framework/ui';
15
+ import { AppSurface, AttentionSigil, type AttentionSigilAction } from '@dxos/app-toolkit/ui';
16
+ import { type Node } from '@dxos/plugin-graph';
17
+ import { Icon, Popover, type ThemedClassName, toLocalizedString, useTranslation } from '@dxos/react-ui';
18
+ import { useAttentionAttributes } from '@dxos/react-ui-attention';
19
+
20
+ import { meta } from '#meta';
21
+
22
+ import { Pane } from '../Pane';
23
+
24
+ type SurfaceProps = ComponentProps<typeof Surface.Surface>;
25
+
26
+ export type PlankProps = ThemedClassName<{
27
+ node: Node.Node;
28
+ /** Attendable id; defaults to the node id. */
29
+ attendableId?: string;
30
+ /** Grouped sigil menu actions; when present the sigil opens a menu, otherwise it is a plain button. */
31
+ actions?: AttentionSigilAction[][];
32
+ onAction?: (action: AttentionSigilAction) => void;
33
+ /** Toolbar controls rendered after the title (e.g. close/solo/fullscreen). */
34
+ controls?: ReactNode;
35
+ /** Toolbar content rendered between the title and the controls (e.g. a NavbarEnd surface). */
36
+ navbarEnd?: ReactNode;
37
+ /** Sigil-menu footer content (e.g. a MenuFooter surface). */
38
+ sigilFooter?: ReactNode;
39
+ /** Highlight the title/sigil when a related (companion) region is attended. */
40
+ related?: boolean;
41
+ /** Render the title in a muted style (e.g. while the subject is loading). */
42
+ pending?: boolean;
43
+ /** When it matches `${meta.key}:${node.id}`, anchors a popover to the sigil. */
44
+ popoverAnchorId?: string;
45
+ /** Extra Article surface data merged over the computed defaults (e.g. companionTo/variant/path). */
46
+ articleData?: Partial<AppSurface.ArticleData>;
47
+ /** Error fallback for the content surface. */
48
+ fallback?: SurfaceProps['fallback'];
49
+ /** Loading placeholder for the content surface. */
50
+ placeholder?: SurfaceProps['placeholder'];
51
+ /** Render only the content surface, omitting the toolbar (e.g. fullscreen). */
52
+ headless?: boolean;
53
+ // TODO(burdon): Why is this required?
54
+ onKeyDown?: KeyboardEventHandler<HTMLDivElement>;
55
+ }>;
56
+
57
+ /**
58
+ * A higher-level deck pane bound to a graph {@link Node}: renders the node's sigil, title and content
59
+ * Surface inside a {@link Pane}, and makes itself the node's attendable region. Toolbar controls
60
+ * (close/solo/etc.) and framework surfaces (NavbarEnd/MenuFooter) are supplied by the container via
61
+ * slots so this component stays free of capabilities/operations.
62
+ */
63
+ export const Plank = forwardRef<HTMLDivElement, PlankProps>(
64
+ (
65
+ {
66
+ classNames,
67
+ node,
68
+ attendableId = node.id,
69
+ actions,
70
+ onAction,
71
+ controls,
72
+ navbarEnd,
73
+ sigilFooter,
74
+ related,
75
+ pending,
76
+ popoverAnchorId,
77
+ articleData,
78
+ fallback,
79
+ placeholder,
80
+ headless,
81
+ onKeyDown,
82
+ },
83
+ forwardedRef,
84
+ ) => {
85
+ const { t } = useTranslation(meta.profile.key);
86
+ const attentionAttrs = useAttentionAttributes(attendableId);
87
+ const icon = node.properties?.icon ?? 'ph--circle-dashed--regular';
88
+ const label = toLocalizedString(node.properties?.label ?? '', t);
89
+ const data = useMemo<AppSurface.ArticleData>(
90
+ () => ({ attendableId, subject: node.data, properties: node.properties, popoverAnchorId, ...articleData }),
91
+ [attendableId, node.data, node.properties, popoverAnchorId, articleData],
92
+ );
93
+
94
+ // Anchor the sigil's popover only when this plank's menu is the active popover target.
95
+ const ActionRoot = popoverAnchorId === `${meta.profile.key}:${node.id}` ? Popover.Anchor : Fragment;
96
+
97
+ return (
98
+ <Pane.Root
99
+ ref={forwardedRef}
100
+ classNames={classNames}
101
+ tabIndex={0}
102
+ onKeyDown={onKeyDown}
103
+ {...attentionAttrs}
104
+ data-testid='deck.plank'
105
+ >
106
+ {!headless && (
107
+ <Pane.Toolbar>
108
+ <ActionRoot>
109
+ {actions && actions.length > 0 ? (
110
+ <AttentionSigil
111
+ icon={icon}
112
+ related={related}
113
+ attendableId={attendableId}
114
+ actions={actions}
115
+ onAction={onAction}
116
+ triggerLabel={label}
117
+ >
118
+ {sigilFooter}
119
+ </AttentionSigil>
120
+ ) : (
121
+ <Pane.Sigil attendableId={attendableId}>
122
+ <span className='sr-only'>{label}</span>
123
+ <Icon icon={icon} />
124
+ </Pane.Sigil>
125
+ )}
126
+ </ActionRoot>
127
+ <Pane.Title attendableId={attendableId} related={related} classNames={pending && 'text-description'}>
128
+ {label}
129
+ </Pane.Title>
130
+ {navbarEnd}
131
+ {controls}
132
+ </Pane.Toolbar>
133
+ )}
134
+ <Pane.Content>
135
+ <Surface.Surface
136
+ key={node.id}
137
+ type={AppSurface.Article}
138
+ data={data}
139
+ limit={1}
140
+ fallback={fallback}
141
+ placeholder={placeholder}
142
+ />
143
+ </Pane.Content>
144
+ </Pane.Root>
145
+ );
146
+ },
147
+ );
148
+
149
+ Plank.displayName = 'Plank';
@@ -0,0 +1,5 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ export * from './Plank';
@@ -2,8 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { type ComponentType, lazy } from 'react';
6
-
7
- export const DeckSettings: ComponentType<any> = lazy(() => import('./DeckSettings'));
8
-
5
+ export { Companion, type CompanionProps } from './Companion';
9
6
  export { Matrix } from './Matrix';
7
+ export { Pane, type PaneTab } from './Pane';
8
+ export { Plank, type PlankProps } from './Plank';
@@ -3,8 +3,8 @@
3
3
  //
4
4
 
5
5
  import { DeckContent, DeckContentProps } from './DeckContent';
6
- import { DeckRoot, DeckLayoutChangeRequest, DeckRootProps } from './DeckRoot';
7
- import { DeckViewport, DeckMultiMode, DeckSoloMode, DeckViewportProps, DeckContentEmpty } from './DeckViewport';
6
+ import { DeckLayoutChangeRequest, DeckRoot, DeckRootProps } from './DeckRoot';
7
+ import { DeckContentEmpty, DeckMultiMode, DeckSoloMode, DeckViewport, DeckViewportProps } from './DeckViewport';
8
8
 
9
9
  /**
10
10
  * Radix-style composite Deck component.
@@ -18,4 +18,4 @@ export const Deck = {
18
18
  SoloMode: DeckSoloMode,
19
19
  };
20
20
 
21
- export type { DeckLayoutChangeRequest, DeckRootProps, DeckContentProps, DeckViewportProps };
21
+ export type { DeckContentProps, DeckLayoutChangeRequest, DeckRootProps, DeckViewportProps };
@@ -0,0 +1,187 @@
1
+ //
2
+ // Copyright 2026 DXOS.org
3
+ //
4
+
5
+ import { useFocusFinders } from '@fluentui/react-tabster';
6
+ import React, { type KeyboardEvent, memo, useCallback, useEffect, useMemo, useRef, useState } from 'react';
7
+
8
+ import { Surface } from '@dxos/app-framework/ui';
9
+ import { AppSurface } from '@dxos/app-toolkit/ui';
10
+ import { Splitter, type ThemedClassName } from '@dxos/react-ui';
11
+
12
+ import { Companion, Plank } from '#components';
13
+ import { useCompanionSplit } from '#hooks';
14
+ import { type LayoutMode, type ResolvedPart, type Settings } from '#types';
15
+
16
+ import { PlankCompanionControls, PlankControls } from './PlankControls';
17
+ import { PlankErrorFallback, PlankLoading } from './PlankFallback';
18
+ import { useDeckPlank } from './useDeckPlank';
19
+
20
+ const PLANK_LOADING = <PlankLoading />;
21
+
22
+ export type DeckPlankProps = ThemedClassName<{
23
+ id: string;
24
+ part: ResolvedPart;
25
+ layoutMode: LayoutMode;
26
+ active?: string[];
27
+ /** Whether the companion pane should be shown for this plank (gated further by attention in multi-mode). */
28
+ companionShown?: boolean;
29
+ settings?: Settings.Settings;
30
+ path?: string[];
31
+ }>;
32
+
33
+ /**
34
+ * Connected deck plank: resolves the node, companions, actions and operation handlers via
35
+ * {@link useDeckPlank} and renders the presentational {@link Plank} — wrapped in a {@link Splitter}
36
+ * beside a {@link Companion} when a companion is open. Replaces the legacy
37
+ * `PlankContainer`/`PlankComponent`/`PlankHeading` tree.
38
+ */
39
+ export const DeckPlank = memo(
40
+ ({ id, part, layoutMode, active, companionShown, settings, path, classNames }: DeckPlankProps) => {
41
+ const { findFirstFocusable } = useFocusFinders();
42
+ const rootRef = useRef<HTMLDivElement>(null);
43
+ const {
44
+ node,
45
+ companions,
46
+ resolvedCompanionId,
47
+ hasCompanion,
48
+ companionOrientation,
49
+ capabilities,
50
+ sigilActions,
51
+ popoverAnchorId,
52
+ scrollIntoView,
53
+ onAction,
54
+ onAdjust,
55
+ onScrollIntoView,
56
+ onUpdateCompanion,
57
+ } = useDeckPlank({ id, part, layoutMode, active, companionShown, deckEnabled: settings?.enableDeck });
58
+
59
+ // Memoize the split point per orientation so toggling side-by-side ↔ stacked restores each one.
60
+ const { size: companionSize, onSizeChange: onCompanionSizeChange } = useCompanionSplit(companionOrientation);
61
+
62
+ // Newly opened/navigated planks are flagged via `scrollIntoView`; focus the pane so it gains
63
+ // attention, then clear the one-shot flag.
64
+ useEffect(() => {
65
+ if (scrollIntoView === id) {
66
+ rootRef.current?.focus();
67
+ onScrollIntoView(undefined);
68
+ }
69
+ }, [scrollIntoView, id, onScrollIntoView]);
70
+
71
+ // Tabster's focus group should move focus to Main on Escape, but something blocks it; handle directly.
72
+ const handleKeyDown = useCallback(
73
+ (event: KeyboardEvent<HTMLDivElement>) => {
74
+ if (event.target === event.currentTarget) {
75
+ switch (event.key) {
76
+ case 'Escape':
77
+ event.currentTarget.closest('main')?.focus();
78
+ break;
79
+ case 'Enter':
80
+ findFirstFocusable(event.currentTarget)?.focus();
81
+ break;
82
+ }
83
+ }
84
+ },
85
+ [findFirstFocusable],
86
+ );
87
+
88
+ // Stable reference so Plank's useMemo on articleData doesn't bust every render.
89
+ const articleData = useMemo(() => ({ path }), [path]);
90
+
91
+ // Keep the companion panel mounted for 250ms after hasCompanion goes false so the Splitter's
92
+ // CSS transition can complete before the subtree is removed from the DOM.
93
+ const [companionMounted, setCompanionMounted] = useState(hasCompanion);
94
+ useEffect(() => {
95
+ if (hasCompanion) {
96
+ setCompanionMounted(true);
97
+ return;
98
+ }
99
+ const timer = setTimeout(() => setCompanionMounted(false), 250);
100
+ return () => clearTimeout(timer);
101
+ }, [hasCompanion]);
102
+
103
+ const companionControls = useMemo(() => <PlankCompanionControls primary={id} />, [id]);
104
+
105
+ if (!node) {
106
+ return PLANK_LOADING;
107
+ }
108
+
109
+ const controls = (
110
+ <PlankControls
111
+ capabilities={capabilities}
112
+ layoutMode={layoutMode}
113
+ close={part === 'complementary' ? 'minify-end' : true}
114
+ onClick={onAdjust}
115
+ />
116
+ );
117
+
118
+ const navbarEnd =
119
+ part !== 'complementary' ? (
120
+ <Surface.Surface type={AppSurface.NavbarEnd} data={{ subject: node.data } satisfies AppSurface.NavbarEndData} />
121
+ ) : undefined;
122
+
123
+ const sigilFooter = (
124
+ <Surface.Surface type={AppSurface.MenuFooter} data={{ subject: node.data } satisfies AppSurface.MenuFooterData} />
125
+ );
126
+
127
+ // In fullscreen the toolbar is hidden so the content fills the viewport.
128
+ const headless = layoutMode === 'solo--fullscreen';
129
+
130
+ // Splitter.Root is always the outer element so <Plank> stays at the same tree position regardless of
131
+ // whether the companion is open. A root-element-type change (Plank ↔ Splitter.Root) would force React
132
+ // to unmount and remount the entire subtree on every companion toggle, resetting article state.
133
+ //
134
+ // `mode` drives the animated open/close: switching between 'split' and 'start' triggers the Splitter's
135
+ // built-in 250ms CSS transition. `companionMounted` trails `hasCompanion` by 250ms on close so the
136
+ // panel stays in the DOM long enough for the collapse animation to finish before being unmounted.
137
+ return (
138
+ <Splitter.Root
139
+ orientation={companionOrientation}
140
+ anchor='end'
141
+ mode={hasCompanion ? 'split' : 'start'}
142
+ resizable={hasCompanion}
143
+ size={companionSize}
144
+ minSize={20}
145
+ onSizeChange={onCompanionSizeChange}
146
+ classNames={classNames}
147
+ >
148
+ <Splitter.Panel position='start'>
149
+ <Plank
150
+ ref={rootRef}
151
+ node={node}
152
+ attendableId={id}
153
+ related={part === 'complementary'}
154
+ actions={sigilActions}
155
+ onAction={onAction}
156
+ popoverAnchorId={popoverAnchorId}
157
+ articleData={articleData}
158
+ controls={controls}
159
+ navbarEnd={navbarEnd}
160
+ sigilFooter={sigilFooter}
161
+ fallback={PlankErrorFallback}
162
+ placeholder={PLANK_LOADING}
163
+ headless={headless}
164
+ onKeyDown={handleKeyDown}
165
+ />
166
+ </Splitter.Panel>
167
+ {companionMounted && (
168
+ <>
169
+ <Splitter.Handle />
170
+ <Splitter.Panel position='end'>
171
+ <Companion
172
+ companions={companions}
173
+ value={resolvedCompanionId}
174
+ onValueChange={onUpdateCompanion}
175
+ attendableId={id}
176
+ companionTo={node.data}
177
+ controls={companionControls}
178
+ />
179
+ </Splitter.Panel>
180
+ </>
181
+ )}
182
+ </Splitter.Root>
183
+ );
184
+ },
185
+ );
186
+
187
+ DeckPlank.displayName = 'DeckPlank';