@dxos/plugin-deck 0.8.1-main.ba2dec9 → 0.8.1-staging.5be625a

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 (75) hide show
  1. package/dist/lib/browser/{app-graph-builder-IYHAGFA3.mjs → app-graph-builder-K4KVSHNT.mjs} +3 -3
  2. package/dist/lib/browser/{check-app-scheme-S3EYUPMF.mjs → check-app-scheme-6SS6I3RN.mjs} +2 -2
  3. package/dist/lib/browser/{chunk-N7TEPFVR.mjs → chunk-2WTHB3TG.mjs} +1 -1
  4. package/dist/lib/browser/{chunk-N7TEPFVR.mjs.map → chunk-2WTHB3TG.mjs.map} +2 -2
  5. package/dist/lib/browser/{chunk-FYKBOM3C.mjs → chunk-7X43JKZG.mjs} +33 -3
  6. package/dist/lib/browser/chunk-7X43JKZG.mjs.map +7 -0
  7. package/dist/lib/browser/{chunk-63K74E3J.mjs → chunk-MWUT66KV.mjs} +175 -70
  8. package/dist/lib/browser/chunk-MWUT66KV.mjs.map +7 -0
  9. package/dist/lib/browser/chunk-NSNAYFAX.mjs +24 -0
  10. package/dist/lib/browser/{chunk-YCKJNTKG.mjs → chunk-RZLH5F56.mjs} +2 -2
  11. package/dist/lib/browser/{chunk-22AQ5IVX.mjs → chunk-WCNPMAR4.mjs} +2 -2
  12. package/dist/lib/browser/index.mjs +5 -4
  13. package/dist/lib/browser/index.mjs.map +2 -2
  14. package/dist/lib/browser/{intent-resolver-P5BVUQKU.mjs → intent-resolver-MEBOMCYI.mjs} +46 -17
  15. package/dist/lib/browser/intent-resolver-MEBOMCYI.mjs.map +7 -0
  16. package/dist/lib/browser/meta.json +1 -1
  17. package/dist/lib/browser/{react-root-ZQCTDM4Y.mjs → react-root-USUAHDML.mjs} +7 -7
  18. package/dist/lib/browser/{react-surface-RG3PVPY3.mjs → react-surface-TQG4YYES.mjs} +7 -7
  19. package/dist/lib/browser/{settings-X3P2HKQJ.mjs → settings-DYS3FFMN.mjs} +3 -3
  20. package/dist/lib/browser/{state-2MOTLKVR.mjs → state-DRRCGMU2.mjs} +7 -11
  21. package/dist/lib/browser/state-DRRCGMU2.mjs.map +7 -0
  22. package/dist/lib/browser/tools-NDEUSO4R.mjs +78 -0
  23. package/dist/lib/browser/tools-NDEUSO4R.mjs.map +7 -0
  24. package/dist/lib/browser/types.mjs +10 -4
  25. package/dist/lib/browser/{url-handler-MVHTKUYA.mjs → url-handler-4BCN7AYC.mjs} +7 -9
  26. package/dist/lib/browser/url-handler-4BCN7AYC.mjs.map +7 -0
  27. package/dist/types/src/capabilities/capabilities.d.ts +26 -2
  28. package/dist/types/src/capabilities/capabilities.d.ts.map +1 -1
  29. package/dist/types/src/capabilities/intent-resolver.d.ts.map +1 -1
  30. package/dist/types/src/capabilities/state.d.ts +13 -1
  31. package/dist/types/src/capabilities/state.d.ts.map +1 -1
  32. package/dist/types/src/capabilities/tools.d.ts +1 -0
  33. package/dist/types/src/capabilities/tools.d.ts.map +1 -1
  34. package/dist/types/src/capabilities/url-handler.d.ts.map +1 -1
  35. package/dist/types/src/components/DeckLayout/DeckLayout.d.ts.map +1 -1
  36. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts +6 -2
  37. package/dist/types/src/components/DeckLayout/NodePlankHeading.d.ts.map +1 -1
  38. package/dist/types/src/components/DeckLayout/Plank.d.ts +4 -4
  39. package/dist/types/src/components/DeckLayout/Plank.d.ts.map +1 -1
  40. package/dist/types/src/components/DeckLayout/PlankControls.d.ts +6 -1
  41. package/dist/types/src/components/DeckLayout/PlankControls.d.ts.map +1 -1
  42. package/dist/types/src/meta.d.ts +2 -5
  43. package/dist/types/src/meta.d.ts.map +1 -1
  44. package/dist/types/src/translations.d.ts +1 -0
  45. package/dist/types/src/translations.d.ts.map +1 -1
  46. package/dist/types/src/types.d.ts +31 -0
  47. package/dist/types/src/types.d.ts.map +1 -1
  48. package/package.json +30 -30
  49. package/src/capabilities/intent-resolver.ts +29 -3
  50. package/src/capabilities/state.ts +2 -9
  51. package/src/capabilities/tools.ts +34 -22
  52. package/src/capabilities/url-handler.ts +2 -8
  53. package/src/components/DeckLayout/DeckLayout.tsx +34 -10
  54. package/src/components/DeckLayout/NodePlankHeading.tsx +49 -29
  55. package/src/components/DeckLayout/Plank.tsx +183 -111
  56. package/src/components/DeckLayout/PlankControls.tsx +34 -3
  57. package/src/meta.ts +2 -2
  58. package/src/translations.ts +1 -0
  59. package/src/types.ts +29 -0
  60. package/dist/lib/browser/chunk-63K74E3J.mjs.map +0 -7
  61. package/dist/lib/browser/chunk-ATPSUXXK.mjs +0 -24
  62. package/dist/lib/browser/chunk-FYKBOM3C.mjs.map +0 -7
  63. package/dist/lib/browser/intent-resolver-P5BVUQKU.mjs.map +0 -7
  64. package/dist/lib/browser/state-2MOTLKVR.mjs.map +0 -7
  65. package/dist/lib/browser/tools-64LXGLYR.mjs +0 -59
  66. package/dist/lib/browser/tools-64LXGLYR.mjs.map +0 -7
  67. package/dist/lib/browser/url-handler-MVHTKUYA.mjs.map +0 -7
  68. /package/dist/lib/browser/{app-graph-builder-IYHAGFA3.mjs.map → app-graph-builder-K4KVSHNT.mjs.map} +0 -0
  69. /package/dist/lib/browser/{check-app-scheme-S3EYUPMF.mjs.map → check-app-scheme-6SS6I3RN.mjs.map} +0 -0
  70. /package/dist/lib/browser/{chunk-ATPSUXXK.mjs.map → chunk-NSNAYFAX.mjs.map} +0 -0
  71. /package/dist/lib/browser/{chunk-YCKJNTKG.mjs.map → chunk-RZLH5F56.mjs.map} +0 -0
  72. /package/dist/lib/browser/{chunk-22AQ5IVX.mjs.map → chunk-WCNPMAR4.mjs.map} +0 -0
  73. /package/dist/lib/browser/{react-root-ZQCTDM4Y.mjs.map → react-root-USUAHDML.mjs.map} +0 -0
  74. /package/dist/lib/browser/{react-surface-RG3PVPY3.mjs.map → react-surface-TQG4YYES.mjs.map} +0 -0
  75. /package/dist/lib/browser/{settings-X3P2HKQJ.mjs.map → settings-DYS3FFMN.mjs.map} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dxos/plugin-deck",
