@dxos/plugin-deck 0.8.2-main.f081794 → 0.8.2-main.fbd8ed0

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 (134) hide show
  1. package/dist/lib/browser/{app-graph-builder-VYZ4IWI3.mjs → app-graph-builder-R7COZ4A6.mjs} +16 -15
  2. package/dist/lib/browser/app-graph-builder-R7COZ4A6.mjs.map +7 -0
  3. package/dist/lib/browser/{check-app-scheme-SEYECDHI.mjs → check-app-scheme-7AXGR6UT.mjs} +2 -3
  4. package/dist/lib/browser/check-app-scheme-7AXGR6UT.mjs.map +7 -0
  5. package/dist/lib/browser/chunk-3O2UZVBA.mjs +121 -0
  6. package/dist/lib/browser/chunk-3O2UZVBA.mjs.map +7 -0
  7. package/dist/lib/browser/chunk-JAYQ5BTF.mjs +157 -0
  8. package/dist/lib/browser/chunk-JAYQ5BTF.mjs.map +7 -0
  9. package/dist/lib/browser/{chunk-VP6FCWFV.mjs → chunk-KIGMELV2.mjs} +257 -247
  10. package/dist/lib/browser/chunk-KIGMELV2.mjs.map +7 -0
  11. package/dist/lib/browser/{state-Z6UY2Z3M.mjs → chunk-OF5RIATN.mjs} +6 -4
  12. package/dist/lib/browser/chunk-OF5RIATN.mjs.map +7 -0
  13. package/dist/lib/browser/chunk-TRFYUEBA.mjs +145 -0
  14. package/dist/lib/browser/chunk-TRFYUEBA.mjs.map +7 -0
  15. package/dist/lib/browser/index.mjs +4 -5
  16. package/dist/lib/browser/index.mjs.map +1 -1
  17. package/dist/lib/browser/{intent-resolver-6AK45PT5.mjs → intent-resolver-MAKOS57L.mjs} +86 -63
  18. package/dist/lib/browser/intent-resolver-MAKOS57L.mjs.map +7 -0
  19. package/dist/lib/browser/meta.json +1 -1
  20. package/dist/lib/browser/{react-root-KA2IL5RA.mjs → react-root-DGQVIHXP.mjs} +6 -6
  21. package/dist/lib/browser/{react-surface-LIPGYEYN.mjs → react-surface-PXBXIOPU.mjs} +6 -6
  22. package/dist/lib/browser/{settings-6NU7CF2B.mjs → settings-UBWJF7J7.mjs} +4 -4
  23. package/dist/lib/browser/{settings-6NU7CF2B.mjs.map → settings-UBWJF7J7.mjs.map} +3 -3
  24. package/dist/lib/browser/state-4WFB4SDO.mjs +10 -0
  25. package/dist/lib/browser/state-4WFB4SDO.mjs.map +7 -0
  26. package/dist/lib/browser/{tools-VDVQTJMD.mjs → tools-IVPIPTVA.mjs} +7 -7
  27. package/dist/lib/browser/tools-IVPIPTVA.mjs.map +7 -0
  28. package/dist/lib/browser/types.mjs +1 -1
  29. package/dist/lib/browser/{url-handler-3CARFXQK.mjs → url-handler-JSYGSVSB.mjs} +4 -4
  30. package/dist/lib/browser/url-handler-JSYGSVSB.mjs.map +7 -0
  31. package/dist/types/src/capabilities/app-graph-builder.d.ts +2 -179
  32. package/dist/types/src/capabilities/app-graph-builder.d.ts.map +1 -1
  33. package/dist/types/src/capabilities/check-app-scheme.d.ts +2 -2
  34. package/dist/types/src/capabilities/check-app-scheme.d.ts.map +1 -1
  35. package/dist/types/src/capabilities/index.d.ts +5 -180
  36. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  37. package/dist/types/src/capabilities/intent-resolver.d.ts +2 -2
  38. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  39. package/dist/types/src/capabilities/state.d.ts +2 -2
  40. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  41. package/dist/types/src/capabilities/tools.d.ts.map +1 -1
  42. package/dist/types/src/capabilities/url-handler.d.ts +2 -2
  43. package/dist/types/src/capabilities/url-handler.d.ts.map +1 -1
  44. package/dist/types/src/components/DeckLayout/Banner.d.ts.map +1 -1
  45. package/dist/types/src/components/DeckLayout/ContentEmpty.d.ts.map +1 -1
  46. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  47. package/dist/types/src/components/DeckLayout/Popover.d.ts.map +1 -1
  48. package/dist/types/src/components/DeckLayout/StatusBar.d.ts.map +1 -1
  49. package/dist/types/src/components/DeckLayout/Toast.d.ts.map +1 -1
  50. package/dist/types/src/components/DeckSettings/DeckSettings.d.ts.map +1 -1
  51. package/dist/types/src/components/Plank/Plank.d.ts +18 -5
  52. package/dist/types/src/components/Plank/Plank.d.ts.map +1 -1
  53. package/dist/types/src/components/Plank/Plank.stories.d.ts +3 -3
  54. package/dist/types/src/components/Plank/Plank.stories.d.ts.map +1 -1
  55. package/dist/types/src/components/Plank/PlankControls.d.ts +1 -0
  56. package/dist/types/src/components/Plank/PlankControls.d.ts.map +1 -1
  57. package/dist/types/src/components/Plank/PlankError.d.ts.map +1 -1
  58. package/dist/types/src/components/Plank/PlankHeading.d.ts.map +1 -1
  59. package/dist/types/src/components/Sidebar/ComplementarySidebar.d.ts.map +1 -1
  60. package/dist/types/src/components/Sidebar/Sidebar.d.ts.map +1 -1
  61. package/dist/types/src/components/Sidebar/SidebarButton.d.ts +2 -1
  62. package/dist/types/src/components/Sidebar/SidebarButton.d.ts.map +1 -1
  63. package/dist/types/src/hooks/index.d.ts +5 -1
  64. package/dist/types/src/hooks/index.d.ts.map +1 -1
  65. package/dist/types/src/hooks/useBreakpoints.d.ts.map +1 -0
  66. package/dist/types/src/hooks/useCompanions.d.ts.map +1 -0
  67. package/dist/types/src/hooks/useDeckCompanions.d.ts +13 -0
  68. package/dist/types/src/hooks/useDeckCompanions.d.ts.map +1 -0
  69. package/dist/types/src/hooks/useHoistStatusbar.d.ts.map +1 -0
  70. package/dist/types/src/hooks/useNodeActionExpander.d.ts.map +1 -1
  71. package/dist/types/src/index.d.ts +1 -1
  72. package/dist/types/src/index.d.ts.map +1 -1
  73. package/dist/types/src/layout.d.ts.map +1 -1
  74. package/dist/types/src/types.d.ts +104 -104
  75. package/dist/types/src/types.d.ts.map +1 -1
  76. package/dist/types/src/util/index.d.ts +1 -4
  77. package/dist/types/src/util/index.d.ts.map +1 -1
  78. package/dist/types/src/util/layoutAppliesTopbar.d.ts.map +1 -1
  79. package/dist/types/src/util/overscroll.d.ts.map +1 -1
  80. package/dist/types/src/util/set-active.d.ts.map +1 -1
  81. package/dist/types/tsconfig.tsbuildinfo +1 -1
  82. package/package.json +36 -29
  83. package/src/capabilities/app-graph-builder.ts +120 -92
  84. package/src/capabilities/check-app-scheme.ts +3 -7
  85. package/src/capabilities/index.ts +1 -0
  86. package/src/capabilities/intent-resolver.ts +140 -114
  87. package/src/capabilities/settings.ts +2 -2
  88. package/src/capabilities/state.ts +4 -2
  89. package/src/capabilities/tools.ts +4 -3
  90. package/src/capabilities/url-handler.ts +4 -4
  91. package/src/components/DeckLayout/ContentEmpty.tsx +3 -2
  92. package/src/components/DeckLayout/DeckLayout.tsx +12 -10
  93. package/src/components/DeckLayout/Popover.tsx +71 -43
  94. package/src/components/Plank/Plank.stories.tsx +20 -8
  95. package/src/components/Plank/Plank.tsx +101 -66
  96. package/src/components/Plank/PlankControls.tsx +26 -30
  97. package/src/components/Plank/PlankError.tsx +2 -6
  98. package/src/components/Plank/PlankHeading.tsx +23 -8
  99. package/src/components/Sidebar/ComplementarySidebar.tsx +4 -35
  100. package/src/components/Sidebar/Sidebar.tsx +2 -1
  101. package/src/components/Sidebar/SidebarButton.tsx +30 -7
  102. package/src/components/fragments.ts +1 -1
  103. package/src/hooks/index.ts +5 -1
  104. package/src/{util → hooks}/useCompanions.ts +3 -3
  105. package/src/hooks/useDeckCompanions.ts +33 -0
  106. package/src/hooks/useNodeActionExpander.ts +3 -8
  107. package/src/index.ts +1 -1
  108. package/src/types.ts +72 -71
  109. package/src/util/index.ts +1 -4
  110. package/dist/lib/browser/app-graph-builder-VYZ4IWI3.mjs.map +0 -7
  111. package/dist/lib/browser/check-app-scheme-SEYECDHI.mjs.map +0 -7
  112. package/dist/lib/browser/chunk-4QSEGMY3.mjs +0 -24
  113. package/dist/lib/browser/chunk-4QSEGMY3.mjs.map +0 -7
  114. package/dist/lib/browser/chunk-6HJZL3WT.mjs +0 -118
  115. package/dist/lib/browser/chunk-6HJZL3WT.mjs.map +0 -7
  116. package/dist/lib/browser/chunk-FLOVGNYB.mjs +0 -81
  117. package/dist/lib/browser/chunk-FLOVGNYB.mjs.map +0 -7
  118. package/dist/lib/browser/chunk-VP6FCWFV.mjs.map +0 -7
  119. package/dist/lib/browser/chunk-ZMJMCN7O.mjs +0 -157
  120. package/dist/lib/browser/chunk-ZMJMCN7O.mjs.map +0 -7
  121. package/dist/lib/browser/intent-resolver-6AK45PT5.mjs.map +0 -7
  122. package/dist/lib/browser/state-Z6UY2Z3M.mjs.map +0 -7
  123. package/dist/lib/browser/tools-VDVQTJMD.mjs.map +0 -7
  124. package/dist/lib/browser/url-handler-3CARFXQK.mjs.map +0 -7
  125. package/dist/types/src/util/useBreakpoints.d.ts.map +0 -1
  126. package/dist/types/src/util/useCompanions.d.ts.map +0 -1
  127. package/dist/types/src/util/useHoistStatusbar.d.ts.map +0 -1
  128. /package/dist/lib/browser/{react-root-KA2IL5RA.mjs.map → react-root-DGQVIHXP.mjs.map} +0 -0
  129. /package/dist/lib/browser/{react-surface-LIPGYEYN.mjs.map → react-surface-PXBXIOPU.mjs.map} +0 -0
  130. /package/dist/types/src/{util → hooks}/useBreakpoints.d.ts +0 -0
  131. /package/dist/types/src/{util → hooks}/useCompanions.d.ts +0 -0
  132. /package/dist/types/src/{util → hooks}/useHoistStatusbar.d.ts +0 -0
  133. /package/src/{util → hooks}/useBreakpoints.ts +0 -0
  134. /package/src/{util → hooks}/useHoistStatusbar.ts +0 -0
