Skip to content
Closed
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { renderToStaticMarkup } from "react-dom/server";
import { describe, expect, it } from "vitest";

import { ContentLoadingShell } from "./ContentLoadingShell";

describe("ContentLoadingShell", () => {
it("renders before client providers without exposing interactive page content", () => {
const html = renderToStaticMarkup(<ContentLoadingShell />);

expect(html).toContain('aria-busy="true"');
expect(html).not.toMatch(/<(button|a|input|textarea)\b/);
expect(html).not.toContain("contenteditable");
expect(html).not.toContain("data-agent-native-loading-label");
});
});
32 changes: 32 additions & 0 deletions templates/content/app/components/layout/ContentLoadingShell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { DocumentEditorSkeleton } from "@/components/editor/DocumentEditorSkeleton";
import { Skeleton } from "@/components/ui/skeleton";

export function ContentLoadingShell() {
return (
<div
aria-busy="true"
className="flex h-dvh overflow-hidden bg-background text-foreground"
>
<div
aria-hidden="true"
className="hidden w-60 shrink-0 flex-col gap-6 border-e border-border bg-sidebar p-3 min-[1100px]:flex"
>
<Skeleton className="h-8 w-full rounded-md" />
<div className="space-y-3">
<Skeleton className="h-6 w-3/4" />
<Skeleton className="h-6 w-2/3" />
<Skeleton className="h-6 w-3/4" />
</div>
<div className="space-y-3">
<Skeleton className="h-4 w-20" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-5/6" />
<Skeleton className="h-6 w-3/4" />
</div>
</div>
<div aria-hidden="true" className="flex min-w-0 flex-1">
<DocumentEditorSkeleton />
</div>
</div>
);
}
2 changes: 2 additions & 0 deletions templates/content/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
import { AppToolkitProvider } from "@/components/ui/toolkit-provider";

import changelog from "../CHANGELOG.md?raw";
import { ContentLoadingShell } from "./components/layout/ContentLoadingShell";
import { LocalFolderLiveSync } from "./components/LocalFolderLiveSync";
import { useDbSync } from "./hooks/use-db-sync";
import { useNavigationState } from "./hooks/use-navigation-state";
Expand Down Expand Up @@ -105,7 +106,7 @@
const resolved = resolveLocaleFromRequest({ request });
const messages =
((await i18nCatalog.loadMessages?.(resolved.locale)) as
| LocaleMessages

Check warning on line 109 in templates/content/app/root.tsx

View workflow job for this annotation

GitHub Actions / Lint & format

typescript(no-redundant-type-constituents)

'LocaleMessages' is an 'error' type that acts as 'any' and overrides all other types in this union type.
| null
| undefined) ?? i18nCatalog.messages;
return {
Expand Down Expand Up @@ -613,6 +614,7 @@
<AppToolkitProvider>
<AppProviders
queryClient={queryClient}
clientOnlyFallback={<ContentLoadingShell />}
disableThemeTransitions={false}
toaster={contentToaster}
i18n={{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
type: improved
date: 2026-09-09
---

Pages show a sidebar and document skeleton while the app starts.
Loading