3
- "version": "0.8.1-main.ba2dec9",
3
+ "version": "0.8.1-staging.5be625a",
4
4
  "description": "DXOS Surface plugin for the main application layout.",
5
5
  "homepage": "https://dxos.org",
6
6
  "bugs": "https://github.com/dxos/dxos/issues",
@@ -31,33 +31,33 @@
31
31
  "src"
32
32
  ],
33
33
  "dependencies": {
34
- "@fluentui/react-tabster": "9.23.3",
34
+ "@fluentui/react-tabster": "^9.24.2",
35
35
  "@preact/signals-core": "^1.6.0",
36
36
  "effect": "3.13.3",
37
37
  "immer": "^10.1.1",
38
- "@dxos/app-framework": "0.8.1-main.ba2dec9",
39
- "@dxos/debug": "0.8.1-main.ba2dec9",
40
- "@dxos/async": "0.8.1-main.ba2dec9",
41
- "@dxos/echo-schema": "0.8.1-main.ba2dec9",
42
- "@dxos/echo-signals": "0.8.1-main.ba2dec9",
43
- "@dxos/artifact": "0.8.1-main.ba2dec9",
44
- "@dxos/invariant": "0.8.1-main.ba2dec9",
45
- "@dxos/live-object": "0.8.1-main.ba2dec9",
46
- "@dxos/log": "0.8.1-main.ba2dec9",
47
- "@dxos/keyboard": "0.8.1-main.ba2dec9",
48
- "@dxos/local-storage": "0.8.1-main.ba2dec9",
49
- "@dxos/plugin-attention": "0.8.1-main.ba2dec9",
50
- "@dxos/plugin-client": "0.8.1-main.ba2dec9",
51
- "@dxos/plugin-graph": "0.8.1-main.ba2dec9",
52
- "@dxos/plugin-observability": "0.8.1-main.ba2dec9",
53
- "@dxos/plugin-theme": "0.8.1-main.ba2dec9",
54
- "@dxos/react-client": "0.8.1-main.ba2dec9",
55
- "@dxos/react-ui-attention": "0.8.1-main.ba2dec9",
56
- "@dxos/react-ui-form": "0.8.1-main.ba2dec9",
57
- "@dxos/react-ui-tabs": "0.8.1-main.ba2dec9",
58
- "@dxos/react-ui-stack": "0.8.1-main.ba2dec9",
59
- "@dxos/util": "0.8.1-main.ba2dec9",
60
- "@dxos/react-ui-text-tooltip": "0.8.1-main.ba2dec9"
38
+ "@dxos/app-framework": "0.8.1-staging.5be625a",
39
+ "@dxos/artifact": "0.8.1-staging.5be625a",
40
+ "@dxos/debug": "0.8.1-staging.5be625a",
41
+ "@dxos/async": "0.8.1-staging.5be625a",
42
+ "@dxos/echo-signals": "0.8.1-staging.5be625a",
43
+ "@dxos/invariant": "0.8.1-staging.5be625a",
44
+ "@dxos/echo-schema": "0.8.1-staging.5be625a",
45
+ "@dxos/keyboard": "0.8.1-staging.5be625a",
46
+ "@dxos/live-object": "0.8.1-staging.5be625a",
47
+ "@dxos/local-storage": "0.8.1-staging.5be625a",
48
+ "@dxos/log": "0.8.1-staging.5be625a",
49
+ "@dxos/plugin-attention": "0.8.1-staging.5be625a",
50
+ "@dxos/plugin-client": "0.8.1-staging.5be625a",
51
+ "@dxos/plugin-graph": "0.8.1-staging.5be625a",
52
+ "@dxos/plugin-observability": "0.8.1-staging.5be625a",
53
+ "@dxos/plugin-theme": "0.8.1-staging.5be625a",
54
+ "@dxos/react-client": "0.8.1-staging.5be625a",
55
+ "@dxos/react-ui-attention": "0.8.1-staging.5be625a",
56
+ "@dxos/react-ui-form": "0.8.1-staging.5be625a",
57
+ "@dxos/react-ui-text-tooltip": "0.8.1-staging.5be625a",
58
+ "@dxos/react-ui-tabs": "0.8.1-staging.5be625a",
59
+ "@dxos/util": "0.8.1-staging.5be625a",
60
+ "@dxos/react-ui-stack": "0.8.1-staging.5be625a"
61
61
  },
