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 4cd93fd7c..f413bed08 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,9 +74,14 @@ 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 the same skeleton BaseTable shows on
- // its own initial load so the swap is visually a no-op.
- content = ;
+ // 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 = ;
} 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 0e502cf3a..03c3cafbf 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,6 +55,13 @@ 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
@@ -530,10 +537,40 @@ const CommandGroups: SlashMenuGroupedItemsType = {
.run();
try {
- const res = await api.post<{ id: string }>("/bases/inline-embed", {
+ // 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", {
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