Since 2026-11-04 every loading state in the signed-in web app has been measured from a sample of the component it stands in for, with skullet. That is now a rule, stated in AGENTS.md, CODING_STANDARDS.md and on Design patterns: a loading skeleton is MeasuredSkeleton from @/platform/skeleton/measured-skeleton, fed data and a sample from the feature's <feature>.samples.ts. Only platform/skeleton/ imports skullet.
scripts/web-skeletons.test.mjs runs in pnpm test. It fails when apps/web/src or packages/ui/src imports skullet outside the wrapper, when app code imports a *Skeleton* component from a package (the shadcn Skeleton, SidebarMenuSkeleton, a UI kit's), when a feature or route draws an animate-pulse box with a muted fill, and when either package.json lists another skeleton library such as react-loading-skeleton or react-content-loader. It also walks the imports of every route outside routes/_auth/ and fails if one reaches the wrapper, so the skullet chunk stays off public pages, as the first-paint rule (W15) requires. A public page renders its data from the route loader, or a Spinner for a pending action. The Enforcement page and the Review checklist list the new check.
The shadcn skeleton.tsx stays in packages/ui, because the vendored sidebar and the ReUI emoji picker draw their own loading state with it. Skullet has no React Native build, so the Expo app keeps its SkeletonRows placeholder.