@dxos/plugin-deck 0.7.5-labs.a279d8c → 0.7.5-labs.a8b535d

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 (236) hide show
  1. package/dist/lib/browser/{app-graph-builder-67VRUD5K.mjs → app-graph-builder-IYHAGFA3.mjs} +61 -31
  2. package/dist/lib/browser/app-graph-builder-IYHAGFA3.mjs.map +7 -0
  3. package/dist/lib/browser/{check-app-scheme-GEX6W2R5.mjs → check-app-scheme-S3EYUPMF.mjs} +3 -3
  4. package/dist/lib/browser/{check-app-scheme-GEX6W2R5.mjs.map → check-app-scheme-S3EYUPMF.mjs.map} +2 -2
  5. package/dist/lib/browser/{chunk-5VFDMW5M.mjs → chunk-22AQ5IVX.mjs} +2 -2
  6. package/dist/lib/browser/chunk-22AQ5IVX.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-FT33W5CI.mjs +128 -0
  8. package/dist/lib/browser/chunk-FT33W5CI.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-G2X3ZDCE.mjs +24 -0
  10. package/dist/lib/browser/chunk-G2X3ZDCE.mjs.map +7 -0
  11. package/dist/lib/browser/chunk-KANJBSIX.mjs +97 -0
  12. package/dist/lib/browser/chunk-KANJBSIX.mjs.map +7 -0
  13. package/dist/lib/browser/{chunk-JQJ5UWVB.mjs → chunk-N7TEPFVR.mjs} +3 -2
  14. package/dist/lib/browser/{chunk-JQJ5UWVB.mjs.map → chunk-N7TEPFVR.mjs.map} +3 -3
  15. package/dist/lib/browser/chunk-O4RFYYQ6.mjs +1114 -0
  16. package/dist/lib/browser/chunk-O4RFYYQ6.mjs.map +7 -0
  17. package/dist/lib/browser/index.mjs +45 -84
  18. package/dist/lib/browser/index.mjs.map +4 -4
  19. package/dist/lib/browser/intent-resolver-ZD67BRUI.mjs +488 -0
  20. package/dist/lib/browser/intent-resolver-ZD67BRUI.mjs.map +7 -0
  21. package/dist/lib/browser/meta.json +1 -1
  22. package/dist/lib/browser/{react-root-AWYSGU4Q.mjs → react-root-6ILKHD5J.mjs} +12 -17
  23. package/dist/lib/browser/react-root-6ILKHD5J.mjs.map +7 -0
  24. package/dist/lib/browser/react-surface-O75FKXAI.mjs +39 -0
  25. package/dist/lib/browser/react-surface-O75FKXAI.mjs.map +7 -0
  26. package/dist/lib/browser/{settings-FNWW6WIJ.mjs → settings-H35U6NHE.mjs} +6 -7
  27. package/dist/lib/browser/settings-H35U6NHE.mjs.map +7 -0
  28. package/dist/lib/browser/state-U4SHOPJW.mjs +129 -0
  29. package/dist/lib/browser/state-U4SHOPJW.mjs.map +7 -0
  30. package/dist/lib/browser/{tools-4XY7KFQF.mjs → tools-64LXGLYR.mjs} +24 -11
  31. package/dist/lib/browser/tools-64LXGLYR.mjs.map +7 -0
  32. package/dist/lib/browser/types.mjs +16 -4
  33. package/dist/lib/browser/url-handler-MVHTKUYA.mjs +72 -0
  34. package/dist/lib/browser/url-handler-MVHTKUYA.mjs.map +7 -0
  35. package/dist/types/src/DeckPlugin.d.ts.map +1 -1
  36. package/dist/types/src/capabilities/app-graph-builder.d.ts +181 -0
  37. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -0
  38. package/dist/types/src/capabilities/capabilities.d.ts +137 -8
  39. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  40. package/dist/types/src/capabilities/check-app-scheme.d.ts.map +1 -0
  41. package/dist/types/src/capabilities/index.d.ts +188 -3
  42. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  43. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -0
  44. package/dist/types/src/capabilities/react-root.d.ts.map +1 -0
  45. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -0
  46. package/dist/types/src/capabilities/settings.d.ts.map +1 -0
  47. package/dist/types/src/capabilities/state.d.ts +79 -0
  48. package/dist/types/src/capabilities/state.d.ts.map +1 -0
  49. package/dist/types/src/capabilities/{navigation/tools.d.ts → tools.d.ts} +1 -0
  50. package/dist/types/src/capabilities/tools.d.ts.map +1 -0
  51. package/dist/types/src/capabilities/url-handler.d.ts.map +1 -0
  52. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts +1 -2
  53. package/dist/types/src/components/DeckLayout/ActiveNode.d.ts.map +1 -1
  54. package/dist/types/src/components/DeckLayout/Banner.d.ts +1 -2
  55. package/dist/types/src/components/DeckLayout/Banner.d.ts.map +1 -1
  56. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts +1 -4
  57. package/dist/types/src/components/DeckLayout/ComplementarySidebar.d.ts.map +1 -1
  58. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts +1 -2
  59. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts.map +1 -1
  60. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts +2 -7
  61. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  62. package/dist/types/src/components/DeckLayout/Fallback.d.ts +1 -2
  63. package/dist/types/src/components/DeckLayout/Fallback.d.ts.map +1 -1
  64. package/dist/types/src/components/DeckLayout/Fullscreen.d.ts +1 -2
  65. package/dist/types/src/components/DeckLayout/Fullscreen.d.ts.map +1 -1
  66. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +3 -3
  67. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
  68. package/dist/types/src/components/DeckLayout/Plank.d.ts +8 -6
  69. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -1
  70. package/dist/types/src/components/DeckLayout/PlankControls.d.ts +2 -2
  71. package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +1 -1
  72. package/dist/types/src/components/DeckLayout/PlankError.d.ts +6 -6
  73. package/dist/types/src/components/DeckLayout/PlankError.d.ts.map +1 -1
  74. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts +1 -2
  75. package/dist/types/src/components/DeckLayout/PlankLoading.d.ts.map +1 -1
  76. package/dist/types/src/components/DeckLayout/Sidebar.d.ts +1 -2
  77. package/dist/types/src/components/DeckLayout/Sidebar.d.ts.map +1 -1
  78. package/dist/types/src/components/DeckLayout/SidebarButton.d.ts +6 -5
  79. package/dist/types/src/components/DeckLayout/SidebarButton.d.ts.map +1 -1
  80. package/dist/types/src/components/DeckLayout/StatusBar.d.ts +1 -2
  81. package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
  82. package/dist/types/src/components/DeckLayout/Toast.d.ts +2 -3
  83. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
  84. package/dist/types/src/components/DeckLayout/Topbar.d.ts +1 -2
  85. package/dist/types/src/components/DeckLayout/Topbar.d.ts.map +1 -1
  86. package/dist/types/src/components/LayoutSettings.d.ts +1 -2
  87. package/dist/types/src/components/LayoutSettings.d.ts.map +1 -1
  88. package/dist/types/src/components/fragments.d.ts +2 -0
  89. package/dist/types/src/components/fragments.d.ts.map +1 -1
  90. package/dist/types/src/components/index.d.ts +0 -2
  91. package/dist/types/src/components/index.d.ts.map +1 -1
  92. package/dist/types/src/events.d.ts +1 -0
  93. package/dist/types/src/events.d.ts.map +1 -1
  94. package/dist/types/src/hooks/useMainSize.d.ts +2 -2
  95. package/dist/types/src/hooks/useNode.d.ts.map +1 -1
  96. package/dist/types/src/layout.d.ts +5 -19
  97. package/dist/types/src/layout.d.ts.map +1 -1
  98. package/dist/types/src/meta.d.ts +1 -0
  99. package/dist/types/src/meta.d.ts.map +1 -1
  100. package/dist/types/src/translations.d.ts +3 -3
  101. package/dist/types/src/types.d.ts +117 -2
  102. package/dist/types/src/types.d.ts.map +1 -1
  103. package/dist/types/src/util/index.d.ts +3 -1
  104. package/dist/types/src/util/index.d.ts.map +1 -1
  105. package/dist/types/src/util/layoutAppliesTopbar.d.ts +2 -0
  106. package/dist/types/src/util/layoutAppliesTopbar.d.ts.map +1 -0
  107. package/dist/types/src/util/set-active.d.ts +9 -0
  108. package/dist/types/src/util/set-active.d.ts.map +1 -0
  109. package/dist/types/src/util/useHoistStatusbar.d.ts.map +1 -1
  110. package/dist/types/tsconfig.tsbuildinfo +1 -1
  111. package/package.json +31 -31
  112. package/src/DeckPlugin.ts +23 -65
  113. package/src/capabilities/{layout/app-graph-builder.ts → app-graph-builder.ts} +40 -28
  114. package/src/capabilities/capabilities.ts +5 -7
  115. package/src/capabilities/{navigation/check-app-scheme.ts → check-app-scheme.ts} +2 -2
  116. package/src/capabilities/index.ts +12 -3
  117. package/src/capabilities/intent-resolver.ts +368 -0
  118. package/src/capabilities/{layout/react-root.tsx → react-root.tsx} +8 -14
  119. package/src/capabilities/react-surface.tsx +31 -0
  120. package/src/capabilities/{settings/settings.ts → settings.ts} +4 -5
  121. package/src/capabilities/state.ts +108 -0
  122. package/src/capabilities/{navigation/tools.ts → tools.ts} +18 -9
  123. package/src/capabilities/url-handler.ts +65 -0
  124. package/src/components/DeckLayout/ActiveNode.tsx +2 -3
  125. package/src/components/DeckLayout/ComplementarySidebar.tsx +185 -77
  126. package/src/components/DeckLayout/ContentEmpty.tsx +7 -10
  127. package/src/components/DeckLayout/DeckLayout.tsx +141 -84
  128. package/src/components/DeckLayout/Fullscreen.tsx +2 -3
  129. package/src/components/DeckLayout/NodePlankHeading.tsx +57 -65
  130. package/src/components/DeckLayout/Plank.tsx +33 -41
  131. package/src/components/DeckLayout/PlankControls.tsx +12 -11
  132. package/src/components/DeckLayout/PlankError.tsx +6 -5
  133. package/src/components/DeckLayout/Sidebar.tsx +17 -20
  134. package/src/components/DeckLayout/SidebarButton.tsx +25 -31
  135. package/src/components/DeckLayout/StatusBar.tsx +5 -11
  136. package/src/components/DeckLayout/Toast.tsx +2 -2
  137. package/src/components/LayoutSettings.tsx +8 -8
  138. package/src/components/fragments.ts +8 -0
  139. package/src/components/index.ts +0 -2
  140. package/src/events.ts +1 -0
  141. package/src/hooks/useMainSize.ts +3 -3
  142. package/src/hooks/useNode.ts +3 -1
  143. package/src/layout.ts +43 -212
  144. package/src/meta.ts +1 -0
  145. package/src/translations.ts +8 -8
  146. package/src/types.ts +103 -4
  147. package/src/util/index.ts +3 -1
  148. package/src/util/layoutAppliesTopbar.ts +7 -0
  149. package/src/util/set-active.ts +47 -0
  150. package/src/util/useHoistStatusbar.ts +13 -8
  151. package/dist/lib/browser/app-graph-builder-67VRUD5K.mjs.map +0 -7
  152. package/dist/lib/browser/chunk-2PJNBVCY.mjs +0 -39
  153. package/dist/lib/browser/chunk-2PJNBVCY.mjs.map +0 -7
  154. package/dist/lib/browser/chunk-4C2AFTET.mjs +0 -186
  155. package/dist/lib/browser/chunk-4C2AFTET.mjs.map +0 -7
  156. package/dist/lib/browser/chunk-5VFDMW5M.mjs.map +0 -7
  157. package/dist/lib/browser/chunk-KY5WXIXY.mjs +0 -44
  158. package/dist/lib/browser/chunk-KY5WXIXY.mjs.map +0 -7
  159. package/dist/lib/browser/chunk-WUMAJGVA.mjs +0 -1052
  160. package/dist/lib/browser/chunk-WUMAJGVA.mjs.map +0 -7
  161. package/dist/lib/browser/deck-PLCSKPGL.mjs +0 -26
  162. package/dist/lib/browser/deck-PLCSKPGL.mjs.map +0 -7
  163. package/dist/lib/browser/intent-resolver-FVOQSTBX.mjs +0 -152
  164. package/dist/lib/browser/intent-resolver-FVOQSTBX.mjs.map +0 -7
  165. package/dist/lib/browser/intent-resolver-K7GW4A2I.mjs +0 -249
  166. package/dist/lib/browser/intent-resolver-K7GW4A2I.mjs.map +0 -7
  167. package/dist/lib/browser/location-AIO6V3MK.mjs +0 -35
  168. package/dist/lib/browser/location-AIO6V3MK.mjs.map +0 -7
  169. package/dist/lib/browser/react-context-G6PDXUI5.mjs +0 -32
  170. package/dist/lib/browser/react-context-G6PDXUI5.mjs.map +0 -7
  171. package/dist/lib/browser/react-root-AWYSGU4Q.mjs.map +0 -7
  172. package/dist/lib/browser/react-surface-CLUABFNX.mjs +0 -28
  173. package/dist/lib/browser/react-surface-CLUABFNX.mjs.map +0 -7
  174. package/dist/lib/browser/settings-FNWW6WIJ.mjs.map +0 -7
  175. package/dist/lib/browser/state-7I5BD7SE.mjs +0 -34
  176. package/dist/lib/browser/state-7I5BD7SE.mjs.map +0 -7
  177. package/dist/lib/browser/tools-4XY7KFQF.mjs.map +0 -7
  178. package/dist/lib/browser/url-handler-JRAQRY73.mjs +0 -76
  179. package/dist/lib/browser/url-handler-JRAQRY73.mjs.map +0 -7
  180. package/dist/types/src/capabilities/layout/app-graph-builder.d.ts +0 -181
  181. package/dist/types/src/capabilities/layout/app-graph-builder.d.ts.map +0 -1
  182. package/dist/types/src/capabilities/layout/deck.d.ts +0 -4
  183. package/dist/types/src/capabilities/layout/deck.d.ts.map +0 -1
  184. package/dist/types/src/capabilities/layout/index.d.ts +0 -229
  185. package/dist/types/src/capabilities/layout/index.d.ts.map +0 -1
  186. package/dist/types/src/capabilities/layout/intent-resolver.d.ts.map +0 -1
  187. package/dist/types/src/capabilities/layout/react-context.d.ts +0 -8
  188. package/dist/types/src/capabilities/layout/react-context.d.ts.map +0 -1
  189. package/dist/types/src/capabilities/layout/react-root.d.ts.map +0 -1
  190. package/dist/types/src/capabilities/layout/state.d.ts +0 -42
  191. package/dist/types/src/capabilities/layout/state.d.ts.map +0 -1
  192. package/dist/types/src/capabilities/navigation/check-app-scheme.d.ts.map +0 -1
  193. package/dist/types/src/capabilities/navigation/index.d.ts +0 -6
  194. package/dist/types/src/capabilities/navigation/index.d.ts.map +0 -1
  195. package/dist/types/src/capabilities/navigation/intent-resolver.d.ts +0 -4
  196. package/dist/types/src/capabilities/navigation/intent-resolver.d.ts.map +0 -1
  197. package/dist/types/src/capabilities/navigation/location.d.ts +0 -4
  198. package/dist/types/src/capabilities/navigation/location.d.ts.map +0 -1
  199. package/dist/types/src/capabilities/navigation/set-location.d.ts +0 -10
  200. package/dist/types/src/capabilities/navigation/set-location.d.ts.map +0 -1
  201. package/dist/types/src/capabilities/navigation/tools.d.ts.map +0 -1
  202. package/dist/types/src/capabilities/navigation/url-handler.d.ts.map +0 -1
  203. package/dist/types/src/capabilities/settings/index.d.ts +0 -3
  204. package/dist/types/src/capabilities/settings/index.d.ts.map +0 -1
  205. package/dist/types/src/capabilities/settings/react-surface.d.ts.map +0 -1
  206. package/dist/types/src/capabilities/settings/settings.d.ts.map +0 -1
  207. package/dist/types/src/components/DeckContext.d.ts +0 -11
  208. package/dist/types/src/components/DeckContext.d.ts.map +0 -1
  209. package/dist/types/src/components/LayoutContext.d.ts +0 -5
  210. package/dist/types/src/components/LayoutContext.d.ts.map +0 -1
  211. package/dist/types/src/layout.test.d.ts +0 -2
  212. package/dist/types/src/layout.test.d.ts.map +0 -1
  213. package/dist/types/src/util/layout-parts.d.ts +0 -7
  214. package/dist/types/src/util/layout-parts.d.ts.map +0 -1
  215. package/src/capabilities/layout/deck.ts +0 -25
  216. package/src/capabilities/layout/index.ts +0 -12
  217. package/src/capabilities/layout/intent-resolver.ts +0 -128
  218. package/src/capabilities/layout/react-context.tsx +0 -26
  219. package/src/capabilities/layout/state.ts +0 -32
  220. package/src/capabilities/navigation/index.ts +0 -11
  221. package/src/capabilities/navigation/intent-resolver.ts +0 -216
  222. package/src/capabilities/navigation/location.ts +0 -28
  223. package/src/capabilities/navigation/set-location.ts +0 -38
  224. package/src/capabilities/navigation/url-handler.ts +0 -67
  225. package/src/capabilities/settings/index.ts +0 -8
  226. package/src/capabilities/settings/react-surface.tsx +0 -23
  227. package/src/components/DeckContext.ts +0 -19
  228. package/src/components/LayoutContext.ts +0 -12
  229. package/src/layout.test.ts +0 -380
  230. package/src/util/layout-parts.ts +0 -12
  231. /package/dist/types/src/capabilities/{navigation/check-app-scheme.d.ts → check-app-scheme.d.ts} +0 -0
  232. /package/dist/types/src/capabilities/{layout/intent-resolver.d.ts → intent-resolver.d.ts} +0 -0
  233. /package/dist/types/src/capabilities/{layout/react-root.d.ts → react-root.d.ts} +0 -0
  234. /package/dist/types/src/capabilities/{settings/react-surface.d.ts → react-surface.d.ts} +0 -0
  235. /package/dist/types/src/capabilities/{settings/settings.d.ts → settings.d.ts} +0 -0
  236. /package/dist/types/src/capabilities/{navigation/url-handler.d.ts → url-handler.d.ts} +0 -0