62
62
  "devDependencies": {
63
63
  "@phosphor-icons/react": "^2.1.5",
@@ -66,16 +66,16 @@
66
66
  "react": "~18.2.0",
67
67
  "react-dom": "~18.2.0",
68
68
  "vite": "5.4.7",
69
- "@dxos/react-ui": "0.8.1-main.ba2dec9",
70
- "@dxos/react-ui-theme": "0.8.1-main.ba2dec9",
71
- "@dxos/storybook-utils": "0.8.1-main.ba2dec9"
69
+ "@dxos/react-ui": "0.8.1-staging.5be625a",
70
+ "@dxos/react-ui-theme": "0.8.1-staging.5be625a",
71
+ "@dxos/storybook-utils": "0.8.1-staging.5be625a"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@phosphor-icons/react": "^2.0.5",
75
75
  "react": "~18.2.0",
76
76
  "react-dom": "~18.2.0",
77
- "@dxos/react-ui-theme": "0.8.1-main.ba2dec9",
78
- "@dxos/react-ui": "0.8.1-main.ba2dec9"
77
+ "@dxos/react-ui": "0.8.1-staging.5be625a",
78
+ "@dxos/react-ui-theme": "0.8.1-staging.5be625a"
79
79
  },
80
80
  "publishConfig": {
81
81
  "access": "public"
@@ -16,6 +16,7 @@ import {
16
16
  chain,
17
17
  } from '@dxos/app-framework';
18
18
  import { getTypename, S } from '@dxos/echo-schema';
19
+ import { invariant } from '@dxos/invariant';
19
20
  import { isReactiveObject } from '@dxos/live-object';
20
21
  import { log } from '@dxos/log';
21
22
  import { AttentionCapabilities } from '@dxos/plugin-attention';
@@ -25,7 +26,7 @@ import { isNonNullable } from '@dxos/util';
25
26
  import { DeckCapabilities } from './capabilities';
26
27
  import { closeEntry, incrementPlank, openEntry } from '../layout';
27
28
  import { DECK_PLUGIN } from '../meta';
28
- import { DeckAction, type LayoutMode, type DeckSettingsProps, isLayoutMode, getMode } from '../types';
29
+ import { DeckAction, type LayoutMode, type DeckSettingsProps, isLayoutMode, getMode, defaultDeck } from '../types';
29
30
  import { setActive } from '../util';
30
31
 
31
32
  export default (context: PluginsContext) =>
@@ -201,7 +202,7 @@ export default (context: PluginsContext) =>
201
202
  }
