@puredesktop/puredesktop-ui-bridge 2.1.4 → 2.1.6

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 (183) hide show
  1. package/package.json +19 -1
  2. package/src/agents/runtime/mapAgentSnapshot.test.ts +1 -3
  3. package/src/agents/runtime/mapAgentSnapshot.ts +8 -4
  4. package/src/agents/runtime/mergeUsageTurn.ts +1 -2
  5. package/src/agents/runtime/pendingToolCalls.test.ts +1 -3
  6. package/src/agents/runtime/unresolvedToolCalls.ts +1 -2
  7. package/src/bridge/agentModelTiers.ts +9 -4
  8. package/src/bridge/agents.mjs +3 -1
  9. package/src/bridge/calendarDraftIntent.ts +12 -4
  10. package/src/bridge/calendarInviteIntent.ts +18 -5
  11. package/src/bridge/client.mjs +45 -45
  12. package/src/bridge/dialog.d.mts +8 -8
  13. package/src/bridge/dialog.mjs +40 -40
  14. package/src/bridge/dialog.test.ts +73 -73
  15. package/src/bridge/fs.d.mts +41 -41
  16. package/src/bridge/fs.mjs +56 -56
  17. package/src/bridge/fs.test.ts +83 -83
  18. package/src/bridge/fs.ts +28 -28
  19. package/src/bridge/googleAuth.ts +3 -1
  20. package/src/bridge/methods.d.mts +6 -0
  21. package/src/bridge/methods.mjs +7 -0
  22. package/src/bridge/network.d.mts +17 -17
  23. package/src/bridge/network.mjs +22 -22
  24. package/src/bridge/pureRender/base.css +324 -76
  25. package/src/bridge/pureRender/compiler.ts +1 -1
  26. package/src/bridge/pureRender/document.test.ts +2 -2
  27. package/src/bridge/pureRender/extraction.test.ts +24 -12
  28. package/src/bridge/pureRender/extraction.ts +10 -7
  29. package/src/bridge/pureRender/flow.css +17 -3
  30. package/src/bridge/pureRender/normalize.ts +28 -26
  31. package/src/bridge/pureRender/presets.ts +7 -7
  32. package/src/bridge/pureRender/profiles/book.css +122 -19
  33. package/src/bridge/pureRender/profiles/manuscript.css +57 -11
  34. package/src/bridge/pureRender/profiles/writer.css +48 -10
  35. package/src/bridge/pureRender/reset.css +20 -5
  36. package/src/bridge/pureRender/theme.ts +37 -9
  37. package/src/bridge/pureRender/tokens/default.css +45 -33
  38. package/src/bridge/react/usePlatformAgentRunLiveState.tsx +4 -1
  39. package/src/bridge/react/usePlatformAgentSessionDrive.tsx +13 -4
  40. package/src/bridge/screenAnnotation.test.ts +4 -1
  41. package/src/bridge/screenAnnotation.ts +6 -2
  42. package/src/bridge/storage.d.mts +27 -27
  43. package/src/bridge/storage.mjs +18 -18
  44. package/src/bridge/storage.test.ts +49 -49
  45. package/src/bridge/types.ts +62 -58
  46. package/src/bridge/window.d.mts +3 -1
  47. package/src/bridge/workspace.d.mts +7 -0
  48. package/src/bridge/workspace.mjs +8 -0
  49. package/src/bridge/workspace.ts +1 -0
  50. package/src/commitments/commitments.test.ts +10 -3
  51. package/src/commitments/commitments.ts +18 -4
  52. package/src/components/accessibility/AccessibilityReviewDialog.tsx +7 -2
  53. package/src/components/accessibility/accessibilityReview.test.ts +11 -4
  54. package/src/components/accessibility/accessibilityReview.ts +18 -10
  55. package/src/components/agent-feedback/ScreenAnnotationAgentDialog.tsx +9 -2
  56. package/src/components/agents/AgentContextUsageBar.tsx +5 -3
  57. package/src/components/agents/AgentDrawerPanel.test.tsx +15 -3
  58. package/src/components/agents/AgentMessageBubble.tsx +1 -3
  59. package/src/components/agents/AgentQuestionPromptList.tsx +3 -3
  60. package/src/components/agents/AgentToolApprovalActions.tsx +1 -3
  61. package/src/components/agents/agentPanelStyles.ts +1 -1
  62. package/src/components/agents/agentToolDisplay.ts +4 -1
  63. package/src/components/assets/asset-library/AssetLibraryDialog.tsx +2 -0
  64. package/src/components/assets/asset-library/AssetLibraryWorkspace.tsx +2 -0
  65. package/src/components/assets/asset-library/detail/AssetLibraryDetailHeader.test.tsx +40 -0
  66. package/src/components/assets/asset-library/detail/AssetLibraryDetailHeader.tsx +7 -3
  67. package/src/components/assets/asset-library/detail/AssetLibraryDetailPreview.tsx +8 -8
  68. package/src/components/assets/asset-library/hooks/useAssetLibraryDialog.ts +13 -2
  69. package/src/components/assets/asset-library/hooks/useAssetLibraryMutations.ts +18 -1
  70. package/src/components/assets/asset-library/sidebar/AssetLibraryListRow.tsx +9 -6
  71. package/src/components/assets/asset-library/sidebar/AssetLibrarySidebarToolbar.tsx +3 -1
  72. package/src/components/assets/asset-library/sidebar/AssetListThumbnail.tsx +1 -1
  73. package/src/components/assets/asset-library/types.ts +6 -0
  74. package/src/components/chrome/OverlayActionCapsule.tsx +2 -2
  75. package/src/components/chrome/PanelTabStrip.tsx +3 -2
  76. package/src/components/chrome/PureAppWordmark.tsx +15 -15
  77. package/src/components/chrome/WorkspaceTabStrip.tsx +19 -14
  78. package/src/components/chrome/documentChromePolicy.test.ts +34 -0
  79. package/src/components/chrome/documentChromePolicy.ts +15 -0
  80. package/src/components/chrome/index.ts +4 -0
  81. package/src/components/chrome/workspaceTabTypes.ts +2 -0
  82. package/src/components/common/buttons/Button.tsx +60 -10
  83. package/src/components/common/buttons/ChoiceChip.tsx +2 -2
  84. package/src/components/common/buttons/IconButton.tsx +26 -0
  85. package/src/components/common/buttons/SegmentedControl.tsx +23 -7
  86. package/src/components/common/canvas/CanvasElementFrame.tsx +37 -28
  87. package/src/components/common/canvas/canvasGeometry.test.ts +4 -5
  88. package/src/components/common/canvas/canvasGeometry.ts +1 -4
  89. package/src/components/common/canvas/useCanvasDragResize.test.tsx +10 -10
  90. package/src/components/common/canvas/useCanvasDragResize.ts +1 -4
  91. package/src/components/common/cards/OpenReviewCard.tsx +6 -6
  92. package/src/components/common/chat/ChatBox.test.tsx +9 -1
  93. package/src/components/common/chat/ChatBox.tsx +3 -4
  94. package/src/components/common/chat/ChatMarkdown.tsx +1 -1
  95. package/src/components/common/chat/ChatModelMenu.tsx +1 -1
  96. package/src/components/common/chat/ChatThinkingIndicator.tsx +1 -1
  97. package/src/components/common/chat/ChatThread.tsx +2 -1
  98. package/src/components/common/chat/ChatToolRow.tsx +2 -2
  99. package/src/components/common/chat/ChatUserBubble.tsx +1 -1
  100. package/src/components/common/connections/ConnectionSetup.tsx +6 -6
  101. package/src/components/common/containers/AppFrame.test.tsx +37 -36
  102. package/src/components/common/containers/AppFrame.tsx +15 -0
  103. package/src/components/common/containers/AppHeader.tsx +6 -3
  104. package/src/components/common/containers/SurfacePanel.tsx +1 -0
  105. package/src/components/common/containers/useOpacityPresence.ts +10 -15
  106. package/src/components/common/context/ContextSelector.tsx +10 -10
  107. package/src/components/common/desk/CreationDesk.tsx +23 -11
  108. package/src/components/common/desk/DailyBrief.tsx +18 -7
  109. package/src/components/common/desk/DeskCard.tsx +11 -6
  110. package/src/components/common/dropdown/MenuButtonDropdown.tsx +58 -6
  111. package/src/components/common/dropdown/MenuDropdownItem.tsx +23 -3
  112. package/src/components/common/dropdown/MenuPanel.tsx +8 -8
  113. package/src/components/common/dropdown/SelectMenu.tsx +8 -3
  114. package/src/components/common/dropdown/menuTypes.ts +2 -1
  115. package/src/components/common/evidence/StructuredEvidenceView.tsx +9 -5
  116. package/src/components/common/feedback/Badge.tsx +19 -5
  117. package/src/components/common/feedback/CapabilityPills.tsx +1 -1
  118. package/src/components/common/feedback/InfoHint.tsx +2 -2
  119. package/src/components/common/feedback/PlatformChip.tsx +9 -9
  120. package/src/components/common/feedback/QuietPill.tsx +5 -8
  121. package/src/components/common/feedback/WorkProgressPanel.tsx +14 -14
  122. package/src/components/common/files/FileTable.tsx +8 -2
  123. package/src/components/common/inputs/Checkbox.tsx +1 -1
  124. package/src/components/common/inputs/EditableSecretField.tsx +1 -3
  125. package/src/components/common/inputs/InlineComposer.tsx +2 -2
  126. package/src/components/common/inputs/PaletteDropdown.tsx +33 -23
  127. package/src/components/common/inputs/SurfacePalettePicker.tsx +1 -1
  128. package/src/components/common/inputs/ToggleSwitch.tsx +2 -2
  129. package/src/components/common/inputs/WorkspacePicker.tsx +3 -3
  130. package/src/components/common/lists/listRowStyles.ts +4 -2
  131. package/src/components/common/lists/quiet-ledger/index.ts +1 -4
  132. package/src/components/common/lists/reorderable/DragHandle.tsx +1 -1
  133. package/src/components/common/lists/reorderable/ReorderableSections.tsx +2 -1
  134. package/src/components/common/media/ImageReferencePicker.tsx +22 -14
  135. package/src/components/common/narrative/NarrativeMention.tsx +15 -9
  136. package/src/components/common/navigation/NavigationCardRow.tsx +2 -2
  137. package/src/components/common/navigation/UnderlineTabs.tsx +1 -0
  138. package/src/components/common/overlays/Modal.tsx +52 -3
  139. package/src/components/common/overlays/ObjectActionStrip.tsx +2 -2
  140. package/src/components/common/people/AutoUpdateReviewList.tsx +2 -2
  141. package/src/components/common/people/EntityList.tsx +7 -3
  142. package/src/components/common/people/EntityPortrait.tsx +6 -1
  143. package/src/components/common/people/LinkedDossierList.tsx +3 -1
  144. package/src/components/common/people/RelationshipMap.tsx +3 -9
  145. package/src/components/common/research/EvidenceDossier.tsx +24 -20
  146. package/src/components/common/tiles/CatalogCard.tsx +1 -1
  147. package/src/components/credentials/CredentialsPanel.tsx +2 -6
  148. package/src/components/data/DataChart.tsx +119 -94
  149. package/src/components/data/dataTable.ts +4 -1
  150. package/src/components/editor/DocumentAppChrome.tsx +172 -0
  151. package/src/components/editor/EditorCommentPromptDialog.tsx +1 -1
  152. package/src/components/editor/EditorLinkPromptDialog.tsx +1 -1
  153. package/src/components/print-design/PrintDesignPanel.test.tsx +8 -6
  154. package/src/components/print-design/PrintDesignPanel.tsx +18 -25
  155. package/src/components/proofing/PagedProofDialog.tsx +17 -4
  156. package/src/editor/EditorViewModeToggle.tsx +6 -2
  157. package/src/editor/index.ts +6 -0
  158. package/src/theme/ThemeProvider.tsx +5 -2
  159. package/src/theme/appAccents.test.ts +51 -0
  160. package/src/theme/appAccents.ts +151 -88
  161. package/src/theme/appIdentityCss.mjs +256 -0
  162. package/src/theme/appIdentityCss.ts +256 -0
  163. package/src/theme/composePlatformTheme.test.ts +43 -14
  164. package/src/theme/platformFont.d.mts +2 -2
  165. package/src/theme/platformFont.mjs +4 -3
  166. package/src/theme/reportsShellChromeSelectors.ts +14 -132
  167. package/src/theme/surfaceCss.mjs +37 -0
  168. package/src/theme/surfaceCss.ts +37 -0
  169. package/src/theme/surfacePalettes.ts +120 -79
  170. package/src/theme/surfaceRoleSelectors.ts +135 -71
  171. package/src/theme/themes/dark.ts +44 -43
  172. package/src/theme/themes/light.ts +40 -38
  173. package/src/theme/themes/palette.ts +32 -30
  174. package/src/theme/themes/shared.ts +17 -17
  175. package/src/theme/utils/GlobalStyles.ts +10 -6
  176. package/src/theme/utils/appFrameDocumentStyles.mjs +6 -1
  177. package/src/theme/utils/cssVariables.ts +169 -0
  178. package/src/writing/DraftAssistantDialog.tsx +85 -85
  179. package/src/writing/WritingStyleManager.tsx +6 -3
  180. package/src/writing/draftTextWithAgent.ts +47 -50
  181. package/src/writing/styleStore.ts +4 -3
  182. package/src/writing/writingStyle.test.ts +8 -2
  183. package/src/writing/writingStyle.ts +19 -8
