@dxos/plugin-deck 0.8.4-staging.60fe92afc8 → 0.9.1-main.c7dcc2e112

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 (158) hide show
  1. package/dist/lib/neutral/{DeckLayout-DEURA3KR.mjs → DeckLayout-VX2BP4Q2.mjs} +35 -22
  2. package/dist/lib/neutral/DeckLayout-VX2BP4Q2.mjs.map +7 -0
  3. package/dist/lib/neutral/DeckPlugin.mjs +2 -2
  4. package/dist/lib/neutral/DeckPlugin.mjs.map +4 -4
  5. package/dist/lib/neutral/{DeckSettings-W5I57OXM.mjs → DeckSettings-CQQZJ2YS.mjs} +3 -3
  6. package/dist/lib/neutral/DeckSettings-CQQZJ2YS.mjs.map +7 -0
  7. package/dist/lib/neutral/{add-toast-TNB6DXWU.mjs → add-toast-APKTCLIA.mjs} +2 -2
  8. package/dist/lib/neutral/{adjust-HNU5CCRO.mjs → adjust-7CZI4GK3.mjs} +3 -3
  9. package/dist/lib/neutral/{app-graph-builder-HMLT627T.mjs → app-graph-builder-6HTXRTU2.mjs} +5 -5
  10. package/dist/lib/neutral/app-graph-builder-6HTXRTU2.mjs.map +7 -0
  11. package/dist/lib/neutral/capabilities/index.mjs +8 -8
  12. package/dist/lib/neutral/{check-app-scheme-INSOF72J.mjs → check-app-scheme-HSAORHHX.mjs} +2 -2
  13. package/dist/lib/neutral/{chunk-BS4EOYMK.mjs → chunk-CL7JIOI5.mjs} +28 -14
  14. package/dist/lib/neutral/chunk-CL7JIOI5.mjs.map +7 -0
  15. package/dist/lib/neutral/{chunk-WAXJPQJI.mjs → chunk-DNG3L5QN.mjs} +31 -33
  16. package/dist/lib/neutral/chunk-DNG3L5QN.mjs.map +7 -0
  17. package/dist/lib/neutral/chunk-KY7LMF4D.mjs +46 -0
  18. package/dist/lib/neutral/chunk-KY7LMF4D.mjs.map +7 -0
  19. package/dist/lib/neutral/{chunk-324PPIZB.mjs → chunk-KZFJ4PBT.mjs} +4 -4
  20. package/dist/lib/neutral/chunk-KZFJ4PBT.mjs.map +7 -0
  21. package/dist/lib/neutral/{chunk-GBIGQKYW.mjs → chunk-NGX6RXNV.mjs} +4 -4
  22. package/dist/lib/neutral/chunk-NGX6RXNV.mjs.map +7 -0
  23. package/dist/lib/neutral/chunk-UZLAR4DR.mjs +8 -0
  24. package/dist/lib/neutral/{close-ASKR22A6.mjs → close-KJUCFIJ5.mjs} +3 -3
  25. package/dist/lib/neutral/components/index.mjs +1 -1
  26. package/dist/lib/neutral/containers/index.mjs +3 -3
  27. package/dist/lib/neutral/index.mjs +12 -2
  28. package/dist/lib/neutral/meta.json +1 -1
  29. package/dist/lib/neutral/meta.mjs +1 -1
  30. package/dist/lib/neutral/{notification-tracker-P36322BH.mjs → notification-tracker-CUDFRDCF.mjs} +9 -9
  31. package/dist/lib/neutral/{notification-tracker-P36322BH.mjs.map → notification-tracker-CUDFRDCF.mjs.map} +3 -3
  32. package/dist/lib/neutral/{open-5OYNO3RT.mjs → open-VU7YS3HB.mjs} +7 -7
  33. package/dist/lib/neutral/open-VU7YS3HB.mjs.map +7 -0
  34. package/dist/lib/neutral/operations/index.mjs +1 -1
  35. package/dist/lib/neutral/plugin.mjs +2 -2
  36. package/dist/lib/neutral/{react-root-HH5DEUOG.mjs → react-root-E6TAFHX6.mjs} +2 -2
  37. package/dist/lib/neutral/react-root-E6TAFHX6.mjs.map +7 -0
  38. package/dist/lib/neutral/{react-surface-3UVVCK3O.mjs → react-surface-O4POQWYL.mjs} +4 -5
  39. package/dist/lib/neutral/react-surface-O4POQWYL.mjs.map +7 -0
  40. package/dist/lib/neutral/{revert-workspace-B2QLT2C4.mjs → revert-workspace-24TKG3I7.mjs} +2 -2
  41. package/dist/lib/neutral/{scroll-into-view-B52C3PJO.mjs → scroll-into-view-3VXT6TWC.mjs} +2 -2
  42. package/dist/lib/neutral/{set-PA35ONXO.mjs → set-RFOLTI57.mjs} +3 -3
  43. package/dist/lib/neutral/{set-layout-mode-RPCCPQRB.mjs → set-layout-mode-AZ73W52Z.mjs} +2 -2
  44. package/dist/lib/neutral/{settings-EGNYUM4T.mjs → settings-6CDQEB6B.mjs} +3 -3
  45. package/dist/lib/neutral/settings-6CDQEB6B.mjs.map +7 -0
  46. package/dist/lib/neutral/{state-IIDXMQUO.mjs → state-D3YXB5NP.mjs} +3 -3
  47. package/dist/lib/neutral/{state-IIDXMQUO.mjs.map → state-D3YXB5NP.mjs.map} +2 -2
  48. package/dist/lib/neutral/{switch-workspace-LZF4KZXH.mjs → switch-workspace-TBPT23CZ.mjs} +4 -4
  49. package/dist/lib/neutral/switch-workspace-TBPT23CZ.mjs.map +7 -0
  50. package/dist/lib/neutral/translations.mjs +1 -1
  51. package/dist/lib/neutral/translations.mjs.map +3 -3
  52. package/dist/lib/neutral/types/index.mjs +11 -1
  53. package/dist/lib/neutral/{update-companion-YUCZZVGY.mjs → update-companion-5LCY6QME.mjs} +2 -2
  54. package/dist/lib/neutral/{update-complementary-7FZNB55J.mjs → update-complementary-HZ7QXZYE.mjs} +2 -2
  55. package/dist/lib/neutral/{update-dialog-FNQTSSAP.mjs → update-dialog-A7W3R5EI.mjs} +2 -2
  56. package/dist/lib/neutral/{update-plank-size-3YW4NXEY.mjs → update-plank-size-XHTYBLSD.mjs} +2 -2
  57. package/dist/lib/neutral/{update-popover-G2VUD7E6.mjs → update-popover-RDIHG73B.mjs} +2 -2
  58. package/dist/lib/neutral/{update-sidebar-KRHPUHUB.mjs → update-sidebar-GEI7USA5.mjs} +2 -2
  59. package/dist/lib/neutral/{url-handler-A6HLW4RB.mjs → url-handler-PUS4X33T.mjs} +10 -10
  60. package/dist/lib/neutral/url-handler-PUS4X33T.mjs.map +7 -0
  61. package/dist/types/dx.config.d.ts +28 -0
  62. package/dist/types/dx.config.d.ts.map +1 -0
  63. package/dist/types/src/capabilities/index.d.ts +8 -196
  64. package/dist/types/src/capabilities/index.d.ts.map +1 -1
  65. package/dist/types/src/capabilities/react-surface.d.ts +2 -2
  66. package/dist/types/src/capabilities/react-surface.d.ts.map +1 -1
  67. package/dist/types/src/capabilities/state.d.ts +2 -2
  68. package/dist/types/src/components/Matrix/Matrix.stories.d.ts.map +1 -1
  69. package/dist/types/src/containers/Deck/Banner.d.ts.map +1 -1
  70. package/dist/types/src/containers/Deck/DeckViewport.d.ts.map +1 -1
  71. package/dist/types/src/containers/Deck/StatusBar.d.ts.map +1 -1
  72. package/dist/types/src/containers/DeckLayout/DeckLayout.stories.d.ts.map +1 -1
  73. package/dist/types/src/containers/DeckLayout/Popover.d.ts.map +1 -1
  74. package/dist/types/src/containers/Plank/PlankHeading.d.ts.map +1 -1
  75. package/dist/types/src/containers/Sidebar/ComplementarySidebar.d.ts.map +1 -1
  76. package/dist/types/src/meta.d.ts +28 -2
  77. package/dist/types/src/meta.d.ts.map +1 -1
  78. package/dist/types/src/operations/open.d.ts.map +1 -1
  79. package/dist/types/src/operations/switch-workspace.d.ts.map +1 -1
  80. package/dist/types/src/types/DeckCapabilities.d.ts +2 -2
  81. package/dist/types/src/types/DeckCapabilities.d.ts.map +1 -1
  82. package/dist/types/src/types/index.d.ts +1 -0
  83. package/dist/types/src/types/index.d.ts.map +1 -1
  84. package/dist/types/src/types/schema.d.ts +4 -2
  85. package/dist/types/src/types/schema.d.ts.map +1 -1
  86. package/dist/types/src/types/surface.d.ts +12 -0
  87. package/dist/types/src/types/surface.d.ts.map +1 -0
  88. package/dist/types/tsconfig.tsbuildinfo +1 -1
  89. package/dx.config.ts +35 -0
  90. package/package.json +39 -39
  91. package/src/DeckPlugin.test.ts +1 -1
  92. package/src/DeckPlugin.ts +1 -1
  93. package/src/capabilities/app-graph-builder.ts +4 -4
  94. package/src/capabilities/check-app-scheme.ts +3 -3
  95. package/src/capabilities/notification-tracker.ts +8 -8
  96. package/src/capabilities/react-root.tsx +1 -1
  97. package/src/capabilities/react-surface.tsx +3 -4
  98. package/src/capabilities/settings.ts +2 -2
  99. package/src/capabilities/state.ts +1 -1
  100. package/src/capabilities/url-handler.ts +7 -15
  101. package/src/components/DeckSettings/DeckSettings.tsx +2 -2
  102. package/src/components/Matrix/Matrix.stories.tsx +2 -1
  103. package/src/containers/Deck/Banner.tsx +5 -4
  104. package/src/containers/Deck/DeckViewport.tsx +3 -4
  105. package/src/containers/Deck/StatusBar.tsx +4 -2
  106. package/src/containers/DeckLayout/DeckLayout.stories.tsx +6 -9
  107. package/src/containers/DeckLayout/Fallback.tsx +1 -1
  108. package/src/containers/DeckLayout/Popover.tsx +37 -20
  109. package/src/containers/DeckLayout/Toast.tsx +1 -1
  110. package/src/containers/Plank/Plank.stories.tsx +2 -2
  111. package/src/containers/Plank/PlankControls.tsx +2 -2
  112. package/src/containers/Plank/PlankError.tsx +1 -1
  113. package/src/containers/Plank/PlankHeading.tsx +5 -6
  114. package/src/containers/Sidebar/ComplementarySidebar.tsx +6 -5
  115. package/src/containers/Sidebar/Sidebar.tsx +1 -1
  116. package/src/containers/Sidebar/SidebarButton.tsx +3 -3
  117. package/src/meta.ts +2 -27
  118. package/src/operations/open.ts +12 -10
  119. package/src/operations/switch-workspace.ts +2 -2
  120. package/src/translations.ts +1 -1
  121. package/src/types/DeckCapabilities.ts +5 -3
  122. package/src/types/DeckEvents.ts +1 -1
  123. package/src/types/DeckOperation.ts +1 -1
  124. package/src/types/index.ts +1 -0
  125. package/src/types/schema.ts +16 -12
  126. package/src/types/surface.ts +28 -0
  127. package/src/util/plank-url-params.ts +3 -3
  128. package/dist/lib/neutral/DeckLayout-DEURA3KR.mjs.map +0 -7
  129. package/dist/lib/neutral/DeckSettings-W5I57OXM.mjs.map +0 -7
  130. package/dist/lib/neutral/app-graph-builder-HMLT627T.mjs.map +0 -7
  131. package/dist/lib/neutral/chunk-324PPIZB.mjs.map +0 -7
  132. package/dist/lib/neutral/chunk-BS4EOYMK.mjs.map +0 -7
  133. package/dist/lib/neutral/chunk-GBIGQKYW.mjs.map +0 -7
  134. package/dist/lib/neutral/chunk-PYEY5SEC.mjs +0 -37
  135. package/dist/lib/neutral/chunk-PYEY5SEC.mjs.map +0 -7
  136. package/dist/lib/neutral/chunk-Q4W6B4IB.mjs +0 -8
  137. package/dist/lib/neutral/chunk-WAXJPQJI.mjs.map +0 -7
  138. package/dist/lib/neutral/open-5OYNO3RT.mjs.map +0 -7
  139. package/dist/lib/neutral/react-root-HH5DEUOG.mjs.map +0 -7
  140. package/dist/lib/neutral/react-surface-3UVVCK3O.mjs.map +0 -7
  141. package/dist/lib/neutral/settings-EGNYUM4T.mjs.map +0 -7
  142. package/dist/lib/neutral/switch-workspace-LZF4KZXH.mjs.map +0 -7
  143. package/dist/lib/neutral/url-handler-A6HLW4RB.mjs.map +0 -7
  144. /package/dist/lib/neutral/{add-toast-TNB6DXWU.mjs.map → add-toast-APKTCLIA.mjs.map} +0 -0
  145. /package/dist/lib/neutral/{adjust-HNU5CCRO.mjs.map → adjust-7CZI4GK3.mjs.map} +0 -0
  146. /package/dist/lib/neutral/{check-app-scheme-INSOF72J.mjs.map → check-app-scheme-HSAORHHX.mjs.map} +0 -0
  147. /package/dist/lib/neutral/{chunk-Q4W6B4IB.mjs.map → chunk-UZLAR4DR.mjs.map} +0 -0
  148. /package/dist/lib/neutral/{close-ASKR22A6.mjs.map → close-KJUCFIJ5.mjs.map} +0 -0
  149. /package/dist/lib/neutral/{revert-workspace-B2QLT2C4.mjs.map → revert-workspace-24TKG3I7.mjs.map} +0 -0
  150. /package/dist/lib/neutral/{scroll-into-view-B52C3PJO.mjs.map → scroll-into-view-3VXT6TWC.mjs.map} +0 -0
  151. /package/dist/lib/neutral/{set-PA35ONXO.mjs.map → set-RFOLTI57.mjs.map} +0 -0
  152. /package/dist/lib/neutral/{set-layout-mode-RPCCPQRB.mjs.map → set-layout-mode-AZ73W52Z.mjs.map} +0 -0
  153. /package/dist/lib/neutral/{update-companion-YUCZZVGY.mjs.map → update-companion-5LCY6QME.mjs.map} +0 -0
  154. /package/dist/lib/neutral/{update-complementary-7FZNB55J.mjs.map → update-complementary-HZ7QXZYE.mjs.map} +0 -0
  155. /package/dist/lib/neutral/{update-dialog-FNQTSSAP.mjs.map → update-dialog-A7W3R5EI.mjs.map} +0 -0
  156. /package/dist/lib/neutral/{update-plank-size-3YW4NXEY.mjs.map → update-plank-size-XHTYBLSD.mjs.map} +0 -0
  157. /package/dist/lib/neutral/{update-popover-G2VUD7E6.mjs.map → update-popover-RDIHG73B.mjs.map} +0 -0
  158. /package/dist/lib/neutral/{update-sidebar-KRHPUHUB.mjs.map → update-sidebar-GEI7USA5.mjs.map} +0 -0