202
203
  state.activeDeck = subject;
203
204
  if (!state.decks[subject]) {
204
- state.decks[subject] = { initialized: false, active: [], inactive: [], fullscreen: false, plankSizing: {} };
205
+ state.decks[subject] = { ...defaultDeck };
205
206
  }
206
207
  });
207
208
 
@@ -287,8 +288,16 @@ export default (context: PluginsContext) =>
287
288
  const active = state.deck.solo ? [state.deck.solo] : state.deck.active;
288
289
  const next = subject.reduce((acc, id) => closeEntry(acc, id), active);
289
290
  const toAttend = setActive({ next, state, attention });
291
+
292
+ const clearCompanionIntents = subject
293
+ .filter((id) => state.deck.activeCompanions && id in state.deck.activeCompanions)
294
+ .map((primary) => createIntent(DeckAction.ChangeCompanion, { primary, companion: null }));
295
+
290
296
  return {
291
- intents: toAttend ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: toAttend })] : [],
297
+ intents: [
298
+ ...clearCompanionIntents,
299
+ ...(toAttend ? [createIntent(LayoutAction.ScrollIntoView, { part: 'current', subject: toAttend })] : []),
300
+ ],
292
301
  };
293
302
  },
294
303
  }),
@@ -321,6 +330,23 @@ export default (context: PluginsContext) =>
321
330
  state.deck.plankSizing[data.id] = data.size;
322
331
  },
323
332
  }),
