@dxos/plugin-deck 0.8.4-main.fffef41 → 0.9.0

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 (504) hide show
  1. package/LICENSE +102 -5
  2. package/PLUGIN.mdl +541 -0
  3. package/README.md +1 -1
  4. package/dist/lib/neutral/DeckLayout-DEURA3KR.mjs +291 -0
  5. package/dist/lib/neutral/DeckLayout-DEURA3KR.mjs.map +7 -0
  6. package/dist/lib/neutral/DeckPlugin.mjs +87 -0
  7. package/dist/lib/neutral/DeckPlugin.mjs.map +7 -0
  8. package/dist/lib/neutral/DeckPlugin.node.mjs +18 -0
  9. package/dist/lib/neutral/DeckPlugin.node.mjs.map +7 -0
  10. package/dist/lib/neutral/DeckPlugin.workerd.mjs +16 -0
  11. package/dist/lib/neutral/DeckPlugin.workerd.mjs.map +7 -0
  12. package/dist/lib/neutral/DeckSettings-W5I57OXM.mjs +27 -0
  13. package/dist/lib/neutral/DeckSettings-W5I57OXM.mjs.map +7 -0
  14. package/dist/lib/neutral/add-toast-TNB6DXWU.mjs +24 -0
  15. package/dist/lib/neutral/add-toast-TNB6DXWU.mjs.map +7 -0
  16. package/dist/lib/neutral/adjust-HNU5CCRO.mjs +93 -0
  17. package/dist/lib/neutral/adjust-HNU5CCRO.mjs.map +7 -0
  18. package/dist/lib/neutral/app-graph-builder-HMLT627T.mjs +129 -0
  19. package/dist/lib/neutral/app-graph-builder-HMLT627T.mjs.map +7 -0
  20. package/dist/lib/neutral/capabilities/index.mjs +25 -0
  21. package/dist/lib/neutral/capabilities/index.mjs.map +7 -0
  22. package/dist/lib/neutral/check-app-scheme-INSOF72J.mjs +10 -0
  23. package/dist/lib/neutral/chunk-324PPIZB.mjs +101 -0
  24. package/dist/lib/neutral/chunk-324PPIZB.mjs.map +7 -0
  25. package/dist/lib/neutral/chunk-BS4EOYMK.mjs +282 -0
  26. package/dist/lib/neutral/chunk-BS4EOYMK.mjs.map +7 -0
  27. package/dist/lib/neutral/chunk-GBIGQKYW.mjs +112 -0
  28. package/dist/lib/neutral/chunk-GBIGQKYW.mjs.map +7 -0
  29. package/dist/lib/neutral/chunk-GLB73Q6U.mjs +22 -0
  30. package/dist/lib/neutral/chunk-GLB73Q6U.mjs.map +7 -0
  31. package/dist/lib/neutral/chunk-J5LGTIGS.mjs +10 -0
  32. package/dist/lib/neutral/chunk-PYEY5SEC.mjs +37 -0
  33. package/dist/lib/neutral/chunk-PYEY5SEC.mjs.map +7 -0
  34. package/dist/lib/neutral/chunk-Q4W6B4IB.mjs +8 -0
  35. package/dist/lib/neutral/chunk-Q4W6B4IB.mjs.map +7 -0
  36. package/dist/lib/neutral/chunk-WAXJPQJI.mjs +1306 -0
  37. package/dist/lib/neutral/chunk-WAXJPQJI.mjs.map +7 -0
  38. package/dist/lib/neutral/chunk-ZYYOSX5I.mjs +69 -0
  39. package/dist/lib/neutral/chunk-ZYYOSX5I.mjs.map +7 -0
  40. package/dist/lib/neutral/close-ASKR22A6.mjs +44 -0
  41. package/dist/lib/neutral/close-ASKR22A6.mjs.map +7 -0
  42. package/dist/lib/neutral/components/index.mjs +126 -0
  43. package/dist/lib/neutral/components/index.mjs.map +7 -0
  44. package/dist/lib/neutral/containers/index.mjs +32 -0
  45. package/dist/lib/neutral/containers/index.mjs.map +7 -0
  46. package/dist/lib/neutral/hooks/index.mjs +159 -0
  47. package/dist/lib/neutral/hooks/index.mjs.map +7 -0
  48. package/dist/lib/neutral/index.mjs +38 -0
  49. package/dist/lib/neutral/index.mjs.map +7 -0
  50. package/dist/lib/neutral/meta.json +1 -0
  51. package/dist/lib/neutral/meta.mjs +8 -0
  52. package/dist/lib/neutral/meta.mjs.map +7 -0
  53. package/dist/lib/neutral/notification-tracker-P36322BH.mjs +182 -0
  54. package/dist/lib/neutral/notification-tracker-P36322BH.mjs.map +7 -0
  55. package/dist/lib/neutral/open-5OYNO3RT.mjs +159 -0
  56. package/dist/lib/neutral/open-5OYNO3RT.mjs.map +7 -0
  57. package/dist/lib/neutral/operation-handler-266CVMTW.mjs +13 -0
  58. package/dist/lib/neutral/operation-handler-266CVMTW.mjs.map +7 -0
  59. package/dist/lib/neutral/operations/index.mjs +8 -0
  60. package/dist/lib/neutral/operations/index.mjs.map +7 -0
  61. package/dist/lib/neutral/plugin.mjs +16 -0
  62. package/dist/lib/neutral/plugin.mjs.map +7 -0
  63. package/dist/lib/neutral/react-root-HH5DEUOG.mjs +44 -0
  64. package/dist/lib/neutral/react-root-HH5DEUOG.mjs.map +7 -0
  65. package/dist/lib/neutral/react-surface-3UVVCK3O.mjs +34 -0
  66. package/dist/lib/neutral/react-surface-3UVVCK3O.mjs.map +7 -0
  67. package/dist/lib/neutral/revert-workspace-B2QLT2C4.mjs +21 -0
  68. package/dist/lib/neutral/revert-workspace-B2QLT2C4.mjs.map +7 -0
  69. package/dist/lib/neutral/scroll-into-view-B52C3PJO.mjs +21 -0
  70. package/dist/lib/neutral/scroll-into-view-B52C3PJO.mjs.map +7 -0
  71. package/dist/lib/neutral/set-PA35ONXO.mjs +37 -0
  72. package/dist/lib/neutral/set-PA35ONXO.mjs.map +7 -0
  73. package/dist/lib/neutral/set-layout-mode-RPCCPQRB.mjs +85 -0
  74. package/dist/lib/neutral/set-layout-mode-RPCCPQRB.mjs.map +7 -0
  75. package/dist/lib/neutral/settings-EGNYUM4T.mjs +33 -0
  76. package/dist/lib/neutral/settings-EGNYUM4T.mjs.map +7 -0
  77. package/dist/lib/neutral/state-IIDXMQUO.mjs +86 -0
  78. package/dist/lib/neutral/state-IIDXMQUO.mjs.map +7 -0
  79. package/dist/lib/neutral/switch-workspace-LZF4KZXH.mjs +60 -0
  80. package/dist/lib/neutral/switch-workspace-LZF4KZXH.mjs.map +7 -0
  81. package/dist/lib/neutral/translations.mjs +63 -0
  82. package/dist/lib/neutral/translations.mjs.map +7 -0
  83. package/dist/lib/neutral/types/index.mjs +34 -0
  84. package/dist/lib/neutral/types/index.mjs.map +7 -0
  85. package/dist/lib/neutral/update-companion-YUCZZVGY.mjs +32 -0
  86. package/dist/lib/neutral/update-companion-YUCZZVGY.mjs.map +7 -0
  87. package/dist/lib/neutral/update-complementary-7FZNB55J.mjs +28 -0
  88. package/dist/lib/neutral/update-complementary-7FZNB55J.mjs.map +7 -0
  89. package/dist/lib/neutral/update-dialog-FNQTSSAP.mjs +29 -0
  90. package/dist/lib/neutral/update-dialog-FNQTSSAP.mjs.map +7 -0
  91. package/dist/lib/neutral/update-plank-size-3YW4NXEY.mjs +26 -0
  92. package/dist/lib/neutral/update-plank-size-3YW4NXEY.mjs.map +7 -0
  93. package/dist/lib/neutral/update-popover-G2VUD7E6.mjs +33 -0
  94. package/dist/lib/neutral/update-popover-G2VUD7E6.mjs.map +7 -0
  95. package/dist/lib/neutral/update-sidebar-KRHPUHUB.mjs +25 -0
  96. package/dist/lib/neutral/update-sidebar-KRHPUHUB.mjs.map +7 -0
  97. package/dist/lib/neutral/url-handler-A6HLW4RB.mjs +229 -0
  98. package/dist/lib/neutral/url-handler-A6HLW4RB.mjs.map +7 -0
  99. package/dist/types/src/DeckPlugin.d.ts +3 -1
  100. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  101. package/dist/types/src/DeckPlugin.node.d.ts +4 -0
  102. package/dist/types/src/DeckPlugin.node.d.ts.map +1 -0
  103. package/dist/types/src/DeckPlugin.test.d.ts +2 -0
  104. package/dist/types/src/DeckPlugin.test.d.ts.map +1 -0
  105. package/dist/types/src/DeckPlugin.workerd.d.ts +4 -0
  106. package/dist/types/src/DeckPlugin.workerd.d.ts.map +1 -0
  107. package/dist/types/src/capabilities/app-graph-builder.d.ts +4 -2
  108. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  109. package/dist/types/src/capabilities/check-app-scheme.d.ts +17 -2
  110. package/dist/types/src/capabilities/check-app-scheme.d.ts.map +1 -1
  111. package/dist/types/src/capabilities/index.d.ts +197 -12
  112. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  113. package/dist/types/src/capabilities/notification-tracker.d.ts +13 -0
  114. package/dist/types/src/capabilities/notification-tracker.d.ts.map +1 -0
  115. package/dist/types/src/capabilities/operation-handler.d.ts +6 -0
  116. package/dist/types/src/capabilities/operation-handler.d.ts.map +1 -0
  117. package/dist/types/src/capabilities/react-root.d.ts +4 -2
  118. package/dist/types/src/capabilities/react-root.d.ts.map +1 -1
  119. package/dist/types/src/capabilities/react-surface.d.ts +3 -2
  120. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  121. package/dist/types/src/capabilities/settings.d.ts +5 -2
  122. package/dist/types/src/capabilities/settings.d.ts.map +1 -1
  123. package/dist/types/src/capabilities/state.d.ts +141 -54
  124. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  125. package/dist/types/src/capabilities/tools.d.ts +4 -3
  126. package/dist/types/src/capabilities/tools.d.ts.map +1 -1
  127. package/dist/types/src/capabilities/url-handler.d.ts +3 -2
  128. package/dist/types/src/capabilities/url-handler.d.ts.map +1 -1
  129. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts +4 -4
  130. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +1 -1
  131. package/dist/types/src/components/DeckSettings/DeckSettings.stories.d.ts +67 -0
  132. package/dist/types/src/components/DeckSettings/DeckSettings.stories.d.ts.map +1 -0
  133. package/dist/types/src/components/DeckSettings/index.d.ts +1 -1
  134. package/dist/types/src/components/DeckSettings/index.d.ts.map +1 -1
  135. package/dist/types/src/components/Matrix/Matrix.d.ts +41 -0
  136. package/dist/types/src/components/Matrix/Matrix.d.ts.map +1 -0
  137. package/dist/types/src/components/Matrix/Matrix.stories.d.ts +17 -0
  138. package/dist/types/src/components/Matrix/Matrix.stories.d.ts.map +1 -0
  139. package/dist/types/src/components/Matrix/index.d.ts +3 -0
  140. package/dist/types/src/components/Matrix/index.d.ts.map +1 -0
  141. package/dist/types/src/components/index.d.ts +3 -2
  142. package/dist/types/src/components/index.d.ts.map +1 -1
  143. package/dist/types/src/{components/DeckLayout → containers/Deck}/Banner.d.ts +1 -1
  144. package/dist/types/src/containers/Deck/Banner.d.ts.map +1 -0
  145. package/dist/types/src/containers/Deck/Deck.d.ts +25 -0
  146. package/dist/types/src/containers/Deck/Deck.d.ts.map +1 -0
  147. package/dist/types/src/containers/Deck/Deck.stories.d.ts +67 -0
  148. package/dist/types/src/containers/Deck/Deck.stories.d.ts.map +1 -0
  149. package/dist/types/src/containers/Deck/DeckContent.d.ts +7 -0
  150. package/dist/types/src/containers/Deck/DeckContent.d.ts.map +1 -0
  151. package/dist/types/src/containers/Deck/DeckRoot.d.ts +39 -0
  152. package/dist/types/src/containers/Deck/DeckRoot.d.ts.map +1 -0
  153. package/dist/types/src/containers/Deck/DeckViewport.d.ts +16 -0
  154. package/dist/types/src/containers/Deck/DeckViewport.d.ts.map +1 -0
  155. package/dist/types/src/containers/Deck/StatusBar.d.ts.map +1 -0
  156. package/dist/types/src/containers/Deck/index.d.ts +2 -0
  157. package/dist/types/src/containers/Deck/index.d.ts.map +1 -0
  158. package/dist/types/src/containers/DeckLayout/ActiveNode.d.ts.map +1 -0
  159. package/dist/types/src/containers/DeckLayout/DeckLayout.d.ts.map +1 -0
  160. package/dist/types/src/containers/DeckLayout/DeckLayout.stories.d.ts +69 -0
  161. package/dist/types/src/containers/DeckLayout/DeckLayout.stories.d.ts.map +1 -0
  162. package/dist/types/src/containers/DeckLayout/Dialog.d.ts.map +1 -0
  163. package/dist/types/src/containers/DeckLayout/Fallback.d.ts +2 -0
  164. package/dist/types/src/containers/DeckLayout/Fallback.d.ts.map +1 -0
  165. package/dist/types/src/containers/DeckLayout/Popover.d.ts +5 -0
  166. package/dist/types/src/containers/DeckLayout/Popover.d.ts.map +1 -0
  167. package/dist/types/src/{components → containers}/DeckLayout/Toast.d.ts +3 -3
  168. package/dist/types/src/containers/DeckLayout/Toast.d.ts.map +1 -0
  169. package/dist/types/src/containers/DeckLayout/constants.d.ts.map +1 -0
  170. package/dist/types/src/containers/DeckLayout/index.d.ts +4 -0
  171. package/dist/types/src/containers/DeckLayout/index.d.ts.map +1 -0
  172. package/dist/types/src/containers/Plank/Plank.d.ts +17 -0
  173. package/dist/types/src/containers/Plank/Plank.d.ts.map +1 -0
  174. package/dist/types/src/containers/Plank/Plank.stories.d.ts +67 -0
  175. package/dist/types/src/containers/Plank/Plank.stories.d.ts.map +1 -0
  176. package/dist/types/src/containers/Plank/PlankComponent.d.ts +17 -0
  177. package/dist/types/src/containers/Plank/PlankComponent.d.ts.map +1 -0
  178. package/dist/types/src/containers/Plank/PlankContent.d.ts +10 -0
  179. package/dist/types/src/containers/Plank/PlankContent.d.ts.map +1 -0
  180. package/dist/types/src/{components → containers}/Plank/PlankControls.d.ts +5 -6
  181. package/dist/types/src/containers/Plank/PlankControls.d.ts.map +1 -0
  182. package/dist/types/src/containers/Plank/PlankError.d.ts +15 -0
  183. package/dist/types/src/containers/Plank/PlankError.d.ts.map +1 -0
  184. package/dist/types/src/{components → containers}/Plank/PlankHeading.d.ts +5 -4
  185. package/dist/types/src/containers/Plank/PlankHeading.d.ts.map +1 -0
  186. package/dist/types/src/{components → containers}/Plank/PlankLoading.d.ts.map +1 -1
  187. package/dist/types/src/containers/Plank/PlankRoot.d.ts +37 -0
  188. package/dist/types/src/containers/Plank/PlankRoot.d.ts.map +1 -0
  189. package/dist/types/src/containers/Plank/index.d.ts +4 -0
  190. package/dist/types/src/containers/Plank/index.d.ts.map +1 -0
  191. package/dist/types/src/containers/Sidebar/ComplementarySidebar.d.ts.map +1 -0
  192. package/dist/types/src/containers/Sidebar/Sidebar.d.ts.map +1 -0
  193. package/dist/types/src/{components → containers}/Sidebar/SidebarButton.d.ts +1 -1
  194. package/dist/types/src/containers/Sidebar/SidebarButton.d.ts.map +1 -0
  195. package/dist/types/src/{components → containers}/Sidebar/index.d.ts.map +1 -1
  196. package/dist/types/src/containers/index.d.ts +6 -0
  197. package/dist/types/src/containers/index.d.ts.map +1 -0
  198. package/dist/types/src/hooks/index.d.ts +2 -1
  199. package/dist/types/src/hooks/index.d.ts.map +1 -1
  200. package/dist/types/src/hooks/useBreakpoints.d.ts +1 -1
  201. package/dist/types/src/hooks/useCompanions.d.ts.map +1 -1
  202. package/dist/types/src/hooks/useDeckCompanions.d.ts +4 -4
  203. package/dist/types/src/hooks/useDeckCompanions.d.ts.map +1 -1
  204. package/dist/types/src/hooks/useDeckState.d.ts +17 -0
  205. package/dist/types/src/hooks/useDeckState.d.ts.map +1 -0
  206. package/dist/types/src/hooks/useMainSize.d.ts +2 -2
  207. package/dist/types/src/hooks/useMainSize.d.ts.map +1 -1
  208. package/dist/types/src/hooks/useNodeActionExpander.d.ts +1 -1
  209. package/dist/types/src/hooks/useNodeActionExpander.d.ts.map +1 -1
  210. package/dist/types/src/hooks/useSelectedCompanion.d.ts +13 -0
  211. package/dist/types/src/hooks/useSelectedCompanion.d.ts.map +1 -0
  212. package/dist/types/src/index.d.ts +1 -4
  213. package/dist/types/src/index.d.ts.map +1 -1
  214. package/dist/types/src/layout.d.ts +20 -9
  215. package/dist/types/src/layout.d.ts.map +1 -1
  216. package/dist/types/src/layout.test.d.ts +2 -0
  217. package/dist/types/src/layout.test.d.ts.map +1 -0
  218. package/dist/types/src/meta.d.ts +2 -2
  219. package/dist/types/src/meta.d.ts.map +1 -1
  220. package/dist/types/src/operations/add-toast.d.ts +5 -0
  221. package/dist/types/src/operations/add-toast.d.ts.map +1 -0
  222. package/dist/types/src/operations/adjust.d.ts +5 -0
  223. package/dist/types/src/operations/adjust.d.ts.map +1 -0
  224. package/dist/types/src/operations/close.d.ts +5 -0
  225. package/dist/types/src/operations/close.d.ts.map +1 -0
  226. package/dist/types/src/operations/helpers.d.ts +3 -0
  227. package/dist/types/src/operations/helpers.d.ts.map +1 -0
  228. package/dist/types/src/operations/index.d.ts +3 -0
  229. package/dist/types/src/operations/index.d.ts.map +1 -0
  230. package/dist/types/src/operations/open.d.ts +5 -0
  231. package/dist/types/src/operations/open.d.ts.map +1 -0
  232. package/dist/types/src/operations/revert-workspace.d.ts +5 -0
  233. package/dist/types/src/operations/revert-workspace.d.ts.map +1 -0
  234. package/dist/types/src/operations/scroll-into-view.d.ts +5 -0
  235. package/dist/types/src/operations/scroll-into-view.d.ts.map +1 -0
  236. package/dist/types/src/operations/set-layout-mode.d.ts +9 -0
  237. package/dist/types/src/operations/set-layout-mode.d.ts.map +1 -0
  238. package/dist/types/src/operations/set.d.ts +5 -0
  239. package/dist/types/src/operations/set.d.ts.map +1 -0
  240. package/dist/types/src/operations/switch-workspace.d.ts +5 -0
  241. package/dist/types/src/operations/switch-workspace.d.ts.map +1 -0
  242. package/dist/types/src/operations/update-companion.d.ts +5 -0
  243. package/dist/types/src/operations/update-companion.d.ts.map +1 -0
  244. package/dist/types/src/operations/update-complementary.d.ts +5 -0
  245. package/dist/types/src/operations/update-complementary.d.ts.map +1 -0
  246. package/dist/types/src/operations/update-dialog.d.ts +5 -0
  247. package/dist/types/src/operations/update-dialog.d.ts.map +1 -0
  248. package/dist/types/src/operations/update-plank-size.d.ts +5 -0
  249. package/dist/types/src/operations/update-plank-size.d.ts.map +1 -0
  250. package/dist/types/src/operations/update-popover.d.ts +5 -0
  251. package/dist/types/src/operations/update-popover.d.ts.map +1 -0
  252. package/dist/types/src/operations/update-sidebar.d.ts +5 -0
  253. package/dist/types/src/operations/update-sidebar.d.ts.map +1 -0
  254. package/dist/types/src/plugin.d.ts +4 -0
  255. package/dist/types/src/plugin.d.ts.map +1 -0
  256. package/dist/types/src/translations.d.ts +48 -55
  257. package/dist/types/src/translations.d.ts.map +1 -1
  258. package/dist/types/src/types/DeckCapabilities.d.ts +188 -0
  259. package/dist/types/src/types/DeckCapabilities.d.ts.map +1 -0
  260. package/dist/types/src/types/DeckEvents.d.ts +5 -0
  261. package/dist/types/src/types/DeckEvents.d.ts.map +1 -0
  262. package/dist/types/src/types/DeckOperation.d.ts +15 -0
  263. package/dist/types/src/types/DeckOperation.d.ts.map +1 -0
  264. package/dist/types/src/types/Settings.d.ts +10 -0
  265. package/dist/types/src/types/Settings.d.ts.map +1 -0
  266. package/dist/types/src/types/index.d.ts +4 -0
  267. package/dist/types/src/types/index.d.ts.map +1 -1
  268. package/dist/types/src/types/schema.d.ts +69 -69
  269. package/dist/types/src/types/schema.d.ts.map +1 -1
  270. package/dist/types/src/util/index.d.ts +2 -1
  271. package/dist/types/src/util/index.d.ts.map +1 -1
  272. package/dist/types/src/util/layoutAppliesTopbar.d.ts +1 -1
  273. package/dist/types/src/util/layoutAppliesTopbar.d.ts.map +1 -1
  274. package/dist/types/src/util/plank-url-params.d.ts +14 -0
  275. package/dist/types/src/util/plank-url-params.d.ts.map +1 -0
  276. package/dist/types/src/util/plank-url-params.test.d.ts +2 -0
  277. package/dist/types/src/util/plank-url-params.test.d.ts.map +1 -0
  278. package/dist/types/src/util/sanitize-persisted-state.d.ts +19 -0
  279. package/dist/types/src/util/sanitize-persisted-state.d.ts.map +1 -0
  280. package/dist/types/src/util/sanitize-persisted-state.test.d.ts +2 -0
  281. package/dist/types/src/util/sanitize-persisted-state.test.d.ts.map +1 -0
  282. package/dist/types/src/util/set-active.d.ts +19 -4
  283. package/dist/types/src/util/set-active.d.ts.map +1 -1
  284. package/dist/types/src/util/set-active.test.d.ts +2 -0
  285. package/dist/types/src/util/set-active.test.d.ts.map +1 -0
  286. package/dist/types/tsconfig.tsbuildinfo +1 -1
  287. package/package.json +126 -62
  288. package/src/DeckPlugin.node.ts +17 -0
  289. package/src/DeckPlugin.test.ts +27 -0
  290. package/src/DeckPlugin.ts +43 -56
  291. package/src/DeckPlugin.workerd.ts +16 -0
  292. package/src/capabilities/app-graph-builder.ts +111 -127
  293. package/src/capabilities/check-app-scheme.ts +123 -24
  294. package/src/capabilities/index.ts +14 -14
  295. package/src/capabilities/notification-tracker.ts +170 -0
  296. package/src/capabilities/operation-handler.ts +16 -0
  297. package/src/capabilities/react-root.tsx +40 -28
  298. package/src/capabilities/react-surface.tsx +28 -22
  299. package/src/capabilities/settings.ts +29 -20
  300. package/src/capabilities/state.ts +74 -96
  301. package/src/capabilities/tools.ts +57 -50
  302. package/src/capabilities/url-handler.ts +290 -48
  303. package/src/components/DeckSettings/DeckSettings.stories.tsx +37 -0
  304. package/src/components/DeckSettings/DeckSettings.tsx +19 -88
  305. package/src/components/DeckSettings/index.ts +2 -2
  306. package/src/components/Matrix/Matrix.stories.tsx +220 -0
  307. package/src/components/Matrix/Matrix.tsx +205 -0
  308. package/src/components/Matrix/SPEC.md +219 -0
  309. package/src/components/Matrix/index.ts +6 -0
  310. package/src/components/index.ts +6 -3
  311. package/src/containers/Deck/Banner.tsx +41 -0
  312. package/src/containers/Deck/Deck.stories.tsx +81 -0
  313. package/src/containers/Deck/Deck.tsx +21 -0
  314. package/src/containers/Deck/DeckContent.tsx +102 -0
  315. package/src/containers/Deck/DeckRoot.tsx +50 -0
  316. package/src/containers/Deck/DeckViewport.tsx +450 -0
  317. package/src/{components/DeckLayout → containers/Deck}/StatusBar.tsx +4 -4
  318. package/src/containers/Deck/index.ts +5 -0
  319. package/src/{components → containers}/DeckLayout/ActiveNode.tsx +9 -4
  320. package/src/containers/DeckLayout/DeckLayout.stories.tsx +395 -0
  321. package/src/containers/DeckLayout/DeckLayout.tsx +62 -0
  322. package/src/containers/DeckLayout/Dialog.tsx +52 -0
  323. package/src/containers/DeckLayout/Fallback.tsx +24 -0
  324. package/src/containers/DeckLayout/Popover.tsx +166 -0
  325. package/src/containers/DeckLayout/Toast.tsx +76 -0
  326. package/src/{components → containers}/DeckLayout/constants.ts +1 -0
  327. package/src/{components → containers}/DeckLayout/index.ts +3 -2
  328. package/src/containers/Plank/Plank.stories.tsx +108 -0
  329. package/src/containers/Plank/Plank.tsx +22 -0
  330. package/src/containers/Plank/PlankComponent.tsx +223 -0
  331. package/src/containers/Plank/PlankContent.tsx +45 -0
  332. package/src/{components → containers}/Plank/PlankControls.tsx +43 -37
  333. package/src/containers/Plank/PlankError.tsx +82 -0
  334. package/src/{components → containers}/Plank/PlankHeading.tsx +67 -64
  335. package/src/{components → containers}/Plank/PlankLoading.tsx +1 -1
  336. package/src/containers/Plank/PlankRoot.tsx +49 -0
  337. package/src/{components → containers}/Plank/index.ts +0 -2
  338. package/src/containers/Sidebar/ComplementarySidebar.tsx +189 -0
  339. package/src/containers/Sidebar/Sidebar.tsx +39 -0
  340. package/src/containers/Sidebar/SidebarButton.tsx +100 -0
  341. package/src/containers/index.ts +11 -0
  342. package/src/hooks/index.ts +2 -1
  343. package/src/hooks/useCompanions.ts +3 -3
  344. package/src/hooks/useDeckCompanions.ts +8 -11
  345. package/src/hooks/useDeckState.ts +73 -0
  346. package/src/hooks/useMainSize.ts +2 -2
  347. package/src/hooks/useNodeActionExpander.ts +4 -4
  348. package/src/hooks/useSelectedCompanion.ts +32 -0
  349. package/src/index.ts +1 -4
  350. package/src/layout.test.ts +59 -0
  351. package/src/layout.ts +38 -40
  352. package/src/meta.ts +24 -6
  353. package/src/operations/add-toast.ts +24 -0
  354. package/src/operations/adjust.ts +82 -0
  355. package/src/operations/close.ts +35 -0
  356. package/src/operations/helpers.ts +22 -0
  357. package/src/operations/index.ts +23 -0
  358. package/src/operations/open.ts +201 -0
  359. package/src/operations/revert-workspace.ts +22 -0
  360. package/src/operations/scroll-into-view.ts +24 -0
  361. package/src/operations/set-layout-mode.ts +84 -0
  362. package/src/operations/set.ts +36 -0
  363. package/src/operations/switch-workspace.ts +66 -0
  364. package/src/operations/update-companion.ts +35 -0
  365. package/src/operations/update-complementary.ts +33 -0
  366. package/src/operations/update-dialog.ts +34 -0
  367. package/src/operations/update-plank-size.ts +28 -0
  368. package/src/operations/update-popover.ts +36 -0
  369. package/src/operations/update-sidebar.ts +28 -0
  370. package/src/plugin.ts +11 -0
  371. package/src/translations.ts +48 -54
  372. package/src/types/DeckCapabilities.ts +34 -0
  373. package/src/types/DeckEvents.ts +21 -0
  374. package/src/types/DeckOperation.ts +56 -0
  375. package/src/types/Settings.ts +36 -0
  376. package/src/types/index.ts +5 -0
  377. package/src/types/schema.ts +49 -58
  378. package/src/util/index.ts +2 -1
  379. package/src/util/layoutAppliesTopbar.ts +2 -2
  380. package/src/util/plank-url-params.test.ts +85 -0
  381. package/src/util/plank-url-params.ts +36 -0
  382. package/src/util/sanitize-persisted-state.test.ts +79 -0
  383. package/src/util/sanitize-persisted-state.ts +52 -0
  384. package/src/util/set-active.test.ts +106 -0
  385. package/src/util/set-active.ts +50 -30
  386. package/src/vite-env.d.ts +10 -0
  387. package/dist/lib/browser/app-graph-builder-D74NTOMK.mjs +0 -128
  388. package/dist/lib/browser/app-graph-builder-D74NTOMK.mjs.map +0 -7
  389. package/dist/lib/browser/check-app-scheme-HIEVFAAX.mjs +0 -32
  390. package/dist/lib/browser/check-app-scheme-HIEVFAAX.mjs.map +0 -7
  391. package/dist/lib/browser/chunk-5KMJPIQC.mjs +0 -16
  392. package/dist/lib/browser/chunk-5KMJPIQC.mjs.map +0 -7
  393. package/dist/lib/browser/chunk-CNTGBCMK.mjs +0 -145
  394. package/dist/lib/browser/chunk-CNTGBCMK.mjs.map +0 -7
  395. package/dist/lib/browser/chunk-F3VCCHVL.mjs +0 -162
  396. package/dist/lib/browser/chunk-F3VCCHVL.mjs.map +0 -7
  397. package/dist/lib/browser/chunk-QKCGZ45E.mjs +0 -128
  398. package/dist/lib/browser/chunk-QKCGZ45E.mjs.map +0 -7
  399. package/dist/lib/browser/chunk-UXLU6CMW.mjs +0 -16
  400. package/dist/lib/browser/chunk-UXLU6CMW.mjs.map +0 -7
  401. package/dist/lib/browser/chunk-VBYJ664A.mjs +0 -132
  402. package/dist/lib/browser/chunk-VBYJ664A.mjs.map +0 -7
  403. package/dist/lib/browser/chunk-VUJ6UNIJ.mjs +0 -1553
  404. package/dist/lib/browser/chunk-VUJ6UNIJ.mjs.map +0 -7
  405. package/dist/lib/browser/index.mjs +0 -174
  406. package/dist/lib/browser/index.mjs.map +0 -7
  407. package/dist/lib/browser/intent-resolver-UA4YQGAC.mjs +0 -524
  408. package/dist/lib/browser/intent-resolver-UA4YQGAC.mjs.map +0 -7
  409. package/dist/lib/browser/meta.json +0 -1
  410. package/dist/lib/browser/react-root-JAMHKYWN.mjs +0 -44
  411. package/dist/lib/browser/react-root-JAMHKYWN.mjs.map +0 -7
  412. package/dist/lib/browser/react-surface-6LW337ZT.mjs +0 -40
  413. package/dist/lib/browser/react-surface-6LW337ZT.mjs.map +0 -7
  414. package/dist/lib/browser/settings-SDPTOCCM.mjs +0 -30
  415. package/dist/lib/browser/settings-SDPTOCCM.mjs.map +0 -7
  416. package/dist/lib/browser/state-7IFAGZQO.mjs +0 -12
  417. package/dist/lib/browser/toolkit-L5CFXJCF.mjs +0 -52
  418. package/dist/lib/browser/toolkit-L5CFXJCF.mjs.map +0 -7
  419. package/dist/lib/browser/types/index.mjs +0 -32
  420. package/dist/lib/browser/url-handler-QEYGYE2H.mjs +0 -70
  421. package/dist/lib/browser/url-handler-QEYGYE2H.mjs.map +0 -7
  422. package/dist/types/src/capabilities/capabilities.d.ts +0 -184
  423. package/dist/types/src/capabilities/capabilities.d.ts.map +0 -1
  424. package/dist/types/src/capabilities/intent-resolver.d.ts +0 -4
  425. package/dist/types/src/capabilities/intent-resolver.d.ts.map +0 -1
  426. package/dist/types/src/capabilities/toolkit.d.ts +0 -25
  427. package/dist/types/src/capabilities/toolkit.d.ts.map +0 -1
  428. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts.map +0 -1
  429. package/dist/types/src/components/DeckLayout/Banner.d.ts.map +0 -1
  430. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts +0 -3
  431. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts.map +0 -1
  432. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +0 -1
  433. package/dist/types/src/components/DeckLayout/DeckLayout.stories.d.ts +0 -74
  434. package/dist/types/src/components/DeckLayout/DeckLayout.stories.d.ts.map +0 -1
  435. package/dist/types/src/components/DeckLayout/DeckMain.d.ts +0 -3
  436. package/dist/types/src/components/DeckLayout/DeckMain.d.ts.map +0 -1
  437. package/dist/types/src/components/DeckLayout/Dialog.d.ts.map +0 -1
  438. package/dist/types/src/components/DeckLayout/Fallback.d.ts +0 -3
  439. package/dist/types/src/components/DeckLayout/Fallback.d.ts.map +0 -1
  440. package/dist/types/src/components/DeckLayout/Popover.d.ts +0 -5
  441. package/dist/types/src/components/DeckLayout/Popover.d.ts.map +0 -1
  442. package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +0 -1
  443. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +0 -1
  444. package/dist/types/src/components/DeckLayout/Topbar.d.ts +0 -3
  445. package/dist/types/src/components/DeckLayout/Topbar.d.ts.map +0 -1
  446. package/dist/types/src/components/DeckLayout/constants.d.ts.map +0 -1
  447. package/dist/types/src/components/DeckLayout/index.d.ts +0 -4
  448. package/dist/types/src/components/DeckLayout/index.d.ts.map +0 -1
  449. package/dist/types/src/components/Plank/Plank.d.ts +0 -27
  450. package/dist/types/src/components/Plank/Plank.d.ts.map +0 -1
  451. package/dist/types/src/components/Plank/Plank.stories.d.ts +0 -89
  452. package/dist/types/src/components/Plank/Plank.stories.d.ts.map +0 -1
  453. package/dist/types/src/components/Plank/PlankControls.d.ts.map +0 -1
  454. package/dist/types/src/components/Plank/PlankError.d.ts +0 -13
  455. package/dist/types/src/components/Plank/PlankError.d.ts.map +0 -1
  456. package/dist/types/src/components/Plank/PlankHeading.d.ts.map +0 -1
  457. package/dist/types/src/components/Plank/index.d.ts +0 -6
  458. package/dist/types/src/components/Plank/index.d.ts.map +0 -1
  459. package/dist/types/src/components/Sidebar/ComplementarySidebar.d.ts.map +0 -1
  460. package/dist/types/src/components/Sidebar/Sidebar.d.ts.map +0 -1
  461. package/dist/types/src/components/Sidebar/SidebarButton.d.ts.map +0 -1
  462. package/dist/types/src/components/fragments.d.ts +0 -4
  463. package/dist/types/src/components/fragments.d.ts.map +0 -1
  464. package/dist/types/src/events.d.ts +0 -4
  465. package/dist/types/src/events.d.ts.map +0 -1
  466. package/dist/types/src/hooks/useHoistStatusbar.d.ts +0 -3
  467. package/dist/types/src/hooks/useHoistStatusbar.d.ts.map +0 -1
  468. package/dist/types/src/util/overscroll.d.ts +0 -47
  469. package/dist/types/src/util/overscroll.d.ts.map +0 -1
  470. package/src/capabilities/capabilities.ts +0 -14
  471. package/src/capabilities/intent-resolver.ts +0 -471
  472. package/src/capabilities/toolkit.ts +0 -66
  473. package/src/components/DeckLayout/Banner.tsx +0 -39
  474. package/src/components/DeckLayout/ContentEmpty.tsx +0 -31
  475. package/src/components/DeckLayout/DeckLayout.stories.tsx +0 -63
  476. package/src/components/DeckLayout/DeckLayout.tsx +0 -32
  477. package/src/components/DeckLayout/DeckMain.tsx +0 -281
  478. package/src/components/DeckLayout/Dialog.tsx +0 -36
  479. package/src/components/DeckLayout/Fallback.tsx +0 -28
  480. package/src/components/DeckLayout/Popover.tsx +0 -95
  481. package/src/components/DeckLayout/Toast.tsx +0 -86
  482. package/src/components/DeckLayout/Topbar.tsx +0 -11
  483. package/src/components/Plank/Plank.stories.tsx +0 -54
  484. package/src/components/Plank/Plank.tsx +0 -299
  485. package/src/components/Plank/PlankError.tsx +0 -49
  486. package/src/components/Sidebar/ComplementarySidebar.tsx +0 -214
  487. package/src/components/Sidebar/Sidebar.tsx +0 -42
  488. package/src/components/Sidebar/SidebarButton.tsx +0 -88
  489. package/src/components/fragments.ts +0 -14
  490. package/src/events.ts +0 -11
  491. package/src/hooks/useHoistStatusbar.ts +0 -27
  492. package/src/util/overscroll.ts +0 -69
  493. /package/dist/lib/{browser/state-7IFAGZQO.mjs.map → neutral/check-app-scheme-INSOF72J.mjs.map} +0 -0
  494. /package/dist/lib/{browser/types/index.mjs.map → neutral/chunk-J5LGTIGS.mjs.map} +0 -0
  495. /package/dist/types/src/{components/DeckLayout → containers/Deck}/StatusBar.d.ts +0 -0
  496. /package/dist/types/src/{components → containers}/DeckLayout/ActiveNode.d.ts +0 -0
  497. /package/dist/types/src/{components → containers}/DeckLayout/DeckLayout.d.ts +0 -0
  498. /package/dist/types/src/{components → containers}/DeckLayout/Dialog.d.ts +0 -0
  499. /package/dist/types/src/{components → containers}/DeckLayout/constants.d.ts +0 -0
  500. /package/dist/types/src/{components → containers}/Plank/PlankLoading.d.ts +0 -0
  501. /package/dist/types/src/{components → containers}/Sidebar/ComplementarySidebar.d.ts +0 -0
  502. /package/dist/types/src/{components → containers}/Sidebar/Sidebar.d.ts +0 -0
  503. /package/dist/types/src/{components → containers}/Sidebar/index.d.ts +0 -0
  504. /package/src/{components → containers}/Sidebar/index.ts +0 -0