@@ -66,7 +66,10 @@ export function formatAgentToolRowSummary(call: AgentUiToolCall): string {
66
66
  export function agentToolResultDetails(result: string): string[] | undefined {
67
67
  const trimmed = result.trim()
68
68
  if (!trimmed) return undefined
69
- const lines = trimmed.split('\n').map(line => line.trim()).filter(Boolean)
69
+ const lines = trimmed
70
+ .split('\n')
71
+ .map(line => line.trim())
72
+ .filter(Boolean)
70
73
  if (lines.length > 1) return lines
71
74
  if (trimmed.length > 80) return [trimmed]
72
75
  return undefined
@@ -14,6 +14,7 @@ export function AssetLibraryDialog({
14
14
  insertTargetLabel = 'document',
15
15
  inlineAssetScope = 'all',
16
16
  uploadRelativeDir,
17
+ flushDocument,
17
18
  onDocumentSourceSaved,
18
19
  }: AssetLibraryDialogProps): React.ReactElement | null {
19
20
  return (
@@ -36,6 +37,7 @@ export function AssetLibraryDialog({
36
37
  insertTargetLabel={insertTargetLabel}
37
38
  inlineAssetScope={inlineAssetScope}
38
39
  uploadRelativeDir={uploadRelativeDir}
40
+ flushDocument={flushDocument}
39
41
  onDocumentSourceSaved={onDocumentSourceSaved}
40
42
  />
41
43
  </Modal>
@@ -15,6 +15,7 @@ export function AssetLibraryWorkspace({
15
15
  insertTargetLabel = 'document',
16
16
  inlineAssetScope = 'all',
17
17
  uploadRelativeDir,
18
+ flushDocument,
18
19
  onDocumentSourceSaved,
19
20
  fill = false,
20
21
  }: AssetLibraryWorkspaceProps): React.ReactElement {
@@ -28,6 +29,7 @@ export function AssetLibraryWorkspace({
28
29
  insertTargetLabel,
29
30
  uploadRelativeDir,
30
31
  documentAdapter,
32
+ flushDocument,
31
33
  onDocumentSourceSaved,
32
34
  })
33
35
 
@@ -0,0 +1,40 @@
1
+ /**
2
+ * @vitest-environment happy-dom
3
+ */
4
+ import { renderToStaticMarkup } from 'react-dom/server'
5
+ import { afterEach, describe, expect, it, vi } from 'vitest'
6
+ import type { PlatformAssetSummary } from '../../../../bridge/types.js'
7
+ import { AssetLibraryDetailHeader } from './AssetLibraryDetailHeader.js'
8
+
9
+ const asset = {
10
+ id: 'asset_1',
11
+ label: 'Sample',
12
+ kind: 'image',
13
+ fileName: 'sample.png',
14
+ relativePath: 'assets/figures/sample.png',
15
+ absolutePath: '/ws/assets/figures/sample.png',
16
+ byteLength: 49834,
17
+ modifiedAt: Date.now(),
18
+ usedIn: [],
19
+ } as unknown as PlatformAssetSummary
20
+
21
+ afterEach(() => {
22
+ vi.restoreAllMocks()
23
+ })
24
+
25
+ describe('AssetLibraryDetailHeader', () => {
26
+ it('renders the meta/path inline without leaking styled props to the DOM', () => {
27
+ const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => {})
28
+ const html = renderToStaticMarkup(<AssetLibraryDetailHeader asset={asset} />)
29
+
30
+ // The forwardedAs="span" fix means size/tone are consumed by Text, not
31
+ // forwarded to the <span> — so no "unknown prop" warning is emitted.
32
+ const unknownProp = errorSpy.mock.calls
33
+ .map(args => String(args[0]))
34
+ .find(message => /unknown prop|shouldForwardProp/i.test(message))
35
+ expect(unknownProp).toBeUndefined()
36
+
37
+ expect(html).toContain('<span')
38
+ expect(html).toContain('assets/figures/sample.png')
39
+ })
40
+ })
@@ -29,7 +29,11 @@ const StyledAssetTitle = styled.h2`
29
29
 
30
30
  const StyledPath = styled(Text)`
31
31
  overflow-wrap: anywhere;
32
- font-family: var(--platform-typography-font-family-mono, ui-monospace, monospace);
32
+ font-family: var(
33
+ --platform-typography-font-family-mono,
34
+ ui-monospace,
35
+ monospace
36
+ );
33
37
  font-size: var(--platform-typography-font-size-xs, 12px);
34
38
  line-height: 1.35;
35
39
  `
@@ -44,11 +48,11 @@ export function AssetLibraryDetailHeader({
44
48
  return (
45
49
  <StyledDetailHeader>
46
50
  <StyledAssetTitle>{asset.label}</StyledAssetTitle>
47
- <StyledMeta as="span" size="sm" tone="secondary">
51
+ <StyledMeta forwardedAs="span" size="sm" tone="secondary">
48
52
  {kindLabel(asset.kind)} · {formatAssetBytes(asset.byteLength)} ·{' '}
49
53
  {formatAssetTimeAgo(asset.modifiedAt)}
50
54
  </StyledMeta>
51
- <StyledPath as="span" size="sm" tone="secondary">
55
+ <StyledPath forwardedAs="span" size="sm" tone="secondary">
52
56
  {asset.relativePath}
53
57
  </StyledPath>
54
58
  </StyledDetailHeader>
@@ -21,18 +21,18 @@ const StyledDetailBody = styled.div`
21
21
  min-width: 0;
22
22
  overflow: hidden;
23
23
  padding: clamp(18px, 2vw, 32px);
24
- background:
25
- linear-gradient(
26
- 180deg,
27
- rgb(from var(--platform-colors-surface) r g b / 0.48),
28
- var(--platform-colors-app-viewport, var(--platform-colors-bg))
29
- );
24
+ background: linear-gradient(
25
+ 180deg,
26
+ rgb(from var(--platform-colors-surface) r g b / 0.48),
27
+ var(--platform-colors-app-viewport, var(--platform-colors-bg))
28
+ );
30
29
 
31
30
  > * {
32
31
  width: min(100%, 860px);
33
32
  max-height: min(680px, 72vh);
34
- border-radius: 8px;
35
- box-shadow: 0 12px 34px rgb(15 23 42 / 0.08);
33
+ border-radius: var(--platform-radius-md, 0px);
34
+ box-shadow: 0 12px 34px
35
+ color-mix(in srgb, var(--platform-colors-text, #1c1a17) 8%, transparent);
36
36
  }
37
37
  `
38
38
 
@@ -38,6 +38,12 @@ export interface UseAssetLibraryDialogOptions {
38
38
  insertTargetLabel?: string
39
39
  uploadRelativeDir?: string
40
40
  documentAdapter?: CollectionAssetDocumentAdapter
41
+ /**
42
+ * Persist the host document before the library scans it (on open and after
43
+ * insert) so inline document assets (tables/math/mermaid) and `usedIn` reflect
44
+ * the live editor content rather than the last autosave. Optional.
45
+ */
46
+ flushDocument?: () => Promise<void> | void
41
47
  onDocumentSourceSaved?: (info: {
42
48
  sourceDocumentPath?: string
43
49
  relativePath: string
@@ -62,6 +68,7 @@ export function useAssetLibraryDialog({
62
68
  insertTargetLabel = 'document',
63
69
  uploadRelativeDir,
64
70
  documentAdapter,
71
+ flushDocument,
65
72
  onDocumentSourceSaved,
66
73
  }: UseAssetLibraryDialogOptions): UseAssetLibraryDialogResult {
67
74
  const insertAssetHandler = documentAdapter?.insertAsset
@@ -109,7 +116,10 @@ export function useAssetLibraryDialog({
109
116
  setLoading(true)
110
117
  setStatus(null)
111
118
 
112
- void refreshAssets()
119
+ void (async () => {
120
+ await flushDocument?.()
121
+ await refreshAssets()
122
+ })()
113
123
  .catch(error => {
114
124
  if (!cancelled) {
115
125
  setStatus({ tone: 'error', message: messageForError(error) })
@@ -122,7 +132,7 @@ export function useAssetLibraryDialog({
122
132
  return () => {
123
133
  cancelled = true
124
134
  }
125
- }, [open, refreshAssets])
135
+ }, [flushDocument, open, refreshAssets])
126
136
 
127
137
  const filteredAssets = useMemo(() => {
128
138
  const matching = filterCollectionAssets(assets, {
@@ -179,6 +189,7 @@ export function useAssetLibraryDialog({
179
189
  insertTargetLabel,
180
190
  uploadRelativeDir,
181
191
  insertAssetHandler,
192
+ flushDocument,
182
193
  onDocumentSourceSaved,
183
194
  selectedAsset,
184
195
  draft,
@@ -32,6 +32,11 @@ export interface UseAssetLibraryMutationsOptions {
32
32
  insertTargetLabel: string
33
33
  uploadRelativeDir?: string
34
34
  insertAssetHandler?: (asset: PlatformAssetSummary) => Promise<void>
35
+ /**
36
+ * Persist the host document to disk. Called after an insert so the on-disk
37
+ * scan that computes `usedIn` sees the new reference. Optional.
38
+ */
39
+ flushDocument?: () => Promise<void> | void
35
40
  selectedAsset: PlatformAssetSummary | null
36
41
  draft: UseAssetDetailDraftResult
37
42
  refreshAssets: (preferredId?: string | null) => Promise<void>
@@ -49,6 +54,7 @@ export function useAssetLibraryMutations({
49
54
  insertTargetLabel,
50
55
  uploadRelativeDir,
51
56
  insertAssetHandler,
57
+ flushDocument,
52
58
  selectedAsset,
53
59
  draft,
54
60
  refreshAssets,
@@ -221,6 +227,10 @@ export function useAssetLibraryMutations({
221
227
  setStatus(null)
222
228
  try {
223
229
  await insertAssetHandler(asset)
230
+ // Persist so the on-disk usage scan reflects the new reference, then
231
+ // refresh so the "used in this workspace" state updates immediately.
232
+ await flushDocument?.()
233
+ await refreshAssets(asset.id)
224
234
  setStatus({
225
235
  tone: 'success',
226
236
  message: `Inserted ${asset.label} into the current ${insertTargetLabel}.`,
@@ -231,7 +241,14 @@ export function useAssetLibraryMutations({
231
241
  setBusy(null)
232
242
  }
233
243
  },
234
- [canInsertAsset, insertAssetHandler, insertTargetLabel, setStatus],
244
+ [
245
+ canInsertAsset,
246
+ flushDocument,
247
+ insertAssetHandler,
248
+ insertTargetLabel,
249
+ refreshAssets,
250
+ setStatus,
251
+ ],
235
252
  )
236
253
 
237
254
  const insertSelected = useCallback(async (): Promise<void> => {
@@ -1,8 +1,6 @@
1
1
  import { styled } from 'styled-components'
2
2
  import type { PlatformAssetSummary } from '../../../../bridge/types.js'
3
- import {
4
- kindLabel,
5
- } from '../utils/assetLibraryUtils.js'
3
+ import { kindLabel } from '../utils/assetLibraryUtils.js'
6
4
  import { AssetListThumbnail } from './AssetListThumbnail.js'
7
5
 
8
6
  export interface AssetLibraryListRowProps {
@@ -25,7 +23,7 @@ const StyledRow = styled.button<{ $active: boolean }>`
25
23
  $active
26
24
  ? 'var(--platform-colors-border-strong, var(--platform-colors-border))'
27
25
  : 'transparent'};
28
- border-radius: 8px;
26
+ border-radius: var(--platform-radius-sm, 0px);
29
27
  background: ${({ $active }) =>
30
28
  $active
31
29
  ? 'var(--platform-colors-surface-hover)'
@@ -68,7 +66,10 @@ const StyledUsage = styled.span`
68
66
  text-overflow: ellipsis;
69
67
  white-space: nowrap;
70
68
  font-size: var(--platform-typography-font-size-xs);
71
- color: var(--platform-colors-text-tertiary, var(--platform-colors-text-secondary));
69
+ color: var(
70
+ --platform-colors-text-tertiary,
71
+ var(--platform-colors-text-secondary)
72
+ );
72
73
  `
73
74
 
74
75
  export function AssetLibraryListRow({
@@ -90,7 +91,9 @@ export function AssetLibraryListRow({
90
91
  <StyledRowMeta>{kindLabel(asset.kind)}</StyledRowMeta>
91
92
  <StyledUsage>
92
93
  {asset.usedIn.length > 0
93
- ? `${asset.usedIn.length} placement${asset.usedIn.length === 1 ? '' : 's'}`
94
+ ? `${asset.usedIn.length} placement${
95
+ asset.usedIn.length === 1 ? '' : 's'
96
+ }`
94
97
  : `Not placed in this ${insertTargetLabel}`}
95
98
  </StyledUsage>
96
99
  </StyledRowBody>
@@ -58,7 +58,9 @@ export function AssetLibrarySidebarToolbar({
58
58
  <StyledToolbar>
59
59
  <StyledHeading as="h3">{collectionLabel} assets</StyledHeading>
60
60
  {hasDocumentScope ? (
61
- <StyledHint as="p">This document first, then the wider library.</StyledHint>
61
+ <StyledHint as="p">
62
+ This document first, then the wider library.
63
+ </StyledHint>
62
64
  ) : null}
63
65
  <SearchField
64
66
  value={list.query}
@@ -15,7 +15,7 @@ const StyledThumb = styled.div`
15
15
  width: 72px;
16
16
  height: 54px;
17
17
  border: 1px solid rgb(from var(--platform-colors-border) r g b / 0.72);
18
- border-radius: 6px;
18
+ border-radius: var(--platform-radius-sm, 0px);
19
19
  background: var(--platform-colors-app-viewport, var(--platform-colors-bg));
20
20
  overflow: hidden;
21
21
 
@@ -27,6 +27,12 @@ export interface AssetLibraryDialogProps {
27
27
  inlineAssetScope?: 'all' | 'current-document'
28
28
  /** Relative path under `collectionPath` for uploads. Default `assets/figures`. */
29
29
  uploadRelativeDir?: string
30
+ /**
31
+ * Persist the host document to disk before the library scans it (on open and
32
+ * after insert) so inline assets (tables/math/mermaid) and `usedIn` reflect the
33
+ * live editor content, not the last autosave.
34
+ */
35
+ flushDocument?: () => Promise<void> | void
30
36
  /** Called after inline source is saved to disk (mermaid/table/math). */
31
37
  onDocumentSourceSaved?: (info: {
32
38
  sourceDocumentPath?: string
@@ -21,7 +21,7 @@ const capsuleButtonBase = styled(CleanButton)`
21
21
  height: 100%;
22
22
  min-height: 0;
23
23
  border: 0;
24
- border-radius: 999px;
24
+ border-radius: var(--platform-radius-sm);
25
25
  background: transparent;
26
26
  color: var(--platform-colors-text-secondary);
27
27
  font: inherit;
@@ -50,7 +50,7 @@ export const StyledOverlayActionCapsule = styled.div`
50
50
  gap: 0.1875em;
51
51
  padding: 0.1875em;
52
52
  border: 1px solid var(--platform-colors-border);
53
- border-radius: 999px;
53
+ border-radius: var(--platform-radius-sm);
54
54
  background: var(--platform-colors-surface);
55
55
  box-shadow: var(--platform-shadow-sm);
56
56
  width: fit-content;
@@ -40,7 +40,7 @@ const StyledTab = styled.button`
40
40
  padding: 0 12px;
41
41
  border: 1px solid transparent;
42
42
  border-bottom-color: transparent;
43
- border-radius: 11px 11px 0 0;
43
+ border-radius: var(--platform-radius-sm) var(--platform-radius-sm) 0 0;
44
44
  background: transparent;
45
45
  color: var(--platform-colors-text-secondary);
46
46
  font: inherit;
@@ -62,7 +62,8 @@ const StyledTab = styled.button`
62
62
  );
63
63
  color: var(--platform-colors-text);
64
64
  font-weight: 600;
65
- box-shadow: 0 -1px 0 rgb(255 255 255 / 70%);
65
+ box-shadow: 0 -1px 0 rgb(255 255 255 / 70%),
66
+ inset 0 -2px 0 0 var(--app-acc, var(--platform-colors-text));
66
67
  }
67
68
 
68
69
  &:hover:not([aria-selected='true']) {
@@ -21,9 +21,9 @@ const sizeStyles = {
21
21
  const StyledWordmark = styled.span<{
22
22
  $size: keyof typeof sizeStyles
23
23
  }>`
24
- --wm-font: 'Geist', system-ui, sans-serif;
25
- --wm-prefix: #68717c;
26
- --wm-name: #2e3338;
24
+ --wm-font: var(--platform-typography-font-family);
25
+ --wm-prefix: var(--platform-colors-text-tertiary, #8f897f);
26
+ --wm-name: var(--platform-colors-text, #1c1a17);
27
27
 
28
28
  display: inline;
29
29
  min-width: 0;
@@ -36,8 +36,8 @@ const StyledWordmark = styled.span<{
36
36
  white-space: nowrap;
37
37
 
38
38
  html[data-platform-theme='dark'] & {
39
- --wm-prefix: #858c96;
40
- --wm-name: #f4f4f2;
39
+ --wm-prefix: var(--platform-colors-text-tertiary, #8f897f);
40
+ --wm-name: var(--platform-colors-text, #f4f2ee);
41
41
  }
42
42
  `
43
43
 
@@ -47,24 +47,20 @@ const StyledWordmark = styled.span<{
47
47
  */
48
48
  const StyledPrefix = styled.span`
49
49
  color: var(--wm-prefix);
50
- font-weight: 300;
51
- letter-spacing: 0.01em;
50
+ font-weight: 400;
51
+ letter-spacing: 0;
52
52
  `
53
53
 
54
54
  const StyledName = styled.span<{ $accent: boolean }>`
55
55
  min-width: 0;
56
56
  overflow: hidden;
57
57
  color: ${({ $accent }) =>
58
- $accent
59
- ? 'color-mix(in srgb, var(--platform-colors-accent, var(--wm-name)) 54%, var(--wm-name) 46%)'
60
- : 'var(--wm-name)'};
58
+ $accent ? 'var(--app-acc, var(--wm-name))' : 'var(--wm-name)'};
61
59
  text-overflow: ellipsis;
62
60
 
63
61
  html[data-platform-theme='dark'] & {
64
62
  color: ${({ $accent }) =>
65
- $accent
66
- ? 'color-mix(in srgb, var(--platform-colors-accent, var(--wm-name)) 46%, var(--platform-colors-text, #f4f4f2) 54%)'
67
- : 'var(--wm-name)'};
63
+ $accent ? 'var(--app-acc, var(--wm-name))' : 'var(--wm-name)'};
68
64
  }
69
65
  `
70
66
 
@@ -87,8 +83,12 @@ export function PureAppWordmark({
87
83
 
88
84
  return (
89
85
  <StyledWordmark {...wordmarkProps} $size={size}>
90
- <StyledPrefix>{normalizedPrefix}</StyledPrefix>{' '}
91
- <StyledName $accent={accentName}>{normalizedName}</StyledName>
86
+ <StyledPrefix className="pure-wordmark__prefix">
87
+ {normalizedPrefix}
88
+ </StyledPrefix>
89
+ <StyledName className="pure-wordmark__name" $accent={accentName}>
90
+ {normalizedName}
91
+ </StyledName>
92
92
  </StyledWordmark>
93
93
  )
94
94
  }
@@ -12,8 +12,12 @@ const StyledStrip = styled.div`
12
12
  min-height: 36px;
13
13
  width: 100%;
14
14
  min-width: 0;
15
- background: var(--platform-colors-chrome-tab-strip);
16
- border-bottom: 1px solid var(--platform-colors-border);
15
+ background: var(
16
+ --pure-chrome-os-strip,
17
+ #ececef
18
+ );
19
+ border-bottom: 1px solid
20
+ var(--pure-chrome-hairline, var(--platform-colors-divider));
17
21
  -webkit-app-region: no-drag;
18
22
  user-select: none;
19
23
  `
@@ -48,7 +52,7 @@ const StyledTab = styled.button<{
48
52
  border-radius: 11px 11px 0 0;
49
53
  font-size: var(--platform-typography-font-size-xs, 13px);
50
54
  font-weight: 500;
51
- color: var(--platform-colors-text-secondary);
55
+ color: var(--pure-chrome-tertiary, #8a857c);
52
56
  background: transparent;
53
57
  cursor: pointer;
54
58
  flex-shrink: 0;
@@ -56,17 +60,17 @@ const StyledTab = styled.button<{
56
60
  box-shadow 150ms ease;
57
61
 
58
62
  &[data-active='true'] {
59
- border-color: var(--platform-colors-border);
63
+ border-color: var(--pure-chrome-hairline, var(--platform-colors-border));
60
64
  border-bottom-color: var(--platform-colors-surface);
61
65
  background: var(--platform-colors-surface);
62
- color: var(--platform-colors-text);
63
- box-shadow: 0 -1px 0 rgb(255 255 255 / 70%);
66
+ color: var(--pure-chrome-ink, var(--platform-colors-text));
67
+ box-shadow: inset 0 -2px 0 0 var(--app-acc, var(--platform-colors-text));
64
68
  }
65
69
 
66
70
  &:hover:not([data-active='true']) {
67
- border-color: var(--platform-colors-border-soft);
71
+ border-color: var(--pure-chrome-hairline, var(--platform-colors-divider));
68
72
  background: var(--platform-colors-surface-hover);
69
- color: var(--platform-colors-text);
73
+ color: var(--pure-chrome-ink, var(--platform-colors-text));
70
74
  }
71
75
 
72
76
  &:focus-visible {
@@ -128,12 +132,12 @@ const StyledTabWordmark = styled(PureAppWordmark)`
128
132
  * The canonical lockup at tab scale: "pure word", lowercase, one size.
129
133
  * The prefix is the quiet family constant — light weight, muted grey,
130
134
  * never resized (a size jump breaks the lockup's line). The app word
131
- * carries identity: medium weight, accent only when active.
135
+ * carries identity by weight. R37 reserves accent for the active tab marker.
132
136
  */
133
137
  const StyledWordPrefix = styled.span`
134
138
  font-weight: 300;
135
139
  text-transform: lowercase;
136
- color: var(--platform-colors-text-secondary);
140
+ color: var(--pure-chrome-tertiary, #8a857c);
137
141
  letter-spacing: 0.01em;
138
142
 
139
143
  [data-platform-theme='dark'] & {
@@ -145,12 +149,12 @@ const StyledWord = styled.span<{ $active: boolean }>`
145
149
  font-weight: ${({ $active }) => ($active ? 600 : 500)};
146
150
  text-transform: lowercase;
147
151
  color: ${({ $active }) =>
148
- $active ? 'var(--platform-colors-accent)' : 'inherit'};
152
+ $active ? 'var(--app-acc, var(--pure-chrome-ink, var(--platform-colors-text)))' : 'inherit'};
149
153
 
150
154
  [data-platform-theme='dark'] & {
151
155
  color: ${({ $active }) =>
152
156
  $active
153
- ? 'color-mix(in srgb, var(--platform-colors-accent) 46%, var(--platform-colors-text) 54%)'
157
+ ? 'var(--app-acc, var(--pure-chrome-ink, var(--platform-colors-text)))'
154
158
  : 'inherit'};
155
159
  }
156
160
  `
@@ -162,7 +166,7 @@ const StyledDocName = styled.span`
162
166
  text-overflow: ellipsis;
163
167
  white-space: nowrap;
164
168
  font-weight: 400;
165
- color: var(--platform-colors-text-secondary);
169
+ color: var(--pure-chrome-tertiary, #8a857c);
166
170
  `
167
171
 
168
172
  const StyledClose = styled(X)`
@@ -232,6 +236,7 @@ export function WorkspaceTabStrip({
232
236
  tabIndex={active ? 0 : -1}
233
237
  aria-selected={active}
234
238
  data-active={active}
239
+ data-app={tab.appSlug}
235
240
  title={tab.label}
236
241
  draggable={Boolean(onReorder)}
237
242
  $dropBefore={dropTarget?.tabId === tab.id && !dropTarget.after}
@@ -289,7 +294,7 @@ export function WorkspaceTabStrip({
289
294
  <StyledLabel>
290
295
  {tab.brand ? (
291
296
  <>
292
- <StyledWordPrefix>pure</StyledWordPrefix>{' '}
297
+ <StyledWordPrefix>pure</StyledWordPrefix>
293
298
  <StyledWord $active={active}>
294
299
  {tab.appName ?? tab.label.replace(/^pure\s*/i, '')}
295
300
  </StyledWord>
@@ -0,0 +1,34 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { shouldUseTitleOnlyWorkspaceTab } from './documentChromePolicy.js'
3
+
4
+ describe('shouldUseTitleOnlyWorkspaceTab', () => {
5
+ it('uses title-only shell tabs for writing document resources', () => {
6
+ expect(
7
+ shouldUseTitleOnlyWorkspaceTab({
8
+ appKind: 'writer',
9
+ hasResource: true,
10
+ }),
11
+ ).toBe(true)
12
+ expect(
13
+ shouldUseTitleOnlyWorkspaceTab({
14
+ appKind: 'book',
15
+ hasResource: true,
16
+ }),
17
+ ).toBe(true)
18
+ })
19
+
20
+ it('keeps app identity in shell tabs outside document resources', () => {
21
+ expect(
22
+ shouldUseTitleOnlyWorkspaceTab({
23
+ appKind: 'writer',
24
+ hasResource: false,
25
+ }),
26
+ ).toBe(false)
27
+ expect(
28
+ shouldUseTitleOnlyWorkspaceTab({
29
+ appKind: 'tasks',
30
+ hasResource: true,
31
+ }),
32
+ ).toBe(false)
33
+ })
34
+ })
@@ -0,0 +1,15 @@
1
+ export interface WorkspaceTabDocumentChromeInput {
2
+ appKind?: string | null
3
+ hasResource?: boolean
4
+ }
5
+
6
+ const IN_APP_DOCUMENT_WORDMARK_KINDS = new Set(['book', 'writer'])
7
+
8
+ export function shouldUseTitleOnlyWorkspaceTab({
9
+ appKind,
10
+ hasResource,
11
+ }: WorkspaceTabDocumentChromeInput): boolean {
12
+ return Boolean(
13
+ hasResource && appKind && IN_APP_DOCUMENT_WORDMARK_KINDS.has(appKind),
14
+ )
15
+ }
@@ -29,3 +29,7 @@ export {
29
29
  PureAppWordmark,
30
30
  type PureAppWordmarkProps,
31
31
  } from './PureAppWordmark.js'
32
+ export {
33
+ shouldUseTitleOnlyWorkspaceTab,
34
+ type WorkspaceTabDocumentChromeInput,
35
+ } from './documentChromePolicy.js'
@@ -2,6 +2,8 @@
2
2
  export interface WorkspaceTabItem {
3
3
  id: string
4
4
  label: string
5
+ /** App slug used for app identity chrome tokens. */
6
+ appSlug?: string
5
7
  /** Optional short app name shown before the label (e.g. "Writer"). */
6
8
  kindLabel?: string
7
9
  /** Product name rendered by the shared chrome wordmark (e.g. "files"). */