@puredesktop/puredesktop-ui-bridge 2.1.7 → 2.1.9

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 (119) hide show
  1. package/package.json +89 -5
  2. package/src/agents/mcpServerPresets.test.ts +105 -0
  3. package/src/agents/mcpServerPresets.ts +380 -0
  4. package/src/agents/runtime/index.ts +7 -0
  5. package/src/agents/runtime/mapAgentSnapshot.ts +11 -8
  6. package/src/agents/runtime/pendingAsks.test.ts +117 -0
  7. package/src/agents/runtime/pendingAsks.ts +110 -0
  8. package/src/bridge/agentScopeDefaults.ts +65 -0
  9. package/src/bridge/agentTypes.ts +57 -3
  10. package/src/bridge/agents.d.mts +6 -0
  11. package/src/bridge/agents.mjs +10 -0
  12. package/src/bridge/agents.ts +4 -0
  13. package/src/bridge/collectionImagePaste.ts +19 -1
  14. package/src/bridge/context.mjs +32 -0
  15. package/src/bridge/credentials.d.mts +62 -0
  16. package/src/bridge/credentials.mjs +53 -0
  17. package/src/bridge/documents.d.mts +76 -0
  18. package/src/bridge/documents.mjs +40 -0
  19. package/src/bridge/events.d.mts +1 -0
  20. package/src/bridge/events.mjs +1 -0
  21. package/src/bridge/googleAuth.ts +7 -152
  22. package/src/bridge/mcpClient.d.mts +74 -0
  23. package/src/bridge/mcpClient.mjs +79 -0
  24. package/src/bridge/methods.d.mts +45 -0
  25. package/src/bridge/methods.mjs +46 -0
  26. package/src/bridge/notifications.d.mts +12 -0
  27. package/src/bridge/notifications.mjs +36 -0
  28. package/src/bridge/operations.d.mts +44 -0
  29. package/src/bridge/operations.mjs +40 -0
  30. package/src/bridge/pureRender/base.css +44 -0
  31. package/src/bridge/pureRender/document.ts +29 -2
  32. package/src/bridge/pureRender/index.ts +3 -0
  33. package/src/bridge/pureRender/normalize.ts +167 -0
  34. package/src/bridge/pureRender/sanitizeExportBody.test.ts +51 -0
  35. package/src/bridge/pureRender/sanitizeExportBody.ts +61 -0
  36. package/src/bridge/pureRender/styles.ts +125 -5
  37. package/src/bridge/pureRender/theme.test.ts +259 -0
  38. package/src/bridge/pureRender/theme.ts +19 -1
  39. package/src/bridge/pureRender/types.ts +33 -0
  40. package/src/bridge/pureRender/visualLayoutInspection.test.ts +26 -0
  41. package/src/bridge/pureRender/visualLayoutInspection.ts +75 -0
  42. package/src/bridge/react/useDocumentHotkeys.ts +41 -0
  43. package/src/bridge/react/useDocumentLifecycle.tsx +359 -0
  44. package/src/bridge/react/usePlatformAgentSessionDrive.test.tsx +29 -10
  45. package/src/bridge/react/usePlatformAgentSessionDrive.tsx +10 -81
  46. package/src/bridge/react/usePlatformAppSettings.test.tsx +105 -0
  47. package/src/bridge/react/usePlatformAppSettings.tsx +104 -0
  48. package/src/bridge/react/usePlatformJsonStore.test.tsx +152 -0
  49. package/src/bridge/react/usePlatformJsonStore.tsx +149 -0
  50. package/src/bridge/react/usePlatformOperations.tsx +75 -0
  51. package/src/bridge/storage.d.mts +23 -10
  52. package/src/bridge/storage.mjs +6 -3
  53. package/src/bridge/storage.test.ts +6 -6
  54. package/src/bridge/types.ts +7 -0
  55. package/src/components/agents/AgentComposer.tsx +10 -6
  56. package/src/components/agents/AgentDrawerPanel.test.tsx +2 -205
  57. package/src/components/agents/AgentDrawerPanel.tsx +23 -62
  58. package/src/components/agents/AgentMessageBubble.tsx +59 -33
  59. package/src/components/agents/AgentMessageList.tsx +4 -4
  60. package/src/components/agents/AgentQuestionPromptList.tsx +34 -212
  61. package/src/components/agents/AgentToolApprovalActions.tsx +13 -11
  62. package/src/components/agents/AgentToolCallCard.tsx +20 -20
  63. package/src/components/agents/AgentToolPendingList.tsx +6 -6
  64. package/src/components/agents/AgentToolPendingReview.tsx +6 -6
  65. package/src/components/agents/ContextPicker.tsx +239 -0
  66. package/src/components/agents/QuestionRequestList.tsx +278 -0
  67. package/src/components/agents/agentPanelStyles.ts +8 -31
  68. package/src/components/agents/agentTypes.ts +25 -41
  69. package/src/components/agents/index.ts +4 -0
  70. package/src/components/assets/asset-library/sidebar/AssetLibraryList.tsx +10 -4
  71. package/src/components/chrome/WorkspaceTabStrip.tsx +22 -4
  72. package/src/components/chrome/workspaceTabTypes.ts +2 -0
  73. package/src/components/common/chat/ChatBox.tsx +9 -9
  74. package/src/components/common/chat/ChatModelMenu.tsx +34 -4
  75. package/src/components/common/chat/ChatThread.tsx +23 -7
  76. package/src/components/common/connections/ProviderConnection.test.tsx +208 -0
  77. package/src/components/common/connections/ProviderConnection.tsx +277 -0
  78. package/src/components/common/connections/index.ts +5 -0
  79. package/src/components/common/containers/AppFrame.test.tsx +4 -4
  80. package/src/components/common/containers/AppFrame.tsx +26 -10
  81. package/src/components/common/containers/AppHeader.tsx +37 -15
  82. package/src/components/common/documents/DocumentHeaderActions.tsx +206 -0
  83. package/src/components/common/documents/DocumentSwitcher.tsx +1181 -0
  84. package/src/components/common/documents/index.ts +8 -0
  85. package/src/components/common/dropdown/MenuDropdownItem.tsx +14 -2
  86. package/src/components/common/dropdown/MenuPortal.tsx +3 -1
  87. package/src/components/common/dropdown/menuViewportPosition.test.ts +20 -10
  88. package/src/components/common/dropdown/menuViewportPosition.ts +35 -27
  89. package/src/components/common/overlays/Modal.tsx +13 -2
  90. package/src/components/common/research/EvidenceDossier.tsx +1232 -150
  91. package/src/components/common/research/index.ts +2 -0
  92. package/src/components/editor/EditorLinkPromptDialog.tsx +1 -1
  93. package/src/components/print-design/PrintDesignPanel.test.tsx +430 -40
  94. package/src/components/print-design/PrintDesignPanel.tsx +1770 -207
  95. package/src/components/print-design/index.ts +2 -0
  96. package/src/components/print-design/previewContent.test.ts +32 -0
  97. package/src/components/print-design/previewContent.ts +31 -0
  98. package/src/context/buildContextDocument.test.ts +240 -0
  99. package/src/context/buildContextDocument.ts +309 -0
  100. package/src/context/contextAccess.ts +66 -0
  101. package/src/context/contextConfig.ts +72 -0
  102. package/src/context/contextDocument.test.ts +155 -0
  103. package/src/context/contextDocument.ts +300 -0
  104. package/src/context/contextSections.test.ts +88 -0
  105. package/src/context/contextSections.ts +84 -0
  106. package/src/context/htmlToContextMarkdown.test.ts +134 -0
  107. package/src/context/htmlToContextMarkdown.ts +369 -0
  108. package/src/ics/generateIcs.test.ts +153 -0
  109. package/src/ics/generateIcs.ts +197 -0
  110. package/src/index.ts +3 -0
  111. package/src/net/httpRetry.test.ts +117 -0
  112. package/src/net/httpRetry.ts +111 -0
  113. package/src/theme/appAccents.test.ts +36 -35
  114. package/src/theme/appAccents.ts +93 -104
  115. package/src/theme/appIdentityCss.mjs +178 -229
  116. package/src/theme/appIdentityCss.ts +222 -231
  117. package/src/theme/themes/dark.ts +3 -1
  118. package/src/theme/themes/light.ts +3 -1
  119. package/src/bridge/googleProviderConfig.ts +0 -73
