diff --git a/.changeset/web-app-server-keepalive.md b/.changeset/web-app-server-keepalive.md new file mode 100644 index 00000000..4381bd70 --- /dev/null +++ b/.changeset/web-app-server-keepalive.md @@ -0,0 +1,5 @@ +--- +"@inkeep/open-knowledge": patch +--- + +Keep an open browser tab's `ok start` server alive. The web editor now holds a single, app-lifetime `/collab/keepalive` WebSocket — the same presence-invisible keepalive the desktop app and MCP shim already use — so the server's 30-minute idle-shutdown can no longer fire out from under an open tab when no document is focused or during a brief reconnect. Previously, with no doc open the only liveness signal was the per-document collab connections, so an idle tab could lose its server and every editor/tool call would fail until reload. Closing the tab still lets the server idle-shut-down normally, and the keepalive reconnects across a server restart on a new port. Multiple tabs each hold their own keepalive. The keepalive adds no presence-bar entry. diff --git a/docs/scripts/generate-og-wordmark.ts b/docs/scripts/generate-og-wordmark.ts index 2acc8793..a28d174c 100644 --- a/docs/scripts/generate-og-wordmark.ts +++ b/docs/scripts/generate-og-wordmark.ts @@ -1,6 +1,7 @@ import { readFileSync, writeFileSync } from 'node:fs'; import path from 'node:path'; + const root = path.resolve(import.meta.dirname, '..'); const svg = readFileSync(path.join(root, 'public', 'ok-wordmark.svg')); const dataUrl = `data:image/svg+xml;base64,${svg.toString('base64')}`; diff --git a/docs/src/app/(home)/sections/eng-specs-graphic.tsx b/docs/src/app/(home)/sections/eng-specs-graphic.tsx index b208fee1..c62ffde1 100644 --- a/docs/src/app/(home)/sections/eng-specs-graphic.tsx +++ b/docs/src/app/(home)/sections/eng-specs-graphic.tsx @@ -1,5 +1,6 @@ import { Code, Sparkles } from 'lucide-react'; + const RINGS = [56, 82, 108] as const; const RING_CENTER = { x: 80, y: 45 }; diff --git a/docs/src/app/(home)/sections/knowledge-base-graphic.tsx b/docs/src/app/(home)/sections/knowledge-base-graphic.tsx index 112feb5b..1482256c 100644 --- a/docs/src/app/(home)/sections/knowledge-base-graphic.tsx +++ b/docs/src/app/(home)/sections/knowledge-base-graphic.tsx @@ -1,6 +1,7 @@ import { Sparkles } from 'lucide-react'; import type { CSSProperties } from 'react'; + const RINGS = [56, 82, 108] as const; const RING_CENTER = { x: 80, y: 45 }; diff --git a/docs/src/components/copy-prompt.tsx b/docs/src/components/copy-prompt.tsx index 6a74157b..28ca3b40 100644 --- a/docs/src/components/copy-prompt.tsx +++ b/docs/src/components/copy-prompt.tsx @@ -26,7 +26,8 @@ export function CopyPrompt({ children }: CopyPromptProps) { await navigator.clipboard.writeText(text); setCopied(true); window.setTimeout(() => setCopied(false), 2000); - } catch {} + } catch { + } }; return ( diff --git a/docs/src/components/ok-editor/bubble-menu.tsx b/docs/src/components/ok-editor/bubble-menu.tsx index 2913be65..145b7cbe 100644 --- a/docs/src/components/ok-editor/bubble-menu.tsx +++ b/docs/src/components/ok-editor/bubble-menu.tsx @@ -113,7 +113,8 @@ export function OkBubbleMenu({ editor }: { editor: Editor }) { popup.style.left = `${x}px`; popup.style.top = `${y}px`; }) - .catch(() => {}); + .catch(() => { + }); }); }; diff --git a/docs/src/components/ok-editor/drag-handle.tsx b/docs/src/components/ok-editor/drag-handle.tsx index dd451784..da2e313d 100644 --- a/docs/src/components/ok-editor/drag-handle.tsx +++ b/docs/src/components/ok-editor/drag-handle.tsx @@ -68,7 +68,8 @@ export const BlockDragHandle = Extension.create({ if (curPos < 0) return; try { editor.chain().focus().setNodeSelection(curPos).run(); - } catch {} + } catch { + } }); return [ diff --git a/docs/src/components/ok-editor/preview-code-block.tsx b/docs/src/components/ok-editor/preview-code-block.tsx index 0c114fd9..2684ed1f 100644 --- a/docs/src/components/ok-editor/preview-code-block.tsx +++ b/docs/src/components/ok-editor/preview-code-block.tsx @@ -9,6 +9,7 @@ import { } from '@tiptap/react'; import { Trash2 } from 'lucide-react'; + const THEME_VARS = PREVIEW_THEME_TOKENS.map((t) => `${t.name}:${t.light}`).join(';'); const CHART_PALETTE = [ diff --git a/docs/src/components/overview-blocks.tsx b/docs/src/components/overview-blocks.tsx index d5404a15..3e563962 100644 --- a/docs/src/components/overview-blocks.tsx +++ b/docs/src/components/overview-blocks.tsx @@ -1,6 +1,7 @@ import { ArrowRight, Bot, Database, type LucideIcon, NotebookPen } from 'lucide-react'; import type { ReactNode } from 'react'; + interface Layer { k: string; Icon: LucideIcon; diff --git a/docs/src/components/page-markdown-actions.tsx b/docs/src/components/page-markdown-actions.tsx index 46d39f08..73c10f4b 100644 --- a/docs/src/components/page-markdown-actions.tsx +++ b/docs/src/components/page-markdown-actions.tsx @@ -34,7 +34,8 @@ export function PageMarkdownActions({ await navigator.clipboard.writeText(await res.text()); setCopied(true); window.setTimeout(() => setCopied(false), 2000); - } catch {} + } catch { + } }; const prompt = `Read ${markdownUrl} so I can ask questions about it.`; diff --git a/docs/src/components/tabs.test.ts b/docs/src/components/tabs.test.ts index 60f3e2a7..df62a0f0 100644 --- a/docs/src/components/tabs.test.ts +++ b/docs/src/components/tabs.test.ts @@ -1,3 +1,4 @@ + import { describe, expect, test } from 'bun:test'; import { composeTabId, slugifyTabId } from './tabs.tsx'; diff --git a/docs/src/components/tabs.tsx b/docs/src/components/tabs.tsx index b58677dc..03179edf 100644 --- a/docs/src/components/tabs.tsx +++ b/docs/src/components/tabs.tsx @@ -1,5 +1,6 @@ 'use client'; + import { Tab as FumadocsTab, Tabs as FumadocsTabs, diff --git a/docs/src/lib/deferred-share.ts b/docs/src/lib/deferred-share.ts index 298df0ad..bdba626a 100644 --- a/docs/src/lib/deferred-share.ts +++ b/docs/src/lib/deferred-share.ts @@ -1,3 +1,4 @@ + export const PENDING_SHARE_COOKIE = 'ok_pending_share'; export const PENDING_SHARE_MAX_AGE_SECONDS = 7 * 24 * 60 * 60; diff --git a/docs/src/lib/share-splash.ts b/docs/src/lib/share-splash.ts index 47ac185c..3b94241a 100644 --- a/docs/src/lib/share-splash.ts +++ b/docs/src/lib/share-splash.ts @@ -1,3 +1,4 @@ + import { SITE_NAME } from './site'; const SHARE_URL_VERSION_V1 = 0x01; diff --git a/docs/src/lib/track.ts b/docs/src/lib/track.ts index f541e2cf..aff26e3e 100644 --- a/docs/src/lib/track.ts +++ b/docs/src/lib/track.ts @@ -1,5 +1,6 @@ import { after } from 'next/server'; + const POSTHOG_CAPTURE_URL = 'https://us.i.posthog.com/capture/'; const CAPTURE_TIMEOUT_MS = 3_000; diff --git a/packages/app/playwright.a11y.config.ts b/packages/app/playwright.a11y.config.ts index b7a2a09b..b9061eca 100644 --- a/packages/app/playwright.a11y.config.ts +++ b/packages/app/playwright.a11y.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from '@playwright/test'; + const isCI = !!process.env.CI; export default defineConfig({ diff --git a/packages/app/playwright.config.ts b/packages/app/playwright.config.ts index f6c9ec05..910a7870 100644 --- a/packages/app/playwright.config.ts +++ b/packages/app/playwright.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from '@playwright/test'; + const isCI = !!process.env.CI; export default defineConfig({ diff --git a/packages/app/playwright.visual.config.ts b/packages/app/playwright.visual.config.ts index e9f60626..c522418a 100644 --- a/packages/app/playwright.visual.config.ts +++ b/packages/app/playwright.visual.config.ts @@ -1,5 +1,6 @@ import { defineConfig } from '@playwright/test'; + const isCI = !!process.env.CI; export default defineConfig({ diff --git a/packages/app/src/App.dom.test.tsx b/packages/app/src/App.dom.test.tsx index f2ea683f..041444e6 100644 --- a/packages/app/src/App.dom.test.tsx +++ b/packages/app/src/App.dom.test.tsx @@ -101,6 +101,10 @@ mock.module('@/lib/api-config', () => ({ fetchApiConfig: (...args: Parameters) => fetchApiConfigMock(...args), })); +mock.module('@/lib/use-server-keepalive', () => ({ + useServerKeepalive: () => {}, +})); + mock.module('@/lib/single-file-mode', () => ({ SingleFileModeProvider: ({ children }: { children: ReactNode }) => (
{children}
diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 5e966067..46d2f44f 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -40,6 +40,7 @@ import { } from '@/lib/doc-hash'; import { mark, ProfilerBoundary } from '@/lib/perf'; import { SingleFileModeProvider, useSingleFileMode } from '@/lib/single-file-mode'; +import { useServerKeepalive } from '@/lib/use-server-keepalive'; import { isSettingsShortcut, SETTINGS_OPEN_HASH } from '@/lib/use-settings-route'; const INSTALL_DIALOG_HASH = '#install-claude-desktop'; @@ -319,6 +320,7 @@ function NewItemShortcutHandler() { function ConfigProviderHost({ children }: { children: ReactNode }) { const { collabUrl } = useDocumentContext(); + useServerKeepalive(collabUrl); return {children}; } diff --git a/packages/app/src/build/app-version.ts b/packages/app/src/build/app-version.ts index 8de3d072..9c69e05d 100644 --- a/packages/app/src/build/app-version.ts +++ b/packages/app/src/build/app-version.ts @@ -12,7 +12,8 @@ export function resolveAppVersion(): string { if (typeof pkg.version === 'string' && pkg.version.length > 0) { return pkg.version; } - } catch {} + } catch { + } return '0.0.0-unknown'; } diff --git a/packages/app/src/build/electron-mode-class.test.ts b/packages/app/src/build/electron-mode-class.test.ts index d3e9a169..c13d9303 100644 --- a/packages/app/src/build/electron-mode-class.test.ts +++ b/packages/app/src/build/electron-mode-class.test.ts @@ -1,3 +1,4 @@ + import { describe, expect, test } from 'bun:test'; import { readFileSync } from 'node:fs'; import { join } from 'node:path'; diff --git a/packages/app/src/components/ActivityModeContent.tsx b/packages/app/src/components/ActivityModeContent.tsx index 44e04f34..7f8d8801 100644 --- a/packages/app/src/components/ActivityModeContent.tsx +++ b/packages/app/src/components/ActivityModeContent.tsx @@ -12,6 +12,7 @@ import { AgentIcon } from './icons/AgentIcon'; import { Button } from './ui/button'; import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip'; + async function postAgentUndo(body: { connectionId: string; docName: string; @@ -31,6 +32,7 @@ async function postAgentUndo(body: { } } + function hashFromDocName(docName: string): string { return `#/${docName .split('/') @@ -43,6 +45,7 @@ function navigateToDoc(docName: string): void { window.location.hash = hashFromDocName(docName); } + function LoadingState(): React.JSX.Element { return (
['data']; status: ReturnType['status']; @@ -304,6 +308,7 @@ function ActivityModeBody({ ); } + export function ActivityModeContent({ showBackButton = true, }: { diff --git a/packages/app/src/components/ArrayOfObjectsWidget.tsx b/packages/app/src/components/ArrayOfObjectsWidget.tsx index dc373f2f..1c9159bc 100644 --- a/packages/app/src/components/ArrayOfObjectsWidget.tsx +++ b/packages/app/src/components/ArrayOfObjectsWidget.tsx @@ -1,3 +1,4 @@ + import { closestCenter, DndContext, diff --git a/packages/app/src/components/AuthModal.dom.test.tsx b/packages/app/src/components/AuthModal.dom.test.tsx index bfa5538b..ad9b4a9e 100644 --- a/packages/app/src/components/AuthModal.dom.test.tsx +++ b/packages/app/src/components/AuthModal.dom.test.tsx @@ -1,3 +1,4 @@ + import { afterEach, describe, expect, jest, test } from 'bun:test'; import { act, cleanup, render, screen } from '@testing-library/react'; import type { OkLocalOpAuthEvent, OkLocalOpAuthStatusResponse } from '@/lib/desktop-bridge-types'; diff --git a/packages/app/src/components/AuthModal.tsx b/packages/app/src/components/AuthModal.tsx index 67ddcf0f..1b87a0f9 100644 --- a/packages/app/src/components/AuthModal.tsx +++ b/packages/app/src/components/AuthModal.tsx @@ -19,10 +19,12 @@ import { } from './ui/dialog'; import { Input } from './ui/input'; + async function copyToClipboard(text: string): Promise { try { await navigator.clipboard.writeText(text); - } catch {} + } catch { + } } interface AuthSuccessResult { @@ -42,6 +44,7 @@ interface AuthModalProps { queryTransport?: AuthQueryTransport; } + interface DeviceFlowPanelProps { onSuccess: (result: AuthSuccessResult) => void; transport: AuthTransport; @@ -202,6 +205,7 @@ function DeviceFlowPanel({ onSuccess, transport }: DeviceFlowPanelProps) { ); } + interface IdentityBodyProps { login: string; name: string; @@ -237,6 +241,7 @@ function IdentityBody({ login, name, onNameChange, email, onEmailChange }: Ident ); } + type AuthStep = 'checking' | 'auth' | 'identity' | 'done'; const IDENTITY_PROBE_TIMEOUT_MS = 10_000; @@ -323,7 +328,8 @@ export function AuthModal({ method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ name, email }), - }).catch(() => {}); + }).catch(() => { + }); const result = { ...(authResult ?? { login: '' }), name, email }; setStep('done'); diff --git a/packages/app/src/components/AutoSyncOnboardingDialog.tsx b/packages/app/src/components/AutoSyncOnboardingDialog.tsx index 80215913..fcce67ea 100644 --- a/packages/app/src/components/AutoSyncOnboardingDialog.tsx +++ b/packages/app/src/components/AutoSyncOnboardingDialog.tsx @@ -42,7 +42,10 @@ export function AutoSyncOnboardingDialog({ open, onResolved }: AutoSyncOnboardin } return ( - {}}> + {}} + > diff --git a/packages/app/src/components/BetaBadge.tsx b/packages/app/src/components/BetaBadge.tsx index 9c227d08..3dd7f5b6 100644 --- a/packages/app/src/components/BetaBadge.tsx +++ b/packages/app/src/components/BetaBadge.tsx @@ -1,3 +1,4 @@ + import { Trans, useLingui } from '@lingui/react/macro'; import { useUpdateChannel } from '@/hooks/use-update-channel'; import { Badge } from './ui/badge'; diff --git a/packages/app/src/components/BottomComposer.dom.test.tsx b/packages/app/src/components/BottomComposer.dom.test.tsx index f57c2b35..b7829577 100644 --- a/packages/app/src/components/BottomComposer.dom.test.tsx +++ b/packages/app/src/components/BottomComposer.dom.test.tsx @@ -265,7 +265,8 @@ beforeEach(() => { toastErrors.length = 0; try { window.localStorage.clear(); - } catch {} + } catch { + } }); afterEach(() => { diff --git a/packages/app/src/components/BottomComposer.tsx b/packages/app/src/components/BottomComposer.tsx index 02b20be0..0f7d794d 100644 --- a/packages/app/src/components/BottomComposer.tsx +++ b/packages/app/src/components/BottomComposer.tsx @@ -1,3 +1,4 @@ + import { type TargetData, TERMINAL_CLI_IDS, @@ -185,7 +186,8 @@ export function BottomComposer({ if (overlap <= 0) return; const scroller = view.dom.closest('.editor-doc-scroll'); if (scroller instanceof HTMLElement) scroller.scrollTop += overlap; - } catch {} + } catch { + } }); }; const card = cardRef.current; diff --git a/packages/app/src/components/ClaudeReadinessBanner.dom.test.tsx b/packages/app/src/components/ClaudeReadinessBanner.dom.test.tsx index daa5874b..2bff838d 100644 --- a/packages/app/src/components/ClaudeReadinessBanner.dom.test.tsx +++ b/packages/app/src/components/ClaudeReadinessBanner.dom.test.tsx @@ -1,3 +1,4 @@ + import { afterEach, beforeEach, describe, expect, mock, test } from 'bun:test'; import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-library/react'; import type { ClaudeReadiness, OkDesktopBridge } from '@/lib/desktop-bridge-types'; diff --git a/packages/app/src/components/CloneDialog.dom.test.tsx b/packages/app/src/components/CloneDialog.dom.test.tsx index ed5bcb30..fe93c4b0 100644 --- a/packages/app/src/components/CloneDialog.dom.test.tsx +++ b/packages/app/src/components/CloneDialog.dom.test.tsx @@ -1,3 +1,4 @@ + import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { cleanup, render, screen } from '@testing-library/react'; import { getLastKnownSignedIn, setLastKnownSignedIn } from '@/lib/auth-state-cache'; diff --git a/packages/app/src/components/ComposerContextChips.dom.test.tsx b/packages/app/src/components/ComposerContextChips.dom.test.tsx index 77d6f350..7b3f25bb 100644 --- a/packages/app/src/components/ComposerContextChips.dom.test.tsx +++ b/packages/app/src/components/ComposerContextChips.dom.test.tsx @@ -1,3 +1,4 @@ + import { afterEach, describe, expect, mock, test } from 'bun:test'; import { cleanup, fireEvent, render, screen } from '@testing-library/react'; import type { ReactNode } from 'react'; diff --git a/packages/app/src/components/ComposerContextChips.tsx b/packages/app/src/components/ComposerContextChips.tsx index 3b102c9c..28098339 100644 --- a/packages/app/src/components/ComposerContextChips.tsx +++ b/packages/app/src/components/ComposerContextChips.tsx @@ -1,3 +1,4 @@ + import { useLingui } from '@lingui/react/macro'; import { X } from 'lucide-react'; import type { ReactNode } from 'react'; diff --git a/packages/app/src/components/ConsentDialog.tsx b/packages/app/src/components/ConsentDialog.tsx index 204a576c..037a8205 100644 --- a/packages/app/src/components/ConsentDialog.tsx +++ b/packages/app/src/components/ConsentDialog.tsx @@ -1,3 +1,4 @@ + import { lazy, Suspense, useSyncExternalStore } from 'react'; import { consentStore } from '@/lib/consent-store'; diff --git a/packages/app/src/components/CopyButton.tsx b/packages/app/src/components/CopyButton.tsx index 50a7763c..8e9beb5d 100644 --- a/packages/app/src/components/CopyButton.tsx +++ b/packages/app/src/components/CopyButton.tsx @@ -1,3 +1,4 @@ + import { useLingui } from '@lingui/react/macro'; import { Check, Copy } from 'lucide-react'; import { useEffect, useState } from 'react'; @@ -39,7 +40,8 @@ export function CopyButton({ .then(() => clipboardWrite(copyContent)) .then( () => setCopyTick((n) => n + 1), - () => {}, + () => { + }, ); }; diff --git a/packages/app/src/components/CreateProjectDialog.cascade-staleness.dom.test.tsx b/packages/app/src/components/CreateProjectDialog.cascade-staleness.dom.test.tsx index 877e33f1..d58e51f3 100644 --- a/packages/app/src/components/CreateProjectDialog.cascade-staleness.dom.test.tsx +++ b/packages/app/src/components/CreateProjectDialog.cascade-staleness.dom.test.tsx @@ -511,6 +511,7 @@ describe('CreateProjectDialog cascade staleness (Tier-3 mount)', () => { }); test('PRD-6649: 5 s polling skips probeNonce bump while a probe is in-flight (race-prevention gate)', async () => { + const stub = makeStubBridge(FIRST_GIT_RESULT, PARENT); const setIntervalSpy = spyOn(globalThis, 'setInterval'); diff --git a/packages/app/src/components/CreateProjectDialog.tsx b/packages/app/src/components/CreateProjectDialog.tsx index 9b56c160..bb46412a 100644 --- a/packages/app/src/components/CreateProjectDialog.tsx +++ b/packages/app/src/components/CreateProjectDialog.tsx @@ -1,3 +1,4 @@ + import { ALL_EDITOR_IDS, CREATE_NEW_PROJECT_FAILURE_REASONS, @@ -308,7 +309,8 @@ export function CreateProjectDialog({ open, onOpenChange, bridge }: CreateProjec if (banner === null) return; if (firedBanners.current.has(banner)) return; firedBanners.current.add(banner); - bridge.project.recordCreateNewBannerShown(banner).catch(() => {}); + bridge.project.recordCreateNewBannerShown(banner).catch(() => { + }); }, [open, cascade, bridge]); const rawName = name; diff --git a/packages/app/src/components/CreateProjectMenuTrigger.tsx b/packages/app/src/components/CreateProjectMenuTrigger.tsx index a4eb9e25..eb105d7f 100644 --- a/packages/app/src/components/CreateProjectMenuTrigger.tsx +++ b/packages/app/src/components/CreateProjectMenuTrigger.tsx @@ -1,3 +1,4 @@ + import { useEffect, useState } from 'react'; import { CreateProjectDialog } from '@/components/CreateProjectDialog'; import type { OkDesktopBridge } from '@/lib/desktop-bridge-types'; diff --git a/packages/app/src/components/DeleteConfirmationDialog.tsx b/packages/app/src/components/DeleteConfirmationDialog.tsx index 0230fda1..75730d65 100644 --- a/packages/app/src/components/DeleteConfirmationDialog.tsx +++ b/packages/app/src/components/DeleteConfirmationDialog.tsx @@ -43,7 +43,9 @@ export function DeleteConfirmationDialog({ {customTitle ?? t`Delete ${itemName}`} - + {customDescription ?? t`Are you sure you want to delete ${itemName}? This action cannot be undone.`} diff --git a/packages/app/src/components/DiffViewBoundary.tsx b/packages/app/src/components/DiffViewBoundary.tsx index 5a95e4ca..f1329e9a 100644 --- a/packages/app/src/components/DiffViewBoundary.tsx +++ b/packages/app/src/components/DiffViewBoundary.tsx @@ -38,7 +38,8 @@ async function fetchConflictSides(file: string): Promise { const payload = (await res.json()) as { detail?: unknown; title?: unknown }; if (typeof payload.detail === 'string') detail = payload.detail; else if (typeof payload.title === 'string') detail = payload.title; - } catch {} + } catch { + } console.warn( JSON.stringify({ event: 'conflict-content-fetch-failed', diff --git a/packages/app/src/components/DocPanel.dom.test.tsx b/packages/app/src/components/DocPanel.dom.test.tsx index b2567fd2..48b8323d 100644 --- a/packages/app/src/components/DocPanel.dom.test.tsx +++ b/packages/app/src/components/DocPanel.dom.test.tsx @@ -1,3 +1,4 @@ + import { afterEach, describe, expect, mock, test } from 'bun:test'; import { cleanup, render, screen } from '@testing-library/react'; import type { ReactNode } from 'react'; diff --git a/packages/app/src/components/DocumentBoundary.test.ts b/packages/app/src/components/DocumentBoundary.test.ts index 776e5aa0..35f25d18 100644 --- a/packages/app/src/components/DocumentBoundary.test.ts +++ b/packages/app/src/components/DocumentBoundary.test.ts @@ -1,3 +1,4 @@ + import { afterEach, beforeEach, describe, expect, test } from 'bun:test'; import { HocuspocusProvider } from '@hocuspocus/provider'; import { __resetSyncPromiseCache, syncPromise } from '@/editor/sync-promise'; @@ -25,7 +26,8 @@ afterEach(() => { for (const p of providers) { try { p.destroy(); - } catch {} + } catch { + } } providers = []; }); diff --git a/packages/app/src/components/DocumentBoundary.tsx b/packages/app/src/components/DocumentBoundary.tsx index cfdb81f0..eaea173e 100644 --- a/packages/app/src/components/DocumentBoundary.tsx +++ b/packages/app/src/components/DocumentBoundary.tsx @@ -1,3 +1,4 @@ + import type { HocuspocusProvider } from '@hocuspocus/provider'; import { type ReactNode, use } from 'react'; import { syncPromise } from '@/editor/sync-promise'; diff --git a/packages/app/src/components/DocumentErrorBoundary.test.ts b/packages/app/src/components/DocumentErrorBoundary.test.ts index 0374eeb9..227ebb82 100644 --- a/packages/app/src/components/DocumentErrorBoundary.test.ts +++ b/packages/app/src/components/DocumentErrorBoundary.test.ts @@ -1,3 +1,4 @@ + import { describe, expect, test } from 'bun:test'; import { MountAbortError } from '@/editor/mount-promise'; import { diff --git a/packages/app/src/components/EditorActivityPool.test.ts b/packages/app/src/components/EditorActivityPool.test.ts index be046da8..d110f573 100644 --- a/packages/app/src/components/EditorActivityPool.test.ts +++ b/packages/app/src/components/EditorActivityPool.test.ts @@ -1,3 +1,4 @@ + import { describe, expect, test } from 'bun:test'; import { SYSTEM_DOC_NAME } from '@inkeep/open-knowledge-core'; import { @@ -367,6 +368,7 @@ describe('computeEditorMountGate — invariant: at least one editor rendered', ( }); }); + describe('shouldEmitFirstToggle — first-toggle mark gate', () => { test('large doc, both editors rendering, not yet emitted → emit', () => { expect( diff --git a/packages/app/src/components/EditorActivityPool.tsx b/packages/app/src/components/EditorActivityPool.tsx index d13639d3..d303ddf2 100644 --- a/packages/app/src/components/EditorActivityPool.tsx +++ b/packages/app/src/components/EditorActivityPool.tsx @@ -563,6 +563,7 @@ function ActivityEntry({ const [warmSnapshot] = useState(() => peekRenameSnapshot(entry.docName)); const warmHtml = warmSnapshot?.html ?? null; + const [hasEmittedFirstToggle, setHasEmittedFirstToggle] = useState(false); useEffect(() => { if ( diff --git a/packages/app/src/components/EditorActivityPool.warm-skeleton.dom.test.tsx b/packages/app/src/components/EditorActivityPool.warm-skeleton.dom.test.tsx index c7d8b88e..6f591d21 100644 --- a/packages/app/src/components/EditorActivityPool.warm-skeleton.dom.test.tsx +++ b/packages/app/src/components/EditorActivityPool.warm-skeleton.dom.test.tsx @@ -12,6 +12,7 @@ import { } from '@/editor/editor-cache'; import { expectVisualClassTokens } from '@/test-utils/visual-contract'; + function WarmContentFallbackReplica({ html }: { html: string }) { return (