@@ -2,59 +2,301 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Capabilities, LayoutAction, type PluginContext, contributes, createIntent } from '@dxos/app-framework';
6
- import { scheduledEffect } from '@dxos/echo-signals/core';
7
-
8
- import { defaultDeck } from '../types';
9
-
10
- import { DeckCapabilities } from './capabilities';
11
-
12
- // TODO(wittjosiah): Cleanup the url handling. May justify introducing routing capabilities.
13
- export default async (context: PluginContext) => {
14
- const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);
15
- const state = context.getCapability(DeckCapabilities.MutableDeckState);
16
-
17
- const handleNavigation = async () => {
18
- const pathname = window.location.pathname;
19
- if (pathname === '/reset') {
20
- state.activeDeck = 'default';
21
- state.decks = {
22
- default: { ...defaultDeck },
23
- };
24
- window.location.pathname = '/';
25
- return;
26
- }
5
+ import * as Effect from 'effect/Effect';
6
+
7
+ import { Capabilities, Capability } from '@dxos/app-framework';
8
+ import {
9
+ AppCapabilities,
10
+ LayoutOperation,
11
+ NOT_FOUND_PATH,
12
+ expandPath,
13
+ fromUrlPath,
14
+ getWorkspaceFromPath,
15
+ toUrlPath,
16
+ } from '@dxos/app-toolkit';
17
+ import { Operation } from '@dxos/compute';
18
+ import { EffectEx } from '@dxos/effect';
19
+ import { invariant } from '@dxos/invariant';
20
+ import { log } from '@dxos/log';
21
+ import { Node } from '@dxos/plugin-graph';
22
+ import { isTauri } from '@dxos/util';
23
+
24
+ import { DeckCapabilities, type StoredDeckState, defaultDeck } from '#types';
25
+
26
+ import { updateActiveDeck } from '../operations/helpers';
27
+ import { deserializePlanks, serializePlanks, stripPlanks } from '../util';
28
+ import { shouldDeferNavigationHandlers } from './check-app-scheme';
29
+
30
+ /** Dispatch all NavigationHandler contributions with a given URL. */
31
+ const dispatchNavigationHandlers = Effect.fn(function* (url: URL) {
32
+ const handlers = yield* Capability.getAll(AppCapabilities.NavigationHandler);
33
+ yield* Effect.all(
34
+ handlers.map((handler) => handler(url)),
35
+ { concurrency: 'unbounded' },
36
+ );
37
+ });
38
+
39
+ export default Capability.makeModule(
40
+ Effect.fnUntraced(function* () {
41
+ const operationService = yield* Capability.get(Capabilities.OperationInvoker);
42
+ const capabilities = yield* Capability.Service;
43
+ const registry = yield* Capability.get(Capabilities.AtomRegistry);
44
+ const stateAtom = yield* Capability.get(DeckCapabilities.State);
45
+ const settingsAtom = yield* Capability.get(DeckCapabilities.Settings);
46
+
47
+ const provideServices = <A, E>(effect: Effect.Effect<A, E, Capability.Service | Operation.Service>) =>
48
+ effect.pipe(
49
+ Effect.provideService(Capability.Service, capabilities),
50
+ Effect.provideService(Operation.Service, operationService),
51
+ );
52
+
53
+ // Helper to get state.
54
+ const getState = () => registry.get(stateAtom);
55
+
56
+ // Helper to get computed deck from state.
57
+ const getDeck = () => {
58
+ const state = getState();
59
+ const deck = state.decks[state.activeDeck];
60
+ invariant(deck, `Deck not found: ${state.activeDeck}`);
61
+ return deck;
62
+ };
63
+
64
+ // Helper to update state.
65
+ const updateState = (fn: (current: StoredDeckState) => StoredDeckState) => {
66
+ registry.set(stateAtom, fn(getState()));
67
+ };
68
+
69
+ const handleNavigation = Effect.fn(function* (url?: URL) {
70
+ const { graph } = yield* Capability.get(AppCapabilities.AppGraph);
71
+ const resolvedUrl = url ?? new URL(window.location.href);
72
+ // When native redirect is active, check-app-scheme owns the initial dispatch
73
+ // to prevent one-time tokens from being consumed before the native app can use them.
74
+ const settings = registry.get(settingsAtom);
75
+ const deferHandlers = settings?.enableNativeRedirect && shouldDeferNavigationHandlers();
76
+ if (!deferHandlers) {
77
+ yield* dispatchNavigationHandlers(resolvedUrl);
78
+ }
79
+
80
+ const pathname = resolvedUrl.pathname;
81
+ const state = getState();
82
+ if (pathname === '/reset') {
83
+ updateState((s) => ({
84
+ ...s,
85
+ activeDeck: 'default',
86
+ decks: {
87
+ default: { ...defaultDeck },
88
+ },
89
+ }));
90
+ window.location.pathname = '/';
91
+ return;
92
+ }
93
+
94
+ const qualifiedId = fromUrlPath(pathname);
95
+ const workspace = getWorkspaceFromPath(qualifiedId);
96
+ if (workspace !== Node.RootId && workspace !== state.activeDeck) {
97
+ yield* Operation.invoke(LayoutOperation.SwitchWorkspace, { subject: workspace });
98
+ }
99
+
100
+ const deck = getDeck();
101
+ const activeId = qualifiedId !== workspace ? qualifiedId : undefined;
102
+ if (activeId) {
103
+ // Ensure the object referenced by the URL is open in the deck.
104
+ // Open validates the target and may redirect to 404, returning the resolved IDs.
105
+ const resolvedIds = yield* Operation.invoke(LayoutOperation.Open, { subject: [activeId] });
106
+ // If not already in solo mode, switch to solo for the resolved target.
107
+ if (!deck.solo) {
108
+ yield* Operation.invoke(LayoutOperation.SetLayoutMode, {
109
+ subject: resolvedIds?.[0] ?? activeId,
110
+ mode: 'solo',
111
+ });
112
+ }
113
+ } else if (deck.solo && deck.solo !== NOT_FOUND_PATH) {
114
+ // Stay in solo mode; redirect URL to reflect the current solo item.
115
+ // Do not switch to deck mode here — only explicit user action should change layout mode.
116
+ const path = toUrlPath(deck.solo);
117
+ if (window.location.pathname !== path) {
118
+ history.replaceState(null, '', `${path}${stripPlanks(window.location.search)}`);
119
+ }
120
+ } else if (!activeId && !deck.solo) {
121
+ // Multi-mode: restore planks from query params.
122
+ const plankIds = deserializePlanks(resolvedUrl);
123
+ if (plankIds.length > 0) {
124
+ for (const plankId of plankIds) {
125
+ expandPath(graph, plankId);
126
+ }
127
+ updateState((state) => updateActiveDeck(state, { active: plankIds, initialized: true }));
128
+ }
129
+ }
130
+ });
131
+
132
+ const onPopState = () => void EffectEx.runAndForwardErrors(provideServices(handleNavigation()));
27
133
 
28
- const [_, nextDeck, nextSolo] = pathname.split('/');
29
- if (nextDeck && nextDeck !== state.activeDeck) {
30
- await dispatch(createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: nextDeck }));
134
+ // Install before handleNavigation()/state-sync push entries on top of the sentinel.
135
+ const sentinelKey = installLeaveTrap();
136
+
137
+ // Landing on the sentinel means a Back is about to leave Composer; confirm and act on it.
138
+ // The guard stops our own back()/forward() from re-entering.
139
+ let handlingSentinel = false;
140
+ const onCurrentEntryChange = () => {
141
+ const current = window.navigation.currentEntry;
142
+ if (handlingSentinel || !current || current.key !== sentinelKey) {
143
+ return;
144
+ }
145
+ handlingSentinel = true;
146
+ queueMicrotask(() => {
147
+ if (window.confirm('Leave Composer?')) {
148
+ history.back(); // Past the sentinel to the prior page.
149
+ } else {
150
+ history.forward(); // Back to where the user was.
151
+ }
152
+ setTimeout(() => {
153
+ handlingSentinel = false;
154
+ });
155
+ });
156
+ };
157
+
158
+ yield* provideServices(handleNavigation());
159
+ window.addEventListener('popstate', onPopState);
160
+ if ('navigation' in window) {
161
+ window.navigation.addEventListener('currententrychange', onCurrentEntryChange);
31
162
  }
32
163
 
33
- if (nextSolo && nextSolo !== state.deck.solo) {
34
- await dispatch(
35
- createIntent(LayoutAction.SetLayoutMode, { part: 'mode', subject: nextSolo, options: { mode: 'solo' } }),
164
+ // Tauri deep link support.
165
+ let unlistenDeepLink: (() => void) | undefined;
166
+ if (isTauri()) {
167
+ yield* Effect.gen(function* () {
168
+ const { getCurrent, onOpenUrl } = yield* Effect.promise(() => import('@tauri-apps/plugin-deep-link'));
169
+
170
+ const launchUrls = yield* Effect.promise(() => getCurrent());
171
+ if (launchUrls && launchUrls.length > 0) {
172
+ log('app launched with deep links', { urls: launchUrls });
173
+ for (const urlString of launchUrls) {
174
+ yield* provideServices(handleDeepLink(urlString, handleNavigation));
175
+ }
176
+ }
177
+
178
+ unlistenDeepLink = yield* Effect.promise(() =>
179
+ onOpenUrl((urls) => {
180
+ for (const urlString of urls) {
181
+ void EffectEx.runAndForwardErrors(provideServices(handleDeepLink(urlString, handleNavigation)));
182
+ }
183
+ }),
184
+ );
185
+ }).pipe(
186
+ Effect.catchAll((error) => Effect.sync(() => log.warn('failed to initialize deep link listener', { error }))),
36
187
  );
37
- } else if (!nextSolo && state.deck.solo) {
38
- await dispatch(createIntent(LayoutAction.SetLayoutMode, { part: 'mode', options: { mode: 'deck' } }));
39
188
  }
40
- };
41
-
42
- await handleNavigation();
43
- window.addEventListener('popstate', handleNavigation);
44
-
45
- const unsubscribe = scheduledEffect(
46
- () => ({ solo: state.deck.solo, activeDeck: state.activeDeck }),
47
- ({ solo, activeDeck }) => {
48
- const path = solo ? `/${activeDeck}/${solo}` : `/${activeDeck}`;
49
- if (window.location.pathname !== path) {
50
- // TODO(thure): In some browsers, this only preserves the most recent state change, even though this is not `history.replace`…
51
- history.pushState(null, '', `${path}${window.location.search}`);
189
+
190
+ // Sync URL with layout state changes.
191
+ let lastSolo: string | undefined;
192
+ let lastActiveDeck: string | undefined;
193
+ let lastActiveKey: string | undefined;
194
+ const unsubscribe = registry.subscribe(stateAtom, () => {
195
+ const state = getState();
196
+ const deck = getDeck();
197
+ const solo = deck.solo;
198
+ const activeDeck = state.activeDeck;
199
+ const activeKey = solo ? undefined : JSON.stringify(deck.active);
200
+
201
+ if (solo !== lastSolo || activeDeck !== lastActiveDeck || activeKey !== lastActiveKey) {
202
+ lastSolo = solo;
203
+ lastActiveDeck = activeDeck;
204
+ lastActiveKey = activeKey;
205
+
206
+ const path = solo && solo !== NOT_FOUND_PATH ? toUrlPath(solo) : toUrlPath(activeDeck);
207
+ const search = !solo
208
+ ? serializePlanks(deck.active, window.location.search)
209
+ : stripPlanks(window.location.search);
210
+ const newUrl = `${path}${search}`;
211
+
212
+ if (`${window.location.pathname}${window.location.search}` !== newUrl) {
213
+ history.pushState(null, '', newUrl);
214
+ }
52
215
  }
53
- },
54
- );
216
+ });
55
217
 
56
- return contributes(Capabilities.Null, null, () => {
57
- window.removeEventListener('popstate', handleNavigation);
58
- unsubscribe();
59
- });
218
+ return Capability.contributes(Capabilities.Null, null, () =>
219
+ Effect.sync(() => {
220
+ window.removeEventListener('popstate', onPopState);
221
+ if ('navigation' in window) {
222
+ window.navigation.removeEventListener('currententrychange', onCurrentEntryChange);
223
+ }
224
+ unsubscribe();
225
+ unlistenDeepLink?.();
226
+ }),
227
+ );
228
+ }),
229
+ );
230
+
231
+ /**
232
+ * sessionStorage key holding the sentinel history entry's key. The entry is identified by its
233
+ * (reload-stable) Navigation API key rather than by entry state, because the deck overwrites
234
+ * history state via replaceState during URL sync — which would erase a state marker. sessionStorage
235
+ * survives reloads within the tab and the deck never touches it.
236
+ */
237
+ const SENTINEL_STORAGE_KEY = 'dxos.composer.deck.leaveTrap.sentinelKey';
238
+
239
+ /**
240
+ * Insert a "sentinel" history entry beneath the app's working entries, so a Back-press that would
241
+ * leave Composer instead lands on the sentinel — where `onCurrentEntryChange` confirms the exit. A
242
+ * cross-document back is uncancelable and `beforeunload` cannot distinguish reload from leave, so
243
+ * this same-document floor is required; reload fires no traversal and is never trapped. Requires the
244
+ * Navigation API (Chromium); no-op otherwise. Idempotent across reloads via the sessionStorage-held
245
+ * entry key, so the sentinel is not duplicated. Returns the sentinel entry's key, or undefined.
246
+ */
247
+ const installLeaveTrap = (): string | undefined => {
248
+ if (!('navigation' in window)) {
249
+ return undefined;
250
+ }
251
+ const saved = sessionStorage.getItem(SENTINEL_STORAGE_KEY);
252
+ if (saved && window.navigation.entries().some((entry) => entry.key === saved)) {
253
+ // The sentinel survived (reload, or the user returned to Composer after leaving). If we are
254
+ // sitting ON it — e.g. the user left via the sentinel then came back Forward onto it — push a
255
+ // working entry above so the user is above the floor again and the trap re-arms.
256
+ if (window.navigation.currentEntry?.key === saved) {
257
+ history.pushState(null, '', window.location.pathname + window.location.search);
258
+ }
259
+ return saved;
260
+ }
261
+ // history.length > 1 (not navigation.canGoBack, which is false for a cross-origin prior entry)
262
+ // means there is somewhere to leave to; otherwise Back can't exit and no sentinel is needed.
263
+ const key = window.navigation.currentEntry?.key;
264
+ if (key && window.history.length > 1) {
265
+ // Record the current (landing) entry as the sentinel, then push the working entry above it.
266
+ sessionStorage.setItem(SENTINEL_STORAGE_KEY, key);
267
+ history.pushState(null, '', window.location.pathname + window.location.search);
268
+ return key;
269
+ }
270
+ return undefined;
60
271
  };
272
+
273
+ /** Check if a path is a redirect path handled elsewhere (e.g., OAuth). */
274
+ const isRedirectPath = (pathname: string): boolean => pathname.startsWith('/redirect/');
275
+
276
+ /** Handle a deep link URL string. Merges query params into window.location and navigates. */
277
+ const handleDeepLink = Effect.fn(function* (urlString: string, navigate: (url?: URL) => Effect.Effect<void, any, any>) {
278
+ log('deep link received', { url: urlString });
279
+
280
+ const deepLinkUrl = new URL(urlString);
281
+
282
+ // For custom schemes (e.g., composer://a/b/c), new URL() treats the first segment as the
283
+ // hostname. Reconstruct the full path from hostname + pathname.
284
+ const fullPath =
285
+ deepLinkUrl.protocol !== 'https:' && deepLinkUrl.protocol !== 'http:' && deepLinkUrl.hostname
286
+ ? '/' + deepLinkUrl.hostname + deepLinkUrl.pathname
287
+ : deepLinkUrl.pathname;
288
+
289
+ if (isRedirectPath(fullPath)) {
290
+ return;
291
+ }
292
+
293
+ // Merge deep link query params into the current window URL so handlers can read them.
294
+ const current = new URL(window.location.href);
295
+ if (deepLinkUrl.search) {
296
+ deepLinkUrl.searchParams.forEach((value, key) => current.searchParams.set(key, value));
297
+ }
298
+ current.pathname = fullPath;
299
+ history.replaceState(null, '', current.pathname + current.search);
300
+
301
+ yield* navigate(current);
302
+ });
@@ -0,0 +1,37 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { type Meta, type StoryObj } from '@storybook/react-vite';
6
+
7
+ import { withLayout, withTheme } from '@dxos/react-ui/testing';
8
+
9
+ import { translations } from '#translations';
10
+
11
+ import { DeckSettings } from './DeckSettings';
12
+
13
+ const meta = {
14
+ title: 'plugins/plugin-deck/components/DeckSettings',
15
+ tags: ['settings'],
16
+ component: DeckSettings,
17
+ decorators: [withTheme(), withLayout({ layout: 'fullscreen' })],
18
+ parameters: {
19
+ layout: 'fullscreen',
20
+ translations,
21
+ },
22
+ } satisfies Meta<typeof DeckSettings>;
23
+
24
+ export default meta;
25
+
26
+ type Story = StoryObj<typeof meta>;
27
+
28
+ export const Default: Story = {
29
+ args: {
30
+ settings: {
31
+ enableDeck: true,
32
+ encapsulatedPlanks: false,
33
+ showHints: true,
34
+ enableNativeRedirect: false,
35
+ },
36
+ },
37
+ };
@@ -4,100 +4,31 @@
4
4
 
5
5
  import React from 'react';
6
6
 
7
- import { Input, Select, useTranslation } from '@dxos/react-ui';
8
- import { ControlGroup, ControlItemInput, ControlPage, ControlSection } from '@dxos/react-ui-form';
7
+ import { type AppSurface } from '@dxos/app-toolkit/ui';
8
+ import { useTranslation } from '@dxos/react-ui';
9
+ import { Settings as SettingsForm } from '@dxos/react-ui-form';
9
10
 
10
- import { meta } from '../../meta';
11
- import {
12
- type DeckSettingsProps,
13
- type NewPlankPositioning,
14
- NewPlankPositions,
15
- type Overscroll,
16
- OverscrollOptions,
17
- } from '../../types';
11
+ import { meta } from '#meta';
12
+ import { Settings } from '#types';
18
13
 
19
14
  const isSocket = !!(globalThis as any).__args;
20
15
 
21
- export const DeckSettings = ({ settings }: { settings: DeckSettingsProps }) => {
16
+ export type DeckSettingsProps = AppSurface.SettingsArticleProps<Settings.Settings>;
17
+
18
+ export const DeckSettings = ({ settings, onSettingsChange }: DeckSettingsProps) => {
22
19
  const { t } = useTranslation(meta.id);
23
20
 
24
21
  return (
25
- <ControlPage>
26
- <ControlSection title={t('settings title', { ns: meta.id })}>
27
- <ControlGroup>
28
- <ControlItemInput title={t('settings enable deck label')}>
29
- <Input.Switch
30
- checked={settings.enableDeck}
31
- onCheckedChange={(checked) => (settings.enableDeck = checked)}
32
- />
33
- </ControlItemInput>
34
- <ControlItemInput title={t('settings encapsulated planks label')}>
35
- <Input.Switch
36
- checked={settings.encapsulatedPlanks ?? false}
37
- onCheckedChange={(checked) => (settings.encapsulatedPlanks = checked)}
38
- />
39
- </ControlItemInput>
40
- <ControlItemInput title={t('select new plank positioning label')}>
41
- <Select.Root
42
- disabled={!settings.enableDeck}
43
- value={settings.newPlankPositioning ?? 'start'}
44
- onValueChange={(value) => (settings.newPlankPositioning = value as NewPlankPositioning)}
45
- >
46
- <Select.TriggerButton placeholder={t('select new plank positioning placeholder')} />
47
- <Select.Portal>
48
- <Select.Content>
49
- <Select.Viewport>
50
- {NewPlankPositions.map((position) => (
51
- <Select.Option key={position} value={position}>
52
- {t(`settings new plank position ${position} label`)}
53
- </Select.Option>
54
- ))}
55
- </Select.Viewport>
56
- <Select.Arrow />
57
- </Select.Content>
58
- </Select.Portal>
59
- </Select.Root>
60
- </ControlItemInput>
61
- <ControlItemInput title={t('settings overscroll label')}>
62
- <Select.Root
63
- disabled={!settings.enableDeck}
64
- value={settings.overscroll ?? 'none'}
65
- onValueChange={(value) => (settings.overscroll = value as Overscroll)}
66
- >
67
- <Select.TriggerButton placeholder={t('select overscroll placeholder')} />
68
- <Select.Portal>
69
- <Select.Content>
70
- <Select.Viewport>
71
- {OverscrollOptions.map((option) => (
72
- <Select.Option key={option} value={option}>
73
- {t(`settings overscroll ${option} label`)}
74
- </Select.Option>
75
- ))}
76
- </Select.Viewport>
77
- <Select.Arrow />
78
- </Select.Content>
79
- </Select.Portal>
80
- </Select.Root>
81
- </ControlItemInput>
82
- <ControlItemInput title={t('settings enable statusbar label')}>
83
- <Input.Switch
84
- checked={settings.enableStatusbar}
85
- onCheckedChange={(checked) => (settings.enableStatusbar = checked)}
86
- />
87
- </ControlItemInput>
88
- <ControlItemInput title={t('settings show hints label')}>
89
- <Input.Switch checked={settings.showHints} onCheckedChange={(checked) => (settings.showHints = checked)} />
90
- </ControlItemInput>
91
- {!isSocket && (
92
- <ControlItemInput title={t('settings native redirect label')}>
93
- <Input.Switch
94
- checked={settings.enableNativeRedirect}
95
- onCheckedChange={(checked) => (settings.enableNativeRedirect = checked)}
96
- />
97
- </ControlItemInput>
98
- )}
99
- </ControlGroup>
100
- </ControlSection>
101
- </ControlPage>
22
+ <SettingsForm.Viewport>
23
+ <SettingsForm.Section title={t('settings.title', { ns: meta.id })}>
24
+ <SettingsForm.FieldSet
25
+ readonly={!onSettingsChange}
26
+ schema={Settings.Settings}
27
+ visible={(path) => path !== 'enableNativeRedirect' || !isSocket}
28
+ values={settings}
29
+ onValuesChanged={(values) => onSettingsChange?.(() => values)}
30
+ />
31
+ </SettingsForm.Section>
32
+ </SettingsForm.Viewport>
102
33
  );
103
34
  };
@@ -1,5 +1,5 @@
1
1
  //
2
- // Copyright 2025 DXOS.org
2
+ // Copyright 2024 DXOS.org
3
3
  //
4
4
 
5
- export * from './DeckSettings';
5
+ export { DeckSettings as default } from './DeckSettings';