@@ -0,0 +1,368 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { batch } from '@preact/signals-core';
6
+ import { pipe } from 'effect';
7
+
8
+ import {
9
+ Capabilities,
10
+ createResolver,
11
+ contributes,
12
+ IntentAction,
13
+ LayoutAction,
14
+ type PluginsContext,
15
+ createIntent,
16
+ chain,
17
+ } from '@dxos/app-framework';
18
+ import { getTypename, S } from '@dxos/echo-schema';
19
+ import { isReactiveObject } from '@dxos/live-object';
20
+ import { log } from '@dxos/log';
21
+ import { AttentionCapabilities } from '@dxos/plugin-attention';
22
+ import { ObservabilityAction } from '@dxos/plugin-observability/types';
23
+ import { isNonNullable } from '@dxos/util';
24
+
25
+ import { DeckCapabilities } from './capabilities';
26
+ import { closeEntry, incrementPlank, openEntry } from '../layout';
27
+ import { DECK_PLUGIN } from '../meta';
28
+ import { DeckAction, type LayoutMode, type DeckSettingsProps, isLayoutMode, getMode } from '../types';
29
+ import { setActive } from '../util';
30
+
31
+ export default (context: PluginsContext) =>
32
+ contributes(Capabilities.IntentResolver, [
33
+ createResolver({
34
+ intent: IntentAction.ShowUndo,
35
+ resolve: (data) => {
36
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
37
+ const { undoPromise: undo } = context.requestCapability(Capabilities.IntentDispatcher);
38
+
39
+ // TODO(wittjosiah): Support undoing further back than the last action.
40
+ if (layout.currentUndoId) {
41
+ layout.toasts = layout.toasts.filter((toast) => toast.id !== layout.currentUndoId);
42
+ }
43
+ layout.currentUndoId = `${IntentAction.ShowUndo._tag}-${Date.now()}`;
44
+ layout.toasts = [
45
+ ...layout.toasts,
46
+ {
47
+ id: layout.currentUndoId,
48
+ title: data.message ?? ['undo available label', { ns: DECK_PLUGIN }],
49
+ duration: 10_000,
50
+ actionLabel: ['undo action label', { ns: DECK_PLUGIN }],
51
+ actionAlt: ['undo action alt', { ns: DECK_PLUGIN }],
52
+ closeLabel: ['undo close label', { ns: DECK_PLUGIN }],
53
+ onAction: () => undo(),
54
+ },
55
+ ];
56
+ },
57
+ }),
58
+ createResolver({
59
+ intent: LayoutAction.UpdateLayout,
60
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdateSidebar.fields.input)`
61
+ // but the filter is not being applied correctly.
62
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdateSidebar.fields.input> =>
63
+ S.is(LayoutAction.UpdateSidebar.fields.input)(data),
64
+ resolve: ({ options }) => {
65
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
66
+ const next = options?.state ?? layout.sidebarState;
67
+ if (next !== layout.sidebarState) {
68
+ layout.sidebarState = next;
69
+ }
70
+ },
71
+ }),
72
+ createResolver({
73
+ intent: LayoutAction.UpdateLayout,
74
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdateComplementary.fields.input)`
75
+ // but the filter is not being applied correctly.
76
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdateComplementary.fields.input> =>
77
+ S.is(LayoutAction.UpdateComplementary.fields.input)(data),
78
+ resolve: ({ subject, options }) => {
79
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
80
+
81
+ if (layout.complementarySidebarPanel !== subject) {
82
+ layout.complementarySidebarPanel = subject;
83
+ }
84
+
85
+ const next = subject ? 'expanded' : options?.state ?? layout.complementarySidebarState;
86
+ if (next !== layout.complementarySidebarState) {
87
+ layout.complementarySidebarState = next;
88
+ }
89
+ },
90
+ }),
91
+ createResolver({
92
+ intent: LayoutAction.UpdateLayout,
93
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdateDialog.fields.input)`
94
+ // but the filter is not being applied correctly.
95
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdateDialog.fields.input> =>
96
+ S.is(LayoutAction.UpdateDialog.fields.input)(data),
97
+ resolve: ({ subject, options }) => {
98
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
99
+ layout.dialogOpen = options.state ?? Boolean(subject);
100
+ layout.dialogContent = subject ? { component: subject, props: options.props } : null;
101
+ layout.dialogBlockAlign = options.blockAlign ?? 'center';
102
+ layout.dialogType = options.type ?? 'default';
103
+ },
104
+ }),
105
+ createResolver({
106
+ intent: LayoutAction.UpdateLayout,
107
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdatePopover.fields.input)`
108
+ // but the filter is not being applied correctly.
109
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdatePopover.fields.input> =>
110
+ S.is(LayoutAction.UpdatePopover.fields.input)(data),
111
+ resolve: ({ subject, options }) => {
112
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
113
+ layout.popoverOpen = options.state ?? Boolean(subject);
114
+ layout.popoverContent = subject ? { component: subject, props: options.props } : null;
115
+ layout.popoverAnchorId = options.anchorId;
116
+ layout.popoverSide = options.side;
117
+ },
118
+ }),
119
+ createResolver({
120
+ intent: LayoutAction.UpdateLayout,
121
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.AddToast.fields.input)`
122
+ // but the filter is not being applied correctly.
123
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.AddToast.fields.input> =>
124
+ S.is(LayoutAction.AddToast.fields.input)(data),
125
+ resolve: ({ subject }) => {
126
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
127
+ layout.toasts.push(subject);
128
+ },
129
+ }),
130
+ createResolver({
131
+ intent: LayoutAction.UpdateLayout,
132
+ // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.SetLayoutMode.fields.input)`
133
+ // but the filter is not being applied correctly.
134
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.SetLayoutMode.fields.input> => {
135
+ if (!S.is(LayoutAction.SetLayoutMode.fields.input)(data)) {
136
+ return false;
137
+ }
138
+
139
+ if ('mode' in data.options) {
140
+ return isLayoutMode(data.options.mode);
141
+ }
142
+
143
+ return true;
144
+ },
145
+ resolve: ({ subject, options }) => {
146
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
147
+
148
+ const setMode = (mode: LayoutMode) => {
149
+ const deck = state.deck;
150
+ const current = deck.solo ? [deck.solo] : deck.active;
151
+ // When un-soloing, the solo entry is added to the deck.
152
+ const next = (
153
+ mode !== 'deck' ? [subject ?? deck.solo ?? deck.active[0]] : [...deck.active, deck.solo]
154
+ ).filter(isNonNullable);
155
+
156
+ const removed = current.filter((id) => !next.includes(id));
157
+ const closed = Array.from(new Set([...deck.inactive.filter((id) => !next.includes(id)), ...removed]));
158
+ deck.inactive = closed;
159
+
160
+ if (mode !== 'deck' && next[0]) {
161
+ deck.solo = next[0];
162
+ } else if (mode === 'deck' && deck.solo) {
163
+ deck.solo = undefined;
164
+ deck.initialized = true;
165
+ }
166
+
167
+ if (mode === 'fullscreen' && !deck.fullscreen) {
168
+ deck.fullscreen = true;
169
+ } else if (mode !== 'fullscreen' && deck.fullscreen) {
170
+ deck.fullscreen = false;
171
+ }
172
+ };
173
+
174
+ return batch(() => {
175
+ if ('mode' in options) {
176
+ const current = getMode(state.deck);
177
+ if (current !== options.mode) {
178
+ state.previousMode[state.activeDeck] = current;
179
+ }
180
+ setMode(options.mode as LayoutMode);
181
+ } else if ('revert' in options) {
182
+ const last = state.previousMode[state.activeDeck];
183
+ setMode(last ?? 'solo');
184
+ } else {
185
+ log.warn('Invalid layout mode', options);
186
+ }
187
+ });
188
+ },
189
+ }),
190
+ createResolver({
191
+ intent: LayoutAction.UpdateLayout,
192
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.SwitchWorkspace.fields.input> =>
193
+ S.is(LayoutAction.SwitchWorkspace.fields.input)(data),
194
+ resolve: ({ subject }) => {
195
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
196
+ batch(() => {
197
+ // TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
198
+ // Ideally this should be worked into the data model in a generic way.
199
+ if (!state.activeDeck.startsWith('!')) {
200
+ state.previousDeck = state.activeDeck;
201
+ }
202
+ state.activeDeck = subject;
203
+ if (!state.decks[subject]) {
204
+ state.decks[subject] = { initialized: false, active: [], inactive: [], fullscreen: false, plankSizing: {} };
205
+ }
206
+ });
207
+
208
+ const first = state.deck.solo ? state.deck.solo : state.deck.active[0];
209
+ if (first) {
210
+ return {
211
+ intents: [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: first })],
212
+ };
213
+ }
214
+ },
215
+ }),
216
+ createResolver({
217
+ intent: LayoutAction.UpdateLayout,
218
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.RevertWorkspace.fields.input> =>
219
+ S.is(LayoutAction.RevertWorkspace.fields.input)(data),
220
+ resolve: () => {
221
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
222
+ return {
223
+ intents: [createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: state.previousDeck })],
224
+ };
225
+ },
226
+ }),
227
+ createResolver({
228
+ intent: LayoutAction.UpdateLayout,
229
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.Open.fields.input> =>
230
+ S.is(LayoutAction.Open.fields.input)(data),
231
+ resolve: ({ subject, options }) => {
232
+ const { graph } = context.requestCapability(Capabilities.AppGraph);
233
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
234
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
235
+ const settings = context
236
+ .requestCapabilities(Capabilities.SettingsStore)[0]
237
+ ?.getStore<DeckSettingsProps>(DECK_PLUGIN)?.value;
238
+
239
+ const previouslyOpenIds = new Set<string>(state.deck.solo ? [state.deck.solo] : state.deck.active);
240
+ batch(() => {
241
+ const next = state.deck.solo
242
+ ? (subject as string[])
243
+ : subject.reduce(
244
+ (acc, entryId) =>
245
+ openEntry(acc, entryId, {
246
+ key: options?.key,
247
+ positioning: options?.positioning ?? settings?.newPlankPositioning,
248
+ pivotId: options?.pivotId,
249
+ }),
250
+ state.deck.active,
251
+ );
252
+
253
+ return setActive({ next, state, attention });
254
+ });
255
+
256
+ const ids = state.deck.solo ? [state.deck.solo] : state.deck.active;
257
+ const newlyOpen = ids.filter((i) => !previouslyOpenIds.has(i));
258
+
259
+ return {
260
+ intents: [
261
+ ...(options?.scrollIntoView !== false
262
+ ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: newlyOpen[0] ?? subject[0] })]
263
+ : []),
264
+ createIntent(LayoutAction.Expose, { part: 'navigation', subject: newlyOpen[0] ?? subject[0] }),
265
+ ...newlyOpen.map((id) => {
266
+ const active = graph?.findNode(id)?.data;
267
+ const typename = isReactiveObject(active) ? getTypename(active) : undefined;
268
+ return createIntent(ObservabilityAction.SendEvent, {
269
+ name: 'navigation.activate',
270
+ properties: {
271
+ id,
272
+ typename,
273
+ },
274
+ });
275
+ }),
276
+ ],
277
+ };
278
+ },
279
+ }),
280
+ createResolver({
281
+ intent: LayoutAction.UpdateLayout,
282
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.Close.fields.input> =>
283
+ S.is(LayoutAction.Close.fields.input)(data),
284
+ resolve: ({ subject }) => {
285
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
286
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
287
+ const active = state.deck.solo ? [state.deck.solo] : state.deck.active;
288
+ const next = subject.reduce((acc, id) => closeEntry(acc, id), active);
289
+ const toAttend = setActive({ next, state, attention });
290
+ return {
291
+ intents: toAttend ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: toAttend })] : [],
292
+ };
293
+ },
294
+ }),
295
+ createResolver({
296
+ intent: LayoutAction.UpdateLayout,
297
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.Set.fields.input> =>
298
+ S.is(LayoutAction.Set.fields.input)(data),
299
+ resolve: ({ subject }) => {
300
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
301
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
302
+ const toAttend = setActive({ next: subject as string[], state, attention });
303
+ return {
304
+ intents: toAttend ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: toAttend })] : [],
305
+ };
306
+ },
307
+ }),
308
+ createResolver({
309
+ intent: LayoutAction.UpdateLayout,
310
+ filter: (data): data is S.Schema.Type<typeof LayoutAction.ScrollIntoView.fields.input> =>
311
+ S.is(LayoutAction.ScrollIntoView.fields.input)(data),
312
+ resolve: ({ subject }) => {
313
+ const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
314
+ layout.scrollIntoView = subject;
315
+ },
316
+ }),
317
+ createResolver({
318
+ intent: DeckAction.UpdatePlankSize,
319
+ resolve: (data) => {
320
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
321
+ state.deck.plankSizing[data.id] = data.size;
322
+ },
323
+ }),
324
+ createResolver({
325
+ intent: DeckAction.Adjust,
326
+ resolve: (adjustment) => {
327
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
328
+ const attention = context.requestCapability(AttentionCapabilities.Attention);
329
+
330
+ return batch(() => {
331
+ if (adjustment.type === 'increment-end' || adjustment.type === 'increment-start') {
332
+ setActive({
333
+ next: incrementPlank(state.deck.active, adjustment),
334
+ state,
335
+ attention,
336
+ });
337
+ }
338
+
339
+ if (adjustment.type === 'solo') {
340
+ const entryId = adjustment.id;
341
+ if (!state.deck.solo) {
342
+ // Solo the entry.
343
+ return {
344
+ intents: [
345
+ createIntent(LayoutAction.SetLayoutMode, {
346
+ part: 'mode',
347
+ subject: entryId,
348
+ options: { mode: 'solo' },
349
+ }),
350
+ ],
351
+ };
352
+ } else {
353
+ // Un-solo the current entry.
354
+ return {
355
+ intents: [
356
+ // NOTE: The order of these is important.
357
+ pipe(
358
+ createIntent(LayoutAction.SetLayoutMode, { part: 'mode', options: { mode: 'deck' } }),
359
+ chain(LayoutAction.Open, { part: 'main', subject: [entryId] }),
360
+ ),
361
+ ],
362
+ };
363
+ }
364
+ }
365
+ });
366
+ },
367
+ }),
368
+ ]);
@@ -4,22 +4,19 @@
4
4
 