@@ -3,7 +3,7 @@
3
3
  //
4
4
 
5
5
  import { batch } from '@preact/signals-core';
6
- import { pipe } from 'effect';
6
+ import { Schema, Effect, pipe, Option } from 'effect';
7
7
 
8
8
  import {
9
9
  Capabilities,
@@ -11,16 +11,16 @@ import {
11
11
  contributes,
12
12
  IntentAction,
13
13
  LayoutAction,
14
- type PluginsContext,
14
+ type PluginContext,
15
15
  createIntent,
16
16
  chain,
17
17
  } from '@dxos/app-framework';
18
- import { getTypename, S } from '@dxos/echo-schema';
18
+ import { getTypename } from '@dxos/echo-schema';
19
19
  import { invariant } from '@dxos/invariant';
20
20
  import { isLiveObject } from '@dxos/live-object';
21
21
  import { log } from '@dxos/log';
22
22
  import { AttentionCapabilities } from '@dxos/plugin-attention';
23
- import { type Node } from '@dxos/plugin-graph';
23
+ import { isActionLike } from '@dxos/plugin-graph';
24
24
  import { ObservabilityAction } from '@dxos/plugin-observability/types';
25
25
  import { byPosition, isNonNullable } from '@dxos/util';
26
26
 
@@ -38,13 +38,13 @@ import {
38
38
  } from '../types';
39
39
  import { setActive } from '../util';
40
40
 
41
- export default (context: PluginsContext) =>
41
+ export default (context: PluginContext) =>
42
42
  contributes(Capabilities.IntentResolver, [
43
43
  createResolver({
44
44
  intent: IntentAction.ShowUndo,
45
45
  resolve: (data) => {
46
- const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
47
- const { undoPromise: undo } = context.requestCapability(Capabilities.IntentDispatcher);
46
+ const layout = context.getCapability(DeckCapabilities.MutableDeckState);
47
+ const { undoPromise: undo } = context.getCapability(Capabilities.IntentDispatcher);
48
48
 
49
49
  // TODO(wittjosiah): Support undoing further back than the last action.
50
50
  if (layout.currentUndoId) {
@@ -67,12 +67,12 @@ export default (context: PluginsContext) =>
67
67
  }),
68
68
  createResolver({
69
69
  intent: LayoutAction.UpdateLayout,
70
- // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdateSidebar.fields.input)`
70
+ // TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.UpdateSidebar.fields.input)`
71
71
  // but the filter is not being applied correctly.
72
- filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdateSidebar.fields.input> =>
73
- S.is(LayoutAction.UpdateSidebar.fields.input)(data),
72
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.UpdateSidebar.fields.input> =>
73
+ Schema.is(LayoutAction.UpdateSidebar.fields.input)(data),
74
74
  resolve: ({ options }) => {
75
- const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
75
+ const layout = context.getCapability(DeckCapabilities.MutableDeckState);
76
76
  const next = options?.state ?? layout.sidebarState;
77
77
  if (next !== layout.sidebarState) {
78
78
  layout.sidebarState = next;
@@ -81,12 +81,12 @@ export default (context: PluginsContext) =>
81
81
  }),
82
82
  createResolver({
83
83
  intent: LayoutAction.UpdateLayout,
84
- // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdateComplementary.fields.input)`
84
+ // TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.UpdateComplementary.fields.input)`
85
85
  // but the filter is not being applied correctly.
86
- filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdateComplementary.fields.input> =>
87
- S.is(LayoutAction.UpdateComplementary.fields.input)(data),
86
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.UpdateComplementary.fields.input> =>
87
+ Schema.is(LayoutAction.UpdateComplementary.fields.input)(data),
88
88
  resolve: ({ subject, options }) => {
89
- const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
89
+ const layout = context.getCapability(DeckCapabilities.MutableDeckState);
90
90
 
91
91
  if (layout.complementarySidebarPanel !== subject) {
92
92
  layout.complementarySidebarPanel = subject;
@@ -100,12 +100,12 @@ export default (context: PluginsContext) =>
100
100
  }),
101
101
  createResolver({
102
102
  intent: LayoutAction.UpdateLayout,
103
- // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdateDialog.fields.input)`
103
+ // TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.UpdateDialog.fields.input)`
104
104
  // but the filter is not being applied correctly.
105
- filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdateDialog.fields.input> =>
106
- S.is(LayoutAction.UpdateDialog.fields.input)(data),
105
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.UpdateDialog.fields.input> =>
106
+ Schema.is(LayoutAction.UpdateDialog.fields.input)(data),
107
107
  resolve: ({ subject, options }) => {
108
- const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
108
+ const layout = context.getCapability(DeckCapabilities.MutableDeckState);
109
109
  layout.dialogOpen = options.state ?? Boolean(subject);
110
110
  layout.dialogContent = subject ? { component: subject, props: options.props } : null;
111
111
  layout.dialogBlockAlign = options.blockAlign ?? 'center';
@@ -114,12 +114,12 @@ export default (context: PluginsContext) =>
114
114
  }),
115
115
  createResolver({
116
116
  intent: LayoutAction.UpdateLayout,
117
- // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.UpdatePopover.fields.input)`
117
+ // TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.UpdatePopover.fields.input)`
118
118
  // but the filter is not being applied correctly.
119
- filter: (data): data is S.Schema.Type<typeof LayoutAction.UpdatePopover.fields.input> =>
120
- S.is(LayoutAction.UpdatePopover.fields.input)(data),
119
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.UpdatePopover.fields.input> =>
120
+ Schema.is(LayoutAction.UpdatePopover.fields.input)(data),
121
121
  resolve: ({ subject, options }) => {
122
- const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
122
+ const layout = context.getCapability(DeckCapabilities.MutableDeckState);
123
123
  layout.popoverOpen = options.state ?? Boolean(subject);
124
124
  layout.popoverContent =
125
125
  typeof subject === 'string' ? { component: subject, props: options.props } : subject ? { subject } : null;
@@ -133,21 +133,21 @@ export default (context: PluginsContext) =>
133
133
  }),
134
134
  createResolver({
135
135
  intent: LayoutAction.UpdateLayout,
136
- // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.AddToast.fields.input)`
136
+ // TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.AddToast.fields.input)`
137
137
  // but the filter is not being applied correctly.
138
- filter: (data): data is S.Schema.Type<typeof LayoutAction.AddToast.fields.input> =>
139
- S.is(LayoutAction.AddToast.fields.input)(data),
138
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.AddToast.fields.input> =>
139
+ Schema.is(LayoutAction.AddToast.fields.input)(data),
140
140
  resolve: ({ subject }) => {
141
- const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
141
+ const layout = context.getCapability(DeckCapabilities.MutableDeckState);
142
142
  layout.toasts.push(subject);
143
143
  },
144
144
  }),
145
145
  createResolver({
146
146
  intent: LayoutAction.UpdateLayout,
147
- // TODO(wittjosiah): This should be able to just be `S.is(LayoutAction.SetLayoutMode.fields.input)`
147
+ // TODO(wittjosiah): This should be able to just be `Schema.is(LayoutAction.SetLayoutMode.fields.input)`
148
148
  // but the filter is not being applied correctly.
149
- filter: (data): data is S.Schema.Type<typeof LayoutAction.SetLayoutMode.fields.input> => {
150
- if (!S.is(LayoutAction.SetLayoutMode.fields.input)(data)) {
149
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.SetLayoutMode.fields.input> => {
150
+ if (!Schema.is(LayoutAction.SetLayoutMode.fields.input)(data)) {
151
151
  return false;
152
152
  }
153
153
 
@@ -158,7 +158,7 @@ export default (context: PluginsContext) =>
158
158
  return true;
159
159
  },
160
160
  resolve: ({ subject, options }) => {
161
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
161
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
162
162
 
163
163
  const setMode = (mode: LayoutMode) => {
164
164
  const deck = state.deck;
@@ -202,10 +202,11 @@ export default (context: PluginsContext) =>
202
202
  }),
203
203
  createResolver({
204
204
  intent: LayoutAction.UpdateLayout,
205
- filter: (data): data is S.Schema.Type<typeof LayoutAction.SwitchWorkspace.fields.input> =>
206
- S.is(LayoutAction.SwitchWorkspace.fields.input)(data),
205
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.SwitchWorkspace.fields.input> =>
206
+ Schema.is(LayoutAction.SwitchWorkspace.fields.input)(data),
207
207
  resolve: ({ subject }) => {
208
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
208
+ const { graph } = context.getCapability(Capabilities.AppGraph);
209
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
209
210
  batch(() => {
210
211
  // TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
211
212
  // Ideally this should be worked into the data model in a generic way.
@@ -223,15 +224,22 @@ export default (context: PluginsContext) =>
223
224
  return {
224
225
  intents: [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: first })],
225
226
  };
227
+ } else {
228
+ const [item] = graph.getConnections(subject).filter((node) => !isActionLike(node));
229
+ if (item) {
230
+ return {
231
+ intents: [createIntent(LayoutAction.Open, { part: 'main', subject: [item.id] })],
232
+ };
233
+ }
226
234
  }
227
235
  },
228
236
  }),
229
237
  createResolver({
230
238
  intent: LayoutAction.UpdateLayout,
231
- filter: (data): data is S.Schema.Type<typeof LayoutAction.RevertWorkspace.fields.input> =>
232
- S.is(LayoutAction.RevertWorkspace.fields.input)(data),
239
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.RevertWorkspace.fields.input> =>
240
+ Schema.is(LayoutAction.RevertWorkspace.fields.input)(data),
233
241
  resolve: () => {
234
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
242
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
235
243
  return {
236
244
  intents: [createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: state.previousDeck })],
237
245
  };
@@ -239,65 +247,78 @@ export default (context: PluginsContext) =>
239
247
  }),
240
248
  createResolver({
241
249
  intent: LayoutAction.UpdateLayout,
242
- filter: (data): data is S.Schema.Type<typeof LayoutAction.Open.fields.input> =>
243
- S.is(LayoutAction.Open.fields.input)(data),
244
- resolve: ({ subject, options }) => {
245
- const { graph } = context.requestCapability(Capabilities.AppGraph);
246
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
247
- const attention = context.requestCapability(AttentionCapabilities.Attention);
248
- const settings = context
249
- .requestCapabilities(Capabilities.SettingsStore)[0]
250
- ?.getStore<DeckSettingsProps>(DECK_PLUGIN)?.value;
250
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.Open.fields.input> =>
251
+ Schema.is(LayoutAction.Open.fields.input)(data),
252
+ resolve: ({ subject, options }) =>
253
+ Effect.gen(function* () {
254
+ const { graph } = context.getCapability(Capabilities.AppGraph);
255
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
256
+ const attention = context.getCapability(AttentionCapabilities.Attention);
257
+ const settings = context
258
+ .getCapabilities(Capabilities.SettingsStore)[0]
259
+ ?.getStore<DeckSettingsProps>(DECK_PLUGIN)?.value;
251
260
 
252
- const previouslyOpenIds = new Set<string>(state.deck.solo ? [state.deck.solo] : state.deck.active);
253
- batch(() => {
254
- const next = state.deck.solo
255
- ? (subject as string[]).map((id) => createEntryId(id, options?.variant))
256
- : subject.reduce(
257
- (acc, entryId) =>
258
- openEntry(acc, entryId, {
259
- key: options?.key,
260
- positioning: options?.positioning ?? settings?.newPlankPositioning,
261
- pivotId: options?.pivotId,
262
- variant: options?.variant,
263
- }),
264
- state.deck.active,
265
- );
261
+ if (options?.workspace && state.activeDeck !== options?.workspace) {
262
+ const { dispatch } = context.getCapability(Capabilities.IntentDispatcher);
263
+ yield* dispatch(
264
+ createIntent(LayoutAction.SwitchWorkspace, { part: 'workspace', subject: options.workspace }),
265
+ );
266
+ }
266
267
 
267
- return setActive({ next, state, attention });
268
- });
268
+ const previouslyOpenIds = new Set<string>(state.deck.solo ? [state.deck.solo] : state.deck.active);
269
+ batch(() => {
270
+ const next = state.deck.solo
271
+ ? (subject as string[]).map((id) => createEntryId(id, options?.variant))
272
+ : subject.reduce(
273
+ (acc, entryId) =>
274
+ openEntry(acc, entryId, {
275
+ key: options?.key,
276
+ positioning: options?.positioning ?? settings?.newPlankPositioning,
277
+ pivotId: options?.pivotId,
278
+ variant: options?.variant,
279
+ }),
280
+ state.deck.active,
281
+ );
269
282
 
270
- const ids = state.deck.solo ? [state.deck.solo] : state.deck.active;
271
- const newlyOpen = ids.filter((i) => !previouslyOpenIds.has(i));
283
+ return setActive({ next, state, attention });
284
+ });
272
285
 
273
- return {
274
- intents: [
275
- ...(options?.scrollIntoView !== false
276
- ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: newlyOpen[0] ?? subject[0] })]
277
- : []),
278
- createIntent(LayoutAction.Expose, { part: 'navigation', subject: newlyOpen[0] ?? subject[0] }),
279
- ...newlyOpen.map((subjectId) => {
280
- const active = graph?.findNode(subjectId)?.data;
281
- const typename = isLiveObject(active) ? getTypename(active) : undefined;
282
- return createIntent(ObservabilityAction.SendEvent, {
283
- name: 'navigation.activate',
284
- properties: {
285
- subjectId,
286
- typename,
287
- },
288
- });
289
- }),
290
- ],
291
- };
292
- },
286
+ const ids = state.deck.solo ? [state.deck.solo] : state.deck.active;
287
+ const newlyOpen = ids.filter((i) => !previouslyOpenIds.has(i));
288
+
289
+ return {
290
+ intents: [
291
+ ...(options?.scrollIntoView !== false
292
+ ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: newlyOpen[0] ?? subject[0] })]
293
+ : []),
294
+ createIntent(LayoutAction.Expose, { part: 'navigation', subject: newlyOpen[0] ?? subject[0] }),
295
+ ...newlyOpen.map((subjectId) => {
296
+ const typename = Option.match(graph.getNode(subjectId), {
297
+ onNone: () => undefined,
298
+ onSome: (node) => {
299
+ const active = node.data;
300
+ return isLiveObject(active) ? getTypename(active) : undefined;
301
+ },
302
+ });
303
+ return createIntent(ObservabilityAction.SendEvent, {
304
+ name: 'navigation.activate',
305
+ properties: {
306
+ subjectId,
307
+ typename,
308
+ },
309
+ });
310
+ }),
311
+ ],
312
+ };
313
+ }),
293
314
  }),
294
315
  createResolver({
295
316
  intent: LayoutAction.UpdateLayout,
296
- filter: (data): data is S.Schema.Type<typeof LayoutAction.Close.fields.input> =>
297
- S.is(LayoutAction.Close.fields.input)(data),
317
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.Close.fields.input> =>
318
+ Schema.is(LayoutAction.Close.fields.input)(data),
298
319
  resolve: ({ subject }) => {
299
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
300
- const attention = context.requestCapability(AttentionCapabilities.Attention);
320
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
321
+ const attention = context.getCapability(AttentionCapabilities.Attention);
301
322
  const active = state.deck.solo ? [state.deck.solo] : state.deck.active;
302
323
  const next = subject.reduce((acc, id) => closeEntry(acc, id), active);
303
324
  const toAttend = setActive({ next, state, attention });
@@ -316,11 +337,11 @@ export default (context: PluginsContext) =>
316
337
  }),
317
338
  createResolver({
318
339
  intent: LayoutAction.UpdateLayout,
319
- filter: (data): data is S.Schema.Type<typeof LayoutAction.Set.fields.input> =>
320
- S.is(LayoutAction.Set.fields.input)(data),
340
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.Set.fields.input> =>
341
+ Schema.is(LayoutAction.Set.fields.input)(data),
321
342
  resolve: ({ subject }) => {
322
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
323
- const attention = context.requestCapability(AttentionCapabilities.Attention);
343
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
344
+ const attention = context.getCapability(AttentionCapabilities.Attention);
324
345
  const toAttend = setActive({ next: subject as string[], state, attention });
325
346
  return {
326
347
  intents: toAttend ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: toAttend })] : [],
@@ -329,24 +350,24 @@ export default (context: PluginsContext) =>
329
350
  }),
330
351
  createResolver({
331
352
  intent: LayoutAction.UpdateLayout,
332
- filter: (data): data is S.Schema.Type<typeof LayoutAction.ScrollIntoView.fields.input> =>
333
- S.is(LayoutAction.ScrollIntoView.fields.input)(data),
353
+ filter: (data): data is Schema.Schema.Type<typeof LayoutAction.ScrollIntoView.fields.input> =>
354
+ Schema.is(LayoutAction.ScrollIntoView.fields.input)(data),
334
355
  resolve: ({ subject }) => {
335
- const layout = context.requestCapability(DeckCapabilities.MutableDeckState);
356
+ const layout = context.getCapability(DeckCapabilities.MutableDeckState);
336
357
  layout.scrollIntoView = subject;
337
358
  },
338
359
  }),
339
360
  createResolver({
340
361
  intent: DeckAction.UpdatePlankSize,
341
362
  resolve: (data) => {
342
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
363
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
343
364
  state.deck.plankSizing[data.id] = data.size;
344
365
  },
345
366
  }),
346
367
  createResolver({
347
368
  intent: DeckAction.ChangeCompanion,
348
369
  resolve: (data) => {
349
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
370
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
350
371
  // TODO(thure): Reactivity only works when creating a lexically new `activeCompanions`… Are these not proxy objects?
351
372
  if (data.companion === null) {
352
373
  const { [data.primary]: _, ...nextActiveCompanions } = state.deck.activeCompanions ?? {};
@@ -363,9 +384,9 @@ export default (context: PluginsContext) =>
363
384
  createResolver({
364
385
  intent: DeckAction.Adjust,
365
386
  resolve: (adjustment) => {
366
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
367
- const attention = context.requestCapability(AttentionCapabilities.Attention);
368
- const { graph } = context.requestCapability(Capabilities.AppGraph);
387
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
388
+ const attention = context.getCapability(AttentionCapabilities.Attention);
389
+ const { graph } = context.getCapability(Capabilities.AppGraph);
369
390
 
370
391
  return batch(() => {
371
392
  if (adjustment.type === 'increment-end' || adjustment.type === 'increment-start') {
@@ -377,20 +398,25 @@ export default (context: PluginsContext) =>
377
398
  }
378
399
 
379
400
  if (adjustment.type === 'companion') {
380
- const node = graph.findNode(adjustment.id);
381
- const [companion] = node
382
- ? graph
383
- .nodes(node, { filter: (n): n is Node<any> => n.type === PLANK_COMPANION_TYPE })
384
- .toSorted((a, b) => byPosition(a.properties, b.properties))
385
- : [];
386
- if (companion) {
387
- return {
388
- intents: [
389
- // TODO(wittjosiah): This should remember the previously selected companion.
390
- createIntent(DeckAction.ChangeCompanion, { primary: adjustment.id, companion: companion.id }),
391
- ],
392
- };
393
- }
401
+ return pipe(
402
+ graph.getNode(adjustment.id),
403
+ Option.map((node) =>
404
+ graph
405
+ .getConnections(node.id)
406
+ .filter((n) => n.type === PLANK_COMPANION_TYPE)
407
+ .toSorted((a, b) => byPosition(a.properties, b.properties)),
408
+ ),
409
+ Option.flatMap((companions) => (companions.length > 0 ? Option.some(companions[0]) : Option.none())),
410
+ Option.match({
411
+ onNone: () => ({}),
412
+ onSome: (companion) => ({
413
+ intents: [
414
+ // TODO(wittjosiah): This should remember the previously selected companion.
415
+ createIntent(DeckAction.ChangeCompanion, { primary: adjustment.id, companion: companion.id }),
416
+ ],
417
+ }),
418
+ }),
419
+ );
394
420
  }
395
421
 
396
422
  if (adjustment.type.startsWith('solo')) {
@@ -11,9 +11,9 @@ import { DeckSettingsSchema, type DeckSettingsProps } from '../types';
11
11
  export default () => {
12
12
  const settings = live<DeckSettingsProps>({
13
13
  showHints: false,
14
- enableDeck: true,
15
- enableNativeRedirect: false,
14
+ enableDeck: false,
16
15
  enableStatusbar: false,
16
+ enableNativeRedirect: false,
17
17
  newPlankPositioning: 'start',
18
18
  overscroll: 'none',
19
19
  });
@@ -10,7 +10,7 @@ import { type SidebarState } from '@dxos/react-ui';
10
10
 
11
11
  import { DeckCapabilities } from './capabilities';
12
12
  import { DECK_PLUGIN } from '../meta';
13
- import { getMode, type DeckState, type DeckPluginState, defaultDeck } from '../types';
13
+ import { getMode, type DeckPluginState, defaultDeck, type DeckState } from '../types';
14
14
 
15
15
  const boolean = /true|false/;
16
16
 
@@ -29,7 +29,7 @@ const migrateSidebarState = () => {
29
29
  });
30
30
  };
31
31
 
32
- export default () => {
32
+ const DeckStateFactory = () => {
33
33
  migrateSidebarState();
34
34
 
35
35
  const state = new LocalStorageStore<DeckPluginState>(DECK_PLUGIN, {
@@ -100,3 +100,5 @@ export default () => {
100
100
  contributes(Capabilities.Layout, layout),
101
101
  ];
102
102
  };
103
+
104
+ export default DeckStateFactory;
@@ -2,6 +2,8 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
+ import { Schema } from 'effect';
6
+
5
7
  import {
6
8
  contributes,
7
9
  createIntent,
@@ -10,7 +12,6 @@ import {
10
12
  type PromiseIntentDispatcher,
11
13
  } from '@dxos/app-framework';
12
14
  import { defineTool, ToolResult } from '@dxos/artifact';
13
- import { S } from '@dxos/echo-schema';
14
15
  import { invariant } from '@dxos/invariant';
15
16
 
16
17
  import { meta } from '../meta';
@@ -35,8 +36,8 @@ export default () =>
35
36
  `,
36
37
  caption: 'Showing item...',
37
38
  // TODO(wittjosiah): Refactor Layout/Navigation/Deck actions so that they can be used directly.
38
- schema: S.Struct({
39
- id: S.String.annotations({
39
+ schema: Schema.Struct({
40
+ id: Schema.String.annotations({
40
41
  description: 'The ID of the item to show.',
41
42
  }),
42
43
  }),
@@ -2,16 +2,16 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { Capabilities, contributes, createIntent, LayoutAction, type PluginsContext } from '@dxos/app-framework';
5
+ import { Capabilities, contributes, createIntent, LayoutAction, type PluginContext } from '@dxos/app-framework';
6
6
  import { scheduledEffect } from '@dxos/echo-signals/core';
7
7
 
8
8
  import { DeckCapabilities } from './capabilities';
9
9
  import { defaultDeck } from '../types';
10
10
 
11
11
  // TODO(wittjosiah): Cleanup the url handling. May justify introducing routing capabilities.
12
- export default async (context: PluginsContext) => {
13
- const { dispatchPromise: dispatch } = context.requestCapability(Capabilities.IntentDispatcher) ?? {};
14
- const state = context.requestCapability(DeckCapabilities.MutableDeckState);
12
+ export default async (context: PluginContext) => {
13
+ const { dispatchPromise: dispatch } = context.getCapability(Capabilities.IntentDispatcher);
14
+ const state = context.getCapability(DeckCapabilities.MutableDeckState);
15
15
 
16
16
  const handleNavigation = async () => {
17
17
  const pathname = window.location.pathname;
@@ -7,8 +7,9 @@ import React from 'react';
7
7
  import { Surface, useCapability } from '@dxos/app-framework';
8
8
 
9
9
  import { DeckCapabilities } from '../../capabilities';
10
+ import { useBreakpoints } from '../../hooks';
10
11
  import { getMode } from '../../types';
11
- import { layoutAppliesTopbar, useBreakpoints } from '../../util';
12
+ import { layoutAppliesTopbar } from '../../util';
12
13
  import { ToggleSidebarButton } from '../Sidebar';
13
14
  import { fixedSidebarToggleStyles } from '../fragments';
14
15
 
@@ -20,7 +21,7 @@ export const ContentEmpty = () => {
20
21
  return (
21
22
  <div
22
23
  role='none'
23
- className='grid place-items-center p-8 relative bg-deck'
24
+ className='grid place-items-center p-8 relative bg-deckSurface'
24
25
  data-testid='layoutPlugin.firstRunMessage'
25
26
  >
26
27
  <Surface role='keyshortcuts' />
@@ -26,9 +26,10 @@ import { StatusBar } from './StatusBar';
26
26
  import { Toast } from './Toast';
27
27
  import { Topbar } from './Topbar';
28
28
  import { DeckCapabilities } from '../../capabilities';
29
+ import { useBreakpoints, useHoistStatusbar } from '../../hooks';
29
30
  import { DECK_PLUGIN } from '../../meta';
30
31
  import { type DeckSettingsProps, getMode } from '../../types';
31
- import { calculateOverscroll, layoutAppliesTopbar, useBreakpoints, useHoistStatusbar } from '../../util';
32
+ import { calculateOverscroll, layoutAppliesTopbar } from '../../util';
32
33
  import { Plank } from '../Plank';
33
34
  import { ComplementarySidebar, Sidebar, ToggleComplementarySidebarButton, ToggleSidebarButton } from '../Sidebar';
34
35
  import { fixedComplementarySidebarToggleStyles, fixedSidebarToggleStyles } from '../fragments';
@@ -38,7 +39,7 @@ export type DeckLayoutProps = {
38
39
  };
39
40
 
40
41
  const PlankSeparator = ({ order }: { order: number }) =>
41
- order > 0 ? <span role='separator' className='row-span-2 bg-deck is-4' style={{ gridColumn: order }} /> : null;
42
+ order > 0 ? <span role='separator' className='row-span-2 bg-deckSurface is-4' style={{ gridColumn: order }} /> : null;
42
43
 
43
44
  export const DeckLayout = ({ onDismissToast }: DeckLayoutProps) => {
44
45
  const { dispatchPromise: dispatch } = useIntentDispatcher();
@@ -59,7 +60,7 @@ export const DeckLayout = ({ onDismissToast }: DeckLayoutProps) => {
59
60
  useEffect(() => {
60
61
  // NOTE: Not `useAttended` so that the layout component is not re-rendered when the attended list changes.
61
62
  const attended = untracked(() => {
62
- const attention = pluginManager.context.requestCapability(AttentionCapabilities.Attention);
63
+ const attention = pluginManager.context.getCapability(AttentionCapabilities.Attention);
63
64
  return attention.current;
64
65
  });
65
66
  const firstId = solo ?? active[0];
@@ -78,7 +79,7 @@ export const DeckLayout = ({ onDismissToast }: DeckLayoutProps) => {
78
79
  if (!isNotMobile && getMode(deck) === 'deck') {
79
80
  // NOTE: Not `useAttended` so that the layout component is not re-rendered when the attended list changes.
80
81
  const attended = untracked(() => {
81
- const attention = pluginManager.context.requestCapability(AttentionCapabilities.Attention);
82
+ const attention = pluginManager.context.getCapability(AttentionCapabilities.Attention);
82
83
  return attention.current;
83
84
  });
84
85
 
@@ -91,14 +92,15 @@ export const DeckLayout = ({ onDismissToast }: DeckLayoutProps) => {
91
92
  }
92
93
  }, [isNotMobile, deck, dispatch]);
93
94
 
94
- // If deck is disabled in settings, ensure that the layout is in solo mode.
95
+ // When deck is disabled in settings, set to solo mode if the current layout mode is deck.
96
+ // TODO(thure): Applying this as an effect should be avoided over emitting the intent only when the setting changes.
95
97
  useEffect(() => {
96
- if (!settings.enableDeck) {
98
+ if (!settings.enableDeck && layoutMode === 'deck') {
97
99
  void dispatch(
98
100
  createIntent(LayoutAction.SetLayoutMode, { part: 'mode', subject: active[0], options: { mode: 'solo' } }),
99
101
  );
100
102
  }
101
- }, [settings.enableDeck, dispatch, active]);
103
+ }, [settings.enableDeck, dispatch, active, layoutMode]);
102
104
 
103
105
  /**
104
106
  * Clear scroll restoration state if the window is resized
@@ -190,8 +192,8 @@ export const DeckLayout = ({ onDismissToast }: DeckLayoutProps) => {
190
192
  {!isEmpty && (
191
193
  <Main.Content
192
194
  bounce
193
- classNames={mainPosition}
194
195
  handlesFocus
196
+ classNames={mainPosition}
195
197
  style={
196
198
  {
197
199
  '--dx-main-sidebarWidth':
@@ -213,7 +215,7 @@ export const DeckLayout = ({ onDismissToast }: DeckLayoutProps) => {
213
215
  >
214
216
  <div
215
217
  role='none'
216
- className={!solo ? 'relative bg-deck overflow-hidden' : 'sr-only'}
218
+ className={!solo ? 'relative bg-deckSurface overflow-hidden' : 'sr-only'}
217
219
  {...(solo && { inert: '' })}
218
220
  >
219
221
  {!topbar && !fullscreen && <ToggleSidebarButton classNames={fixedSidebarToggleStyles} />}
@@ -247,7 +249,7 @@ export const DeckLayout = ({ onDismissToast }: DeckLayoutProps) => {
247
249
  </div>
248
250
  <div
249
251
  role='none'
250
- className={solo ? 'relative bg-deck overflow-hidden' : 'sr-only'}
252
+ className={solo ? 'relative bg-deckSurface overflow-hidden' : 'sr-only'}
251
253
  {...(!solo && { inert: '' })}
252
254
  >
253
255
  {!topbar && !fullscreen && <ToggleSidebarButton classNames={fixedSidebarToggleStyles} />}