diff --git a/.github/workflows/deploy-dotcom.yml b/.github/workflows/deploy-dotcom.yml index 6a5371d2d717..b950b9879f97 100644 --- a/.github/workflows/deploy-dotcom.yml +++ b/.github/workflows/deploy-dotcom.yml @@ -200,3 +200,5 @@ jobs: ZERO_R2_BUCKET_NAME: ${{ secrets.ZERO_R2_BUCKET_NAME }} ZERO_R2_ACCESS_KEY_ID: ${{ secrets.ZERO_R2_ACCESS_KEY_ID }} ZERO_R2_SECRET_ACCESS_KEY: ${{ secrets.ZERO_R2_SECRET_ACCESS_KEY }} + ZERO_OTEL_EXPORTER_OTLP_ENDPOINT: ${{ secrets.ZERO_OTEL_EXPORTER_OTLP_ENDPOINT }} + ZERO_OTEL_EXPORTER_OTLP_HEADERS: ${{ secrets.ZERO_OTEL_EXPORTER_OTLP_HEADERS }} diff --git a/apps/docs/content/sdk-features/ui-components.mdx b/apps/docs/content/sdk-features/ui-components.mdx index 7fe81c8fbd4e..13790b3b2e3e 100644 --- a/apps/docs/content/sdk-features/ui-components.mdx +++ b/apps/docs/content/sdk-features/ui-components.mdx @@ -1,7 +1,7 @@ --- title: UI components created_at: 12/17/2025 -updated_at: 12/29/2025 +updated_at: 3/25/2026 keywords: - ui - components @@ -9,7 +9,7 @@ keywords: - menu - customization status: published -date: 12/29/2025 +date: 3/25/2026 readability: 8 voice: 8 completeness: 9 @@ -76,6 +76,46 @@ The **NavigationPanel** provides page navigation, zoom controls, and the minimap Each slot is optional. Pass `null` as an override to hide a component entirely, or provide your own React component to replace the default implementation. +### Slot props + +The UI portion of the `components` prop is shaped by [TLUiComponents](?). Every key is optional: use `null` to hide that slot, or pass a React component. When a slot has a documented props type in the table below, import that type from `tldraw` and type your replacement as `React.ComponentType<…>` (or implement the matching props). When the props column says **none**, the SDK does not declare extra props for that slot beyond what a plain `ComponentType` allows. + +| Slot | Props (import from `tldraw`) | +| ------------------------- | ---------------------------------------------------------------------- | +| `ContextMenu` | [TLUiContextMenuProps](?) | +| `ActionsMenu` | [TLUiActionsMenuProps](?) | +| `HelpMenu` | [TLUiHelpMenuProps](?) | +| `ZoomMenu` | [TLUiZoomMenuProps](?) | +| `MainMenu` | [TLUiMainMenuProps](?) | +| `Minimap` | none | +| `StylePanel` | [TLUiStylePanelProps](?) | +| `PageMenu` | none | +| `NavigationPanel` | none | +| `Toolbar` | none | +| `RichTextToolbar` | [TLUiRichTextToolbarProps](?) | +| `ImageToolbar` | none | +| `VideoToolbar` | none | +| `KeyboardShortcutsDialog` | [TLUiKeyboardShortcutsDialogProps](?) | +| `QuickActions` | [TLUiQuickActionsProps](?) | +| `HelperButtons` | [TLUiHelperButtonsProps](?) | +| `DebugPanel` | none | +| `DebugMenu` | none | +| `MenuPanel` | none | +| `TopPanel` | none | +| `SharePanel` | none | +| `CursorChatBubble` | none | +| `Dialogs` | none | +| `Toasts` | none | +| `A11y` | none | +| `FollowingIndicator` | none | +| `PeopleMenu` | none (default component: optional `children` via [PeopleMenuProps](?)) | +| `PeopleMenuAvatar` | [TLUiPeopleMenuAvatarProps](?) | +| `PeopleMenuFacePile` | [TLUiPeopleMenuFacePileProps](?) | +| `PeopleMenuItem` | [TLUiPeopleMenuItemProps](?) | +| `UserPresenceEditor` | none | + +This table is an index; the authoritative list and types remain [TLUiComponents](?) in the API reference and in the package typings. + ### UI hooks Components access editor functionality through specialized hooks. diff --git a/apps/dotcom/client/package.json b/apps/dotcom/client/package.json index afcf5f2a0d02..9dd6c219eb88 100644 --- a/apps/dotcom/client/package.json +++ b/apps/dotcom/client/package.json @@ -31,7 +31,7 @@ "dependencies": { "@clerk/clerk-react": "^5.53.3", "@clerk/elements": "^0.23.74", - "@rocicorp/zero": "1.0.0", + "@rocicorp/zero": "1.2.0", "@sentry/integrations": "^7.120.3", "@sentry/react": "^7.120.3", "@tldraw/assets": "workspace:*", diff --git a/apps/dotcom/sync-worker/package.json b/apps/dotcom/sync-worker/package.json index 8f5c78b42f22..2c51f56bfdbb 100644 --- a/apps/dotcom/sync-worker/package.json +++ b/apps/dotcom/sync-worker/package.json @@ -24,7 +24,7 @@ "dependencies": { "@clerk/backend": "^1.23.7", "@pierre/storage": "^1.1.0", - "@rocicorp/zero": "1.0.0", + "@rocicorp/zero": "1.2.0", "@supabase/auth-helpers-remix": "^0.2.6", "@supabase/supabase-js": "^2.48.1", "@tldraw/dotcom-shared": "workspace:*", diff --git a/apps/dotcom/sync-worker/src/healthCheckRoutes.ts b/apps/dotcom/sync-worker/src/healthCheckRoutes.ts index 5bf0b72ec3ab..d96c61c37f98 100644 --- a/apps/dotcom/sync-worker/src/healthCheckRoutes.ts +++ b/apps/dotcom/sync-worker/src/healthCheckRoutes.ts @@ -91,4 +91,137 @@ export const healthCheckRoutes = createRouter() await db.destroy() } }) + // Combined postgres health check: db size, changelog size, WAL retention, replication slots, and + // tlpr replicator status. Grouped into a single endpoint because updown.io charges per check + // invocation. Failures include the sub-check name so alerts remain distinguishable. + .get('/health-check/postgres', async (_, env) => { + const db = createPostgresConnectionPool(env, '/health-check/postgres') + const failures: string[] = [] + const okDetails: string[] = [] + try { + // db-size + try { + const thresholdGb = parseFloat(env.HEALTH_CHECK_DB_SIZE_THRESHOLD_GB ?? '4') + const result = await sql<{ size_bytes: string }>` + SELECT pg_database_size(current_database()) AS size_bytes + `.execute(db) + const sizeGb = parseInt(result.rows[0].size_bytes, 10) / (1024 * 1024 * 1024) + if (sizeGb > thresholdGb) { + failures.push(`db-size: ${sizeGb.toFixed(2)} GB > ${thresholdGb} GB threshold`) + } else { + okDetails.push(`db: ${sizeGb.toFixed(2)} GB`) + } + } catch (_e) { + failures.push('db-size: query failed') + } + + // changelog-size + try { + const thresholdMb = parseFloat(env.HEALTH_CHECK_CHANGELOG_SIZE_THRESHOLD_MB ?? '1024') + const result = await sql<{ size_bytes: string }>` + SELECT pg_total_relation_size('"zero_0/cdc"."changeLog"') AS size_bytes + `.execute(db) + const sizeMb = parseInt(result.rows[0].size_bytes, 10) / (1024 * 1024) + if (sizeMb > thresholdMb) { + failures.push(`changelog-size: ${sizeMb.toFixed(0)} MB > ${thresholdMb} MB threshold`) + } else { + okDetails.push(`changelog: ${sizeMb.toFixed(0)} MB`) + } + } catch (_e) { + failures.push('changelog-size: query failed') + } + + // wal-size + try { + const thresholdMb = parseFloat(env.HEALTH_CHECK_WAL_SIZE_THRESHOLD_MB ?? '1024') + const result = await sql<{ + slot_name: string + retained_bytes: string + }>` + SELECT slot_name, pg_wal_lsn_diff(pg_current_wal_lsn(), restart_lsn) AS retained_bytes + FROM pg_replication_slots + `.execute(db) + const overThreshold = result.rows.filter( + (row) => parseInt(row.retained_bytes, 10) / (1024 * 1024) > thresholdMb + ) + if (overThreshold.length > 0) { + const details = overThreshold + .map((r) => { + const mb = (parseInt(r.retained_bytes, 10) / (1024 * 1024)).toFixed(0) + return `${r.slot_name}: ${mb} MB` + }) + .join(', ') + failures.push(`wal-size: ${details} > ${thresholdMb} MB threshold`) + } else { + const maxMb = result.rows.reduce( + (max, r) => Math.max(max, parseInt(r.retained_bytes, 10) / (1024 * 1024)), + 0 + ) + okDetails.push(`wal: ${maxMb.toFixed(0)} MB`) + } + } catch (_e) { + failures.push('wal-size: query failed') + } + + // replication-slots + try { + const result = await sql<{ + slot_name: string + active: boolean + wal_status: string | null + }>` + SELECT slot_name, active, wal_status + FROM pg_replication_slots + WHERE slot_name LIKE 'zero_%' OR slot_name LIKE 'tlpr_%' + `.execute(db) + const unhealthy = result.rows.filter( + (row) => row.wal_status === 'lost' || row.wal_status === 'unreserved' + ) + if (unhealthy.length > 0) { + const details = unhealthy + .map((r) => `${r.slot_name}: wal_status=${r.wal_status}`) + .join(', ') + failures.push(`replication-slots: ${details}`) + } else { + okDetails.push(`slots: ${result.rows.length} ok`) + } + } catch (_e) { + failures.push('replication-slots: query failed') + } + + // tlpr-replicator + try { + const result = await sql<{ + slot_name: string + active: boolean + wal_status: string | null + }>` + SELECT slot_name, active, wal_status + FROM pg_replication_slots + WHERE slot_name LIKE 'tlpr_%' + `.execute(db) + if (result.rows.length === 0) { + failures.push('tlpr-replicator: no slot found') + } else { + const slot = result.rows[0] + if (!slot.active) { + failures.push(`tlpr-replicator: ${slot.slot_name} not active`) + } else if (slot.wal_status === 'lost' || slot.wal_status === 'unreserved') { + failures.push(`tlpr-replicator: ${slot.slot_name} wal_status=${slot.wal_status}`) + } else { + okDetails.push('tlpr: active') + } + } + } catch (_e) { + failures.push('tlpr-replicator: query failed') + } + + if (failures.length > 0) { + return new Response(`FAIL ${failures.join('; ')}`, { status: 500 }) + } + return new Response(`ok (${okDetails.join(', ')})`, { status: 200 }) + } finally { + await db.destroy() + } + }) .all('*', notFound) diff --git a/apps/dotcom/sync-worker/src/types.ts b/apps/dotcom/sync-worker/src/types.ts index 5eb4d5cbf059..b384676b8bb2 100644 --- a/apps/dotcom/sync-worker/src/types.ts +++ b/apps/dotcom/sync-worker/src/types.ts @@ -68,6 +68,9 @@ export interface Environment { MULTIPLAYER_SERVER: string | undefined HEALTH_CHECK_BEARER_TOKEN: string | undefined + HEALTH_CHECK_DB_SIZE_THRESHOLD_GB: string | undefined + HEALTH_CHECK_CHANGELOG_SIZE_THRESHOLD_MB: string | undefined + HEALTH_CHECK_WAL_SIZE_THRESHOLD_MB: string | undefined ANALYTICS_API_URL: string | undefined ANALYTICS_API_TOKEN: string | undefined diff --git a/apps/dotcom/sync-worker/wrangler.toml b/apps/dotcom/sync-worker/wrangler.toml index 9c37faaa29d2..61b911becc51 100644 --- a/apps/dotcom/sync-worker/wrangler.toml +++ b/apps/dotcom/sync-worker/wrangler.toml @@ -68,11 +68,21 @@ MULTIPLAYER_SERVER = "http://localhost:3000" name = "main-tldraw-multiplayer" workers_dev = true # todo: remove this once clients are updated +[env.staging.vars] +HEALTH_CHECK_DB_SIZE_THRESHOLD_GB = "4" +HEALTH_CHECK_CHANGELOG_SIZE_THRESHOLD_MB = "1024" +HEALTH_CHECK_WAL_SIZE_THRESHOLD_MB = "1024" + # production gets the proper name [env.production] name = "tldraw-multiplayer" workers_dev = true # todo: remove this once clients are updated +[env.production.vars] +HEALTH_CHECK_DB_SIZE_THRESHOLD_GB = "10" +HEALTH_CHECK_CHANGELOG_SIZE_THRESHOLD_MB = "1024" +HEALTH_CHECK_WAL_SIZE_THRESHOLD_MB = "2048" + #################### Routing #################### # no custom routes for previews and development diff --git a/apps/dotcom/zero-cache/flyio-replication-manager.template.toml b/apps/dotcom/zero-cache/flyio-replication-manager.template.toml index 48b51963043e..6e918f2551a7 100644 --- a/apps/dotcom/zero-cache/flyio-replication-manager.template.toml +++ b/apps/dotcom/zero-cache/flyio-replication-manager.template.toml @@ -57,3 +57,5 @@ ZERO_LITESTREAM_CONFIG_PATH = "/etc/litestream.yml" ZERO_LITESTREAM_BACKUP_URL = "s3://__ZERO_R2_BUCKET_NAME/__BACKUP_PATH" ZERO_LOG_LEVEL = "debug" DO_NOT_TRACK = "1" +OTEL_NODE_RESOURCE_DETECTORS = "env,host,os" +OTEL_RESOURCE_ATTRIBUTES = "service.name=zero-rm,service.namespace=dotcom,deployment.environment=__TLDRAW_ENV,service.version=__ZERO_VERSION" diff --git a/apps/dotcom/zero-cache/flyio-view-syncer.template.toml b/apps/dotcom/zero-cache/flyio-view-syncer.template.toml index 48d120adcf71..4ec8cc17befd 100644 --- a/apps/dotcom/zero-cache/flyio-view-syncer.template.toml +++ b/apps/dotcom/zero-cache/flyio-view-syncer.template.toml @@ -58,5 +58,7 @@ ZERO_CVR_MAX_CONNS = "__VS_CVR_MAX_CONNS" ZERO_CHANGE_MAX_CONNS = "__VS_CHANGE_MAX_CONNS" ZERO_LOG_LEVEL = "debug" DO_NOT_TRACK = "1" +OTEL_NODE_RESOURCE_DETECTORS = "env,host,os" +OTEL_RESOURCE_ATTRIBUTES = "service.name=zero-vs,service.namespace=dotcom,deployment.environment=__TLDRAW_ENV,service.version=__ZERO_VERSION" ZERO_LITESTREAM_CONFIG_PATH = "/etc/litestream.yml" ZERO_LITESTREAM_BACKUP_URL = "s3://__ZERO_R2_BUCKET_NAME/__BACKUP_PATH" diff --git a/apps/dotcom/zero-cache/package.json b/apps/dotcom/zero-cache/package.json index 9cbb1b6349f5..0139372e9d64 100644 --- a/apps/dotcom/zero-cache/package.json +++ b/apps/dotcom/zero-cache/package.json @@ -26,7 +26,7 @@ }, "type": "module", "dependencies": { - "@rocicorp/zero": "1.0.0", + "@rocicorp/zero": "1.2.0", "kysely": "^0.28.12", "pg": "^8.13.1" }, diff --git a/apps/examples/src/examples/ui/ui-components-hidden/UiComponentsHiddenExample.tsx b/apps/examples/src/examples/ui/ui-components-hidden/UiComponentsHiddenExample.tsx index c4f0535445b1..c6d0db9f9f12 100644 --- a/apps/examples/src/examples/ui/ui-components-hidden/UiComponentsHiddenExample.tsx +++ b/apps/examples/src/examples/ui/ui-components-hidden/UiComponentsHiddenExample.tsx @@ -29,6 +29,11 @@ const components: Required = { Toasts: null, A11y: null, FollowingIndicator: null, + PeopleMenu: null, + PeopleMenuAvatar: null, + PeopleMenuItem: null, + PeopleMenuFacePile: null, + UserPresenceEditor: null, } export default function UiComponentsHiddenExample() { diff --git a/internal/scripts/deploy-dotcom.ts b/internal/scripts/deploy-dotcom.ts index 05ce9eb48778..c8961165a2a4 100644 --- a/internal/scripts/deploy-dotcom.ts +++ b/internal/scripts/deploy-dotcom.ts @@ -100,6 +100,8 @@ const env = makeEnv([ 'ZERO_R2_BUCKET_NAME', 'ZERO_R2_ACCESS_KEY_ID', 'ZERO_R2_SECRET_ACCESS_KEY', + 'ZERO_OTEL_EXPORTER_OTLP_ENDPOINT', + 'ZERO_OTEL_EXPORTER_OTLP_HEADERS', ]) // Multinode (flyio-multinode) is for staging + production, previews use single as it is faster / cheaper @@ -738,6 +740,8 @@ function updateFlyioReplicationManagerToml(appName: string, backupPath: string): .replaceAll('__VM_CPUS', String(zeroVm.rm.cpus)) .replaceAll('__VM_MEMORY', zeroVm.rm.memory) .replaceAll('__VOLUME_SIZE', zeroVm.volumeSize) + .replaceAll('__TLDRAW_ENV', env.TLDRAW_ENV) + .replaceAll('__ZERO_VERSION', zeroVersion) fs.writeFileSync(flyioTomlFile, updatedContent, 'utf-8') } @@ -774,6 +778,8 @@ function updateFlyioViewSyncerToml( .replaceAll('__VM_CPUS', String(zeroVm.vs.cpus)) .replaceAll('__VM_MEMORY', zeroVm.vs.memory) .replaceAll('__VOLUME_SIZE', zeroVm.volumeSize) + .replaceAll('__TLDRAW_ENV', env.TLDRAW_ENV) + .replaceAll('__ZERO_VERSION', zeroVersion) fs.writeFileSync(flyioTomlFile, updatedContent, 'utf-8') @@ -812,6 +818,8 @@ async function deployZeroViaFlyIoMultiNode() { // Zero uses the AWS SDK to talk to R2 (S3-compatible), so it expects AWS_* env vars `AWS_ACCESS_KEY_ID=${env.ZERO_R2_ACCESS_KEY_ID}`, `AWS_SECRET_ACCESS_KEY=${env.ZERO_R2_SECRET_ACCESS_KEY}`, + `OTEL_EXPORTER_OTLP_ENDPOINT=${env.ZERO_OTEL_EXPORTER_OTLP_ENDPOINT}`, + `OTEL_EXPORTER_OTLP_HEADERS=${env.ZERO_OTEL_EXPORTER_OTLP_HEADERS}`, '-a', flyioReplAppName, ], @@ -841,6 +849,8 @@ async function deployZeroViaFlyIoMultiNode() { // Zero uses the AWS SDK to talk to R2 (S3-compatible), so it expects AWS_* env vars `AWS_ACCESS_KEY_ID=${env.ZERO_R2_ACCESS_KEY_ID}`, `AWS_SECRET_ACCESS_KEY=${env.ZERO_R2_SECRET_ACCESS_KEY}`, + `OTEL_EXPORTER_OTLP_ENDPOINT=${env.ZERO_OTEL_EXPORTER_OTLP_ENDPOINT}`, + `OTEL_EXPORTER_OTLP_HEADERS=${env.ZERO_OTEL_EXPORTER_OTLP_HEADERS}`, '-a', flyioAppName, ], diff --git a/packages/dotcom-shared/package.json b/packages/dotcom-shared/package.json index cb330deea483..1f6d8c8ec93a 100644 --- a/packages/dotcom-shared/package.json +++ b/packages/dotcom-shared/package.json @@ -9,7 +9,7 @@ "files": [], "type": "module", "dependencies": { - "@rocicorp/zero": "1.0.0", + "@rocicorp/zero": "1.2.0", "@tldraw/state": "workspace:*", "@tldraw/store": "workspace:*", "@tldraw/tlschema": "workspace:*", diff --git a/packages/tldraw/api-report.api.md b/packages/tldraw/api-report.api.md index 4d71782ff982..97ac89b2d732 100644 --- a/packages/tldraw/api-report.api.md +++ b/packages/tldraw/api-report.api.md @@ -1079,6 +1079,37 @@ export const DefaultNavigationPanel: NamedExoticComponent; // @public (undocumented) export const DefaultPageMenu: NamedExoticComponent; +// @public (undocumented) +function DefaultPeopleMenu({ children }: DefaultPeopleMenuProps): JSX.Element | null; +export { DefaultPeopleMenu } +export { DefaultPeopleMenu as PeopleMenu } + +// @public (undocumented) +export function DefaultPeopleMenuAvatar({ userId }: TLUiPeopleMenuAvatarProps): JSX.Element | null; + +// @public (undocumented) +export function DefaultPeopleMenuContent({ userIds }: DefaultPeopleMenuContentProps): JSX.Element; + +// @public (undocumented) +export interface DefaultPeopleMenuContentProps { + // (undocumented) + userIds: string[]; +} + +// @public (undocumented) +export function DefaultPeopleMenuFacePile({ userIds, userName, userColor }: TLUiPeopleMenuFacePileProps): JSX.Element; + +// @public (undocumented) +export const DefaultPeopleMenuItem: NamedExoticComponent; + +// @public (undocumented) +interface DefaultPeopleMenuProps { + // (undocumented) + children?: ReactNode; +} +export { DefaultPeopleMenuProps } +export { DefaultPeopleMenuProps as PeopleMenuProps } + // @public (undocumented) export const DefaultQuickActions: NamedExoticComponent; @@ -1106,7 +1137,7 @@ export const defaultShapeTools: readonly [typeof TextShapeTool, typeof DrawShape export const defaultShapeUtils: readonly [typeof TextShapeUtil, typeof BookmarkShapeUtil, typeof DrawShapeUtil, typeof GeoShapeUtil, typeof NoteShapeUtil, typeof LineShapeUtil, typeof FrameShapeUtil, typeof ArrowShapeUtil, typeof HighlightShapeUtil, typeof EmbedShapeUtil, typeof ImageShapeUtil, typeof VideoShapeUtil]; // @public (undocumented) -export function DefaultSharePanel(): JSX.Element; +export function DefaultSharePanel(): JSX.Element | null; // @public (undocumented) export const DefaultStylePanel: NamedExoticComponent; @@ -1142,6 +1173,9 @@ export interface DefaultToolbarProps { // @public (undocumented) export const defaultTools: readonly [typeof EraserTool, typeof HandTool, typeof LaserTool, typeof ZoomTool, typeof SelectTool]; +// @public (undocumented) +export function DefaultUserPresenceEditor(): JSX.Element; + // @public (undocumented) export const DefaultVideoToolbar: NamedExoticComponent; @@ -2767,15 +2801,6 @@ export interface PathBuilderToDOpts { startIdx?: number; } -// @public (undocumented) -export function PeopleMenu({ children }: PeopleMenuProps): JSX.Element | null; - -// @public (undocumented) -export interface PeopleMenuProps { - // (undocumented) - children?: ReactNode; -} - // @public export const PlainTextArea: React_3.ForwardRefExoticComponent>; @@ -4103,6 +4128,14 @@ export interface TLUiComponents { // (undocumented) PageMenu?: ComponentType | null; // (undocumented) + PeopleMenu?: ComponentType | null; + // (undocumented) + PeopleMenuAvatar?: ComponentType | null; + // (undocumented) + PeopleMenuFacePile?: ComponentType | null; + // (undocumented) + PeopleMenuItem?: ComponentType | null; + // (undocumented) QuickActions?: ComponentType | null; // (undocumented) RichTextToolbar?: ComponentType | null; @@ -4117,6 +4150,8 @@ export interface TLUiComponents { // (undocumented) TopPanel?: ComponentType | null; // (undocumented) + UserPresenceEditor?: ComponentType | null; + // (undocumented) VideoToolbar?: ComponentType | null; // (undocumented) ZoomMenu?: ComponentType | null; @@ -4855,6 +4890,28 @@ export interface TLUiOverrides { translations?: TLUiTranslationProviderProps['overrides']; } +// @public (undocumented) +export interface TLUiPeopleMenuAvatarProps { + // (undocumented) + userId: string; +} + +// @public (undocumented) +export interface TLUiPeopleMenuFacePileProps { + // (undocumented) + userColor: string; + // (undocumented) + userIds: string[]; + // (undocumented) + userName: string; +} + +// @public (undocumented) +export interface TLUiPeopleMenuItemProps { + // (undocumented) + userId: string; +} + // @public (undocumented) export interface TLUiPopoverContentProps { // (undocumented) diff --git a/packages/tldraw/src/index.ts b/packages/tldraw/src/index.ts index 9cb44aad05a2..1b31fc8fff31 100644 --- a/packages/tldraw/src/index.ts +++ b/packages/tldraw/src/index.ts @@ -1,6 +1,7 @@ /// import { registerTldrawLibraryVersion } from '@tldraw/editor' +import { DefaultPeopleMenu } from './lib/ui/components/SharePanel/DefaultPeopleMenu' export { getPointsFromDrawSegment, getPointsFromDrawSegments } from './lib/shapes/draw/getPath' export { PathBuilder, @@ -506,7 +507,30 @@ export { } from './lib/ui/components/QuickActions/DefaultQuickActions' export { DefaultQuickActionsContent } from './lib/ui/components/QuickActions/DefaultQuickActionsContent' export { DefaultSharePanel } from './lib/ui/components/SharePanel/DefaultSharePanel' -export { PeopleMenu, type PeopleMenuProps } from './lib/ui/components/SharePanel/PeopleMenu' +export { + DefaultPeopleMenu, + type DefaultPeopleMenuProps, + // legacy + DefaultPeopleMenu as PeopleMenu, + type DefaultPeopleMenuProps as PeopleMenuProps, +} from './lib/ui/components/SharePanel/DefaultPeopleMenu' +export { + DefaultPeopleMenuContent, + type DefaultPeopleMenuContentProps, +} from './lib/ui/components/SharePanel/DefaultPeopleMenuContent' +export { + DefaultPeopleMenuFacePile, + type TLUiPeopleMenuFacePileProps, +} from './lib/ui/components/SharePanel/DefaultPeopleMenuFacePile' +export { + type TLUiPeopleMenuAvatarProps, + DefaultPeopleMenuAvatar, +} from './lib/ui/components/SharePanel/DefaultPeopleMenuAvatar' +export { + type TLUiPeopleMenuItemProps, + DefaultPeopleMenuItem, +} from './lib/ui/components/SharePanel/DefaultPeopleMenuItem' +export { DefaultUserPresenceEditor } from './lib/ui/components/SharePanel/DefaultUserPresenceEditor' export { Spinner } from './lib/ui/components/Spinner' export { DefaultStylePanel, diff --git a/packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenu.tsx b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenu.tsx similarity index 50% rename from packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenu.tsx rename to packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenu.tsx index 0e91479d6a4c..96f6c9264f8b 100644 --- a/packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenu.tsx +++ b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenu.tsx @@ -1,24 +1,20 @@ import { useContainer, useEditor, usePeerIds, useValue } from '@tldraw/editor' import { Popover as _Popover } from 'radix-ui' import { ReactNode } from 'react' -import { PORTRAIT_BREAKPOINT } from '../../constants' -import { useBreakpoint } from '../../context/breakpoints' +import { useTldrawUiComponents } from '../../context/components' import { useCollaborationStatus } from '../../hooks/useCollaborationStatus' import { useMenuIsOpen } from '../../hooks/useMenuIsOpen' import { useDirection, useTranslation } from '../../hooks/useTranslation/useTranslation' import { OfflineIndicator } from '../OfflineIndicator/OfflineIndicator' -import { PeopleMenuAvatar } from './PeopleMenuAvatar' -import { PeopleMenuItem } from './PeopleMenuItem' -import { PeopleMenuMore } from './PeopleMenuMore' -import { UserPresenceEditor } from './UserPresenceEditor' +import { DefaultPeopleMenuContent } from './DefaultPeopleMenuContent' /** @public */ -export interface PeopleMenuProps { +export interface DefaultPeopleMenuProps { children?: ReactNode } /** @public @react */ -export function PeopleMenu({ children }: PeopleMenuProps) { +export function DefaultPeopleMenu({ children }: DefaultPeopleMenuProps) { const msg = useTranslation() const dir = useDirection() @@ -30,37 +26,28 @@ export function PeopleMenu({ children }: PeopleMenuProps) { const userName = useValue('user', () => editor.user.getName(), [editor]) const [isOpen, onOpenChange] = useMenuIsOpen('people menu') - const breakpoint = useBreakpoint() - const maxAvatars = breakpoint <= PORTRAIT_BREAKPOINT.MOBILE_XS ? 1 : 5 const collaborationStatus = useCollaborationStatus() + const { PeopleMenuFacePile } = useTldrawUiComponents() + if (collaborationStatus === 'offline') { return } - if (!userIds.length) return null + if (!userIds.length || (!children && !PeopleMenuFacePile)) { + return null + } + + const content = children ?? return ( <_Popover.Root onOpenChange={onOpenChange} open={isOpen}> <_Popover.Trigger dir={dir} asChild> <_Popover.Portal container={container}> @@ -71,19 +58,7 @@ export function PeopleMenu({ children }: PeopleMenuProps) { sideOffset={2} collisionPadding={4} > -
-
- -
- {userIds.length > 0 && ( -
- {userIds.map((userId) => { - return - })} -
- )} - {children} -
+
{content}
diff --git a/packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenuAvatar.tsx b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.tsx similarity index 60% rename from packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenuAvatar.tsx rename to packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.tsx index cea47e799efa..bfaad4efd3d9 100644 --- a/packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenuAvatar.tsx +++ b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenuAvatar.tsx @@ -1,13 +1,18 @@ import { usePresence } from '@tldraw/editor' -export function PeopleMenuAvatar({ userId }: { userId: string }) { +/** @public */ +export interface TLUiPeopleMenuAvatarProps { + userId: string +} + +/** @public @react */ +export function DefaultPeopleMenuAvatar({ userId }: TLUiPeopleMenuAvatarProps) { const presence = usePresence(userId) if (!presence) return null return (
+ {UserPresenceEditor && ( +
+ +
+ )} + {PeopleMenuItem && userIds.length > 0 && ( +
+ {userIds.map((userId) => { + return + })} +
+ )} + + ) +} diff --git a/packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.tsx b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.tsx new file mode 100644 index 000000000000..03f044b042b5 --- /dev/null +++ b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenuFacePile.tsx @@ -0,0 +1,46 @@ +import { PORTRAIT_BREAKPOINT } from '../../constants' +import { useBreakpoint } from '../../context/breakpoints' +import { useTldrawUiComponents } from '../../context/components' + +/** @public */ +export interface TLUiPeopleMenuFacePileProps { + userIds: string[] + userName: string + userColor: string +} + +/** @public @react */ +export function DefaultPeopleMenuFacePile({ + userIds, + userName, + userColor, +}: TLUiPeopleMenuFacePileProps) { + const { PeopleMenuAvatar } = useTldrawUiComponents() + + const breakpoint = useBreakpoint() + const maxAvatars = breakpoint <= PORTRAIT_BREAKPOINT.MOBILE_XS ? 1 : 5 + + return ( +
+ {PeopleMenuAvatar && + userIds + .slice(-maxAvatars) + .map((userId) => )} + {userIds.length > 0 && ( +
+ {userName?.[0] ?? ''} +
+ )} + {userIds.length > maxAvatars && ( +
+ {Math.abs(userIds.length - maxAvatars)} +
+ )} +
+ ) +} diff --git a/packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenuItem.tsx b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenuItem.tsx similarity index 91% rename from packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenuItem.tsx rename to packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenuItem.tsx index 6f4bea21c249..564e6062087c 100644 --- a/packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenuItem.tsx +++ b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultPeopleMenuItem.tsx @@ -7,7 +7,15 @@ import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon' import { TldrawUiRow } from '../primitives/layout' import { TldrawUiIcon } from '../primitives/TldrawUiIcon' -export const PeopleMenuItem = track(function PeopleMenuItem({ userId }: { userId: string }) { +/** @public */ +export interface TLUiPeopleMenuItemProps { + userId: string +} + +/** @public @react */ +export const DefaultPeopleMenuItem = track(function DefaultPeopleMenuItem({ + userId, +}: TLUiPeopleMenuItemProps) { const editor = useEditor() const msg = useTranslation() const trackEvent = useUiEvents() diff --git a/packages/tldraw/src/lib/ui/components/SharePanel/DefaultSharePanel.tsx b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultSharePanel.tsx index 86a178814f0c..6aef5913f301 100644 --- a/packages/tldraw/src/lib/ui/components/SharePanel/DefaultSharePanel.tsx +++ b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultSharePanel.tsx @@ -1,7 +1,11 @@ -import { PeopleMenu } from './PeopleMenu' +import { useTldrawUiComponents } from '../../context/components' /** @public @react */ export function DefaultSharePanel() { + const { PeopleMenu } = useTldrawUiComponents() + + if (!PeopleMenu) return null + return (
diff --git a/packages/tldraw/src/lib/ui/components/SharePanel/UserPresenceEditor.tsx b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultUserPresenceEditor.tsx similarity index 97% rename from packages/tldraw/src/lib/ui/components/SharePanel/UserPresenceEditor.tsx rename to packages/tldraw/src/lib/ui/components/SharePanel/DefaultUserPresenceEditor.tsx index cd8466007cda..81fb35edf7b8 100644 --- a/packages/tldraw/src/lib/ui/components/SharePanel/UserPresenceEditor.tsx +++ b/packages/tldraw/src/lib/ui/components/SharePanel/DefaultUserPresenceEditor.tsx @@ -7,7 +7,8 @@ import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon' import { TldrawUiInput } from '../primitives/TldrawUiInput' import { UserPresenceColorPicker } from './UserPresenceColorPicker' -export function UserPresenceEditor() { +/** @public @react */ +export function DefaultUserPresenceEditor() { const editor = useEditor() const trackEvent = useUiEvents() const userName = useValue('userName', () => editor.user.getName(), []) diff --git a/packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenuMore.tsx b/packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenuMore.tsx deleted file mode 100644 index c4d1e442e548..000000000000 --- a/packages/tldraw/src/lib/ui/components/SharePanel/PeopleMenuMore.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export function PeopleMenuMore({ count }: { count: number }) { - return
{Math.abs(count)}
-} diff --git a/packages/tldraw/src/lib/ui/components/SharePanel/UserPresenceColorPicker.tsx b/packages/tldraw/src/lib/ui/components/SharePanel/UserPresenceColorPicker.tsx index 9d21126fa6c5..d37bb227bffa 100644 --- a/packages/tldraw/src/lib/ui/components/SharePanel/UserPresenceColorPicker.tsx +++ b/packages/tldraw/src/lib/ui/components/SharePanel/UserPresenceColorPicker.tsx @@ -7,6 +7,7 @@ import { TldrawUiButton } from '../primitives/Button/TldrawUiButton' import { TldrawUiButtonIcon } from '../primitives/Button/TldrawUiButtonIcon' import { TldrawUiGrid } from '../primitives/layout' +/** @public @react */ export const UserPresenceColorPicker = track(function UserPresenceColorPicker() { const editor = useEditor() const container = useContainer() diff --git a/packages/tldraw/src/lib/ui/context/components.tsx b/packages/tldraw/src/lib/ui/context/components.tsx index 56238b3a48e5..caedb8fbfc76 100644 --- a/packages/tldraw/src/lib/ui/context/components.tsx +++ b/packages/tldraw/src/lib/ui/context/components.tsx @@ -32,7 +32,21 @@ import { DefaultQuickActions, TLUiQuickActionsProps, } from '../components/QuickActions/DefaultQuickActions' +import { DefaultPeopleMenu } from '../components/SharePanel/DefaultPeopleMenu' +import { + DefaultPeopleMenuAvatar, + TLUiPeopleMenuAvatarProps, +} from '../components/SharePanel/DefaultPeopleMenuAvatar' +import { + TLUiPeopleMenuFacePileProps, + DefaultPeopleMenuFacePile, +} from '../components/SharePanel/DefaultPeopleMenuFacePile' +import { + DefaultPeopleMenuItem, + TLUiPeopleMenuItemProps, +} from '../components/SharePanel/DefaultPeopleMenuItem' import { DefaultSharePanel } from '../components/SharePanel/DefaultSharePanel' +import { DefaultUserPresenceEditor } from '../components/SharePanel/DefaultUserPresenceEditor' import { DefaultStylePanel, TLUiStylePanelProps } from '../components/StylePanel/DefaultStylePanel' import { DefaultToasts } from '../components/Toasts' import { DefaultImageToolbar } from '../components/Toolbar/DefaultImageToolbar' @@ -73,6 +87,11 @@ export interface TLUiComponents { Toasts?: ComponentType | null A11y?: ComponentType | null FollowingIndicator?: ComponentType | null + PeopleMenu?: ComponentType | null + PeopleMenuAvatar?: ComponentType | null + PeopleMenuItem?: ComponentType | null + PeopleMenuFacePile?: ComponentType | null + UserPresenceEditor?: ComponentType | null } const TldrawUiComponentsContext = createContext(null) @@ -121,6 +140,11 @@ export function TldrawUiComponentsProvider({ Toasts: DefaultToasts, A11y: DefaultA11yAnnouncer, FollowingIndicator: DefaultFollowingIndicator, + PeopleMenu: DefaultPeopleMenu, + PeopleMenuAvatar: DefaultPeopleMenuAvatar, + PeopleMenuItem: DefaultPeopleMenuItem, + PeopleMenuFacePile: DefaultPeopleMenuFacePile, + UserPresenceEditor: DefaultUserPresenceEditor, ..._overrides, }), [_overrides, showCollaborationUi] diff --git a/packages/tldraw/src/lib/ui/hooks/useClipboardEvents.ts b/packages/tldraw/src/lib/ui/hooks/useClipboardEvents.ts index 639fbf8fb655..00ee4e5c32e9 100644 --- a/packages/tldraw/src/lib/ui/hooks/useClipboardEvents.ts +++ b/packages/tldraw/src/lib/ui/hooks/useClipboardEvents.ts @@ -918,7 +918,9 @@ export function useNativeClipboardEvents() { // input instead; e.g. when pasting text into a text shape's content if (editor.getEditingShapeId() !== null || areShortcutsDisabled(editor)) return - // Cmd+Shift+V / Ctrl+Shift+V = paste as plain text (no formatting) + // Cmd+Shift+V / Ctrl+Shift+V = paste as plain text (no formatting). + // If there's no plain text on the clipboard (e.g., a copied PNG), fall + // through to the normal paste handler so the file still gets pasted. if (nativeShiftKey) { const text = e.clipboardData?.getData('text/plain') if (text?.trim()) { @@ -927,10 +929,10 @@ export function useNativeClipboardEvents() { : editor.getViewportPageBounds().center editor.markHistoryStoppingPoint('paste') defaultHandleExternalTextContent(editor, { text, point }) + preventDefault(e) + trackEvent('paste', { source: 'kbd' }) + return } - preventDefault(e) - trackEvent('paste', { source: 'kbd' }) - return } // Cmd+V: paste at center by default, or at cursor when the preference is on. diff --git a/yarn.lock b/yarn.lock index 3c21d0e5f365..4796b8d00d8d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6712,6 +6712,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-android-arm-eabi@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-android-arm-eabi@npm:0.43.0" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@oxfmt/binding-android-arm64@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-android-arm64@npm:0.41.0" @@ -6719,6 +6726,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-android-arm64@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-android-arm64@npm:0.43.0" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@oxfmt/binding-darwin-arm64@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-darwin-arm64@npm:0.41.0" @@ -6726,6 +6740,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-darwin-arm64@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-darwin-arm64@npm:0.43.0" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@oxfmt/binding-darwin-x64@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-darwin-x64@npm:0.41.0" @@ -6733,6 +6754,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-darwin-x64@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-darwin-x64@npm:0.43.0" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@oxfmt/binding-freebsd-x64@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-freebsd-x64@npm:0.41.0" @@ -6740,6 +6768,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-freebsd-x64@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-freebsd-x64@npm:0.43.0" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@oxfmt/binding-linux-arm-gnueabihf@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-arm-gnueabihf@npm:0.41.0" @@ -6747,6 +6782,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-arm-gnueabihf@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-arm-gnueabihf@npm:0.43.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@oxfmt/binding-linux-arm-musleabihf@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-arm-musleabihf@npm:0.41.0" @@ -6754,6 +6796,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-arm-musleabihf@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-arm-musleabihf@npm:0.43.0" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@oxfmt/binding-linux-arm64-gnu@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-arm64-gnu@npm:0.41.0" @@ -6761,6 +6810,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-arm64-gnu@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-arm64-gnu@npm:0.43.0" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@oxfmt/binding-linux-arm64-musl@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-arm64-musl@npm:0.41.0" @@ -6768,6 +6824,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-arm64-musl@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-arm64-musl@npm:0.43.0" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + "@oxfmt/binding-linux-ppc64-gnu@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-ppc64-gnu@npm:0.41.0" @@ -6775,6 +6838,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-ppc64-gnu@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-ppc64-gnu@npm:0.43.0" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + "@oxfmt/binding-linux-riscv64-gnu@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-riscv64-gnu@npm:0.41.0" @@ -6782,6 +6852,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-riscv64-gnu@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-riscv64-gnu@npm:0.43.0" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + "@oxfmt/binding-linux-riscv64-musl@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-riscv64-musl@npm:0.41.0" @@ -6789,6 +6866,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-riscv64-musl@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-riscv64-musl@npm:0.43.0" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + "@oxfmt/binding-linux-s390x-gnu@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-s390x-gnu@npm:0.41.0" @@ -6796,6 +6880,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-s390x-gnu@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-s390x-gnu@npm:0.43.0" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@oxfmt/binding-linux-x64-gnu@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-x64-gnu@npm:0.41.0" @@ -6803,6 +6894,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-x64-gnu@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-x64-gnu@npm:0.43.0" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@oxfmt/binding-linux-x64-musl@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-linux-x64-musl@npm:0.41.0" @@ -6810,6 +6908,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-linux-x64-musl@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-linux-x64-musl@npm:0.43.0" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + "@oxfmt/binding-openharmony-arm64@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-openharmony-arm64@npm:0.41.0" @@ -6817,6 +6922,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-openharmony-arm64@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-openharmony-arm64@npm:0.43.0" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@oxfmt/binding-win32-arm64-msvc@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-win32-arm64-msvc@npm:0.41.0" @@ -6824,6 +6936,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-win32-arm64-msvc@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-win32-arm64-msvc@npm:0.43.0" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@oxfmt/binding-win32-ia32-msvc@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-win32-ia32-msvc@npm:0.41.0" @@ -6831,6 +6950,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-win32-ia32-msvc@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-win32-ia32-msvc@npm:0.43.0" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@oxfmt/binding-win32-x64-msvc@npm:0.41.0": version: 0.41.0 resolution: "@oxfmt/binding-win32-x64-msvc@npm:0.41.0" @@ -6838,6 +6964,13 @@ __metadata: languageName: node linkType: hard +"@oxfmt/binding-win32-x64-msvc@npm:0.43.0": + version: 0.43.0 + resolution: "@oxfmt/binding-win32-x64-msvc@npm:0.43.0" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@oxlint-tsgolint/darwin-arm64@npm:0.18.1": version: 0.18.1 resolution: "@oxlint-tsgolint/darwin-arm64@npm:0.18.1" @@ -8672,9 +8805,9 @@ __metadata: languageName: node linkType: hard -"@rocicorp/zero@npm:1.0.0": - version: 1.0.0 - resolution: "@rocicorp/zero@npm:1.0.0" +"@rocicorp/zero@npm:1.2.0": + version: 1.2.0 + resolution: "@rocicorp/zero@npm:1.2.0" dependencies: "@badrap/valita": "npm:0.3.11" "@databases/escape-identifier": "npm:^1.0.3" @@ -8707,7 +8840,7 @@ __metadata: cloudevents: "npm:^10.0.0" command-line-args: "npm:^6.0.1" command-line-usage: "npm:^7.0.3" - compare-utf8: "npm:^0.1.1" + compare-utf8: "npm:^0.2.0" defu: "npm:^6.1.4" eventemitter3: "npm:^5.0.1" fastify: "npm:^5.0.0" @@ -8717,10 +8850,10 @@ __metadata: json-custom-numbers: "npm:^3.1.1" kasi: "npm:^1.1.0" nanoid: "npm:^5.1.2" + oxfmt: "npm:^0.43.0" parse-prometheus-text-format: "npm:^1.1.1" pg-format: "npm:pg-format-fix@^1.0.5" postgres: "npm:3.4.7" - prettier: "npm:^3.8.1" semver: "npm:^7.5.4" tsx: "npm:^4.21.0" url-pattern: "npm:^1.0.3" @@ -8743,7 +8876,7 @@ __metadata: zero-cache-dev: out/zero/src/zero-cache-dev.js zero-deploy-permissions: out/zero/src/deploy-permissions.js zero-out: out/zero/src/zero-out.js - checksum: 10/1443dfa77642010faab6f67353d3773a2408eaa1dc08889af5b391aa288af059a2d1fae43e29a7ac504a91c246a562d669a6d4deffd1a1074bb85f06637583c6 + checksum: 10/634a232f67cd04cdecfff912fa9bf28fc44c8f4c8f794a89c8a7b80f0c8fe629bb6e9dd2dd284ecc98bc06fca1aa5281521126daca08be2d2dd8f38c11c0e0c3 languageName: node linkType: hard @@ -11113,7 +11246,7 @@ __metadata: version: 0.0.0-use.local resolution: "@tldraw/dotcom-shared@workspace:packages/dotcom-shared" dependencies: - "@rocicorp/zero": "npm:1.0.0" + "@rocicorp/zero": "npm:1.2.0" "@tldraw/state": "workspace:*" "@tldraw/store": "workspace:*" "@tldraw/tlschema": "workspace:*" @@ -11137,7 +11270,7 @@ __metadata: "@clerk/backend": "npm:^1.23.7" "@cloudflare/workers-types": "npm:^4.20260302.0" "@pierre/storage": "npm:^1.1.0" - "@rocicorp/zero": "npm:1.0.0" + "@rocicorp/zero": "npm:1.2.0" "@supabase/auth-helpers-remix": "npm:^0.2.6" "@supabase/supabase-js": "npm:^2.48.1" "@tldraw/dotcom-shared": "workspace:*" @@ -11582,7 +11715,7 @@ __metadata: version: 0.0.0-use.local resolution: "@tldraw/zero-cache@workspace:apps/dotcom/zero-cache" dependencies: - "@rocicorp/zero": "npm:1.0.0" + "@rocicorp/zero": "npm:1.2.0" concurrently: "npm:^9.1.2" dotenv: "npm:^16.4.7" esbuild: "npm:^0.27.4" @@ -15868,10 +16001,10 @@ __metadata: languageName: node linkType: hard -"compare-utf8@npm:^0.1.1": - version: 0.1.1 - resolution: "compare-utf8@npm:0.1.1" - checksum: 10/8a0229ace61c7709030915107dae1e2f0a460e8c9fc4b427576c355576548d1d55500f63109ff9166bd38944c7c37b3eb7eb501d8707569f9e5bf7643c12890a +"compare-utf8@npm:^0.2.0": + version: 0.2.0 + resolution: "compare-utf8@npm:0.2.0" + checksum: 10/d544c38d763984dbf03afa44554b636aa34fd59aef1ae4fe9b7989eb7688f8ffe0e7b243e5eb4ccf0c5cbe0f9accb180d1aa6dfc9f6b5659ab1cbd34315334f2 languageName: node linkType: hard @@ -17078,7 +17211,7 @@ __metadata: "@formatjs/cli": "npm:^6.5.1" "@formatjs/unplugin": "npm:^1.1.0" "@playwright/test": "npm:^1.58.2" - "@rocicorp/zero": "npm:1.0.0" + "@rocicorp/zero": "npm:1.2.0" "@sentry/cli": "npm:^2.41.1" "@sentry/integrations": "npm:^7.120.3" "@sentry/react": "npm:^7.120.3" @@ -24474,6 +24607,75 @@ __metadata: languageName: node linkType: hard +"oxfmt@npm:^0.43.0": + version: 0.43.0 + resolution: "oxfmt@npm:0.43.0" + dependencies: + "@oxfmt/binding-android-arm-eabi": "npm:0.43.0" + "@oxfmt/binding-android-arm64": "npm:0.43.0" + "@oxfmt/binding-darwin-arm64": "npm:0.43.0" + "@oxfmt/binding-darwin-x64": "npm:0.43.0" + "@oxfmt/binding-freebsd-x64": "npm:0.43.0" + "@oxfmt/binding-linux-arm-gnueabihf": "npm:0.43.0" + "@oxfmt/binding-linux-arm-musleabihf": "npm:0.43.0" + "@oxfmt/binding-linux-arm64-gnu": "npm:0.43.0" + "@oxfmt/binding-linux-arm64-musl": "npm:0.43.0" + "@oxfmt/binding-linux-ppc64-gnu": "npm:0.43.0" + "@oxfmt/binding-linux-riscv64-gnu": "npm:0.43.0" + "@oxfmt/binding-linux-riscv64-musl": "npm:0.43.0" + "@oxfmt/binding-linux-s390x-gnu": "npm:0.43.0" + "@oxfmt/binding-linux-x64-gnu": "npm:0.43.0" + "@oxfmt/binding-linux-x64-musl": "npm:0.43.0" + "@oxfmt/binding-openharmony-arm64": "npm:0.43.0" + "@oxfmt/binding-win32-arm64-msvc": "npm:0.43.0" + "@oxfmt/binding-win32-ia32-msvc": "npm:0.43.0" + "@oxfmt/binding-win32-x64-msvc": "npm:0.43.0" + tinypool: "npm:2.1.0" + dependenciesMeta: + "@oxfmt/binding-android-arm-eabi": + optional: true + "@oxfmt/binding-android-arm64": + optional: true + "@oxfmt/binding-darwin-arm64": + optional: true + "@oxfmt/binding-darwin-x64": + optional: true + "@oxfmt/binding-freebsd-x64": + optional: true + "@oxfmt/binding-linux-arm-gnueabihf": + optional: true + "@oxfmt/binding-linux-arm-musleabihf": + optional: true + "@oxfmt/binding-linux-arm64-gnu": + optional: true + "@oxfmt/binding-linux-arm64-musl": + optional: true + "@oxfmt/binding-linux-ppc64-gnu": + optional: true + "@oxfmt/binding-linux-riscv64-gnu": + optional: true + "@oxfmt/binding-linux-riscv64-musl": + optional: true + "@oxfmt/binding-linux-s390x-gnu": + optional: true + "@oxfmt/binding-linux-x64-gnu": + optional: true + "@oxfmt/binding-linux-x64-musl": + optional: true + "@oxfmt/binding-openharmony-arm64": + optional: true + "@oxfmt/binding-win32-arm64-msvc": + optional: true + "@oxfmt/binding-win32-ia32-msvc": + optional: true + "@oxfmt/binding-win32-x64-msvc": + optional: true + bin: + oxfmt: bin/oxfmt + checksum: 10/99ab1ac89e23eeede08f1cba5a8daebf8704527968621e0e23b7a30e9de1d3774e44176b8f7e1b80873450c191250dbec05ba344a9d395843f6216fbfe6d80ce + languageName: node + linkType: hard + "oxlint-tsgolint@npm:^0.18.0": version: 0.18.1 resolution: "oxlint-tsgolint@npm:0.18.1" @@ -25507,7 +25709,7 @@ __metadata: languageName: node linkType: hard -"prettier@npm:^3.2.5, prettier@npm:^3.8.1": +"prettier@npm:^3.2.5": version: 3.8.1 resolution: "prettier@npm:3.8.1" bin: