@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.
- package/package.json +89 -5
- package/src/agents/mcpServerPresets.test.ts +105 -0
- package/src/agents/mcpServerPresets.ts +380 -0
- package/src/agents/runtime/index.ts +7 -0
- package/src/agents/runtime/mapAgentSnapshot.ts +11 -8
- package/src/agents/runtime/pendingAsks.test.ts +117 -0
- package/src/agents/runtime/pendingAsks.ts +110 -0
- package/src/bridge/agentScopeDefaults.ts +65 -0
- package/src/bridge/agentTypes.ts +57 -3
- package/src/bridge/agents.d.mts +6 -0
- package/src/bridge/agents.mjs +10 -0
- package/src/bridge/agents.ts +4 -0
- package/src/bridge/collectionImagePaste.ts +19 -1
- package/src/bridge/context.mjs +32 -0
- package/src/bridge/credentials.d.mts +62 -0
- package/src/bridge/credentials.mjs +53 -0
- package/src/bridge/documents.d.mts +76 -0
- package/src/bridge/documents.mjs +40 -0
- package/src/bridge/events.d.mts +1 -0
- package/src/bridge/events.mjs +1 -0
- package/src/bridge/googleAuth.ts +7 -152
- package/src/bridge/mcpClient.d.mts +74 -0
- package/src/bridge/mcpClient.mjs +79 -0
- package/src/bridge/methods.d.mts +45 -0
- package/src/bridge/methods.mjs +46 -0
- package/src/bridge/notifications.d.mts +12 -0
- package/src/bridge/notifications.mjs +36 -0
- package/src/bridge/operations.d.mts +44 -0
- package/src/bridge/operations.mjs +40 -0
- package/src/bridge/pureRender/base.css +44 -0
- package/src/bridge/pureRender/document.ts +29 -2
- package/src/bridge/pureRender/index.ts +3 -0
- package/src/bridge/pureRender/normalize.ts +167 -0
- package/src/bridge/pureRender/sanitizeExportBody.test.ts +51 -0
- package/src/bridge/pureRender/sanitizeExportBody.ts +61 -0
- package/src/bridge/pureRender/styles.ts +125 -5
- package/src/bridge/pureRender/theme.test.ts +259 -0
- package/src/bridge/pureRender/theme.ts +19 -1
- package/src/bridge/pureRender/types.ts +33 -0
- package/src/bridge/pureRender/visualLayoutInspection.test.ts +26 -0
- package/src/bridge/pureRender/visualLayoutInspection.ts +75 -0
- package/src/bridge/react/useDocumentHotkeys.ts +41 -0
- package/src/bridge/react/useDocumentLifecycle.tsx +359 -0
- package/src/bridge/react/usePlatformAgentSessionDrive.test.tsx +29 -10
- package/src/bridge/react/usePlatformAgentSessionDrive.tsx +10 -81
- package/src/bridge/react/usePlatformAppSettings.test.tsx +105 -0
- package/src/bridge/react/usePlatformAppSettings.tsx +104 -0
- package/src/bridge/react/usePlatformJsonStore.test.tsx +152 -0
- package/src/bridge/react/usePlatformJsonStore.tsx +149 -0
- package/src/bridge/react/usePlatformOperations.tsx +75 -0
- package/src/bridge/storage.d.mts +23 -10
- package/src/bridge/storage.mjs +6 -3
- package/src/bridge/storage.test.ts +6 -6
- package/src/bridge/types.ts +7 -0
- package/src/components/agents/AgentComposer.tsx +10 -6
- package/src/components/agents/AgentDrawerPanel.test.tsx +2 -205
- package/src/components/agents/AgentDrawerPanel.tsx +23 -62
- package/src/components/agents/AgentMessageBubble.tsx +59 -33
- package/src/components/agents/AgentMessageList.tsx +4 -4
- package/src/components/agents/AgentQuestionPromptList.tsx +34 -212
- package/src/components/agents/AgentToolApprovalActions.tsx +13 -11
- package/src/components/agents/AgentToolCallCard.tsx +20 -20
- package/src/components/agents/AgentToolPendingList.tsx +6 -6
- package/src/components/agents/AgentToolPendingReview.tsx +6 -6
- package/src/components/agents/ContextPicker.tsx +239 -0
- package/src/components/agents/QuestionRequestList.tsx +278 -0
- package/src/components/agents/agentPanelStyles.ts +8 -31
- package/src/components/agents/agentTypes.ts +25 -41
- package/src/components/agents/index.ts +4 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryList.tsx +10 -4
- package/src/components/chrome/WorkspaceTabStrip.tsx +22 -4
- package/src/components/chrome/workspaceTabTypes.ts +2 -0
- package/src/components/common/chat/ChatBox.tsx +9 -9
- package/src/components/common/chat/ChatModelMenu.tsx +34 -4
- package/src/components/common/chat/ChatThread.tsx +23 -7
- package/src/components/common/connections/ProviderConnection.test.tsx +208 -0
- package/src/components/common/connections/ProviderConnection.tsx +277 -0
- package/src/components/common/connections/index.ts +5 -0
- package/src/components/common/containers/AppFrame.test.tsx +4 -4
- package/src/components/common/containers/AppFrame.tsx +26 -10
- package/src/components/common/containers/AppHeader.tsx +37 -15
- package/src/components/common/documents/DocumentHeaderActions.tsx +206 -0
- package/src/components/common/documents/DocumentSwitcher.tsx +1181 -0
- package/src/components/common/documents/index.ts +8 -0
- package/src/components/common/dropdown/MenuDropdownItem.tsx +14 -2
- package/src/components/common/dropdown/MenuPortal.tsx +3 -1
- package/src/components/common/dropdown/menuViewportPosition.test.ts +20 -10
- package/src/components/common/dropdown/menuViewportPosition.ts +35 -27
- package/src/components/common/overlays/Modal.tsx +13 -2
- package/src/components/common/research/EvidenceDossier.tsx +1232 -150
- package/src/components/common/research/index.ts +2 -0
- package/src/components/editor/EditorLinkPromptDialog.tsx +1 -1
- package/src/components/print-design/PrintDesignPanel.test.tsx +430 -40
- package/src/components/print-design/PrintDesignPanel.tsx +1770 -207
- package/src/components/print-design/index.ts +2 -0
- package/src/components/print-design/previewContent.test.ts +32 -0
- package/src/components/print-design/previewContent.ts +31 -0
- package/src/context/buildContextDocument.test.ts +240 -0
- package/src/context/buildContextDocument.ts +309 -0
- package/src/context/contextAccess.ts +66 -0
- package/src/context/contextConfig.ts +72 -0
- package/src/context/contextDocument.test.ts +155 -0
- package/src/context/contextDocument.ts +300 -0
- package/src/context/contextSections.test.ts +88 -0
- package/src/context/contextSections.ts +84 -0
- package/src/context/htmlToContextMarkdown.test.ts +134 -0
- package/src/context/htmlToContextMarkdown.ts +369 -0
- package/src/ics/generateIcs.test.ts +153 -0
- package/src/ics/generateIcs.ts +197 -0
- package/src/index.ts +3 -0
- package/src/net/httpRetry.test.ts +117 -0
- package/src/net/httpRetry.ts +111 -0
- package/src/theme/appAccents.test.ts +36 -35
- package/src/theme/appAccents.ts +93 -104
- package/src/theme/appIdentityCss.mjs +178 -229
- package/src/theme/appIdentityCss.ts +222 -231
- package/src/theme/themes/dark.ts +3 -1
- package/src/theme/themes/light.ts +3 -1
- package/src/bridge/googleProviderConfig.ts +0 -73
|
@@ -228,13 +228,13 @@ describe('PrintDesignPanel', () => {
|
|
|
228
228
|
|
|
229
229
|
expect(container.textContent).toContain('Sample page snapshot')
|
|
230
230
|
expect(container.textContent).toContain('Visual preflight sample')
|
|
231
|
-
expect(container.textContent).toContain('No export inspection has run yet.')
|
|
232
|
-
expect(container.textContent).toContain(
|
|
233
|
-
'The next beta export will render with Paged.js and capture a real page snapshot.',
|
|
234
|
-
)
|
|
235
231
|
expect(
|
|
236
232
|
container.querySelector('[aria-label="Sample PDF page snapshot"]'),
|
|
237
233
|
).not.toBeNull()
|
|
234
|
+
// No run has finished — the rail (final run report) stays hidden.
|
|
235
|
+
expect(
|
|
236
|
+
container.querySelector('[aria-label="Live design readout"]'),
|
|
237
|
+
).toBeNull()
|
|
238
238
|
})
|
|
239
239
|
|
|
240
240
|
it('shows a provided live beta vision snapshot instead of the sample page', () => {
|
|
@@ -261,7 +261,6 @@ describe('PrintDesignPanel', () => {
|
|
|
261
261
|
expect(
|
|
262
262
|
container.querySelector('[aria-label="Sample PDF page snapshot"]'),
|
|
263
263
|
).toBeNull()
|
|
264
|
-
expect(container.textContent).toContain('Rendered page 2 inspected.')
|
|
265
264
|
})
|
|
266
265
|
|
|
267
266
|
it('shows multiple beta vision evidence snapshots with annotations', () => {
|
|
@@ -434,29 +433,38 @@ describe('PrintDesignPanel', () => {
|
|
|
434
433
|
/>,
|
|
435
434
|
)
|
|
436
435
|
|
|
437
|
-
|
|
438
|
-
|
|
436
|
+
// ONE merged Typesetter view: no Design/vision tabs, controls at left,
|
|
437
|
+
// vision canvas as the well — all visible at once.
|
|
439
438
|
expect(
|
|
440
439
|
container.querySelector('[data-panel-variant="workspace"]'),
|
|
441
440
|
).not.toBeNull()
|
|
441
|
+
expect(
|
|
442
|
+
Array.from(container.querySelectorAll('button')).some(button =>
|
|
443
|
+
button.textContent?.includes('Beta vision typesetting'),
|
|
444
|
+
),
|
|
445
|
+
).toBe(false)
|
|
442
446
|
expect(container.textContent).toContain('Design - PDF Typesetter')
|
|
443
|
-
expect(container.textContent).toContain('Clean · Justified ·
|
|
447
|
+
expect(container.textContent).toContain('Clean · Justified · 38rem')
|
|
444
448
|
expect(container.textContent).toContain('In flight')
|
|
445
449
|
expect(container.textContent).toContain('Pass 2 · checking Fix 1')
|
|
446
450
|
expect(container.textContent).toContain('Beta settings')
|
|
447
451
|
expect(container.textContent).toContain('Use beta vision typesetting')
|
|
448
452
|
expect(container.textContent).toContain('Max fixes per issue')
|
|
449
453
|
expect(container.textContent).toContain('Baseline page 2')
|
|
450
|
-
expect(container.textContent).toContain('
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
+
expect(container.textContent).toContain('Presets')
|
|
455
|
+
// The right rail carries the run summary / Activity ledger.
|
|
456
|
+
const readout = container.querySelector(
|
|
457
|
+
'[aria-label="Live design readout"]',
|
|
458
|
+
)
|
|
459
|
+
expect(readout).not.toBeNull()
|
|
460
|
+
expect(readout?.textContent).toContain('Activity')
|
|
461
|
+
expect(readout?.textContent).toContain('Issue 1: stranded heading')
|
|
454
462
|
expect(document.head.textContent).not.toMatch(
|
|
455
463
|
/grid-template-columns:\s*minmax\(190px,\s*240px\)\s*minmax\(0,\s*1fr\)/,
|
|
456
464
|
)
|
|
457
465
|
})
|
|
458
466
|
|
|
459
|
-
it('
|
|
467
|
+
it('shows the run ledger in the controls-column Activity section', () => {
|
|
460
468
|
const { container } = render(
|
|
461
469
|
<PrintDesignPanel
|
|
462
470
|
variant="workspace"
|
|
@@ -491,18 +499,12 @@ describe('PrintDesignPanel', () => {
|
|
|
491
499
|
/>,
|
|
492
500
|
)
|
|
493
501
|
|
|
494
|
-
|
|
495
|
-
|
|
502
|
+
// Evidence in the well and the ledger in the left column Activity —
|
|
503
|
+
// simultaneously, no tab switching.
|
|
496
504
|
expect(container.textContent).toContain('Baseline page 2')
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
const readout = container.querySelector(
|
|
501
|
-
'[aria-label="Live design readout"]',
|
|
502
|
-
)
|
|
503
|
-
expect(readout?.textContent).toContain('Issue 1: stranded heading')
|
|
504
|
-
expect(readout?.textContent).toContain('baseline.pdf')
|
|
505
|
-
expect(container.textContent).not.toContain('Baseline page 2')
|
|
505
|
+
expect(container.textContent).toContain('Issue 1: stranded heading')
|
|
506
|
+
expect(container.textContent).toContain('baseline.pdf')
|
|
507
|
+
expect(container.textContent).toContain('Activity')
|
|
506
508
|
})
|
|
507
509
|
|
|
508
510
|
it('groups workspace readout entries by visual pass', () => {
|
|
@@ -521,21 +523,18 @@ describe('PrintDesignPanel', () => {
|
|
|
521
523
|
/>,
|
|
522
524
|
)
|
|
523
525
|
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
const readout = container.querySelector(
|
|
527
|
-
'[aria-label="Live design readout"]',
|
|
528
|
-
)
|
|
526
|
+
// Merged view: the grouped ledger renders in the controls-column
|
|
527
|
+
// Activity section — no tabs, no readout aside.
|
|
529
528
|
expect(
|
|
530
|
-
|
|
529
|
+
container.querySelector('[aria-label="Pass 2 event group"]'),
|
|
531
530
|
).not.toBeNull()
|
|
532
531
|
expect(
|
|
533
|
-
|
|
532
|
+
container.querySelector('[aria-label="Pass 1 event group"]'),
|
|
534
533
|
).not.toBeNull()
|
|
535
|
-
expect(
|
|
536
|
-
expect(
|
|
537
|
-
expect(
|
|
538
|
-
expect(
|
|
534
|
+
expect(container.textContent).toContain('CHECK')
|
|
535
|
+
expect(container.textContent).toContain('FIX')
|
|
536
|
+
expect(container.textContent).toContain('ISSUE')
|
|
537
|
+
expect(container.textContent).toContain('baseline.pdf')
|
|
539
538
|
})
|
|
540
539
|
|
|
541
540
|
it('opens passed fix comparisons from evidence only when available', () => {
|
|
@@ -618,7 +617,6 @@ describe('PrintDesignPanel', () => {
|
|
|
618
617
|
|
|
619
618
|
click(buttonByText(container, 'Beta vision typesetting'))
|
|
620
619
|
|
|
621
|
-
expect(container.textContent).toContain('FIX')
|
|
622
620
|
expect(container.textContent).toContain('View fix comparison')
|
|
623
621
|
expect(
|
|
624
622
|
Array.from(container.querySelectorAll('button')).filter(button =>
|
|
@@ -791,8 +789,11 @@ describe('PrintDesignPanel', () => {
|
|
|
791
789
|
)
|
|
792
790
|
changeInput(textInputs[0]!, '32rem')
|
|
793
791
|
changeInput(textInputs[1]!, '1.6')
|
|
792
|
+
// Font roles, align, flourishes (heading rule, drop cap), page
|
|
793
|
+
// furniture (columns, running header, footer + slot) and Cover.
|
|
794
|
+
// (Contents only appears once Cover is on.)
|
|
794
795
|
const selects = Array.from(container.querySelectorAll('select'))
|
|
795
|
-
expect(selects).toHaveLength(
|
|
796
|
+
expect(selects).toHaveLength(15)
|
|
796
797
|
changeInput(selects[0]!, 'sans')
|
|
797
798
|
changeInput(selects[1]!, 'serif')
|
|
798
799
|
changeInput(selects[2]!, 'sans')
|
|
@@ -1272,7 +1273,10 @@ describe('PrintDesignPanel', () => {
|
|
|
1272
1273
|
expect(onRenderPdf).toHaveBeenCalledOnce()
|
|
1273
1274
|
})
|
|
1274
1275
|
|
|
1275
|
-
it('routes
|
|
1276
|
+
it('never routes Export PDF through the vision loop, even when enabled', () => {
|
|
1277
|
+
// Export is always a plain export; the loop has its own Run button in
|
|
1278
|
+
// the vision tab (previously Export silently ran the whole inspection
|
|
1279
|
+
// loop and there was no way to just export).
|
|
1276
1280
|
const onRenderPdf = vi.fn()
|
|
1277
1281
|
const onVisionExport = vi.fn()
|
|
1278
1282
|
const { container } = render(
|
|
@@ -1295,8 +1299,8 @@ describe('PrintDesignPanel', () => {
|
|
|
1295
1299
|
|
|
1296
1300
|
click(buttonByText(container, 'Export PDF'))
|
|
1297
1301
|
|
|
1298
|
-
expect(
|
|
1299
|
-
expect(
|
|
1302
|
+
expect(onRenderPdf).toHaveBeenCalledOnce()
|
|
1303
|
+
expect(onVisionExport).not.toHaveBeenCalled()
|
|
1300
1304
|
})
|
|
1301
1305
|
|
|
1302
1306
|
it('keeps beta vision report entries in the persistent live readout', () => {
|
|
@@ -1445,4 +1449,390 @@ describe('PrintDesignPanel', () => {
|
|
|
1445
1449
|
expect(container.textContent).toContain('#111111')
|
|
1446
1450
|
expect(container.textContent).toContain('#335f8f')
|
|
1447
1451
|
})
|
|
1452
|
+
|
|
1453
|
+
it('inherits the host identity instead of baking in a colour (issue spec)', () => {
|
|
1454
|
+
render(<PrintDesignPanel variant="workspace" />)
|
|
1455
|
+
const css = document.head.textContent ?? ''
|
|
1456
|
+
// Export primary + markers resolve through the host's --app-acc/--app-block
|
|
1457
|
+
// (whitespace-tolerant: styled-components may collapse the declarations).
|
|
1458
|
+
expect(css).toMatch(/--print-design-hue:\s*var\(\s*--app-acc/)
|
|
1459
|
+
expect(css).toMatch(/--app-block/)
|
|
1460
|
+
// Selected preset carries the host-hue edge/underline.
|
|
1461
|
+
expect(css).toMatch(/inset 0 -2px 0 0 var\(--print-design-hue\)/)
|
|
1462
|
+
})
|
|
1463
|
+
|
|
1464
|
+
it('presents Ink/Paper/Accent as swatch chips with hex, not full-width bars', () => {
|
|
1465
|
+
const { container } = render(
|
|
1466
|
+
<PrintDesignPanel variant="workspace" modes={['preset', 'manual']} />,
|
|
1467
|
+
)
|
|
1468
|
+
const pickers = Array.from(
|
|
1469
|
+
container.querySelectorAll<HTMLInputElement>('input[type="color"]'),
|
|
1470
|
+
)
|
|
1471
|
+
expect(pickers).toHaveLength(3)
|
|
1472
|
+
// Each picker is embedded in a 28px chip, and the hex value is visible.
|
|
1473
|
+
for (const picker of pickers) {
|
|
1474
|
+
expect(picker.closest('span')).not.toBeNull()
|
|
1475
|
+
}
|
|
1476
|
+
expect(container.textContent).toMatch(/#[0-9a-fA-F]{6}/)
|
|
1477
|
+
})
|
|
1478
|
+
|
|
1479
|
+
it('previews the host document by default with a Sample opt-out', () => {
|
|
1480
|
+
const { container } = render(
|
|
1481
|
+
<PrintDesignPanel
|
|
1482
|
+
variant="workspace"
|
|
1483
|
+
previewDocument={{
|
|
1484
|
+
title: 'Emergent Workflows',
|
|
1485
|
+
byline: 'A. Hyde',
|
|
1486
|
+
paragraphs: ['A first paragraph from the actual manuscript body.'],
|
|
1487
|
+
}}
|
|
1488
|
+
/>,
|
|
1489
|
+
)
|
|
1490
|
+
expect(container.textContent).toContain('Emergent Workflows')
|
|
1491
|
+
expect(container.textContent).toContain(
|
|
1492
|
+
'A first paragraph from the actual manuscript body.',
|
|
1493
|
+
)
|
|
1494
|
+
expect(container.textContent).not.toContain('The Shape of a Page')
|
|
1495
|
+
|
|
1496
|
+
// The Sample toggle lives in the well header (the first checkbox in the
|
|
1497
|
+
// merged view is the beta vision toggle).
|
|
1498
|
+
const toggle = container.querySelector<HTMLInputElement>(
|
|
1499
|
+
'label[data-disabled] input[type="checkbox"]',
|
|
1500
|
+
)
|
|
1501
|
+
expect(toggle?.disabled).toBe(false)
|
|
1502
|
+
act(() => {
|
|
1503
|
+
toggle?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
1504
|
+
})
|
|
1505
|
+
expect(container.textContent).toContain('The Shape of a Page')
|
|
1506
|
+
})
|
|
1507
|
+
|
|
1508
|
+
it('forces the sample when the host supplies no preview content', () => {
|
|
1509
|
+
const { container } = render(<PrintDesignPanel variant="workspace" />)
|
|
1510
|
+
expect(container.textContent).toContain('The Shape of a Page')
|
|
1511
|
+
const toggle = container.querySelector<HTMLInputElement>(
|
|
1512
|
+
'label[data-disabled] input[type="checkbox"]',
|
|
1513
|
+
)
|
|
1514
|
+
expect(toggle?.disabled).toBe(true)
|
|
1515
|
+
})
|
|
1516
|
+
|
|
1517
|
+
it('shows the vision page snapshot in the preview pane in vision mode', () => {
|
|
1518
|
+
const { container } = render(
|
|
1519
|
+
<PrintDesignPanel
|
|
1520
|
+
variant="workspace"
|
|
1521
|
+
visionTypesetting={{
|
|
1522
|
+
enabled: true,
|
|
1523
|
+
snapshotUrl: 'file:///tmp/page-1.png',
|
|
1524
|
+
snapshotAlt: 'Rendered page 1',
|
|
1525
|
+
onToggle: vi.fn(),
|
|
1526
|
+
}}
|
|
1527
|
+
/>,
|
|
1528
|
+
)
|
|
1529
|
+
const snapshot = container.querySelector(
|
|
1530
|
+
'img[alt="Rendered page 1"]',
|
|
1531
|
+
)
|
|
1532
|
+
expect(snapshot?.getAttribute('src')).toBe('file:///tmp/page-1.png')
|
|
1533
|
+
// The theme facsimile yields to the real snapshot in vision mode.
|
|
1534
|
+
expect(container.textContent).not.toContain('The Shape of a Page')
|
|
1535
|
+
})
|
|
1536
|
+
|
|
1537
|
+
it('separates Run (vision loop) from Export PDF (plain export)', () => {
|
|
1538
|
+
const onRun = vi.fn()
|
|
1539
|
+
const onPlainExport = vi.fn()
|
|
1540
|
+
const { container } = render(
|
|
1541
|
+
<PrintDesignPanel
|
|
1542
|
+
variant="workspace"
|
|
1543
|
+
actions={[
|
|
1544
|
+
{
|
|
1545
|
+
id: 'export-pdf',
|
|
1546
|
+
label: 'Export PDF',
|
|
1547
|
+
variant: 'primary',
|
|
1548
|
+
onClick: onPlainExport,
|
|
1549
|
+
},
|
|
1550
|
+
]}
|
|
1551
|
+
visionTypesetting={{
|
|
1552
|
+
enabled: true,
|
|
1553
|
+
onToggle: vi.fn(),
|
|
1554
|
+
onExport: onRun,
|
|
1555
|
+
}}
|
|
1556
|
+
/>,
|
|
1557
|
+
)
|
|
1558
|
+
|
|
1559
|
+
// Merged view: Run and Export sit side by side; Export stays a plain
|
|
1560
|
+
// export and Run starts the loop.
|
|
1561
|
+
click(buttonByText(container, 'Export PDF'))
|
|
1562
|
+
expect(onPlainExport).toHaveBeenCalledOnce()
|
|
1563
|
+
expect(onRun).not.toHaveBeenCalled()
|
|
1564
|
+
|
|
1565
|
+
click(buttonByText(container, 'Run typesetting'))
|
|
1566
|
+
expect(onRun).toHaveBeenCalledOnce()
|
|
1567
|
+
})
|
|
1568
|
+
|
|
1569
|
+
it('swaps Run for Stop while the vision loop is running', () => {
|
|
1570
|
+
const onRun = vi.fn()
|
|
1571
|
+
const onStop = vi.fn()
|
|
1572
|
+
const { container } = render(
|
|
1573
|
+
<PrintDesignPanel
|
|
1574
|
+
variant="workspace"
|
|
1575
|
+
visionTypesetting={{
|
|
1576
|
+
enabled: true,
|
|
1577
|
+
onToggle: vi.fn(),
|
|
1578
|
+
onExport: onRun,
|
|
1579
|
+
onStop,
|
|
1580
|
+
progress: { state: 'running', label: 'Page 2 of 9 · inspecting' },
|
|
1581
|
+
}}
|
|
1582
|
+
/>,
|
|
1583
|
+
)
|
|
1584
|
+
// While running, the Run slot becomes a Stop control — always enabled.
|
|
1585
|
+
expect(
|
|
1586
|
+
Array.from(container.querySelectorAll('button')).some(button =>
|
|
1587
|
+
button.textContent?.includes('Run typesetting'),
|
|
1588
|
+
),
|
|
1589
|
+
).toBe(false)
|
|
1590
|
+
const stop = buttonByText(container, 'Stop')
|
|
1591
|
+
expect(stop.hasAttribute('disabled')).toBe(false)
|
|
1592
|
+
click(stop)
|
|
1593
|
+
expect(onStop).toHaveBeenCalledOnce()
|
|
1594
|
+
expect(onRun).not.toHaveBeenCalled()
|
|
1595
|
+
})
|
|
1596
|
+
|
|
1597
|
+
it('shows open/fixed issue chips beside the latest page snapshot', () => {
|
|
1598
|
+
const { container } = render(
|
|
1599
|
+
<PrintDesignPanel
|
|
1600
|
+
variant="workspace"
|
|
1601
|
+
visionTypesetting={{
|
|
1602
|
+
enabled: true,
|
|
1603
|
+
onToggle: vi.fn(),
|
|
1604
|
+
snapshotUrl: 'file:///tmp/page-3.png',
|
|
1605
|
+
snapshotAlt: 'Page 3 of 9',
|
|
1606
|
+
snapshotIssues: [
|
|
1607
|
+
{ id: 'stranded-heading', label: 'stranded heading', state: 'fixed' },
|
|
1608
|
+
{ id: 'sparse-sample-page', label: 'sparse sample page', state: 'open' },
|
|
1609
|
+
],
|
|
1610
|
+
}}
|
|
1611
|
+
/>,
|
|
1612
|
+
)
|
|
1613
|
+
const list = container.querySelector('[aria-label="Latest page issues"]')
|
|
1614
|
+
expect(list).not.toBeNull()
|
|
1615
|
+
expect(list?.textContent).toContain('fixed')
|
|
1616
|
+
expect(list?.textContent).toContain('stranded heading')
|
|
1617
|
+
expect(list?.textContent).toContain('issue')
|
|
1618
|
+
expect(list?.textContent).toContain('sparse sample page')
|
|
1619
|
+
})
|
|
1620
|
+
|
|
1621
|
+
it('accumulates page snapshots as a scrollable strip with per-card chips', () => {
|
|
1622
|
+
const { container } = render(
|
|
1623
|
+
<PrintDesignPanel
|
|
1624
|
+
variant="workspace"
|
|
1625
|
+
visionTypesetting={{
|
|
1626
|
+
enabled: true,
|
|
1627
|
+
onToggle: vi.fn(),
|
|
1628
|
+
snapshotHistory: [
|
|
1629
|
+
{
|
|
1630
|
+
id: 'p1-check',
|
|
1631
|
+
url: 'file:///tmp/page-1.png',
|
|
1632
|
+
label: 'Page 1',
|
|
1633
|
+
issues: [],
|
|
1634
|
+
},
|
|
1635
|
+
{
|
|
1636
|
+
id: 'p2-check',
|
|
1637
|
+
url: 'file:///tmp/page-2.png',
|
|
1638
|
+
label: 'Page 2',
|
|
1639
|
+
issues: [
|
|
1640
|
+
{ id: 'stranded-heading', label: 'stranded heading', state: 'open' },
|
|
1641
|
+
],
|
|
1642
|
+
},
|
|
1643
|
+
{
|
|
1644
|
+
id: 'p2-fix1',
|
|
1645
|
+
url: 'file:///tmp/page-2-fix.png',
|
|
1646
|
+
label: 'Page 2 · fix 1',
|
|
1647
|
+
issues: [
|
|
1648
|
+
{ id: 'stranded-heading', label: 'stranded heading', state: 'fixed' },
|
|
1649
|
+
],
|
|
1650
|
+
},
|
|
1651
|
+
],
|
|
1652
|
+
}}
|
|
1653
|
+
/>,
|
|
1654
|
+
)
|
|
1655
|
+
const strip = container.querySelector('[aria-label="Page snapshot history"]')
|
|
1656
|
+
expect(strip).not.toBeNull()
|
|
1657
|
+
const images = Array.from(strip?.querySelectorAll('img') ?? [])
|
|
1658
|
+
expect(images.map(img => img.getAttribute('src'))).toEqual([
|
|
1659
|
+
'file:///tmp/page-1.png',
|
|
1660
|
+
'file:///tmp/page-2.png',
|
|
1661
|
+
'file:///tmp/page-2-fix.png',
|
|
1662
|
+
])
|
|
1663
|
+
// Clean page → pass chip; the fix card flips its chip to fixed.
|
|
1664
|
+
expect(strip?.textContent).toContain('no layout issues')
|
|
1665
|
+
expect(strip?.textContent).toContain('Page 2 · fix 1')
|
|
1666
|
+
expect(strip?.textContent).toContain('fixed')
|
|
1667
|
+
})
|
|
1668
|
+
|
|
1669
|
+
it('zooms a page snapshot in-window instead of opening a viewer', () => {
|
|
1670
|
+
const onOpen = vi.fn()
|
|
1671
|
+
const { container } = render(
|
|
1672
|
+
<PrintDesignPanel
|
|
1673
|
+
variant="workspace"
|
|
1674
|
+
visionTypesetting={{
|
|
1675
|
+
enabled: true,
|
|
1676
|
+
onToggle: vi.fn(),
|
|
1677
|
+
snapshotHistory: [
|
|
1678
|
+
{
|
|
1679
|
+
id: 'p1-check',
|
|
1680
|
+
url: 'file:///tmp/page-1.png',
|
|
1681
|
+
label: 'Page 1',
|
|
1682
|
+
issues: [],
|
|
1683
|
+
onOpen,
|
|
1684
|
+
},
|
|
1685
|
+
],
|
|
1686
|
+
}}
|
|
1687
|
+
/>,
|
|
1688
|
+
)
|
|
1689
|
+
const card = container.querySelector<HTMLButtonElement>(
|
|
1690
|
+
'button[aria-label="Page 1"]',
|
|
1691
|
+
)
|
|
1692
|
+
expect(card).not.toBeNull()
|
|
1693
|
+
if (!card) throw new Error('page card missing')
|
|
1694
|
+
click(card)
|
|
1695
|
+
|
|
1696
|
+
const zoom = container.querySelector('[aria-label="Page snapshot zoom"]')
|
|
1697
|
+
expect(zoom).not.toBeNull()
|
|
1698
|
+
expect(zoom?.querySelector('img')?.getAttribute('src')).toBe(
|
|
1699
|
+
'file:///tmp/page-1.png',
|
|
1700
|
+
)
|
|
1701
|
+
// No external viewer involved.
|
|
1702
|
+
expect(onOpen).not.toHaveBeenCalled()
|
|
1703
|
+
|
|
1704
|
+
act(() => {
|
|
1705
|
+
zoom?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
1706
|
+
})
|
|
1707
|
+
expect(
|
|
1708
|
+
container.querySelector('[aria-label="Page snapshot zoom"]'),
|
|
1709
|
+
).toBeNull()
|
|
1710
|
+
})
|
|
1711
|
+
|
|
1712
|
+
it('applies flourish controls to the theme', () => {
|
|
1713
|
+
const onThemeChange = vi.fn()
|
|
1714
|
+
const { container } = render(
|
|
1715
|
+
<PrintDesignPanel variant="workspace" onThemeChange={onThemeChange} />,
|
|
1716
|
+
)
|
|
1717
|
+
const headingRule = Array.from(
|
|
1718
|
+
container.querySelectorAll<HTMLSelectElement>('select'),
|
|
1719
|
+
).find(select =>
|
|
1720
|
+
Array.from(select.options).some(
|
|
1721
|
+
option => option.value === 'underline',
|
|
1722
|
+
),
|
|
1723
|
+
)
|
|
1724
|
+
expect(headingRule).not.toBeNull()
|
|
1725
|
+
if (!headingRule) throw new Error('heading rule select missing')
|
|
1726
|
+
changeInput(headingRule, 'underline')
|
|
1727
|
+
const theme = onThemeChange.mock.calls.at(-1)?.[0]
|
|
1728
|
+
expect(theme?.setting?.headingRule).toBe('underline')
|
|
1729
|
+
})
|
|
1730
|
+
|
|
1731
|
+
it('flips to live preview and marks run pages stale on style edits', () => {
|
|
1732
|
+
const { container } = render(
|
|
1733
|
+
<PrintDesignPanel
|
|
1734
|
+
variant="workspace"
|
|
1735
|
+
onThemeChange={vi.fn()}
|
|
1736
|
+
visionTypesetting={{
|
|
1737
|
+
enabled: true,
|
|
1738
|
+
onToggle: vi.fn(),
|
|
1739
|
+
snapshotHistory: [
|
|
1740
|
+
{
|
|
1741
|
+
id: 'p1-check',
|
|
1742
|
+
url: 'file:///tmp/page-1.png',
|
|
1743
|
+
label: 'Page 1',
|
|
1744
|
+
issues: [],
|
|
1745
|
+
},
|
|
1746
|
+
],
|
|
1747
|
+
}}
|
|
1748
|
+
/>,
|
|
1749
|
+
)
|
|
1750
|
+
// With run output present, the pages view shows by default.
|
|
1751
|
+
expect(
|
|
1752
|
+
container.querySelector('[aria-label="Page snapshot history"]'),
|
|
1753
|
+
).not.toBeNull()
|
|
1754
|
+
|
|
1755
|
+
// Change a style control → live preview returns, pages marked stale.
|
|
1756
|
+
const headingRule = Array.from(
|
|
1757
|
+
container.querySelectorAll<HTMLSelectElement>('select'),
|
|
1758
|
+
).find(select =>
|
|
1759
|
+
Array.from(select.options).some(option => option.value === 'underline'),
|
|
1760
|
+
)
|
|
1761
|
+
if (!headingRule) throw new Error('heading rule select missing')
|
|
1762
|
+
changeInput(headingRule, 'underline')
|
|
1763
|
+
|
|
1764
|
+
expect(
|
|
1765
|
+
container.querySelector('[aria-label="Page snapshot history"]'),
|
|
1766
|
+
).toBeNull()
|
|
1767
|
+
expect(container.textContent).toContain('Run pages · stale')
|
|
1768
|
+
expect(container.textContent).toContain('The Shape of a Page')
|
|
1769
|
+
|
|
1770
|
+
// The user can flip back to the (stale) pages manually.
|
|
1771
|
+
click(buttonByText(container, 'Run pages · stale'))
|
|
1772
|
+
expect(
|
|
1773
|
+
container.querySelector('[aria-label="Page snapshot history"]'),
|
|
1774
|
+
).not.toBeNull()
|
|
1775
|
+
})
|
|
1776
|
+
|
|
1777
|
+
it('shows the real Paged.js live preview in the well when supplied', () => {
|
|
1778
|
+
const { container } = render(
|
|
1779
|
+
<PrintDesignPanel
|
|
1780
|
+
variant="workspace"
|
|
1781
|
+
livePreview={{
|
|
1782
|
+
html: '<html><body><div class="pagedjs_page">Page one</div></body></html>',
|
|
1783
|
+
pageCount: 3,
|
|
1784
|
+
}}
|
|
1785
|
+
/>,
|
|
1786
|
+
)
|
|
1787
|
+
const frame = container.querySelector<HTMLIFrameElement>(
|
|
1788
|
+
'iframe[title="Live paged preview"]',
|
|
1789
|
+
)
|
|
1790
|
+
expect(frame).not.toBeNull()
|
|
1791
|
+
expect(frame?.getAttribute('srcdoc')).toContain('pagedjs_page')
|
|
1792
|
+
// The grey well + paper shadow ride along inside the preview document.
|
|
1793
|
+
expect(frame?.getAttribute('srcdoc')).toContain('data-live-preview-chrome')
|
|
1794
|
+
expect(container.textContent).toContain('Live preview · 3 page(s)')
|
|
1795
|
+
// The facsimile (and its Sample toggle) yields to the real preview.
|
|
1796
|
+
expect(container.textContent).not.toContain('The Shape of a Page')
|
|
1797
|
+
})
|
|
1798
|
+
|
|
1799
|
+
it('selects presets from a dropdown in the merged workspace view', () => {
|
|
1800
|
+
const onThemeChange = vi.fn()
|
|
1801
|
+
const { container } = render(
|
|
1802
|
+
<PrintDesignPanel variant="workspace" onThemeChange={onThemeChange} />,
|
|
1803
|
+
)
|
|
1804
|
+
const presetSelect = container.querySelector<HTMLSelectElement>(
|
|
1805
|
+
'select[aria-label="Print theme presets"]',
|
|
1806
|
+
)
|
|
1807
|
+
expect(presetSelect).not.toBeNull()
|
|
1808
|
+
if (!presetSelect) throw new Error('preset select missing')
|
|
1809
|
+
const journal = Array.from(presetSelect.options).find(option =>
|
|
1810
|
+
option.textContent?.includes('Journal Article'),
|
|
1811
|
+
)
|
|
1812
|
+
expect(journal).toBeDefined()
|
|
1813
|
+
changeInput(presetSelect, journal!.value)
|
|
1814
|
+
expect(onThemeChange).toHaveBeenCalled()
|
|
1815
|
+
})
|
|
1816
|
+
|
|
1817
|
+
it('lets the host set max fixes per issue in the vision tab', () => {
|
|
1818
|
+
const onMaxFixesPerIssueChange = vi.fn()
|
|
1819
|
+
const { container } = render(
|
|
1820
|
+
<PrintDesignPanel
|
|
1821
|
+
variant="workspace"
|
|
1822
|
+
visionTypesetting={{
|
|
1823
|
+
enabled: true,
|
|
1824
|
+
onToggle: vi.fn(),
|
|
1825
|
+
maxFixesPerIssue: 3,
|
|
1826
|
+
onMaxFixesPerIssueChange,
|
|
1827
|
+
}}
|
|
1828
|
+
/>,
|
|
1829
|
+
)
|
|
1830
|
+
const input = container.querySelector<HTMLInputElement>(
|
|
1831
|
+
'input[type="number"]',
|
|
1832
|
+
)
|
|
1833
|
+
expect(input?.value).toBe('3')
|
|
1834
|
+
if (!input) throw new Error('max fixes input missing')
|
|
1835
|
+
changeInput(input, '5')
|
|
1836
|
+
expect(onMaxFixesPerIssueChange).toHaveBeenCalledWith(5)
|
|
1837
|
+
})
|
|
1448
1838
|
})
|