From dac65914ed36efbeaacd5366429c7cffa97dac8c Mon Sep 17 00:00:00 2001 From: Philipinho <16838612+Philipinho@users.noreply.github.com> Date: Tue, 28 Apr 2026 11:29:28 +0100 Subject: [PATCH] chore(base): drop pre-virtualizer-fix scroll-jump workarounds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two pieces of code added under the wrong height-mismatch theory of the inline-embed scroll jump are no longer load-bearing now that the real cause (virtual-core's _willUpdate calling _scrollToOffset(NaN)) is fixed by the initialOffset seed in grid-container.tsx: - BaseTableSkeleton's `rows` prop, whose only consumer was the "creating database" placeholder passing rows=0 to "match" the eventual empty-base height. Reverted to a fixed 10-row skeleton. - The Database slash command's React Query cache prefill of ["bases", id] and ["base-rows", id, …] (~30 lines), which existed to skip BaseTable's own loading skeleton on swap. The create endpoint return type is back to { id }. The placeholder approach (pendingKey + setNodeMarkup patch) stays — that's what gives the user visible state during the create request, unrelated to the scroll jump. --- .../base/components/base-table-skeleton.tsx | 16 ++----- .../components/base-embed/base-embed-view.tsx | 11 ++--- .../components/slash-menu/menu-items.ts | 46 ++----------------- 3 files changed, 10 insertions(+), 63 deletions(-) diff --git a/apps/client/src/features/base/components/base-table-skeleton.tsx b/apps/client/src/features/base/components/base-table-skeleton.tsx index 287789d5b..dfcae12fc 100644 --- a/apps/client/src/features/base/components/base-table-skeleton.tsx +++ b/apps/client/src/features/base/components/base-table-skeleton.tsx @@ -5,24 +5,14 @@ import classes from "@/features/base/styles/base-table-skeleton.module.css"; const ROW_NUMBER_WIDTH = 64; const COLUMN_WIDTH = 180; const COLUMN_COUNT = 6; -const DEFAULT_ROW_COUNT = 10; +const ROW_COUNT = 10; // Deterministic per-cell widths so the skeleton doesn't flicker between // renders. Values are rough normal distribution around 55-85 % of cell. const CELL_WIDTH_RATIOS = [0.78, 0.62, 0.84, 0.55, 0.71, 0.66]; const HEADER_WIDTH_RATIOS = [0.42, 0.58, 0.5, 0.64, 0.46, 0.54]; -type BaseTableSkeletonProps = { - // Override the body row count. Pass 0 when rendering as the - // "creating database" placeholder for a freshly-inserted inline embed - // — the eventual empty base has no rows, so a 10-row skeleton would - // shrink ~330px on swap and trip the browser's scrollY clamp. - rows?: number; -}; - -export function BaseTableSkeleton({ - rows = DEFAULT_ROW_COUNT, -}: BaseTableSkeletonProps = {}) { +export function BaseTableSkeleton() { const gridTemplateColumns = [ `${ROW_NUMBER_WIDTH}px`, ...Array.from({ length: COLUMN_COUNT }, () => `${COLUMN_WIDTH}px`), @@ -64,7 +54,7 @@ export function BaseTableSkeleton({ ))} - {Array.from({ length: rows }).map((_, rowIndex) => ( + {Array.from({ length: ROW_COUNT }).map((_, rowIndex) => (
diff --git a/apps/client/src/features/editor/components/base-embed/base-embed-view.tsx b/apps/client/src/features/editor/components/base-embed/base-embed-view.tsx index f413bed08..062deca8c 100644 --- a/apps/client/src/features/editor/components/base-embed/base-embed-view.tsx +++ b/apps/client/src/features/editor/components/base-embed/base-embed-view.tsx @@ -74,14 +74,9 @@ export function BaseEmbedView({ node }: NodeViewProps) { let content: React.ReactNode; if (pendingKey) { // Slash command inserted the embed and is awaiting the server's - // assigned pageId. Render with `rows={0}` so the placeholder - // matches the height of the eventual empty base shell — anything - // taller would shrink hundreds of px on swap, and on a short doc - // the browser would clamp scrollY (looks like "page jumps to top - // of editor" when the create response lands). The slash command - // also prefills the React Query cache so BaseTable mounts with - // baseLoading/rowsLoading already false and skips its own skeleton. - content = ; + // assigned pageId — render the same skeleton BaseTable shows + // during its own initial load so the swap is visually a no-op. + content = ; } else if (!pageId) { content = ( diff --git a/apps/client/src/features/editor/components/slash-menu/menu-items.ts b/apps/client/src/features/editor/components/slash-menu/menu-items.ts index 03c3cafbf..511f36b47 100644 --- a/apps/client/src/features/editor/components/slash-menu/menu-items.ts +++ b/apps/client/src/features/editor/components/slash-menu/menu-items.ts @@ -55,13 +55,6 @@ import { import api from "@/lib/api-client"; import { notifications } from "@mantine/notifications"; import type { Editor } from "@tiptap/core"; -import type { InfiniteData } from "@tanstack/react-query"; -import { queryClient } from "@/main"; -import type { - IBase, - IBaseRow, -} from "@/features/base/types/base.types"; -import type { IPagination } from "@/lib/types"; // Resolve the position of a baseEmbed placeholder by its pendingKey. // Used by the Database slash command to patch in the real pageId once @@ -520,10 +513,9 @@ const CommandGroups: SlashMenuGroupedItemsType = { if (!parentPageId) return; // Insert a placeholder embed at the slash position synchronously - // so (a) the position is established before any focus/selection - // drift during the await, and (b) the user sees a skeleton in - // the document instead of an empty gap. The API call then patches - // the real pageId into this exact node, identified by pendingKey. + // so the user sees a skeleton immediately while we wait on the + // create-base API. Once the response lands we look the + // placeholder up by its pendingKey and patch in the real pageId. const pendingKey = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() @@ -537,40 +529,10 @@ const CommandGroups: SlashMenuGroupedItemsType = { .run(); try { - // The create endpoint returns the full base (properties + - // views), not just an id — see base.service.ts `create`. Type - // it as IBase so we can seed the React Query cache below. - const res = await api.post("/bases/inline-embed", { + const res = await api.post<{ id: string }>("/bases/inline-embed", { parentPageId, }); - // Seed the caches BaseTable will read on mount so it doesn't - // render its own (10-row) skeleton between our placeholder - // and the actual content. Without this seeding the wrapper - // would grow to ~436px during BaseTable's load and shrink to - // ~112px once the rows resolve — on a short doc the shrink - // pushes scrollY past the new doc bottom and the browser - // clamps to 0, which is the "jump to top" the user reported. - queryClient.setQueryData(["bases", res.data.id], res.data); - queryClient.setQueryData>>( - ["base-rows", res.data.id, undefined, undefined, undefined], - { - pages: [ - { - items: [], - meta: { - limit: 100, - hasNextPage: false, - hasPrevPage: false, - nextCursor: null, - prevCursor: null, - }, - }, - ], - pageParams: [undefined], - }, - ); - const pos = findBaseEmbedPlaceholderPos(editor, pendingKey); if (pos === null) return; editor