diff --git a/docs/ABSTRACTIONS.md b/docs/ABSTRACTIONS.md index ab7bf159..0d80946a 100644 --- a/docs/ABSTRACTIONS.md +++ b/docs/ABSTRACTIONS.md @@ -596,6 +596,7 @@ Defined in `src/utils/durableMarkdownBlocks.ts`, `src/utils/editorDurableMarkdow - Each `tldrawBlock` stores a stable `boardId` plus the tldraw document snapshot JSON. Session state such as camera, selected tool, and current selection is not persisted into the note. - The rich editor renders the block with the `tldraw` package and saves debounced document snapshot changes back into the block props, so normal Tolaria autosave writes the board into the `.md` file. - Whiteboard prop writes re-resolve the live BlockNote block by id before mutating it, and disappear as no-ops if a note reload or mode switch has already removed that block. +- The tldraw runtime receives Tolaria's resolved light/dark mode as its user color scheme, so embedded whiteboards follow the app appearance and update while mounted. - Mermaid and tldraw both register small codecs with the shared durable fenced-block pipeline; scanner, token, block injection, and mixed serialization mechanics live in one owner. - The `/whiteboard` slash command inserts an empty tldraw block using the same Markdown-durable storage path. Preview images are intentionally omitted; thumbnails can be added later as derived cache artifacts. diff --git a/src/components/TldrawWhiteboard.test.tsx b/src/components/TldrawWhiteboard.test.tsx index c036ca67..0d7bb5cb 100644 --- a/src/components/TldrawWhiteboard.test.tsx +++ b/src/components/TldrawWhiteboard.test.tsx @@ -1,11 +1,18 @@ -import { render, screen } from '@testing-library/react' -import { describe, expect, it, vi } from 'vitest' +import { act, cleanup, render, screen, waitFor } from '@testing-library/react' +import { afterEach, describe, expect, it, vi } from 'vitest' import type { Editor } from 'tldraw' import { TldrawWhiteboard } from './TldrawWhiteboard' interface MockTldrawProps { assetUrls: MockAssetUrls onMount: (editor: Editor) => () => void + user?: MockTldrawUser +} + +interface MockTldrawUser { + userPreferences: { + get: () => { colorScheme?: string } + } } interface MockAssetUrls { @@ -64,8 +71,18 @@ vi.mock('tldraw', async () => { createTLStore: vi.fn(() => ({ listen: vi.fn(() => vi.fn()), })), + defaultUserPreferences: { + colorScheme: 'light', + locale: 'en', + }, getSnapshot: vi.fn(() => ({ document: {} })), loadSnapshot: vi.fn(), + useTldrawUser: vi.fn(({ userPreferences }: { userPreferences: { colorScheme: string } }) => ({ + setUserPreferences: vi.fn(), + userPreferences: { + get: () => userPreferences, + }, + })), } }) @@ -76,7 +93,7 @@ function renderedTldrawAssetUrls(): MockAssetUrls { } function renderedTldrawProps(): MockTldrawProps { - const props = tldrawMock.Tldraw.mock.calls[0]?.[0] as MockTldrawProps + const props = tldrawMock.Tldraw.mock.lastCall?.[0] as MockTldrawProps expect(props).toBeDefined() return props } @@ -125,6 +142,13 @@ function expectBundledTldrawAssetUrls(assetUrls: MockAssetUrls) { } describe('TldrawWhiteboard', () => { + afterEach(() => { + cleanup() + document.documentElement.removeAttribute('data-theme') + document.documentElement.classList.remove('dark') + vi.clearAllMocks() + }) + it('uses bundled tldraw assets instead of CDN URLs', () => { render( { expectBundledTldrawAssetUrls(renderedTldrawAssetUrls()) }) + it('passes Tolaria dark mode to tldraw', () => { + document.documentElement.setAttribute('data-theme', 'dark') + document.documentElement.classList.add('dark') + + render( + + ) + + expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('dark') + }) + + it('updates the tldraw color scheme when Tolaria theme changes', async () => { + document.documentElement.setAttribute('data-theme', 'light') + + render( + + ) + + expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('light') + + act(() => { + document.documentElement.setAttribute('data-theme', 'dark') + document.documentElement.classList.add('dark') + }) + + await waitFor(() => { + expect(renderedTldrawProps().user?.userPreferences.get().colorScheme).toBe('dark') + }) + }) + it('installs the text measurement guard when the canvas mounts', () => { render( normalizeSize({ height, width }), [height, width]) const [resizingSize, setResizingSize] = useState(null) const visibleSize = resizingSize ?? persistedSize + const themeMode = useDocumentThemeMode() + const userPreferences = useMemo(() => tldrawUserPreferences(themeMode), [themeMode]) + const tldrawUser = useTldrawUser({ + setUserPreferences: ignoreTldrawUserPreferencesUpdate, + userPreferences, + }) useEffect(() => { onSnapshotChangeRef.current = onSnapshotChange @@ -483,6 +507,7 @@ export function TldrawWhiteboard({ components={tldrawUiComponents} onMount={installWhiteboardRuntimeGuards} store={store} + user={tldrawUser} />
{ + await openNote(page, 'Whiteboard Embed') + + const tldrawContainer = page.locator('.tldraw-whiteboard .tl-container').first() + await expect(tldrawContainer).toBeVisible({ timeout: 20_000 }) + + const initialMode = await tldrawContainer.evaluate((element) => + element.classList.contains('tl-theme__dark') ? 'dark' : 'light' + ) + + await page.getByTestId('status-theme-mode').click() + const toggledMode = initialMode === 'dark' ? 'light' : 'dark' + await expect(tldrawContainer).toHaveClass(new RegExp(`tl-theme__${toggledMode}`)) + await expect(tldrawContainer).toHaveAttribute('data-color-mode', toggledMode) + + await page.getByTestId('status-theme-mode').click() + await expect(tldrawContainer).toHaveClass(new RegExp(`tl-theme__${initialMode}`)) + await expect(tldrawContainer).toHaveAttribute('data-color-mode', initialMode) +}) + test('embedded tldraw interactions stay inside the whiteboard', async ({ page }) => { await openNote(page, 'Whiteboard Embed')