Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions PUBLIC_SOURCE_MANIFEST.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"schemaVersion": 1,
"license": "GPL-3.0-only",
"historyPolicy": "sanitized-single-snapshot",
"generatedAt": "2026-08-13T05:21:04.140Z",
"generatedAt": "2026-08-13T07:09:25.850Z",
"fileCount": 662,
"snapshotSha256": "2d3dd4f702abfab6cb7596cdc26ee9dc20e4c14f44cdffbe7e5215f45d2c6961",
"snapshotSha256": "0b83a8e782c8c7757047f97ef8a431156bbafbac2acf5645685fbe4d152d5e53",
"files": [
{
"path": ".github/ISSUE_TEMPLATE/bug_report.yml",
Expand Down Expand Up @@ -1333,8 +1333,8 @@
},
{
"path": "apps/windows/package.json",
"bytes": 1867,
"sha256": "1f69a2553ef25ebe45fd36b9b47aacee12cc79696232f080b969c37aa3a7e604"
"bytes": 1902,
"sha256": "bf447e38230931494f1cdae3829d75455c5384f38c461954aa8589f015951937"
},
{
"path": "apps/windows/src/App.test.tsx",
Expand Down Expand Up @@ -2133,23 +2133,23 @@
},
{
"path": "apps/windows/src/ui/components/SessionSourceEditor.test.tsx",
"bytes": 24331,
"sha256": "38b4f1810ceeea0c16c644c66503c8f58cd2071820659fe076da6655b3dbbaf5"
"bytes": 25296,
"sha256": "cedebb9dcb485d445723627bb9242f19be0d9aa0ee783b11c21f04588dd2a0b5"
},
{
"path": "apps/windows/src/ui/components/SessionSourceEditor.tsx",
"bytes": 70499,
"sha256": "f7ccf688f05acd5f83cd03f4544b4d62f96b635e713894b0a2349b51b83bf102"
"bytes": 72334,
"sha256": "56a6593b2eba0181d29140ab1d2542f43bb052fb92b64d089a86e1e3ded1f1f2"
},
{
"path": "apps/windows/src/ui/components/SourceTanStackLab.test.tsx",
"bytes": 2545,
"sha256": "b490b7e3041a94c09a1dca5c0124da4063eadc2ed8fd6087577efd27cbab6924"
"bytes": 2817,
"sha256": "f31dac0e71641bea8077e3800d61e574222765f6ec6ea909e4787d34686ed55b"
},
{
"path": "apps/windows/src/ui/components/SourceTanStackLab.tsx",
"bytes": 4268,
"sha256": "481ea273b60ed9ab4c8ebfb9dbf308b99d8b716ae6f7671abc95ac718e2f1d3b"
"bytes": 4261,
"sha256": "697d5377b5c87770594d10a945439169a910f53f1def978163a38194387e8a57"
},
{
"path": "apps/windows/src/ui/components/sessionSourceEditorModel.test.ts",
Expand Down Expand Up @@ -2333,13 +2333,13 @@
},
{
"path": "package-lock.json",
"bytes": 354932,
"sha256": "f546ff664639e6445ee05ca3b00de206eb722d218cd9fba3ed7e206b5546613c"
"bytes": 354975,
"sha256": "efe35fef34a3de44e9bbf41aa6577c5bb2073b86609763e35538875311903d40"
},
{
"path": "package.json",
"bytes": 8069,
"sha256": "5bcb28a9d95ee4a9e9ff7843e2aca4a0d1d265c490ca21182d4cd022beef0f4e"
"bytes": 8494,
"sha256": "35c59f95ac9a6e8b4a9cc7f8a125d1c6b038918b45d46ff8a6e65a863faad6c8"
},
{
"path": "packages/core-server/package.json",
Expand Down Expand Up @@ -2998,8 +2998,8 @@
},
{
"path": "test_tool/editor_ui_performance_lab.mjs",
"bytes": 143768,
"sha256": "7bb96b6d41a3e7d6c8724df7732bb44dc92ecd96d7a242cbe1eb10f17deaf54a"
"bytes": 152015,
"sha256": "88f97c2dffd82fe250630336edb9b5c289b9d81b14ffd25ebb683951fca1994d"
},
{
"path": "test_tool/electron_actual_state_probe.mjs",
Expand All @@ -3008,8 +3008,8 @@
},
{
"path": "test_tool/electron_app_smoke.mjs",
"bytes": 51960,
"sha256": "604a2c4db8323cf964ab9b6ab818c5fce09b39e6013b2bb13fcd9d4a4b035a7e"
"bytes": 52403,
"sha256": "7d30a27a98bb4d7ed50004d37d9d3ba3d92d6c47b8b6c3e3c9c50e2743523c5e"
},
{
"path": "test_tool/export_public_source.mjs",
Expand Down
1 change: 1 addition & 0 deletions apps/windows/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"dependencies": {
"@codemirror/lang-markdown": "^6.5.0",
"@codemirror/state": "^6.7.0",
"@codemirror/view": "6.43.8",
"@mathnotes/core-server": "file:../../packages/core-server",
"@mathnotes/shared": "file:../../packages/shared",
"@tanstack/react-virtual": "^3.14.6",
Expand Down
15 changes: 15 additions & 0 deletions apps/windows/src/ui/components/SessionSourceEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
buildPerformanceLabVirtualLayout,
expandPerformanceLabVirtualRange,
findPerformanceLabVirtualRange,
getTanStackSourceWindowingEffectiveMode,
getTanStackSourceWindowingFallbackReason,
groupAssistantRemarksByBlockId,
findEditorCaretTargetAtPoint,
SessionSourceEditor,
Expand Down Expand Up @@ -233,6 +235,19 @@ describe("SessionSourceEditor", () => {
expect(screen.queryByTestId("editor-context-menu")).toBeNull();
});

it("keeps small sessions and sessions with a very long block in normal document flow", () => {
expect(getTanStackSourceWindowingFallbackReason(["one", "two"])).toBe("small-session");
expect(getTanStackSourceWindowingEffectiveMode(["one", "two"])).toBe("off");

const ordinaryBlocks = Array.from({ length: 13 }, (_, index) => `block ${index + 1}\nsecond line`);
expect(getTanStackSourceWindowingFallbackReason(ordinaryBlocks)).toBeNull();
expect(getTanStackSourceWindowingEffectiveMode(ordinaryBlocks)).toBe("tanstack-virtual");

const longBlock = Array.from({ length: 85 }, (_, index) => `line ${index + 1}`).join("\n");
expect(getTanStackSourceWindowingFallbackReason([...ordinaryBlocks.slice(0, 12), longBlock])).toBe("long-block");
expect(getTanStackSourceWindowingEffectiveMode([...ordinaryBlocks.slice(0, 12), longBlock])).toBe("off");
});

it("sends the exact UTF-16 selection to the AI edit callback from the editor context menu", async () => {
Range.prototype.getClientRects = () => [] as unknown as DOMRectList;
Range.prototype.getBoundingClientRect = () =>
Expand Down
58 changes: 51 additions & 7 deletions apps/windows/src/ui/components/SessionSourceEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ type EditorStateSnapshot = {
const performanceLabEditorViewLimit = 12;
const performanceLabOverscanPx = 1200;
const performanceLabVirtualSegmentSize = 12;
const performanceLabLongSourceBlockLineLimit = 85;
const performanceLabStorageKey = "mathnotes:editor-windowing-lab";
const performanceLabSourceOverscanStorageKey = "mathnotes:source-overscan-lab";
const performanceLabVirtualHeaderHeight = 39;
Expand Down Expand Up @@ -217,6 +218,34 @@ export function buildPerformanceLabVirtualLayout(markdowns: readonly string[]):
return { offsets, sizes, totalSize };
}

export function getTanStackSourceWindowingFallbackReason(
markdowns: readonly string[]
): "small-session" | "long-block" | null {
// A session with at most twelve blocks gains little from a second layer of
// virtualization because CodeMirror already virtualizes the lines inside
// each block. Keeping these editors in normal document flow also avoids
// competing viewport calculations during native scrollbar drags.
if (markdowns.length <= performanceLabEditorViewLimit) return "small-session";

for (const markdown of markdowns) {
let lineCount = 1;
for (let index = 0; index < markdown.length; index += 1) {
if (markdown.charCodeAt(index) !== 10) continue;
lineCount += 1;
if (lineCount >= performanceLabLongSourceBlockLineLimit) return "long-block";
}
}
return null;
}

export function getTanStackSourceWindowingEffectiveMode(
markdowns: readonly string[]
): "off" | "tanstack-virtual" {
const fallbackReason = getTanStackSourceWindowingFallbackReason(markdowns);
if (fallbackReason) return "off";
return "tanstack-virtual";
}

export function findPerformanceLabVirtualRange({
layout,
scrollTop,
Expand Down Expand Up @@ -325,11 +354,7 @@ export function SessionSourceEditor({
onRerecognizeBlockRequest,
onTransferBlocksRequest
}: SessionSourceEditorProps) {
const editorWindowingLabMode = getEditorWindowingPerformanceLabMode();
const dynamicEditorWindowingLab = editorWindowingLabMode === "dynamic";
const virtualBlockWindowingLab = editorWindowingLabMode === "virtual";
const tanStackSourceWindowingLab = editorWindowingLabMode === "tanstack-virtual";
const statefulEditorWindowingLab = dynamicEditorWindowingLab || virtualBlockWindowingLab || tanStackSourceWindowingLab;
const requestedEditorWindowingLabMode = getEditorWindowingPerformanceLabMode();
const editorScrollerRef = useRef<HTMLDivElement>(null);
const blockElementsRef = useRef(new Map<string, HTMLElement>());
const editorViewsRef = useRef(new Map<string, EditorView>());
Expand Down Expand Up @@ -393,10 +418,26 @@ export function SessionSourceEditor({
[document.markdownBlocks]
);
const remarksByBlockId = useMemo(() => groupAssistantRemarksByBlockId(assistantRemarks), [assistantRemarks]);
const virtualBlockLayout = useMemo(
() => buildPerformanceLabVirtualLayout(document.markdownBlocks.map((block) => markdownByBlockId[block.blockId] ?? "")),
const sourceMarkdowns = useMemo(
() => document.markdownBlocks.map((block) => markdownByBlockId[block.blockId] ?? ""),
[document.markdownBlocks, markdownBlockIds, markdownByBlockId]
);
const tanStackFallbackReason = requestedEditorWindowingLabMode === "tanstack-virtual"
? getTanStackSourceWindowingFallbackReason(sourceMarkdowns)
: null;
const editorWindowingLabMode: EditorWindowingLabMode = requestedEditorWindowingLabMode === "tanstack-virtual"
? getTanStackSourceWindowingEffectiveMode(sourceMarkdowns)
: requestedEditorWindowingLabMode;
const dynamicEditorWindowingLab = editorWindowingLabMode === "dynamic";
const virtualBlockWindowingLab = editorWindowingLabMode === "virtual";
const tanStackSourceWindowingLab = editorWindowingLabMode === "tanstack-virtual";
const statefulEditorWindowingLab = dynamicEditorWindowingLab ||
virtualBlockWindowingLab ||
requestedEditorWindowingLabMode === "tanstack-virtual";
const virtualBlockLayout = useMemo(
() => buildPerformanceLabVirtualLayout(sourceMarkdowns),
[sourceMarkdowns]
);

useEffect(() => {
// A local CodeMirror edit is projected to the aggregate source on the next frame.
Expand Down Expand Up @@ -1005,6 +1046,8 @@ export function SessionSourceEditor({
<div
className="session-source-editor source-block-shell"
data-editor-windowing-lab={editorWindowingLabMode}
data-editor-windowing-requested={requestedEditorWindowingLabMode}
data-editor-windowing-fallback={tanStackFallbackReason ?? undefined}
data-testid="session-source-editor"
ref={editorScrollerRef}
>
Expand Down Expand Up @@ -1206,6 +1249,7 @@ function SourceBlockSection({
].filter(Boolean).join(" ")}
data-testid="source-block"
data-block-id={block.blockId}
data-locked={block.locked ? "true" : "false"}
data-locating-nonce={locatingNonce || undefined}
data-source={block.sourceId}
data-index={virtualIndex}
Expand Down
11 changes: 9 additions & 2 deletions apps/windows/src/ui/components/SourceTanStackLab.test.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { render, waitFor } from "@testing-library/react";
import { createRef } from "react";
import { createRef, type CSSProperties } from "react";
import { describe, expect, it, vi } from "vitest";
import { SourceTanStackLab } from "./SourceTanStackLab";

vi.mock("@tanstack/react-virtual", () => ({
useVirtualizer: () => ({
getVirtualItems: () => [{ index: 0, start: 0 }],
getVirtualItems: () => [{ index: 0, start: 37 }],
getTotalSize: () => 100,
itemSizeCache: new Map(),
measureElement: () => undefined,
Expand Down Expand Up @@ -73,5 +73,12 @@ describe("SourceTanStackLab preview locate handshake", () => {
</div>
);
expect(renderItem).toHaveBeenCalledWith(block, 0, expect.any(Function), expect.any(Object));
const style = (renderItem.mock.calls as unknown as Array<[unknown, number, unknown, CSSProperties]>)[0]?.[3];
expect(style).toMatchObject({
position: "absolute",
top: 0,
transform: "translateY(37px)",
width: "100%"
});
});
});
7 changes: 4 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,9 @@
"test:editor-performance": "node test_tool/editor_performance_smoke.mjs",
"test:editor-performance:lab": "node test_tool/editor_performance_lab.mjs",
"test:editor-performance:ui-lab": "node test_tool/editor_ui_performance_lab.mjs",
"test:windows-scroll-blank": "node test_tool/editor_ui_performance_lab.mjs --experiment=scroll-blank --shapes=80 --fixture-profile=heterogeneous --product-defaults=1 --output=output/performance/windows-scroll-blank-hotfix.json",
"test:windows-scroll-blank": "npm run test:windows-scroll-blank:long && npm run test:windows-scroll-blank:many",
"test:windows-scroll-blank:long": "node test_tool/editor_ui_performance_lab.mjs --experiment=scroll-blank --shapes=2 --fixture-profile=single-long --product-defaults=1 --scroll-direction-ms=200 --output=output/performance/windows-fast-scroll-single-long-v2.json",
"test:windows-scroll-blank:many": "node test_tool/editor_ui_performance_lab.mjs --experiment=scroll-blank --shapes=80 --fixture-profile=heterogeneous --product-defaults=1 --scroll-direction-ms=300 --output=output/performance/windows-fast-scroll-heterogeneous-v2.json",
"test:prototype": "node test_tool/prototype_smoke.mjs http://127.0.0.1:4173/",
"test:windows-smoke": "node test_tool/windows_app_smoke.mjs http://127.0.0.1:5173/",
"test:electron-smoke": "node test_tool/electron_app_smoke.mjs",
Expand Down
Loading