5
5
  import React, { useCallback } from 'react';
6
6
 
7
- import { Capabilities, contributes, useCapabilities, useCapability } from '@dxos/app-framework';
7
+ import { Capabilities, contributes, useCapability } from '@dxos/app-framework';
8
8
 
9
- import { DeckLayout } from '../../components';
10
- import { DECK_PLUGIN } from '../../meta';
11
- import { type DeckSettingsProps } from '../../types';
12
- import { DeckCapabilities } from '../capabilities';
9
+ import { DeckCapabilities } from './capabilities';
10
+ import { DeckLayout } from '../components';
11
+ import { DECK_PLUGIN } from '../meta';
12
+ import { type DeckSettingsProps } from '../types';
13
13
 
14
14
  export default () =>
15
15
  contributes(Capabilities.ReactRoot, {
16
16
  id: DECK_PLUGIN,
17
17
  root: () => {
18
- const layout = useCapability(Capabilities.Layout);
19
- const location = useCapability(Capabilities.Location);
20
- const deck = useCapability(DeckCapabilities.MutableDeckState);
18
+ const layout = useCapability(DeckCapabilities.MutableDeckState);
21
19
  const settings = useCapability(Capabilities.SettingsStore).getStore<DeckSettingsProps>(DECK_PLUGIN)!.value;
22
- const panels = useCapabilities(DeckCapabilities.ComplementaryPanel);
23
20
 
24
21
  const handleDismissToast = useCallback(
25
22
  (id: string) => {
@@ -28,8 +25,8 @@ export default () =>
28
25
  // Allow time for the toast to animate out.
29
26
  // TODO(burdon): Factor out and unregister timeout.
30
27
  setTimeout(() => {
31
- if (layout.toasts[index].id === deck.currentUndoId) {
32
- deck.currentUndoId = undefined;
28
+ if (layout.toasts[index].id === layout.currentUndoId) {
29
+ layout.currentUndoId = undefined;
33
30
  }
34
31
  layout.toasts.splice(index, 1);
35
32
  }, 1_000);
@@ -40,11 +37,8 @@ export default () =>
40
37
 
41
38
  return (
42
39
  <DeckLayout
43
- layoutParts={location.active}
44
40
  showHints={settings.showHints}
45
41
  overscroll={settings.overscroll}
46
- toasts={layout.toasts}
47
- panels={panels}
48
42
  onDismissToast={handleDismissToast}
49
43
  />
50
44
  );
@@ -0,0 +1,31 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import React from 'react';
6
+
7
+ import { Capabilities, contributes, createSurface } from '@dxos/app-framework';
8
+ import { SettingsStore } from '@dxos/local-storage';
9
+
10
+ import { LayoutSettings } from '../components';
11
+ import { Banner } from '../components/DeckLayout/Banner';
12
+ import { DECK_PLUGIN } from '../meta';
13
+ import { type DeckSettingsProps } from '../types';
14
+
15
+ export default () =>
16
+ contributes(Capabilities.ReactSurface, [
17
+ createSurface({
18
+ id: `${DECK_PLUGIN}/settings`,
19
+ role: 'article',
20
+ filter: (data): data is { subject: SettingsStore<DeckSettingsProps> } =>
21
+ data.subject instanceof SettingsStore && data.subject.prefix === DECK_PLUGIN,
22
+ component: ({ data: { subject } }) => <LayoutSettings settings={subject.value} />,
23
+ }),
24
+ createSurface({
25
+ id: `${DECK_PLUGIN}/banner`,
26
+ role: 'banner',
27
+ component: ({ data }) => {
28
+ return <Banner variant={data.variant} />;
29
+ },
30
+ }),
31
+ ]);
@@ -5,17 +5,16 @@
5
5
  import { Capabilities, contributes } from '@dxos/app-framework';
6
6
  import { create } from '@dxos/live-object';
7
7
 
8
- import { DECK_PLUGIN } from '../../meta';
9
- import { DeckSettingsSchema, type DeckSettingsProps } from '../../types';
8
+ import { DECK_PLUGIN } from '../meta';
9
+ import { DeckSettingsSchema, type DeckSettingsProps } from '../types';
10
10
 
11
11
  export default () => {
12
12
  const settings = create<DeckSettingsProps>({
13
13
  showHints: false,
14
- customSlots: false,
15
- flatDeck: false,
16
14
  enableNativeRedirect: false,
15
+ enableIdeStyleStatusbar: true,
17
16
  newPlankPositioning: 'start',
18
- overscroll: 'centering',
17
+ overscroll: 'none',
19
18
  });
20
19
 
21
20
  return contributes(Capabilities.Settings, { schema: DeckSettingsSchema, prefix: DECK_PLUGIN, value: settings });
@@ -0,0 +1,108 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Capabilities, contributes } from '@dxos/app-framework';
6
+ import { invariant } from '@dxos/invariant';
7
+ import { create } from '@dxos/live-object';
8
+ import { LocalStorageStore } from '@dxos/local-storage';
9
+ import { type SidebarState } from '@dxos/react-ui';
10
+
11
+ import { DeckCapabilities } from './capabilities';
12
+ import { DECK_PLUGIN } from '../meta';
13
+ import { getMode, type Deck, type DeckState } from '../types';
14
+
15
+ const boolean = /true|false/;
16
+
17
+ // TODO(thure, 18 Feb 2025): Remove after the next release.
18
+
19
+ const migrateSidebarStateDefaults = {
20
+ [`${DECK_PLUGIN}/complementary-sidebar-state`]: 'expanded',
21
+ [`${DECK_PLUGIN}/sidebar-state`]: 'collapsed',
22
+ };
23
+
24
+ const migrateSidebarState = () => {
25
+ Object.entries(migrateSidebarStateDefaults).forEach(([key, defaultValue]) => {
26
+ if (boolean.test(localStorage.getItem(key) ?? 'never')) {
27
+ localStorage.setItem(key, defaultValue);
28
+ }
29
+ });
30
+ };
31
+
32
+ export default () => {
33
+ migrateSidebarState();
34
+
35
+ const state = new LocalStorageStore<DeckState>(DECK_PLUGIN, {
36
+ sidebarState: 'expanded',
37
+ complementarySidebarState: 'collapsed',
38
+ complementarySidebarPanel: undefined,
39
+ dialogContent: null,
40
+ dialogOpen: false,
41
+ dialogBlockAlign: undefined,
42
+ dialogType: undefined,
43
+ popoverContent: null,
44
+ popoverAnchorId: undefined,
45
+ popoverOpen: false,
46
+ toasts: [],
47
+ currentUndoId: undefined,
48
+ activeDeck: 'default',
49
+ previousDeck: 'default',
50
+ decks: {
51
+ default: {
52
+ initialized: false,
53
+ active: [],
54
+ inactive: [],
55
+ fullscreen: false,
56
+ solo: undefined,
57
+ plankSizing: {},
58
+ },
59
+ },
60
+ get deck() {
61
+ const deck = this.decks[this.activeDeck];
62
+ invariant(deck, `Deck not found: ${this.activeDeck}`);
63
+ return deck;
64
+ },
65
+ previousMode: {},
66
+ scrollIntoView: undefined,
67
+ });
68
+
69
+ state
70
+ .prop({ key: 'sidebarState', type: LocalStorageStore.enum<SidebarState>() })
71
+ .prop({ key: 'complementarySidebarState', type: LocalStorageStore.enum<SidebarState>() })
72
+ .prop({ key: 'complementarySidebarPanel', type: LocalStorageStore.string({ allowUndefined: true }) })
73
+ .prop({ key: 'decks', type: LocalStorageStore.json<Record<string, Deck>>() })
74
+ .prop({ key: 'activeDeck', type: LocalStorageStore.string() })
75
+ .prop({ key: 'previousDeck', type: LocalStorageStore.string() });
76
+
77
+ const layout = create<Capabilities.Layout>({
78
+ get mode() {
79
+ return getMode(state.values.deck);
80
+ },
81
+ get dialogOpen() {
82
+ return state.values.dialogOpen;
83
+ },
84
+ get sidebarOpen() {
85
+ return state.values.sidebarState === 'expanded';
86
+ },
87
+ get complementarySidebarOpen() {
88
+ return state.values.complementarySidebarState === 'expanded';
89
+ },
90
+ get workspace() {
91
+ return state.values.activeDeck;
92
+ },
93
+ get active() {
94
+ return state.values.deck.solo ? [state.values.deck.solo] : state.values.deck.active;
95
+ },
96
+ get inactive() {
97
+ return state.values.deck.inactive;
98
+ },
99
+ get scrollIntoView() {
100
+ return state.values.scrollIntoView;
101
+ },
102
+ });
103
+
104
+ return [
105
+ contributes(DeckCapabilities.DeckState, state.values, () => state.close()),
106
+ contributes(Capabilities.Layout, layout),
107
+ ];
108
+ };
@@ -3,30 +3,36 @@
3
3
  //
4
4
 
5
5
  import {
6
- Capabilities,
7
6
  contributes,
8
7
  createIntent,
9
- NavigationAction,
8
+ Capabilities,
9
+ LayoutAction,
10
10
  type PromiseIntentDispatcher,
11
11
  } from '@dxos/app-framework';
12
12
  import { defineTool, ToolResult } from '@dxos/artifact';
13
13
  import { S } from '@dxos/echo-schema';
14
14
  import { invariant } from '@dxos/invariant';
15
15
 
16
+ import { meta } from '../meta';
17
+
16
18
  // TODO(burdon): Factor out.
17
19
  declare global {
18
20
  interface ToolContextExtensions {
19
21
  dispatch?: PromiseIntentDispatcher;
22
+ pivotId?: string;
20
23
  }
21
24
  }
22
25
 
23
26
  export default () =>
24
27
  contributes(Capabilities.Tools, [
25
- defineTool({
28
+ defineTool(meta.id, {
26
29
  name: 'show',
27
30
  // TODO(ZaymonFC): We should update the prompt to teach the LLM the difference between object ids and fully qualified ids.
28
- description:
29
- 'Show an item in the app. Use this tool to open an artifact. When supplying IDs to show, they must be fully qualified like space:object.',
31
+ description: `
32
+ Show an item in the app. Use this tool to open an artifact.
33
+ When supplying IDs to show, they must be fully qualified like space:object.
34
+ `,
35
+ caption: 'Showing item...',
30
36
  // TODO(wittjosiah): Refactor Layout/Navigation/Deck actions so that they can be used directly.
31
37
  schema: S.Struct({
32
38
  id: S.String.annotations({
@@ -38,13 +44,16 @@ export default () =>
38
44
  }),
39
45
  ),
40
46
  }),
41
- execute: async ({ id, pivotId }, { extensions }) => {
47
+ execute: async ({ id }, { extensions }) => {
42
48
  invariant(extensions?.dispatch, 'No intent dispatcher');
43
49
  const { data, error } = await extensions.dispatch(
44
- createIntent(NavigationAction.AddToActive, {
45
- id,
50
+ createIntent(LayoutAction.Open, {
51
+ subject: [id],
46
52
  part: 'main',
47
- pivotId,
53
+ options: {
54
+ pivotId: extensions.pivotId,
55
+ positioning: 'end',
56
+ },
48
57
  }),
49
58
  );
50
59
  if (error) {