333
+ createResolver({
334
+ intent: DeckAction.ChangeCompanion,
335
+ resolve: (data) => {
336
+ const state = context.requestCapability(DeckCapabilities.MutableDeckState);
337
+ // TODO(thure): Reactivity only works when creating a lexically new `activeCompanions`… Are these not proxy objects?
338
+ if (data.companion === null) {
339
+ const { [data.primary]: _, ...nextActiveCompanions } = state.deck.activeCompanions ?? {};
340
+ state.deck.activeCompanions = nextActiveCompanions;
341
+ } else {
342
+ invariant(data.companion !== data.primary);
343
+ state.deck.activeCompanions = {
344
+ ...state.deck.activeCompanions,
345
+ [data.primary]: data.companion,
346
+ };
347
+ }
348
+ },
349
+ }),
324
350
  createResolver({
325
351
  intent: DeckAction.Adjust,
326
352
  resolve: (adjustment) => {
@@ -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 Deck, type DeckState } from '../types';
13
+ import { getMode, type Deck, type DeckState, defaultDeck } from '../types';
14
14
 
15
15
  const boolean = /true|false/;
16
16
 
@@ -48,14 +48,7 @@ export default () => {
48
48
  activeDeck: 'default',
49
49
  previousDeck: 'default',
50
50
  decks: {
51
- default: {
52
- initialized: false,
53
- active: [],
54
- inactive: [],
55
- fullscreen: false,
56
- solo: undefined,
57
- plankSizing: {},
58
- },
51
+ default: { ...defaultDeck },
59
52
  },
60
53
  get deck() {
61
54
  const deck = this.decks[this.activeDeck];
@@ -14,12 +14,14 @@ import { S } from '@dxos/echo-schema';
14
14
  import { invariant } from '@dxos/invariant';
15
15
 
16
16
  import { meta } from '../meta';
17
+ import { DeckAction } from '../types';
17
18
 
18
19
  // TODO(burdon): Factor out.
19
20
  declare global {
20
21
  interface ToolContextExtensions {
21
22
  dispatch?: PromiseIntentDispatcher;
22
23
  pivotId?: string;
24
+ part?: 'deck' | 'dialog';
23
25
  }
24
26
  }
25
27
 
@@ -27,10 +29,9 @@ export default () =>
27
29
  contributes(Capabilities.Tools, [
28
30
  defineTool(meta.id, {
29
31
  name: 'show',
30
- // TODO(ZaymonFC): We should update the prompt to teach the LLM the difference between object ids and fully qualified ids.
31
32
  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.
33
+ Show an item as a companion to an existing plank. This will make the item appear alongside the primary content.
34
+ When supplying IDs, they must be fully qualified like space:object.
34
35
  `,
35
36
  caption: 'Showing item...',
36
37
  // TODO(wittjosiah): Refactor Layout/Navigation/Deck actions so that they can be used directly.
@@ -38,29 +39,40 @@ export default () =>
38
39
  id: S.String.annotations({
39
40
  description: 'The ID of the item to show.',
40
41
  }),
41
- pivotId: S.optional(
42
- S.String.annotations({
43
- description: 'The ID of the chat. If provided, the item will be added after the pivot item.',
44
- }),
45
- ),
46
42
  }),
47
43
  execute: async ({ id }, { extensions }) => {
44
+ invariant(extensions?.pivotId, 'No pivot ID');
48
45
  invariant(extensions?.dispatch, 'No intent dispatcher');
49
- const { data, error } = await extensions.dispatch(
50
- createIntent(LayoutAction.Open, {
51
- subject: [id],
52
- part: 'main',
53
- options: {
54
- pivotId: extensions.pivotId,
55
- positioning: 'end',
56
- },
57
- }),
58
- );
59
- if (error) {
60
- return ToolResult.Error(error.message);
61
- }
62
46
 
63
- return ToolResult.Success(data);
47
+ if (extensions.part === 'deck') {
48
+ const { data, error } = await extensions.dispatch(
49
+ createIntent(DeckAction.ChangeCompanion, {
50
+ primary: extensions.pivotId,
51
+ companion: id,
52
+ }),
53
+ );
54
+ if (error) {
55
+ return ToolResult.Error(error.message);
56
+ }
57
+
58
+ return ToolResult.Success(data);
59
+ } else {
60
+ const { data, error } = await extensions.dispatch(
61
+ createIntent(LayoutAction.Open, {
62
+ subject: [id],
63
+ part: 'main',
64
+ options: {
65
+ pivotId: extensions.pivotId,
66
+ positioning: 'end',
67
+ },
68
+ }),
69
+ );
70
+ if (error) {
71
+ return ToolResult.Error(error.message);
72
+ }
73
+
74
+ return ToolResult.Success(data);
75
+ }
64
76
  },
65
77
  }),
66
78
  ]);
@@ -6,6 +6,7 @@ import { Capabilities, contributes, createIntent, LayoutAction, type PluginsCont
6
6
  import { scheduledEffect } from '@dxos/echo-signals/core';
7
7
 
8
8
  import { DeckCapabilities } from './capabilities';
9
+ import { defaultDeck } from '../types';
9
10
 
10
11
  // TODO(wittjosiah): Cleanup the url handling. May justify introducing routing capabilities.
11
12
  export default async (context: PluginsContext) => {
@@ -17,14 +18,7 @@ export default async (context: PluginsContext) => {
17
18
  if (pathname === '/reset') {
18
19
  state.activeDeck = 'default';
19
20
  state.decks = {
20
- default: {
21
- initialized: false,
22
- active: [],
23
- inactive: [],
24
- fullscreen: false,
25
- solo: undefined,
26
- plankSizing: {},
27
- },
21
+ default: { ...defaultDeck },
28
22
  };
29
23
  window.location.pathname = '/';
30
24
  return;
@@ -19,9 +19,9 @@ import {
19
19
  Dialog as NaturalDialog,
20
20
  Main,
21
21
  Popover,
22
- useOnTransition,
23
22
  type MainProps,
24
23
  useMediaQuery,
24
+ useOnTransition,
25
25
  } from '@dxos/react-ui';
26
26
  import { Stack, StackContext, DEFAULT_HORIZONTAL_SIZE } from '@dxos/react-ui-stack';
27
27
  import { mainPaddingTransitions } from '@dxos/react-ui-theme';
@@ -48,8 +48,8 @@ export type DeckLayoutProps = {
48
48
  onDismissToast: (id: string) => void;
49
49
  };
50
50
 
51
- const PlankSeparator = ({ index }: { index: number }) =>
52
- index > 0 ? <span role='separator' className='row-span-2 bg-deck is-4' style={{ gridColumn: index * 2 }} /> : null;
51
+ const PlankSeparator = ({ order }: { order: number }) =>
52
+ order > 0 ? <span role='separator' className='row-span-2 bg-deck is-4' style={{ gridColumn: order }} /> : null;
53
53
 
54
54
  export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayoutProps) => {
55
55
  const { dispatchPromise: dispatch } = useIntentDispatcher();
@@ -68,7 +68,7 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
68
68
  deck,
69
69
  toasts,
70
70
  } = context;
71
- const { active, fullscreen, solo, plankSizing } = deck;
71
+ const { active, activeCompanions, fullscreen, solo, plankSizing } = deck;
72
72
  const breakpoint = useBreakpoints();
73
73
  const topbar = layoutAppliesTopbar(breakpoint);
74
74
  const hoistStatusbar = useHoistStatusbar(breakpoint);
@@ -187,6 +187,17 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
187
187
  );
188
188
  const handlePopoverClose = useCallback(() => handlePopoverOpenChange(false), [handlePopoverOpenChange]);
189
189
 
190
+ const { order, itemsCount }: { order: Record<string, number>; itemsCount: number } = useMemo(() => {
191
+ return active.reduce(
192
+ (acc: { order: Record<string, number>; itemsCount: number }, entryId) => {
193
+ acc.order[entryId] = acc.itemsCount + 1;
194
+ acc.itemsCount += activeCompanions?.[entryId] ? 3 : 2;
195
+ return acc;
196
+ },
197
+ { order: {}, itemsCount: 0 },
198
+ );
199
+ }, [active, activeCompanions]);
200
+
190
201
  return (
191
202
  <Popover.Root modal open={!!(popoverAnchorId && delayedPopoverVisibility)} onOpenChange={handlePopoverOpenChange}>
192
203
  <ActiveNode />
@@ -253,14 +264,21 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
253
264
  size='contain'
254
265
  classNames={['absolute inset-block-0 -inset-inline-px', mainPaddingTransitions]}
255
266
  onScroll={handleScroll}
256
- itemsCount={2 * (active.length ?? 0) - 1}
267
+ itemsCount={itemsCount - 1}
257
268
  style={padding}
258
269
  ref={deckRef}
259
270
  >
260
- {active.map((entryId, index) => (
271
+ {active.map((entryId) => (
261
272
  <Fragment key={entryId}>
262
- <PlankSeparator index={index} />
263
- <Plank id={entryId} part='deck' order={index * 2 + 1} active={active} layoutMode={layoutMode} />
273
+ <PlankSeparator order={order[entryId] - 1} />
274
+ <Plank
275
+ id={entryId}
276
+ companionId={activeCompanions?.[entryId]}
277
+ part='deck'
278
+ order={order[entryId]}
279
+ active={active}
280
+ layoutMode={layoutMode}
281
+ />
264
282
  </Fragment>
265
283
  ))}
266
284
  </Stack>
@@ -273,7 +291,12 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
273
291
  {!topbar && <ToggleSidebarButton classNames={fixedSidebarToggleStyles} />}
274
292
  {!topbar && <ToggleComplementarySidebarButton classNames={fixedComplementarySidebarToggleStyles} />}
275
293
  <StackContext.Provider value={{ size: 'contain', orientation: 'horizontal', rail: true }}>
276
- <Plank id={solo} part='solo' layoutMode={layoutMode} />
294
+ <Plank
295
+ id={solo}
296
+ companionId={solo ? activeCompanions?.[solo] : undefined}
297
+ part='solo'
298
+ layoutMode={layoutMode}
299
+ />
277
300
  </StackContext.Provider>
278
301
  </div>
279
302
  </Main.Content>
@@ -304,7 +327,8 @@ export const DeckLayout = ({ overscroll, showHints, onDismissToast }: DeckLayout
304
327
  onOpenChange={(nextOpen) => (context.dialogOpen = nextOpen)}
305
328
  >
306
329
  {dialogBlockAlign === 'end' ? (
307
- <Surface role='dialog' data={dialogContent} limit={1} fallback={PlankContentError} />
330
+ // TODO(burdon): Placeholder creates a suspense boundary; replace with defaults.
331
+ <Surface role='dialog' data={dialogContent} limit={1} fallback={PlankContentError} placeholder={<div />} />
308
332
  ) : (
309
333
  <Dialog.Overlay blockAlign={dialogBlockAlign}>
310
334
  <Surface role='dialog' data={dialogContent} limit={1} fallback={PlankContentError} />
@@ -10,21 +10,24 @@ import { Icon, Popover, toLocalizedString, useTranslation } from '@dxos/react-ui
10
10
  import { StackItem, type StackItemSigilAction } from '@dxos/react-ui-stack';
11
11
  import { TextTooltip } from '@dxos/react-ui-text-tooltip';
12
12
 
13
- import { PlankControls } from './PlankControls';
13
+ import { PlankCompanionControls, PlankControls } from './PlankControls';
14
14
  import { DECK_PLUGIN } from '../../meta';
15
- import { DeckAction, SLUG_PATH_SEPARATOR } from '../../types';
15
+ import { DeckAction, type ResolvedPart, SLUG_PATH_SEPARATOR } from '../../types';
16
16
  import { useBreakpoints } from '../../util';
17
17
  import { soloInlinePadding } from '../fragments';
18
18
 
19
19
  export type NodePlankHeadingProps = {
20
20
  id: string;
21
- part: 'solo' | 'deck' | 'complementary';
21
+ part: ResolvedPart;
22
22
  node?: Node;
23
23
  canIncrementStart?: boolean;
24
24
  canIncrementEnd?: boolean;
25
25
  popoverAnchorId?: string;
26
26
  pending?: boolean;
27
27
  actions?: StackItemSigilAction[];
28
+ companioned?: 'primary' | 'companion';
29
+ primaryId?: string;
30
+ surfaceVariant?: string;
28
31
  };
29
32
 
30
33
  export const NodePlankHeading = memo(
@@ -37,6 +40,9 @@ export const NodePlankHeading = memo(
37
40
  popoverAnchorId,
38
41
  pending,
39
42
  actions = [],
43
+ companioned,
44
+ primaryId,
45
+ surfaceVariant,
40
46
  }: NodePlankHeadingProps) => {
41
47
  const { t } = useTranslation(DECK_PLUGIN);
42
48
  const { graph } = useAppGraph();
@@ -44,7 +50,14 @@ export const NodePlankHeading = memo(
44
50
  const icon = node?.properties?.icon ?? 'ph--placeholder--regular';
45
51
  const label = pending
46
52
  ? t('pending heading')
47
- : toLocalizedString(node?.properties?.label ?? ['plank heading fallback label', { ns: DECK_PLUGIN }], t);
53
+ : toLocalizedString(
54
+ (surfaceVariant
55
+ ? Array.isArray(node?.properties?.label)
56
+ ? [`${surfaceVariant} plank heading`, node.properties.label[1]]
57
+ : ['companion plank heading fallback label', { ns: DECK_PLUGIN }]
58
+ : node?.properties?.label) ?? ['plank heading fallback label', { ns: DECK_PLUGIN }],
59
+ t,
60
+ );
48
61
  const { dispatchPromise: dispatch } = useIntentDispatcher();
49
62
  const ActionRoot = node && popoverAnchorId === `dxos.org/ui/${DECK_PLUGIN}/${node.id}` ? Popover.Anchor : Fragment;
50
63
 
@@ -105,27 +118,30 @@ export const NodePlankHeading = memo(
105
118
  classNames={[
106
119
  'plb-1 border-be border-separator items-stretch gap-1 sticky inline-start-12 app-drag',
107
120
  part === 'solo' ? soloInlinePadding : 'pli-1',
121
+ surfaceVariant && 'pis-3',
108
122
  ]}
109
123
  >
110
- <ActionRoot>
111
- {node && sigilActions ? (
112
- <StackItem.Sigil
113
- icon={icon}
114
- related={part === 'complementary'}
115
- attendableId={attendableId}
116
- triggerLabel={t('actions menu label')}
117
- actions={sigilActions}
118
- onAction={handleAction}
119
- >
120
- <Surface role='menu-footer' data={{ subject: node.data }} />
121
- </StackItem.Sigil>
122
- ) : (
123
- <StackItem.SigilButton>
124
- <span className='sr-only'>{label}</span>
125
- <Icon icon={icon} size={5} />
126
- </StackItem.SigilButton>
127
- )}
128
- </ActionRoot>
124
+ {!surfaceVariant && (
125
+ <ActionRoot>
126
+ {node && sigilActions ? (
127
+ <StackItem.Sigil
128
+ icon={icon}
129
+ related={part === 'complementary'}
130
+ attendableId={attendableId}
131
+ triggerLabel={t('actions menu label')}
132
+ actions={sigilActions}
133
+ onAction={handleAction}
134
+ >
135
+ <Surface role='menu-footer' data={{ subject: node.data }} />
136
+ </StackItem.Sigil>
137
+ ) : (
138
+ <StackItem.SigilButton>
139
+ <span className='sr-only'>{label}</span>
140
+ <Icon icon={icon} size={5} />
141
+ </StackItem.SigilButton>
142
+ )}
143
+ </ActionRoot>
144
+ )}
129
145
  <TextTooltip text={label} onlyWhenTruncating>
130
146
  <StackItem.HeadingLabel
131
147
  attendableId={attendableId}
@@ -136,12 +152,16 @@ export const NodePlankHeading = memo(
136
152
  </StackItem.HeadingLabel>
137
153
  </TextTooltip>
138
154
  {node && part !== 'complementary' && <Surface role='navbar-end' data={{ subject: node.data }} />}
139
- <PlankControls
140
- capabilities={capabilities}
141
- isSolo={part === 'solo'}
142
- onClick={handlePlankAction}
143
- close={part === 'complementary' ? 'minify-end' : true}
144
- />
155
+ {companioned === 'companion' ? (
156
+ <PlankCompanionControls primary={surfaceVariant ? id : primaryId} />
157
+ ) : (
158
+ <PlankControls
159
+ capabilities={capabilities}
160
+ isSolo={part === 'solo'}
161
+ onClick={handlePlankAction}
162
+ close={part === 'complementary' ? 'minify-end' : true}
163
+ />
164
+ )}
145
165
  </StackItem.Heading>
146
166
  );
147
167
  },