@@ -10,10 +10,12 @@ import { AppSurface, useObjectMenuItems } from '@dxos/app-toolkit/ui';
10
10
  import { Obj } from '@dxos/echo';
11
11
  import {
12
12
  Card,
13
+ Icon,
14
+ IconButton,
13
15
  Popover,
14
16
  type PopoverContentInteractOutsideEvent,
15
17
  toLocalizedString,
16
- Toolbar,
18
+ useMediaQuery,
17
19
  useTranslation,
18
20
  } from '@dxos/react-ui';
19
21
  import { Menu } from '@dxos/react-ui-menu';
@@ -54,9 +56,12 @@ export const PopoverRoot = ({ children }: PopoverRootProps) => {
54
56
  }
55
57
  }, [state.popoverOpen, state.popoverAnchorId, state.popoverAnchor, state.popoverContent]);
56
58
 
59
+ // The rename popover is modal so other navtree item menus are inert while it is open.
60
+ const modal = state.popoverKind === 'rename';
61
+
57
62
  return (
58
63
  <DeckPopoverProvider setOpen={setOpen}>
59
- <Popover.Root modal={false} open={open}>
64
+ <Popover.Root modal={modal} open={open}>
60
65
  {state.popoverAnchor && <Popover.VirtualTrigger key={virtualIter} virtualRef={virtualRef} />}
61
66
  {children}
62
67
  </Popover.Root>
@@ -65,7 +70,7 @@ export const PopoverRoot = ({ children }: PopoverRootProps) => {
65
70
  };
66
71
 
67
72
  export const PopoverContent = () => {
68
- const { t } = useTranslation(meta.id);
73
+ const { t } = useTranslation(meta.profile.key);
69
74
  const { state, updateEphemeral } = useDeckState();
70
75
  const { setOpen } = useDeckPopoverContext('PopoverContent');
71
76
  const popoverSubject =
@@ -75,8 +80,14 @@ export const PopoverContent = () => {
75
80
  const title = state.popoverTitle ? toLocalizedString(state.popoverTitle, t) : 'Unknown';
76
81
  const icon = isObjectPopover ? (Obj.getIcon(popoverSubject)?.icon ?? 'ph--circle-dashed--regular') : undefined;
77
82
  const content = state.popoverContent;
78
- // A base popover renders a plugin-provided component; everything else falls through to the card.
79
- const isBasePopover = state.popoverKind === 'base' && !!content && 'component' in content;
83
+ // Base and rename popovers render a plugin-provided component; everything else falls through to the card.
84
+ const isComponentPopover =
85
+ (state.popoverKind === 'base' || state.popoverKind === 'rename') && !!content && 'component' in content;
86
+ const isRename = state.popoverKind === 'rename';
87
+
88
+ // Anchor to the right of the row on wide displays; drop centered below on narrow ones.
89
+ const [isLg] = useMediaQuery('lg', { fallback: [true] });
90
+ const side = isRename ? (isLg ? 'right' : 'bottom') : state.popoverSide;
80
91
 
81
92
  const handleClose = useCallback(() => {
82
93
  setOpen(false);
@@ -91,15 +102,14 @@ export const PopoverContent = () => {
91
102
 
92
103
  const handleInteractOutside = useCallback(
93
104
  (event: KeyboardEvent | PopoverContentInteractOutsideEvent) => {
94
- if (
95
- // TODO(thure): CodeMirror should not focus itself when it updates.
96
- event.type === 'dismissableLayer.focusOutside' &&
97
- (event.currentTarget as HTMLElement | undefined)?.classList.contains('cm-content')
98
- ) {
105
+ // Focus leaving the popover (clicking into the card surfaces a portaled menu, or CodeMirror
106
+ // re-focusing itself) must not dismiss it only a pointer-down genuinely outside the card, or
107
+ // Escape, closes. (Clicks inside the card never reach here; Radix scopes them to the content.)
108
+ if (event.type === 'dismissableLayer.focusOutside') {
99
109
  event.preventDefault();
100
- } else {
101
- handleClose();
110
+ return;
102
111
  }
112
+ handleClose();
103
113
  },
104
114
  [handleClose],
105
115
  );
@@ -107,15 +117,22 @@ export const PopoverContent = () => {
107
117
  return (
108
118
  <Popover.Portal>
109
119
  <Popover.Content
110
- side={state.popoverSide}
120
+ side={side}
111
121
  sticky='always'
112
122
  hideWhenDetached
113
- onOpenAutoFocus={(event) => event.preventDefault()}
123
+ // Rename focuses its input; other popovers keep focus where it was.
124
+ onOpenAutoFocus={isRename ? undefined : (event) => event.preventDefault()}
114
125
  onInteractOutside={handleInteractOutside}
115
126
  onEscapeKeyDown={handleInteractOutside}
127
+ // Reuse the dialog's enter/exit motion so the rename popover does not flicker on open.
128
+ classNames={
129
+ isRename
130
+ ? ['data-[state=open]:animate-slide-up-and-fade', 'data-[state=closed]:animate-slide-down-and-fade']
131
+ : undefined
132
+ }
116
133
  >
117
134
  <Popover.Viewport>
118
- {isBasePopover && content && 'component' in content ? (
135
+ {isComponentPopover && content && 'component' in content ? (
119
136
  /* Base popover: a plugin-provided component (e.g. editor link preview). */
120
137
  <Surface.Surface type={AppSurface.Popover} data={content} limit={1} />
121
138
  ) : (
@@ -129,12 +146,12 @@ export const PopoverContent = () => {
129
146
  <Menu.Root>
130
147
  <Card.Root border={false} classNames='dx-card-popover'>
131
148
  <Card.Header>
132
- <Card.IconBlock>{icon && <Card.Icon icon={icon} />}</Card.IconBlock>
149
+ <Card.Block>{icon && <Icon icon={icon} />}</Card.Block>
133
150
  <Card.Title>{title}</Card.Title>
134
151
  {/* TODO(wittjosiah): Reconcile with Card.Menu. */}
135
- <Card.IconBlock>
152
+ <Card.Block end>
136
153
  <Menu.Trigger asChild disabled={!objectMenuItems.length}>
137
- <Toolbar.IconButton
154
+ <IconButton
138
155
  variant='ghost'
139
156
  density='sm'
140
157
  icon='ph--dots-three-vertical--regular'
@@ -143,11 +160,11 @@ export const PopoverContent = () => {
143
160
  />
144
161
  </Menu.Trigger>
145
162
  <Menu.Content items={objectMenuItems} />
146
- </Card.IconBlock>
163
+ </Card.Block>
147
164
  </Card.Header>
148
165
 
149
166
  {content && 'subject' in content ? (
150
- <Surface.Surface type={AppSurface.Card} data={content} limit={1} />
167
+ <Surface.Surface type={AppSurface.CardContent} data={content} limit={1} />
151
168
  ) : (
152
169
  <Card.Body classNames='min-bs-8'>
153
170
  <Card.Row>
@@ -21,7 +21,7 @@ export const Toast = ({
21
21
  onAction,
22
22
  onOpenChange,
23
23
  }: LayoutOperation.Toast & Pick<ToastRootProps, 'onOpenChange'>) => {
24
- const { t } = useTranslation(meta.id);
24
+ const { t } = useTranslation(meta.profile.key);
25
25
 
26
26
  // Control the open state so closing flips Radix's `open` (playing the exit animation) rather than
27
27
  // unmounting abruptly. Both the close button and Radix's own timeout/swipe route through here.
@@ -9,7 +9,7 @@ import { Capabilities, Capability, Plugin } from '@dxos/app-framework';
9
9
  import { withPluginManager } from '@dxos/app-framework/testing';
10
10
  import { Surface } from '@dxos/app-framework/ui';
11
11
  import { AppActivationEvents } from '@dxos/app-toolkit';
12
- import { useAppGraph } from '@dxos/app-toolkit/ui';
12
+ import { AppSurface, useAppGraph } from '@dxos/app-toolkit/ui';
13
13
  import { corePlugins } from '@dxos/plugin-testing';
14
14
  import { random } from '@dxos/random';
15
15
  import { Main } from '@dxos/react-ui';
@@ -43,7 +43,7 @@ const storySurfaceExtension = Capability.contributes(
43
43
  Capabilities.ReactSurface,
44
44
  Surface.create({
45
45
  id: 'storyArticle',
46
- role: 'article',
46
+ filter: Surface.makeFilter(AppSurface.Article),
47
47
  component: ({ data }) => {
48
48
  const subject = (data as any)?.subject;
49
49
  if (!subject) {
@@ -34,7 +34,7 @@ export type PlankCompanionControlsProps = {
34
34
 
35
35
  export const PlankCompanionControls = forwardRef<HTMLDivElement, PlankCompanionControlsProps>(
36
36
  ({ primary }, forwardedRef) => {
37
- const { t } = useTranslation(meta.id);
37
+ const { t } = useTranslation(meta.profile.key);
38
38
  const { invokePromise } = useOperationInvoker();
39
39
  const handleCloseCompanion = useCallback(() => {
40
40
  return invokePromise(LayoutOperation.UpdateCompanion, { subject: null });
@@ -78,7 +78,7 @@ export const PlankControls = forwardRef<HTMLDivElement, PlankControlsProps>(
78
78
  { children, classNames, variant = 'default', capabilities, layoutMode, pin, close = false, onClick, ...props },
79
79
  forwardedRef,
80
80
  ) => {
81
- const { t } = useTranslation(meta.id);
81
+ const { t } = useTranslation(meta.profile.key);
82
82
  const buttonClassNames =
83
83
  variant === 'hide-disabled' ? `disabled:hidden ${plankControlSpacing}` : plankControlSpacing;
84
84
 
@@ -52,7 +52,7 @@ export const PlankError = ({
52
52
  * User facing error fallback.
53
53
  */
54
54
  export const PlankErrorFallback = ({ error }: ErrorFallbackProps) => {
55
- const { t } = useTranslation(meta.id);
55
+ const { t } = useTranslation(meta.profile.key);
56
56
 
57
57
  useEffect(() => {
58
58
  if (error) {
@@ -7,10 +7,9 @@ import React, { Fragment, type MouseEvent, memo, useCallback, useEffect, useMemo
7
7
  import { Surface } from '@dxos/app-framework/ui';
8
8
  import { AppSurface } from '@dxos/app-toolkit/ui';
9
9
  import { Graph, type Node, useActionRunner } from '@dxos/plugin-graph';
10
- import { Icon, IconButton, Popover, toLocalizedString, useTranslation } from '@dxos/react-ui';
10
+ import { Icon, IconButton, Popover, TextTooltip, toLocalizedString, useTranslation } from '@dxos/react-ui';
11
11
  import { getLinkedVariant } from '@dxos/react-ui-attention';
12
12
  import { StackItem, type StackItemSigilAction } from '@dxos/react-ui-stack';
13
- import { TextTooltip } from '@dxos/react-ui-text-tooltip';
14
13
  import { hoverableControls, hoverableFocusedWithinControls, iconSize } from '@dxos/ui-theme';
15
14
 
16
15
  import { useBreakpoints } from '#hooks';
@@ -57,14 +56,14 @@ export const PlankHeading = memo(
57
56
  actions = [],
58
57
  debug = false,
59
58
  }: PlankHeadingProps) => {
60
- const { t } = useTranslation(meta.id);
59
+ const { t } = useTranslation(meta.profile.key);
61
60
  const { graph, onAdjust, onUpdateCompanion } = usePlankContext('PlankHeading');
62
61
  const runAction = useActionRunner();
63
62
  const breakpoint = useBreakpoints();
64
63
  const icon = node?.properties?.icon ?? 'ph--circle-dashed--regular';
65
64
  const label = pending
66
65
  ? t('pending.heading')
67
- : toLocalizedString(node?.properties?.label ?? ['plank-heading-fallback.label', { ns: meta.id }], t);
66
+ : toLocalizedString(node?.properties?.label ?? ['plank-heading-fallback.label', { ns: meta.profile.key }], t);
68
67
 
69
68
  const isCompanionNode = node?.type === PLANK_COMPANION_TYPE;
70
69
 
@@ -111,7 +110,7 @@ export const PlankHeading = memo(
111
110
  const handleAction = useCallback(
112
111
  (action: StackItemSigilAction) => {
113
112
  if (typeof action.data === 'function') {
114
- void runAction(action as Node.Action, { parent: node, caller: meta.id });
113
+ void runAction(action as Node.Action, { parent: node, caller: meta.profile.key });
115
114
  }
116
115
  },
117
116
  [node, runAction],
@@ -124,7 +123,7 @@ export const PlankHeading = memo(
124
123
  [onAdjust, id],
125
124
  );
126
125
 
127
- const ActionRoot = node && popoverAnchorId === `${meta.id}:${node.id}` ? Popover.Anchor : Fragment;
126
+ const ActionRoot = node && popoverAnchorId === `${meta.profile.key}:${node.id}` ? Popover.Anchor : Fragment;
128
127
 
129
128
  const handleTabClick = useCallback(
130
129
  (event: MouseEvent) => {
@@ -6,6 +6,7 @@ import React, { type MouseEvent, useCallback, useEffect, useMemo, useState } fro
6
6
 
7
7
  import { Surface, useOperationInvoker } from '@dxos/app-framework/ui';
8
8
  import { LayoutOperation } from '@dxos/app-toolkit';
9
+ import { AppSurface } from '@dxos/app-toolkit/ui';
9
10
  import { IconButton, type Label, Main, Panel, toLocalizedString, Toolbar, useTranslation } from '@dxos/react-ui';
10
11
  import { getLinkedVariant } from '@dxos/react-ui-attention';
11
12
  import { Tabs } from '@dxos/react-ui-tabs';
@@ -19,7 +20,7 @@ import { layoutAppliesTopbar } from '../../util';
19
20
  import { PlankErrorFallback, PlankLoading } from '../Plank';
20
21
  import { ToggleComplementarySidebarButton } from './SidebarButton';
21
22
 
22
- const label = ['complementary-sidebar.title', { ns: meta.id }] satisfies Label;
23
+ const label = ['complementary-sidebar.title', { ns: meta.profile.key }] satisfies Label;
23
24
 
24
25
  export type ComplementarySidebarProps = {
25
26
  current?: string;
@@ -27,7 +28,7 @@ export type ComplementarySidebarProps = {
27
28
 
28
29
  export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) => {
29
30
  const { invokePromise } = useOperationInvoker();
30
- const { t } = useTranslation(meta.id);
31
+ const { t } = useTranslation(meta.profile.key);
31
32
  const { state, deck, updateState } = useDeckState();
32
33
  const layoutMode = getMode(deck);
33
34
  const breakpoint = useBreakpoints();
@@ -117,7 +118,7 @@ export const ComplementarySidebar = ({ current }: ComplementarySidebarProps) =>
117
118
  className='grid grid-cols-1 auto-rows-(--dx-rail-item) py-0.5 gap-0.5 overflow-y-auto scrollbar-none'
118
119
  style={iconSize(4)}
119
120
  >
120
- <Surface.Surface role='status-indicator' />
121
+ <Surface.Surface type={AppSurface.StatusIndicator} />
121
122
  </div>
122
123
  <div className='hidden lg:grid grid-cols-1 auto-rows-(--dx-rail-action) p-1'>
123
124
  <ToggleComplementarySidebarButton />
@@ -154,7 +155,7 @@ type ComplementarySidebarPanelProps = {
154
155
  };
155
156
 
156
157
  const ComplementarySidebarPanel = ({ companion, activeId, data }: ComplementarySidebarPanelProps) => {
157
- const { t } = useTranslation(meta.id);
158
+ const { t } = useTranslation(meta.profile.key);
158
159
 
159
160
  if (getLinkedVariant(companion.id) !== activeId && !data) {
160
161
  return null;
@@ -178,7 +179,7 @@ const ComplementarySidebarPanel = ({ companion, activeId, data }: ComplementaryS
178
179
  </Panel.Toolbar>
179
180
  <Panel.Content classNames='bg-r1-surface'>
180
181
  <Surface.Surface
181
- role={`deck-companion--${getLinkedVariant(companion.id)}`}
182
+ type={AppSurface.deckCompanion(getLinkedVariant(companion.id))}
182
183
  data={data}
183
184
  fallback={PlankErrorFallback}
184
185
  placeholder={<PlankLoading />}
@@ -14,7 +14,7 @@ import { getMode } from '#types';
14
14
 
15
15
  import { layoutAppliesTopbar } from '../../util';
16
16
 
17
- const label = ['sidebar.title', { ns: meta.id }] satisfies Label;
17
+ const label = ['sidebar.title', { ns: meta.profile.key }] satisfies Label;
18
18
 
19
19
  export const Sidebar = () => {
20
20
  const { state, deck } = useDeckState();
@@ -17,7 +17,7 @@ export const ToggleSidebarButton = ({
17
17
  variant = 'ghost',
18
18
  }: ThemedClassName<Pick<IconButtonProps, 'variant'>>) => {
19
19
  const { updateState } = useDeckState();
20
- const { t } = useTranslation(meta.id);
20
+ const { t } = useTranslation(meta.profile.key);
21
21
 
22
22
  const handleClick = useCallback(() => {
23
23
  updateState((state) => ({
@@ -41,7 +41,7 @@ export const ToggleSidebarButton = ({
41
41
 
42
42
  export const CloseSidebarButton = () => {
43
43
  const { updateState } = useDeckState();
44
- const { t } = useTranslation(meta.id);
44
+ const { t } = useTranslation(meta.profile.key);
45
45
 
46
46
  const handleClick = useCallback(() => {
47
47
  updateState((state) => ({ ...state, sidebarState: 'collapsed' }));
@@ -67,7 +67,7 @@ export const ToggleComplementarySidebarButton = ({
67
67
  }: ThemedClassName<{ inR0?: boolean; current?: string }>) => {
68
68
  const { invokePromise } = useOperationInvoker();
69
69
  const { state, updateState } = useDeckState();
70
- const { t } = useTranslation(meta.id);
70
+ const { t } = useTranslation(meta.profile.key);
71
71
 
72
72
  const companions = useDeckCompanions();
73
73
  const handleClick = useCallback(() => {
package/src/meta.ts CHANGED
@@ -3,32 +3,7 @@
3
3
  //
4
4
 
5
5
  import { Plugin } from '@dxos/app-framework';
6
- import { DXN } from '@dxos/keys';
7
- import { trim } from '@dxos/util';
8
6
 
9
- export const meta = Plugin.makeMeta({
10
- key: DXN.make('org.dxos.plugin.deck'),
11
- name: 'Layout',
12
- author: 'DXOS',
13
- spec: 'PLUGIN.mdl',
14
- description: trim`
15
- The Deck plugin is the core layout engine for DXOS Composer. It manages the multi-plank
16
- workspace (the "deck"), sidebar panels, dialogs, popovers, and toast notifications, giving
17
- users a flexible, persistent workspace they can arrange to match their workflow.
7
+ import config from '../dx.config';
18
8
 
19
- In multi mode, subjects are opened as resizable "planks" arranged side by side. Users can
20
- navigate with stack semantics — opening from a pivot truncates planks to the right and
21
- appends the new one — or switch to solo or fullscreen mode for focused, distraction-free
22
- viewing.
23
-
24
- Layout state (active planks, sidebar visibility, plank sizes, companion pane) is persisted
25
- across sessions via KVS/localStorage. URL routing is handled by the plugin so that any
26
- workspace configuration can be bookmarked or shared as a deep link.
27
-
28
- All layout changes are expressed through typed LayoutOperations (Open, Close, SetLayoutMode,
29
- UpdateSidebar, UpdateDialog, UpdatePopover, etc.) that any plugin in the system can dispatch,
30
- keeping the layout logic centralised and easy to extend.
31
- `,
32
- icon: 'ph--layout--regular',
33
- tags: ['system'],
34
- });
9
+ export const meta = Plugin.getMetaFromConfig(config);
@@ -6,13 +6,7 @@ import * as Effect from 'effect/Effect';
6
6
  import * as Option from 'effect/Option';
7
7
 
8
8
  import { Capabilities, Capability } from '@dxos/app-framework';
9
- import {
10
- AppCapabilities,
11
- LayoutOperation,
12
- createEdgeExistenceChecker,
13
- expandPath,
14
- validateNavigationTarget,
15
- } from '@dxos/app-toolkit';
9
+ import { AppCapabilities, LayoutOperation, NotFound } from '@dxos/app-toolkit';
16
10
  import { Operation } from '@dxos/compute';
17
11
  import { Context } from '@dxos/context';
18
12
  import { Database, EID, Obj } from '@dxos/echo';
@@ -56,13 +50,15 @@ const handler: Operation.WithHandler<typeof LayoutOperation.Open> = LayoutOperat
56
50
  }
57
51
  : undefined;
58
52
  const checkRemoteExistence = client
59
- ? createEdgeExistenceChecker((spaceId, body) => client.edge.http.execQuery(new Context(), spaceId, body))
53
+ ? NotFound.createEdgeExistenceChecker((spaceId, body) =>
54
+ client.edge.http.execQuery(new Context(), spaceId, body),
55
+ )
60
56
  : undefined;
61
57
 
62
58
  // Immediate: skip 404 / resolver checks but still expand the path (same as validate’s first step).
63
59
  if (input.navigation === 'immediate') {
64
60
  for (const subjectId of input.subject) {
65
- expandPath(graph, subjectId);
61
+ NotFound.expandPath(graph, subjectId);
66
62
  }
67
63
  }
68
64
 
@@ -70,7 +66,13 @@ const handler: Operation.WithHandler<typeof LayoutOperation.Open> = LayoutOperat
70
66
  input.subject.map((subjectId) =>
71
67
  input.navigation === 'immediate'
72
68
  ? Effect.succeed(subjectId)
73
- : validateNavigationTarget({ graph, subjectId, pathResolvers, checkLocalExistence, checkRemoteExistence }),
69
+ : NotFound.validateNavigationTarget({
70
+ graph,
71
+ subjectId,
72
+ pathResolvers,
73
+ checkLocalExistence,
74
+ checkRemoteExistence,
75
+ }),
74
76
  ),
75
77
  );
76
78
  input = { ...input, subject: validatedSubjects };
@@ -5,7 +5,7 @@
5
5
  import * as Effect from 'effect/Effect';
6
6
 
7
7
  import { Capabilities, Capability } from '@dxos/app-framework';
8
- import { AppCapabilities, isPinnedWorkspace, LayoutOperation } from '@dxos/app-toolkit';
8
+ import { AppCapabilities, LayoutOperation, Paths } from '@dxos/app-toolkit';
9
9
  import { Operation } from '@dxos/compute';
10
10
  import { invariant } from '@dxos/invariant';
11
11
  import { Graph, Node } from '@dxos/plugin-graph';
@@ -21,7 +21,7 @@ const handler: Operation.WithHandler<typeof LayoutOperation.SwitchWorkspace> = L
21
21
  const state = yield* Capabilities.getAtomValue(DeckCapabilities.State);
22
22
  // TODO(wittjosiah): This is a hack to prevent the previous deck from being set for pinned items.
23
23
  // Ideally this should be worked into the data model in a generic way.
24
- const shouldUpdatePrevious = !isPinnedWorkspace(state.activeDeck);
24
+ const shouldUpdatePrevious = !Paths.isPinnedWorkspace(state.activeDeck);
25
25
 
26
26
  yield* Capabilities.updateAtomValue(DeckCapabilities.State, (state) => {
27
27
  const newDecks = state.decks[input.subject]
@@ -9,7 +9,7 @@ import { meta } from '#meta';
9
9
  export const translations = [
10
10
  {
11
11
  'en-US': {
12
- [meta.id]: {
12
+ [meta.profile.key]: {
13
13
  'plugin.name': 'Deck',
14
14
  'settings.title': 'Deck settings',
15
15
  'main-header.label': 'Main header',
@@ -14,14 +14,16 @@ import { meta } from '#meta';
14
14
 
15
15
  import { type EphemeralDeckState, type DeckState, type StoredDeckState } from './schema';
16
16
 
17
- export const Settings = Capability.make<Atom.Writable<import('./Settings').Settings>>(`${meta.id}.capability.settings`);
17
+ export const Settings = Capability.make<Atom.Writable<import('./Settings').Settings>>(
18
+ `${meta.profile.key}.capability.settings`,
19
+ );
18
20
 
19
21
  /** Persisted state (stored in KVS/localStorage). */
20
- export const State = Capability.make<Atom.Writable<StoredDeckState>>(`${meta.id}.capability.state`);
22
+ export const State = Capability.make<Atom.Writable<StoredDeckState>>(`${meta.profile.key}.capability.state`);
21
23
 
22
24
  /** Transient/ephemeral state (not persisted). */
23
25
  export const EphemeralState = Capability.make<Atom.Writable<EphemeralDeckState>>(
24
- `${meta.id}.capability.ephemeral-state`,
26
+ `${meta.profile.key}.capability.ephemeral-state`,
25
27
  );
26
28
 
27
29
  /** Get the current active deck from state. */
@@ -12,7 +12,7 @@ import { meta } from '#meta';
12
12
  import * as DeckCapabilities from './DeckCapabilities';
13
13
 
14
14
  export const StateReady: ActivationEvent.ActivationEvent = AppActivationEvents.createStateEvent(
15
- `${meta.id}.state-ready`,
15
+ `${meta.profile.key}.state-ready`,
16
16
  );
17
17
 
18
18
  /** Fired when DeckSettings capability is ready. */
@@ -12,7 +12,7 @@ import { DXN } from '@dxos/keys';
12
12
 
13
13
  import { meta } from '#meta';
14
14
 
15
- const makeKey = (name: string) => DXN.make(`${meta.id}.operation.${name}`);
15
+ const makeKey = (name: string) => DXN.make(`${meta.profile.key}.operation.${name}`);
16
16
 
17
17
  const PartAdjustmentSchema = Schema.Union(
18
18
  Schema.Literal('close').annotations({ description: 'Close the plank.' }),
@@ -3,6 +3,7 @@
3
3
  //
4
4
 
5
5
  export * from './schema';
6
+ export * from './surface';
6
7
 
7
8
  export * as DeckCapabilities from './DeckCapabilities';
8
9
  export * as DeckEvents from './DeckEvents';
@@ -4,12 +4,13 @@
4
4
 
5
5
  import * as Schema from 'effect/Schema';
6
6
 
7
- import { Label, LayoutOperation } from '@dxos/app-toolkit';
7
+ import { AppNode, LayoutOperation, Translations } from '@dxos/app-toolkit';
8
8
  import { type DeepReadonly } from '@dxos/util';
9
9
 
10
10
  import { meta } from '#meta';
11
11
 
12
- export { PLANK_COMPANION_TYPE, DECK_COMPANION_TYPE } from '@dxos/app-toolkit';
12
+ export const PLANK_COMPANION_TYPE = AppNode.PLANK_COMPANION_TYPE;
13
+ export const DECK_COMPANION_TYPE = AppNode.DECK_COMPANION_TYPE;
13
14
 
14
15
  export type Part = 'solo' | 'multi' | 'complementary';
15
16
  export type ResolvedPart = Part | 'solo-primary' | 'solo-companion';
@@ -91,8 +92,8 @@ export const EphemeralDeckState = Schema.Struct({
91
92
  popoverSide: Schema.optional(Schema.Literal('top', 'right', 'bottom', 'left')),
92
93
  popoverAnchor: Schema.optional(Schema.Any),
93
94
  popoverAnchorId: Schema.optional(Schema.String),
94
- popoverKind: Schema.optional(Schema.Literal('base', 'card')),
95
- popoverTitle: Schema.optional(Label.annotations({ description: 'The title of the popover.' })),
95
+ popoverKind: Schema.optional(Schema.Literal('base', 'card', 'rename')),
96
+ popoverTitle: Schema.optional(Translations.Label.annotations({ description: 'The title of the popover.' })),
96
97
  /** Ref of the subject to be passed to the popover Surface. */
97
98
  popoverContentRef: Schema.optional(Schema.String),
98
99
  /** Data to be passed to the popover Surface. */
@@ -126,16 +127,19 @@ export namespace DeckAction {
126
127
  export type Adjustment = Schema.Schema.Type<typeof Adjustment>;
127
128
 
128
129
  // An atomic transaction to apply to the deck, describing which element to move to which location.
129
- export class Adjust extends Schema.TaggedClass<Adjust>()(`${meta.id}.action.adjust`, {
130
+ export class Adjust extends Schema.TaggedClass<Adjust>()(`${meta.profile.key}.action.adjust`, {
130
131
  input: Adjustment,
131
132
  output: Schema.Void,
132
133
  }) {}
133
134
 
134
- export class UpdatePlankSize extends Schema.TaggedClass<UpdatePlankSize>()(`${meta.id}.action.update-plank-size`, {
135
- input: Schema.Struct({
136
- id: Schema.String,
137
- size: Schema.Number,
138
- }),
139
- output: Schema.Void,
140
- }) {}
135
+ export class UpdatePlankSize extends Schema.TaggedClass<UpdatePlankSize>()(
136
+ `${meta.profile.key}.action.update-plank-size`,
137
+ {
138
+ input: Schema.Struct({
139
+ id: Schema.String,
140
+ size: Schema.Number,
141
+ }),
142
+ output: Schema.Void,
143
+ },
144
+ ) {}
141
145
  }
@@ -0,0 +1,28 @@
1
+ //
2
+ // Copyright 2025 DXOS.org
3
+ //
4
+
5
+ import { Surface } from '@dxos/app-framework/ui';
6
+
7
+ /** Slot for the main status-bar container. */
8
+ export const StatusBar: Surface.RoleToken<Record<string, unknown>> = Surface.makeType(
9
+ 'org.dxos.plugin.statusBar.role.statusBar',
10
+ );
11
+
12
+ /** Slot for the status-bar footer region. */
13
+ export const StatusBarFooter: Surface.RoleToken<Record<string, unknown>> = Surface.makeType(
14
+ 'org.dxos.plugin.statusBar.role.footer',
15
+ );
16
+
17
+ /** Slot for the version-info widget in the banner. */
18
+ export const VersionInfo: Surface.RoleToken<Record<string, unknown>> = Surface.makeType(
19
+ 'org.dxos.plugin.statusBar.role.versionInfo',
20
+ );
21
+
22
+ /** Slot for the keyboard-shortcuts hints overlay. */
23
+ export const Hints: Surface.RoleToken<Record<string, unknown>> = Surface.makeType('org.dxos.plugin.support.role.hints');
24
+
25
+ /** Slot for the full keyboard-shortcuts list. */
26
+ export const Keyshortcuts: Surface.RoleToken<Record<string, unknown>> = Surface.makeType(
27
+ 'org.dxos.plugin.support.role.keyshortcuts',
28
+ );
@@ -2,7 +2,7 @@
2
2
  // Copyright 2025 DXOS.org
3
3
  //
4
4
 
5
- import { fromUrlPath, toUrlPath } from '@dxos/app-toolkit';
5
+ import { Paths } from '@dxos/app-toolkit';
6
6
 
7
7
  const PLANK_PARAM = 'plank';
8
8
 
@@ -14,7 +14,7 @@ export const serializePlanks = (active: readonly string[], existingSearch: strin
14
14
  const params = new URLSearchParams(existingSearch);
15
15
  params.delete(PLANK_PARAM);
16
16
  for (const id of active) {
17
- params.append(PLANK_PARAM, toUrlPath(id));
17
+ params.append(PLANK_PARAM, Paths.toUrlPath(id));
18
18
  }
19
19
  return params.size > 0 ? `?${params.toString()}` : '';
20
20
  };
@@ -23,7 +23,7 @@ export const serializePlanks = (active: readonly string[], existingSearch: strin
23
23
  * Deserialize plank query params from a URL back to qualified graph IDs.
24
24
  */
25
25
  export const deserializePlanks = (url: URL): string[] => {
26
- return url.searchParams.getAll(PLANK_PARAM).map(fromUrlPath);
26
+ return url.searchParams.getAll(PLANK_PARAM).map(Paths.fromUrlPath);
27
27
  };
28
28
 
29
29
  /**