@puredesktop/puredesktop-ui-bridge 2.1.3 → 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.
- package/package.json +1132 -45
- package/src/agents/runtime/mapAgentSnapshot.test.ts +1 -3
- package/src/agents/runtime/mapAgentSnapshot.ts +8 -4
- package/src/agents/runtime/mergeUsageTurn.ts +1 -2
- package/src/agents/runtime/pendingToolCalls.test.ts +1 -3
- package/src/agents/runtime/unresolvedToolCalls.ts +1 -2
- package/src/bridge/agentModelTiers.ts +9 -4
- package/src/bridge/agents.mjs +3 -1
- package/src/bridge/calendarDraftIntent.ts +12 -4
- package/src/bridge/calendarInviteIntent.ts +18 -5
- package/src/bridge/client.mjs +45 -45
- package/src/bridge/dialog.d.mts +8 -8
- package/src/bridge/dialog.mjs +40 -40
- package/src/bridge/dialog.test.ts +73 -73
- package/src/bridge/fs.d.mts +41 -41
- package/src/bridge/fs.mjs +56 -56
- package/src/bridge/fs.test.ts +83 -83
- package/src/bridge/fs.ts +28 -28
- package/src/bridge/googleAuth.ts +3 -1
- package/src/bridge/methods.d.mts +6 -0
- package/src/bridge/methods.mjs +7 -0
- package/src/bridge/network.d.mts +17 -17
- package/src/bridge/network.mjs +22 -22
- package/src/bridge/pureRender/base.css +324 -76
- package/src/bridge/pureRender/compiler.ts +1 -1
- package/src/bridge/pureRender/document.test.ts +2 -2
- package/src/bridge/pureRender/extraction.test.ts +24 -12
- package/src/bridge/pureRender/extraction.ts +10 -7
- package/src/bridge/pureRender/flow.css +17 -3
- package/src/bridge/pureRender/normalize.ts +28 -26
- package/src/bridge/pureRender/presets.ts +7 -7
- package/src/bridge/pureRender/profiles/book.css +122 -19
- package/src/bridge/pureRender/profiles/manuscript.css +57 -11
- package/src/bridge/pureRender/profiles/writer.css +48 -10
- package/src/bridge/pureRender/reset.css +20 -5
- package/src/bridge/pureRender/theme.ts +37 -9
- package/src/bridge/pureRender/tokens/default.css +45 -33
- package/src/bridge/react/usePlatformAgentRunLiveState.tsx +4 -1
- package/src/bridge/react/usePlatformAgentSessionDrive.tsx +13 -4
- package/src/bridge/screenAnnotation.test.ts +4 -1
- package/src/bridge/screenAnnotation.ts +6 -2
- package/src/bridge/storage.d.mts +27 -27
- package/src/bridge/storage.mjs +18 -18
- package/src/bridge/storage.test.ts +49 -49
- package/src/bridge/types.ts +62 -58
- package/src/bridge/window.d.mts +3 -1
- package/src/bridge/workspace.d.mts +7 -0
- package/src/bridge/workspace.mjs +8 -0
- package/src/bridge/workspace.ts +1 -0
- package/src/commitments/commitments.test.ts +10 -3
- package/src/commitments/commitments.ts +18 -4
- package/src/components/accessibility/AccessibilityReviewDialog.tsx +7 -2
- package/src/components/accessibility/accessibilityReview.test.ts +11 -4
- package/src/components/accessibility/accessibilityReview.ts +18 -10
- package/src/components/agent-feedback/ScreenAnnotationAgentDialog.tsx +9 -2
- package/src/components/agents/AgentContextUsageBar.tsx +5 -3
- package/src/components/agents/AgentDrawerPanel.test.tsx +15 -3
- package/src/components/agents/AgentMessageBubble.tsx +1 -3
- package/src/components/agents/AgentQuestionPromptList.tsx +3 -3
- package/src/components/agents/AgentToolApprovalActions.tsx +1 -3
- package/src/components/agents/agentPanelStyles.ts +1 -1
- package/src/components/agents/agentToolDisplay.ts +4 -1
- package/src/components/assets/asset-library/AssetLibraryDialog.tsx +2 -0
- package/src/components/assets/asset-library/AssetLibraryWorkspace.tsx +2 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailHeader.test.tsx +40 -0
- package/src/components/assets/asset-library/detail/AssetLibraryDetailHeader.tsx +7 -3
- package/src/components/assets/asset-library/detail/AssetLibraryDetailPreview.tsx +8 -8
- package/src/components/assets/asset-library/hooks/useAssetLibraryDialog.ts +13 -2
- package/src/components/assets/asset-library/hooks/useAssetLibraryMutations.ts +18 -1
- package/src/components/assets/asset-library/sidebar/AssetLibraryListRow.tsx +9 -6
- package/src/components/assets/asset-library/sidebar/AssetLibrarySidebarToolbar.tsx +3 -1
- package/src/components/assets/asset-library/sidebar/AssetListThumbnail.tsx +1 -1
- package/src/components/assets/asset-library/types.ts +6 -0
- package/src/components/chrome/OverlayActionCapsule.tsx +2 -2
- package/src/components/chrome/PanelTabStrip.tsx +3 -2
- package/src/components/chrome/PureAppWordmark.tsx +15 -15
- package/src/components/chrome/WorkspaceTabStrip.tsx +19 -14
- package/src/components/chrome/documentChromePolicy.test.ts +34 -0
- package/src/components/chrome/documentChromePolicy.ts +15 -0
- package/src/components/chrome/index.ts +4 -0
- package/src/components/chrome/workspaceTabTypes.ts +2 -0
- package/src/components/common/buttons/Button.tsx +60 -10
- package/src/components/common/buttons/ChoiceChip.tsx +2 -2
- package/src/components/common/buttons/IconButton.tsx +26 -0
- package/src/components/common/buttons/SegmentedControl.tsx +23 -7
- package/src/components/common/canvas/CanvasElementFrame.tsx +37 -28
- package/src/components/common/canvas/canvasGeometry.test.ts +4 -5
- package/src/components/common/canvas/canvasGeometry.ts +1 -4
- package/src/components/common/canvas/useCanvasDragResize.test.tsx +10 -10
- package/src/components/common/canvas/useCanvasDragResize.ts +1 -4
- package/src/components/common/cards/OpenReviewCard.tsx +6 -6
- package/src/components/common/chat/ChatBox.test.tsx +9 -1
- package/src/components/common/chat/ChatBox.tsx +3 -4
- package/src/components/common/chat/ChatMarkdown.tsx +1 -1
- package/src/components/common/chat/ChatModelMenu.tsx +1 -1
- package/src/components/common/chat/ChatThinkingIndicator.tsx +1 -1
- package/src/components/common/chat/ChatThread.tsx +2 -1
- package/src/components/common/chat/ChatToolRow.tsx +2 -2
- package/src/components/common/chat/ChatUserBubble.tsx +1 -1
- package/src/components/common/connections/ConnectionSetup.tsx +6 -6
- package/src/components/common/containers/AppFrame.test.tsx +37 -36
- package/src/components/common/containers/AppFrame.tsx +15 -0
- package/src/components/common/containers/AppHeader.tsx +6 -3
- package/src/components/common/containers/SurfacePanel.tsx +1 -0
- package/src/components/common/containers/useOpacityPresence.ts +10 -15
- package/src/components/common/context/ContextSelector.tsx +10 -10
- package/src/components/common/desk/CreationDesk.tsx +23 -11
- package/src/components/common/desk/DailyBrief.tsx +18 -7
- package/src/components/common/desk/DeskCard.tsx +11 -6
- package/src/components/common/dropdown/MenuButtonDropdown.tsx +58 -6
- package/src/components/common/dropdown/MenuDropdownItem.tsx +23 -3
- package/src/components/common/dropdown/MenuPanel.tsx +8 -8
- package/src/components/common/dropdown/SelectMenu.tsx +8 -3
- package/src/components/common/dropdown/menuTypes.ts +2 -1
- package/src/components/common/evidence/StructuredEvidenceView.tsx +9 -5
- package/src/components/common/feedback/Badge.tsx +19 -5
- package/src/components/common/feedback/CapabilityPills.tsx +1 -1
- package/src/components/common/feedback/InfoHint.tsx +2 -2
- package/src/components/common/feedback/PlatformChip.tsx +9 -9
- package/src/components/common/feedback/QuietPill.tsx +5 -8
- package/src/components/common/feedback/WorkProgressPanel.tsx +14 -14
- package/src/components/common/files/FileTable.tsx +8 -2
- package/src/components/common/inputs/Checkbox.tsx +1 -1
- package/src/components/common/inputs/EditableSecretField.tsx +1 -3
- package/src/components/common/inputs/InlineComposer.tsx +2 -2
- package/src/components/common/inputs/PaletteDropdown.tsx +33 -23
- package/src/components/common/inputs/SurfacePalettePicker.tsx +1 -1
- package/src/components/common/inputs/ToggleSwitch.tsx +2 -2
- package/src/components/common/inputs/WorkspacePicker.tsx +3 -3
- package/src/components/common/lists/listRowStyles.ts +4 -2
- package/src/components/common/lists/quiet-ledger/index.ts +1 -4
- package/src/components/common/lists/reorderable/DragHandle.tsx +1 -1
- package/src/components/common/lists/reorderable/ReorderableSections.tsx +2 -1
- package/src/components/common/media/ImageReferencePicker.tsx +22 -14
- package/src/components/common/narrative/NarrativeMention.tsx +15 -9
- package/src/components/common/navigation/NavigationCardRow.tsx +2 -2
- package/src/components/common/navigation/UnderlineTabs.tsx +1 -0
- package/src/components/common/overlays/Modal.tsx +52 -3
- package/src/components/common/overlays/ObjectActionStrip.tsx +2 -2
- package/src/components/common/people/AutoUpdateReviewList.tsx +2 -2
- package/src/components/common/people/EntityList.tsx +7 -3
- package/src/components/common/people/EntityPortrait.tsx +6 -1
- package/src/components/common/people/LinkedDossierList.tsx +3 -1
- package/src/components/common/people/RelationshipMap.tsx +3 -9
- package/src/components/common/research/EvidenceDossier.tsx +24 -20
- package/src/components/common/tiles/CatalogCard.tsx +1 -1
- package/src/components/credentials/CredentialsPanel.tsx +2 -6
- package/src/components/data/DataChart.tsx +119 -94
- package/src/components/data/dataTable.ts +4 -1
- package/src/components/editor/DocumentAppChrome.tsx +172 -0
- package/src/components/editor/EditorCommentPromptDialog.tsx +1 -1
- package/src/components/editor/EditorLinkPromptDialog.tsx +1 -1
- package/src/components/print-design/PrintDesignPanel.test.tsx +8 -6
- package/src/components/print-design/PrintDesignPanel.tsx +18 -25
- package/src/components/proofing/PagedProofDialog.tsx +17 -4
- package/src/editor/EditorViewModeToggle.tsx +6 -2
- package/src/editor/index.ts +6 -0
- package/src/theme/ThemeProvider.tsx +5 -2
- package/src/theme/appAccents.test.ts +51 -0
- package/src/theme/appAccents.ts +151 -88
- package/src/theme/appIdentityCss.mjs +256 -0
- package/src/theme/appIdentityCss.ts +256 -0
- package/src/theme/composePlatformTheme.test.ts +43 -14
- package/src/theme/platformFont.d.mts +2 -2
- package/src/theme/platformFont.mjs +4 -3
- package/src/theme/reportsShellChromeSelectors.ts +14 -132
- package/src/theme/surfaceCss.mjs +37 -0
- package/src/theme/surfaceCss.ts +37 -0
- package/src/theme/surfacePalettes.ts +120 -79
- package/src/theme/surfaceRoleSelectors.ts +135 -71
- package/src/theme/themes/dark.ts +44 -43
- package/src/theme/themes/light.ts +40 -38
- package/src/theme/themes/palette.ts +32 -30
- package/src/theme/themes/shared.ts +17 -17
- package/src/theme/utils/GlobalStyles.ts +10 -6
- package/src/theme/utils/appFrameDocumentStyles.mjs +6 -1
- package/src/theme/utils/cssVariables.ts +169 -0
- package/src/writing/DraftAssistantDialog.tsx +85 -85
- package/src/writing/WritingStyleManager.tsx +6 -3
- package/src/writing/draftTextWithAgent.ts +47 -50
- package/src/writing/styleStore.ts +4 -3
- package/src/writing/writingStyle.test.ts +8 -2
- 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
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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:
|
|
35
|
-
box-shadow: 0 12px 34px
|
|
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
|
|
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
|
-
[
|
|
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:
|
|
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(
|
|
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${
|
|
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">
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
25
|
-
--wm-prefix: #
|
|
26
|
-
--wm-name: #
|
|
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: #
|
|
40
|
-
--wm-name: #
|
|
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:
|
|
51
|
-
letter-spacing: 0
|
|
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>
|
|
91
|
-
|
|
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(
|
|
16
|
-
|
|
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(--
|
|
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 -
|
|
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-
|
|
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
|
|
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(--
|
|
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-
|
|
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
|
-
? '
|
|
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(--
|
|
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
|
+
}
|
|
@@ -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"). */
|