@@ -0,0 +1,8 @@
1
+ export {
2
+ DocumentHeaderActions,
3
+ type DocumentHeaderActionsProps,
4
+ } from './DocumentHeaderActions.js'
5
+ export {
6
+ DocumentSwitcher,
7
+ type DocumentSwitcherProps,
8
+ } from './DocumentSwitcher.js'
@@ -83,12 +83,24 @@ export function MenuDropdownItem({
83
83
  $hasIcon={Boolean(item.icon)}
84
84
  $tone={item.tone}
85
85
  onPointerDown={event => {
86
- if (event.pointerType === 'mouse' || event.pointerType === 'touch') {
86
+ // Select on pointerdown for pointing devices (fires before the menu
87
+ // closes) and preventDefault so the item doesn't steal focus.
88
+ if (
89
+ event.pointerType === 'mouse' ||
90
+ event.pointerType === 'touch' ||
91
+ event.pointerType === 'pen'
92
+ ) {
87
93
  event.preventDefault()
88
94
  selectItem()
89
95
  }
90
96
  }}
91
- onClick={selectItem}
97
+ onClick={event => {
98
+ // A pointer interaction already selected on pointerdown above; the
99
+ // browser still fires a follow-up click, which would run the action a
100
+ // SECOND time (e.g. inserting two equations). Only handle genuine
101
+ // keyboard activation here — Enter/Space produce a click with detail 0.
102
+ if (event.detail === 0) selectItem()
103
+ }}
92
104
  >
93
105
  {item.icon ? (
94
106
  <span className="menu-item-icon" aria-hidden="true">
@@ -137,9 +137,11 @@ export function MenuPortal({
137
137
 
138
138
  const surfaceStyle: CSSProperties = layout
139
139
  ? {
140
- top: layout.top,
141
140
  left: layout.left,
142
141
  maxHeight: layout.maxHeight,
142
+ // Above-opening menus are pinned by `bottom` so they grow upward.
143
+ ...(layout.top !== undefined ? { top: layout.top } : {}),
144
+ ...(layout.bottom !== undefined ? { bottom: layout.bottom } : {}),
143
145
  ...(layout.width !== undefined ? { width: layout.width } : {}),
144
146
  ...style,
145
147
  }
@@ -71,7 +71,8 @@ describe('resolveMenuViewportLayout', () => {
71
71
  expect(layout.width).toBeGreaterThanOrEqual(120)
72
72
  })
73
73
 
74
- it('opens above the anchor when there is more space above', () => {
74
+ it('bottom-anchors a below-start menu that flips above at the viewport bottom', () => {
75
+ const viewportHeight = 760
75
76
  const layout = resolveMenuViewportLayout({
76
77
  anchorRect: {
77
78
  top: 720,
@@ -83,16 +84,23 @@ describe('resolveMenuViewportLayout', () => {
83
84
  preferredTop: 752,
84
85
  preferredLeft: 120,
85
86
  placement: 'below-start',
87
+ // Menu measured collapsed (pre-expansion) — must not drive the height.
86
88
  menuWidth: 200,
87
- menuHeight: 220,
89
+ menuHeight: 8,
88
90
  viewportWidth: 1000,
89
- viewportHeight: 760,
91
+ viewportHeight,
90
92
  })
91
93
 
92
- expect(layout.top).toBeLessThan(720)
94
+ // Pinned by bottom (grows upward), not top; its bottom edge sits above the
95
+ // anchor and it gets the real space above (not the ~0 measured height).
96
+ expect(layout.top).toBeUndefined()
97
+ expect(layout.bottom).toBeGreaterThan(0)
98
+ expect(viewportHeight - (layout.bottom ?? 0)).toBeLessThanOrEqual(720)
99
+ expect(layout.maxHeight).toBeGreaterThan(100)
93
100
  })
94
101
 
95
- it('opens above-start menus upward from the anchor', () => {
102
+ it('bottom-anchors above-start menus so they grow upward from the anchor', () => {
103
+ const viewportHeight = 760
96
104
  const layout = resolveMenuViewportLayout({
97
105
  anchorRect: {
98
106
  top: 720,
@@ -105,13 +113,14 @@ describe('resolveMenuViewportLayout', () => {
105
113
  preferredLeft: 120,
106
114
  placement: 'above-start',
107
115
  menuWidth: 200,
108
- menuHeight: 180,
116
+ menuHeight: 8,
109
117
  viewportWidth: 1000,
110
- viewportHeight: 760,
118
+ viewportHeight,
111
119
  })
112
120
 
113
- expect(layout.top).toBeLessThan(720)
114
- expect(layout.top + layout.maxHeight).toBeLessThanOrEqual(720)
121
+ expect(layout.top).toBeUndefined()
122
+ expect(viewportHeight - (layout.bottom ?? 0)).toBeLessThanOrEqual(720)
123
+ expect(layout.maxHeight).toBeGreaterThan(100)
115
124
  })
116
125
 
117
126
  it('limits menu height to the remaining viewport space', () => {
@@ -134,6 +143,7 @@ describe('resolveMenuViewportLayout', () => {
134
143
  })
135
144
 
136
145
  expect(layout.maxHeight).toBeLessThanOrEqual(280)
137
- expect(layout.top + layout.maxHeight).toBeLessThanOrEqual(300)
146
+ expect(layout.top).toBeDefined()
147
+ expect((layout.top ?? 0) + layout.maxHeight).toBeLessThanOrEqual(300)
138
148
  })
139
149
  })
@@ -10,7 +10,14 @@ export const MENU_DEFAULT_MAX_HEIGHT_PX = 320
10
10
  export type MenuWidthMode = 'intrinsic' | 'anchor'
11
11
 
12
12
  export interface ResolvedMenuLayout {
13
- top: number
13
+ /**
14
+ * Exactly one of `top`/`bottom` is set. Above-opening menus pin by `bottom`
15
+ * so they grow upward — deriving `top`/height from the menu's own measurement
16
+ * is circular (it's measured collapsed, before the open transition expands
17
+ * it, which clipped bottom-anchored menus to ~0px).
18
+ */
19
+ top?: number
20
+ bottom?: number
14
21
  left: number
15
22
  maxHeight: number
16
23
  /** Set only for `anchor` width mode. */
@@ -78,45 +85,46 @@ export function resolveMenuViewportLayout({
78
85
  Math.max(margin, viewportWidth - layoutWidth - margin),
79
86
  )
80
87
 
81
- let top = preferredTop
88
+ const spaceAbove = Math.max(0, anchorRect.top - gap - margin)
82
89
 
90
+ let opensAbove: boolean
83
91
  if (placement === 'above-start') {
84
- top = anchorRect.top - gap - menuHeight
85
- const spaceAbove = Math.max(0, anchorRect.top - gap - margin)
86
92
  const spaceBelow = Math.max(
87
93
  0,
88
94
  viewportHeight - anchorRect.bottom - gap - margin,
89
95
  )
90
- if (menuHeight > spaceAbove && spaceBelow > spaceAbove) {
91
- top = anchorRect.bottom + gap
92
- }
93
- } else if (
94
- placement === 'below-start' ||
95
- placement === 'below-menubar-start' ||
96
- placement === 'at-point'
97
- ) {
96
+ // Prefer above; flip below only if it can't fit above but fits better below.
97
+ opensAbove = !(menuHeight > spaceAbove && spaceBelow > spaceAbove)
98
+ } else {
99
+ // below-start / below-menubar-start / at-point: prefer below, flip above
100
+ // when there's no room below but more above.
98
101
  const opensBelow = preferredTop >= anchorRect.bottom - 0.5
99
- if (opensBelow) {
100
- const spaceBelow = Math.max(0, viewportHeight - preferredTop - margin)
101
- const spaceAbove = Math.max(0, anchorRect.top - gap - margin)
102
- if (menuHeight > spaceBelow && spaceAbove > spaceBelow) {
103
- top = anchorRect.top - gap - menuHeight
104
- }
105
- }
102
+ const spaceBelow = Math.max(0, viewportHeight - preferredTop - margin)
103
+ opensAbove =
104
+ opensBelow && menuHeight > spaceBelow && spaceAbove > spaceBelow
106
105
  }
107
106
 
108
- top = clamp(top, margin, Math.max(margin, viewportHeight - margin))
109
-
110
- const opensAboveAnchor = top < anchorRect.top
111
- const availableHeight = opensAboveAnchor
112
- ? Math.max(0, anchorRect.top - gap - top)
113
- : Math.max(0, viewportHeight - top - margin)
114
- const resolvedMaxHeight = Math.min(maxHeight, availableHeight)
107
+ if (opensAbove) {
108
+ // Pin the panel's bottom just above the anchor so it grows UPWARD; its
109
+ // pre-expansion measured height can't misplace or clip it.
110
+ const bottom = Math.max(margin, viewportHeight - (anchorRect.top - gap))
111
+ return {
112
+ bottom,
113
+ left,
114
+ maxHeight: Math.min(maxHeight, spaceAbove),
115
+ ...(widthMode === 'anchor' ? { width: layoutWidth } : {}),
116
+ }
117
+ }
115
118
 
119
+ const top = clamp(
120
+ placement === 'above-start' ? anchorRect.bottom + gap : preferredTop,
121
+ margin,
122
+ Math.max(margin, viewportHeight - margin),
123
+ )
116
124
  return {
117
125
  top,
118
126
  left,
119
- maxHeight: resolvedMaxHeight,
127
+ maxHeight: Math.min(maxHeight, Math.max(0, viewportHeight - top - margin)),
120
128
  ...(widthMode === 'anchor' ? { width: layoutWidth } : {}),
121
129
  }
122
130
  }
@@ -15,7 +15,7 @@ import { zi } from '../../../theme/themeVars.js'
15
15
  import { PlatformIcon } from '../../chrome/PlatformIcon.js'
16
16
  import { ModalChromeHeader } from './ModalChromeHeader.js'
17
17
 
18
- export type ModalSize = 'sm' | 'md' | 'lg' | 'xl' | 'fullscreen'
18
+ export type ModalSize = 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'fullscreen'
19
19
 
20
20
  export interface ModalProps {
21
21
  open: boolean
@@ -42,6 +42,7 @@ const FOCUSABLE_SELECTOR = [
42
42
  ].join(',')
43
43
 
44
44
  const sizeWidths = {
45
+ xs: 'min(420px, calc(100vw - var(--platform-spacing-xl)))',
45
46
  sm: '42dvw',
46
47
  md: '50dvw',
47
48
  lg: '68dvw',
@@ -122,6 +123,9 @@ const StyledDialog = styled.div<{ $size: ModalSize; $chrome: boolean }>`
122
123
  }};
123
124
  min-height: ${({ $size, $chrome }) => {
124
125
  if ($size === 'fullscreen') return '100%'
126
+ // Compact dialogs (confirmations, single-field prompts) hug their content —
127
+ // no forced aspect-ratio min-height that leaves a tall, empty box.
128
+ if ($size === 'xs') return 'auto'
125
129
  if ($chrome && $size === 'xl') {
126
130
  return 'min(460px, calc(100vh - var(--platform-spacing-xl)))'
127
131
  }
@@ -314,7 +318,14 @@ export function Modal({
314
318
  title={title}
315
319
  titleId={titleId}
316
320
  onClose={onClose}
317
- onMaximize={() => setMaximized(current => !current)}
321
+ // A modal that opens fullscreen has nothing to maximize — hide the
322
+ // no-op toggle. (Gate on the requested size, not the computed one,
323
+ // so a modal maximized via this button keeps its Restore control.)
324
+ onMaximize={
325
+ size === 'fullscreen'
326
+ ? undefined
327
+ : () => setMaximized(current => !current)
328
+ }
318
329
  maximized={maximized}
319
330
  trailing={chromeTrailing}
320
331
  />