this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

make up verticals

+399 -262
+118
CONTEXT.md
··· 1 + # Preloading Example 2 + 3 + This context describes the product language and architectural vocabulary for a chaptered learning lab about TanStack preloading, pagination, filtering, and synced local data patterns. 4 + 5 + ## Language 6 + 7 + **Vertical**: 8 + A product slice organized by what the code does for the user, not by technical category. 9 + _Avoid_: Code grouping, feature folder, technical layer 10 + 11 + **Shared vertical**: 12 + A vertical for cross-cutting code with a coherent responsibility that is used by more than one product slice. 13 + _Avoid_: Shared dumping ground, miscellaneous utilities 14 + 15 + **Chapter**: 16 + A learning unit in the reading sequence that pairs exactly one article with exactly one demo. 17 + _Avoid_: Page, route, layout 18 + 19 + **Article**: 20 + An explanatory content piece paired with a demo inside a chapter. 21 + _Avoid_: Blog component, copy block 22 + 23 + **Demo**: 24 + An interactive Pokemon listing example paired with an article inside a chapter. 25 + _Avoid_: Table, widget, example component 26 + 27 + **Chapter route**: 28 + A route module that composes a chapter by wiring its article, demo, route data, and search state. 29 + _Avoid_: Implementation dumping ground, generated registry entry 30 + 31 + **Data**: 32 + The persistence model and direct database access for Pokemon records. 33 + _Avoid_: Data layer, server functions, query options, sync routes 34 + 35 + **Landing**: 36 + The home screen that introduces the learning lab and presents entry points into the chapter sequence. 37 + _Avoid_: Table of contents owner, chapter registry 38 + 39 + **Design System**: 40 + The shared visual foundation for app-wide primitive UI, styling tokens, global CSS, and theme controls. 41 + _Avoid_: Shared, common components, miscellaneous UI 42 + 43 + **Framework adapter**: 44 + A file kept in a framework-mandated location to register routes, app wiring, or configuration while delegating implementation to verticals where practical. 45 + _Avoid_: Vertical owner, product slice 46 + 47 + ## Relationships 48 + 49 + - A **Vertical** owns code that changes together for a product concern. 50 + - A **Shared vertical** may be used by multiple **Verticals** through a small public interface. 51 + - A **Chapter** contains exactly one **Article** and exactly one **Demo**. 52 + - The **Article** vertical owns markdown content, markdown rendering, article presentation, and server functions that produce renderable articles. 53 + - A **Chapter route** is the composition boundary for one **Chapter**. 54 + - A **Chapter route** may own route-specific integration between TanStack Router loaders, route context, search state, and TanStack Query options. 55 + - A **Chapter route** should compose **Verticals**, not contain substantial **Article** rendering internals or **Demo** implementation internals. 56 + - The **Demo** vertical owns the interactive Pokemon listing system, including shared demo UI, server functions, TanStack Query helpers, Electric shape routes, TanStack DB collections, and loading strategy helpers. 57 + - The **Data** vertical owns schema, relations, seed data, and direct database wrapper queries. 58 + - The **Demo** vertical consumes the **Data** vertical through direct query interfaces exposed by **Data**. 59 + - The **Chapters** vertical owns the canonical reading sequence, chapter metadata, table-of-contents data, and chapter navigation. 60 + - The **Chapters** vertical owns navigation chrome for the chaptered reading experience, including the header, chapter selector, chapter pager, and route navigation helpers. 61 + - The **Chapters** vertical owns chapter framing and status language, including section headers and status indicators. 62 + - The **Landing** vertical owns home-page presentation and consumes the **Chapters** vertical for the table of contents. 63 + - The **Design System** vertical owns primitive UI components, CSS files, styling tokens, utility class merging, and the theme toggle. 64 + - The **Demo** vertical owns console cards and panel frames used by the interactive demo surface. 65 + - Small implementation helpers used only by one vertical belong to that vertical; for example, the lazy component helper belongs to **Demo** while only the demo table uses it. 66 + - **Framework adapters** may remain in TanStack Start, TanStack Router, or Vite+ default locations even when they register behavior for a vertical. 67 + - API shape route files are **Framework adapters**; **Demo** owns the Electric collections that depend on those route URLs. 68 + - Root route, router creation, generated route tree, TanStack Start declarations, and Vite+ configuration are **Framework adapters** unless enough cohesive app-shell implementation emerges to justify a separate vertical. 69 + - `src/env.ts` remains app-wide runtime configuration and is not owned by **Data** or **Demo**. 70 + - Product-owned verticals live directly under `src/*` as named top-level directories such as `src/articles`, `src/chapters`, `src/data`, `src/demos`, `src/design-system`, and `src/landing`; framework-mandated files remain in their default locations as **Framework adapters**. 71 + - Do not introduce a `src/verticals` grouping directory. The earlier `src/verticals/*` plan was reversed because the extra nesting added path noise without improving the domain model. 72 + - Verticals should not expose app-code barrel files; imports should target the concrete module that owns the thing being imported, while files inside a vertical may use relative imports. 73 + - Technical categories such as components, server functions, content, utilities, and libraries are not primary ownership boundaries. 74 + 75 + ## Example dialogue 76 + 77 + > **Dev:** "Should this table component stay under components because it renders UI?" 78 + > **Domain expert:** "No. If it exists to explain a preloading strategy in a demo, it belongs with that **Vertical** unless another **Vertical** consumes it through a public interface." 79 + 80 + > **Dev:** "Is the `/basic` route the **Chapter**, or just the layout for one?" 81 + > **Domain expert:** "It is the **Chapter**: the route is the reading unit that composes one article and one demo." 82 + 83 + > **Dev:** "Should markdown rendering utilities live in a generic utils folder?" 84 + > **Domain expert:** "No. Markdown rendering exists to produce **Article** content, so it belongs to **Article**." 85 + 86 + > **Dev:** "Should the route file be empty glue that delegates all chapter composition elsewhere?" 87 + > **Domain expert:** "No. The **Chapter route** is allowed to compose the **Article** and **Demo**, especially where route data and preloading behavior are part of the lesson." 88 + 89 + > **Dev:** "Do Electric shape routes belong to **Data** because they expose tables?" 90 + > **Domain expert:** "No. They belong to **Demo** because they deliver synced Pokemon listing data to the interactive examples; **Data** owns the persistence model and direct database queries." 91 + 92 + > **Dev:** "Should the home page own the chapter list because it renders the table of contents?" 93 + > **Domain expert:** "No. **Landing** renders the home-page view, but **Chapters** owns the canonical reading sequence." 94 + 95 + > **Dev:** "Should the header live in an app-shell vertical?" 96 + > **Domain expert:** "No. The header is chapter navigation chrome, so it belongs to **Chapters**." 97 + 98 + > **Dev:** "Should status dots be part of the demo console?" 99 + > **Domain expert:** "No. Status indicators are used by **Landing** and chapter navigation, so they belong to **Chapters**." 100 + 101 + > **Dev:** "Should `src/components/ui` live in a generic shared folder?" 102 + > **Domain expert:** "No. Primitive UI, tokens, global styles, and theme controls belong to **Design System**." 103 + 104 + > **Dev:** "Should API shape route files move into **Demo** because Electric collections use them?" 105 + > **Domain expert:** "No. The route files stay in the framework route tree as **Framework adapters**; **Demo** owns the Electric collections that depend on those URLs." 106 + 107 + > **Dev:** "Should we create an App Shell vertical for root route and router files?" 108 + > **Domain expert:** "Not yet. Keep framework-mandated files as **Framework adapters** and extract an app-shell vertical only if cohesive app-shell implementation grows." 109 + 110 + > **Dev:** "Should each vertical expose an `index.ts` barrel as its public API?" 111 + > **Domain expert:** "No. App-code barrels hide dependency shape and can create circular imports; import concrete modules directly." 112 + 113 + > **Dev:** "Should verticals live under `src/verticals` so the ownership model is explicit?" 114 + > **Domain expert:** "No. The ownership model is explicit in the named top-level directories under `src`; `src/verticals` was removed to keep import paths shorter while preserving the same vertical boundaries." 115 + 116 + ## Flagged ambiguities 117 + 118 + - "Shared" can mean either reusable product infrastructure or a dumping ground for unrelated code. Resolved: only use **Shared vertical** for cross-cutting code with a coherent responsibility.
+5
docs/adr/0002-organize-product-code-into-verticals.md
··· 1 + # Organize product code into verticals while keeping framework adapters conventional 2 + 3 + Status: Superseded by [ADR-0003](0003-keep-verticals-directly-under-src.md). 4 + 5 + We will organize product-owned implementation under `src/verticals/*`, with verticals such as Landing, Chapters, Articles, Demos, Data, and Design System. TanStack Start, TanStack Router, and Vite+ framework-mandated files stay in their conventional locations as framework adapters, because changing route/config conventions for a purer file tree would add maintenance cost without improving the product model. Verticals will use direct module imports instead of app-code barrel files so dependency shape stays visible.
+34
docs/adr/0003-keep-verticals-directly-under-src.md
··· 1 + # Keep verticals directly under src 2 + 3 + Status: Accepted 4 + 5 + Date: 2026-04-30 6 + 7 + ## Context 8 + 9 + ADR-0002 initially placed product-owned implementation under `src/verticals/*`. That made vertical ownership explicit, but it also added an extra path segment to every product import. 10 + 11 + The codebase has since moved the same verticals back directly under `src`. 12 + 13 + ## Decision 14 + 15 + Product-owned verticals live directly under `src/*` as named top-level directories: 16 + 17 + - `src/articles` 18 + - `src/chapters` 19 + - `src/data` 20 + - `src/demos` 21 + - `src/design-system` 22 + - `src/landing` 23 + 24 + Do not recreate `src/verticals`. 25 + 26 + TanStack Start, TanStack Router, API route modules, generated route files, runtime environment files, and Vite+ framework files remain in their conventional locations as framework adapters. 27 + 28 + ## Consequences 29 + 30 + The codebase keeps the vertical ownership model from ADR-0002 while removing the extra `verticals` nesting from import paths. 31 + 32 + The directory name is no longer the only signal that a folder is a vertical, so agents should use `CONTEXT.md` as the source of truth for vertical ownership. 33 + 34 + ADR-0002 remains useful for understanding why the code is organized around vertical ownership, but its target path is superseded by this ADR.
src/components/blog-table-split-column.tsx src/chapters/chapter-split.tsx
+7 -2
src/components/chapter-navigation.tsx src/chapters/chapter-navigation.tsx
··· 1 1 "use client"; 2 2 3 3 import { Link, useNavigate, useRouterState } from "@tanstack/react-router"; 4 - import { chapters, getChapterByPath, getChapterNeighbors, type ChapterPath } from "~/lib/chapters"; 5 - import { cn } from "~/lib/utils"; 4 + import { 5 + chapters, 6 + getChapterByPath, 7 + getChapterNeighbors, 8 + type ChapterPath, 9 + } from "~/chapters/chapters"; 10 + import { cn } from "~/design-system/utils/cn"; 6 11 7 12 export function ChapterSelect() { 8 13 const navigate = useNavigate();
+1 -1
src/components/console/console-card.tsx src/demos/components/console-card.tsx
··· 1 - import { cn } from "~/lib/utils"; 1 + import { cn } from "~/design-system/utils/cn"; 2 2 import type { ReactNode } from "react"; 3 3 4 4 interface ConsoleCardProps {
+1 -1
src/components/console/section-header.tsx src/chapters/section-header.tsx
··· 1 - import { cn } from "~/lib/utils"; 1 + import { cn } from "~/design-system/utils/cn"; 2 2 3 3 interface SectionHeaderProps { 4 4 title: string;
+1 -1
src/components/console/status-dot.tsx src/chapters/status-dot.tsx
··· 1 - import { cn } from "~/lib/utils"; 1 + import { cn } from "~/design-system/utils/cn"; 2 2 3 3 interface StatusDotProps { 4 4 status: "cached" | "fetching" | "idle" | "error";
+3 -3
src/components/filter-form.tsx src/demos/components/filter-form.tsx
··· 1 1 import * as React from "react"; 2 - import { Input } from "~/components/ui/input"; 3 - import { Label } from "~/components/ui/label"; 4 - import { Button } from "~/components/ui/button"; 2 + import { Input } from "~/design-system/ui/input"; 3 + import { Label } from "~/design-system/ui/label"; 4 + import { Button } from "~/design-system/ui/button"; 5 5 6 6 type FilterFormProps = { 7 7 initialName: string;
+1 -1
src/components/header.tsx src/chapters/header.tsx
··· 1 1 import { Link } from "@tanstack/react-router"; 2 + import { ThemeToggle } from "~/design-system/theme/theme-toggle"; 2 3 import { ChapterSelect } from "./chapter-navigation"; 3 - import { ThemeToggle } from "./theme-toggle"; 4 4 5 5 export function Header() { 6 6 return (
+1 -1
src/components/nav-item.tsx src/chapters/nav-item.tsx
··· 1 1 "use client"; 2 2 3 3 import { Link, useRouterState } from "@tanstack/react-router"; 4 - import { StatusDot } from "~/components/console/status-dot"; 4 + import { StatusDot } from "~/chapters/status-dot"; 5 5 6 6 interface NavItemProps { 7 7 to: string;
+1 -1
src/components/pagination-nav.tsx src/demos/components/pagination-nav.tsx
··· 1 1 import { Link } from "@tanstack/react-router"; 2 2 import type { FileRoutesByTo } from "~/routeTree.gen"; 3 - import { cn } from "~/lib/utils"; 3 + import { cn } from "~/design-system/utils/cn"; 4 4 5 5 interface PaginationNavProps { 6 6 prefetch?: "intent" | "viewport" | "render" | false;
+1 -1
src/components/strategy-article.tsx src/articles/strategy-article.tsx
··· 1 - import { renderMarkdown } from "~/utils/markdown"; 1 + import { renderMarkdown } from "~/articles/markdown"; 2 2 3 3 interface StrategyArticleProps { 4 4 title: string;
+3 -3
src/components/tables/pokedex-table-section.tsx src/demos/components/pokedex-table-section.tsx
··· 1 1 import * as React from "react"; 2 - import { POKEMON_LIMIT } from "~/constants"; 3 - import { lazily } from "~/lib/lazily"; 4 - import { PaginationNav } from "~/components/pagination-nav"; 2 + import { POKEMON_LIMIT } from "~/demos/pokemon-listing/constants"; 3 + import { lazily } from "~/demos/utils/lazily"; 4 + import { PaginationNav } from "~/demos/components/pagination-nav"; 5 5 import { PokemonTableSkeleton } from "./pokemon-table-skeleton"; 6 6 7 7 const { PokemonTable } = lazily(() => import("./pokemon-table"));
+2 -2
src/components/tables/pokemon-table-skeleton.tsx src/demos/components/pokemon-table-skeleton.tsx
··· 6 6 TableHead, 7 7 TableHeader, 8 8 TableRow, 9 - } from "~/components/ui/table"; 10 - import { cn } from "~/lib/utils"; 9 + } from "~/design-system/ui/table"; 10 + import { cn } from "~/design-system/utils/cn"; 11 11 12 12 interface PokemonTableSkeletonProps { 13 13 /**
+2 -2
src/components/tables/pokemon-table.tsx src/demos/components/pokemon-table.tsx
··· 5 5 TableHead, 6 6 TableHeader, 7 7 TableRow, 8 - } from "~/components/ui/table"; 8 + } from "~/design-system/ui/table"; 9 9 import { TypeBadge } from "./type-badge"; 10 - import { cn } from "~/lib/utils"; 10 + import { cn } from "~/design-system/utils/cn"; 11 11 12 12 interface Pokemon { 13 13 id: number;
+1 -1
src/components/tables/type-badge.tsx src/demos/components/type-badge.tsx
··· 1 - import { cn } from "~/lib/utils"; 1 + import { cn } from "~/design-system/utils/cn"; 2 2 3 3 interface TypeBadgeProps { 4 4 type: string;
src/components/theme-toggle.tsx src/design-system/theme/theme-toggle.tsx
+1 -1
src/components/ui/button.tsx src/design-system/ui/button.tsx
··· 1 1 import { Button as ButtonPrimitive } from "@base-ui/react/button"; 2 2 import { cva, type VariantProps } from "class-variance-authority"; 3 3 4 - import { cn } from "~/lib/utils"; 4 + import { cn } from "~/design-system/utils/cn"; 5 5 6 6 const buttonVariants = cva( 7 7 "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
+1 -1
src/components/ui/input.tsx src/design-system/ui/input.tsx
··· 1 1 import * as React from "react"; 2 2 import { Input as InputPrimitive } from "@base-ui/react/input"; 3 3 4 - import { cn } from "~/lib/utils"; 4 + import { cn } from "~/design-system/utils/cn"; 5 5 6 6 function Input({ className, type, ...props }: React.ComponentProps<"input">) { 7 7 return (
+1 -1
src/components/ui/label.tsx src/design-system/ui/label.tsx
··· 2 2 3 3 import * as React from "react"; 4 4 5 - import { cn } from "~/lib/utils"; 5 + import { cn } from "~/design-system/utils/cn"; 6 6 7 7 function Label({ className, ...props }: React.ComponentProps<"label">) { 8 8 return (
+1 -1
src/components/ui/table.tsx src/design-system/ui/table.tsx
··· 2 2 3 3 import * as React from "react"; 4 4 5 - import { cn } from "~/lib/utils"; 5 + import { cn } from "~/design-system/utils/cn"; 6 6 7 7 function Table({ className, ...props }: React.ComponentProps<"table">) { 8 8 return (
src/constants.ts src/demos/pokemon-listing/constants.ts
src/content/strategies.server.ts src/articles/strategies.server.ts
src/data/local/collections.ts src/demos/live-query/collections.ts
+143
src/landing/landing-page.tsx
··· 1 + import { Link } from "@tanstack/react-router"; 2 + import { createServerFn } from "@tanstack/react-start"; 3 + import { renderServerComponent } from "@tanstack/react-start/rsc"; 4 + import { StatusDot, StatusDotWithLabel } from "~/chapters/status-dot"; 5 + import { chapterGroups } from "~/chapters/chapters"; 6 + 7 + export const getLandingPage = createServerFn({ method: "GET" }).handler(async () => { 8 + return renderServerComponent(<LandingPageDocument />); 9 + }); 10 + 11 + export function LandingPage(props: Readonly<{ landingPage: React.ReactNode }>) { 12 + const { landingPage } = props; 13 + return <>{landingPage}</>; 14 + } 15 + 16 + function LandingPageDocument() { 17 + return ( 18 + <main className="min-h-screen flex flex-col bg-(--bg-primary)"> 19 + <section className="border-b border-(--border-default) bg-(--bg-secondary)"> 20 + <div className="mx-auto grid max-w-6xl gap-10 px-6 py-12 lg:grid-cols-[minmax(0,0.78fr)_minmax(20rem,0.42fr)] lg:items-end"> 21 + <div> 22 + <div className="flex items-center gap-3 mb-8"> 23 + <StatusDot status="cached" /> 24 + <span className="text-sm font-mono uppercase text-(--text-muted)"> 25 + Interactive technical book 26 + </span> 27 + </div> 28 + <h1 className="max-w-3xl text-4xl font-semibold text-(--text-primary) mb-5 leading-[1.05] sm:text-5xl"> 29 + Prefetching Patterns 30 + </h1> 31 + <p className="text-base text-(--text-secondary) max-w-2xl leading-relaxed"> 32 + A chaptered learning lab for comparing no preloading, route preloading, intent 33 + preloading, search-param driven data, and synced local collections. 34 + </p> 35 + </div> 36 + 37 + <aside className="toc-status" aria-label="Reading status legend"> 38 + <StatusDot status="cached" /> 39 + <span className="text-xs font-mono uppercase text-(--text-muted)"> 40 + Read in order. Each chapter keeps the console beside the explanation. 41 + </span> 42 + <div className="mt-5 grid gap-3 text-sm font-mono text-(--text-muted)"> 43 + <StatusDotWithLabel status="cached" label="Cached route data" /> 44 + <StatusDotWithLabel status="fetching" label="Fetching in progress" /> 45 + <StatusDotWithLabel status="idle" label="Idle route" /> 46 + </div> 47 + </aside> 48 + </div> 49 + </section> 50 + 51 + <section className="mx-auto w-full max-w-6xl px-6 py-12"> 52 + <h2 className="mb-8 border-b border-(--border-default) pb-4 text-xl font-semibold text-(--text-primary)"> 53 + Table of contents 54 + </h2> 55 + 56 + <div className="grid gap-10"> 57 + {chapterGroups.map((group) => ( 58 + <section key={group.label} className="toc-group" aria-labelledby={`toc-${group.label}`}> 59 + <div className="toc-group__heading"> 60 + <h3 id={`toc-${group.label}`} className="toc-group__title"> 61 + {group.label} 62 + </h3> 63 + <p className="toc-group__description">{group.description}</p> 64 + </div> 65 + 66 + <ol className="toc-list"> 67 + {group.chapters.map((chapter, chapterIndex) => ( 68 + <li key={chapter.to}> 69 + <Link 70 + to={chapter.to} 71 + preload={chapterIndex === 0 ? false : "intent"} 72 + className="toc-link group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color)" 73 + > 74 + <span className="toc-link__number">{chapter.number}</span> 75 + <span className="toc-link__body"> 76 + <span className="toc-link__title">{chapter.title}</span> 77 + <span className="toc-link__summary">{chapter.summary}</span> 78 + <span className="toc-link__tags" aria-label="Concepts"> 79 + {chapter.tags.map((tag) => ( 80 + <span key={tag} className="toc-link__tag"> 81 + {tag} 82 + </span> 83 + ))} 84 + </span> 85 + </span> 86 + <span className="toc-link__open" aria-hidden="true"> 87 + &gt; 88 + </span> 89 + </Link> 90 + </li> 91 + ))} 92 + </ol> 93 + </section> 94 + ))} 95 + </div> 96 + 97 + <div className="mt-12 border-t border-(--border-default) pt-6"> 98 + <Link 99 + to="/basic" 100 + className="inline-flex items-center gap-2 border border-(--accent-default) bg-(--accent-surface) px-4 py-3 font-mono text-sm font-semibold text-(--text-primary) transition-colors duration-fast hover:bg-(--accent-subtle) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color)" 101 + > 102 + Start reading 103 + <span aria-hidden="true">&gt;</span> 104 + </Link> 105 + </div> 106 + </section> 107 + 108 + <footer className="border-t border-(--border-default) bg-(--bg-card) mt-auto"> 109 + <div className="max-w-6xl mx-auto px-6 py-6 text-sm font-mono text-(--text-muted)"> 110 + <p> 111 + Built with{" "} 112 + <a 113 + href="https://tanstack.com/router/latest" 114 + target="_blank" 115 + rel="noopener noreferrer" 116 + className="text-(--accent-default) hover:text-(--accent-hover) hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-(--bg-card) transition-colors duration-fast" 117 + > 118 + TanStack Router 119 + </a> 120 + {" · "} 121 + <a 122 + href="https://tanstack.com/query/latest" 123 + target="_blank" 124 + rel="noopener noreferrer" 125 + className="text-(--accent-default) hover:text-(--accent-hover) hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-(--bg-card) transition-colors duration-fast" 126 + > 127 + TanStack Query 128 + </a> 129 + {" · "} 130 + <a 131 + href="https://tanstack.com/start/latest" 132 + target="_blank" 133 + rel="noopener noreferrer" 134 + className="text-(--accent-default) hover:text-(--accent-hover) hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-(--bg-card) transition-colors duration-fast" 135 + > 136 + TanStack Start 137 + </a> 138 + </p> 139 + </div> 140 + </footer> 141 + </main> 142 + ); 143 + }
src/lib/chapters.ts src/chapters/chapters.ts
src/lib/lazily.ts src/demos/utils/lazily.ts
+1 -1
src/lib/pokemon-listing.test.ts src/demos/pokemon-listing/pokemon-listing.test.ts
··· 1 1 import { describe, expect, it } from "vite-plus/test"; 2 - import { POKEMON_LIMIT } from "~/constants"; 2 + import { POKEMON_LIMIT } from "~/demos/pokemon-listing/constants"; 3 3 import { normalizePokemonNameFilter, toPokemonListing } from "./pokemon-listing"; 4 4 5 5 const makeRows = (count: number) =>
+1 -1
src/lib/pokemon-listing.ts src/demos/pokemon-listing/pokemon-listing.ts
··· 1 - import { POKEMON_LIMIT } from "~/constants"; 1 + import { POKEMON_LIMIT } from "~/demos/pokemon-listing/constants"; 2 2 3 3 export type PokemonListingRow = { 4 4 id: number;
src/lib/utils.ts src/design-system/utils/cn.ts
-44
src/logo.svg
··· 1 - <?xml version="1.0" encoding="UTF-8"?> 2 - <svg id="Layer_1" 3 - xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 841.9 595.3"> 4 - <!-- Generator: Adobe Illustrator 29.3.0, SVG Export Plug-In . SVG Version: 2.1.0 Build 146) --> 5 - <defs> 6 - <style> 7 - .st0 { 8 - fill: #9ae7fc; 9 - } 10 - 11 - .st1 { 12 - fill: #61dafb; 13 - } 14 - </style> 15 - </defs> 16 - <g> 17 - <path class="st1" d="M666.3,296.5c0-32.5-40.7-63.3-103.1-82.4,14.4-63.6,8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6,0,8.3.9,11.4,2.6,13.6,7.8,19.5,37.5,14.9,75.7-1.1,9.4-2.9,19.3-5.1,29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50,32.6-30.3,63.2-46.9,84-46.9v-22.3c-27.5,0-63.5,19.6-99.9,53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7,0,51.4,16.5,84,46.6-14,14.7-28,31.4-41.3,49.9-22.6,2.4-44,6.1-63.6,11-2.3-10-4-19.7-5.2-29-4.7-38.2,1.1-67.9,14.6-75.8,3-1.8,6.9-2.6,11.5-2.6v-22.3c-8.4,0-16,1.8-22.6,5.6-28.1,16.2-34.4,66.7-19.9,130.1-62.2,19.2-102.7,49.9-102.7,82.3s40.7,63.3,103.1,82.4c-14.4,63.6-8,114.2,20.2,130.4,6.5,3.8,14.1,5.6,22.5,5.6,27.5,0,63.5-19.6,99.9-53.6,36.4,33.8,72.4,53.2,99.9,53.2,8.4,0,16-1.8,22.6-5.6,28.1-16.2,34.4-66.7,19.9-130.1,62-19.1,102.5-49.9,102.5-82.3zm-130.2-66.7c-3.7,12.9-8.3,26.2-13.5,39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4,14.2,2.1,27.9,4.7,41,7.9zm-45.8,106.5c-7.8,13.5-15.8,26.3-24.1,38.2-14.9,1.3-30,2-45.2,2s-30.2-.7-45-1.9c-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8,6.2-13.4,13.2-26.8,20.7-39.9,7.8-13.5,15.8-26.3,24.1-38.2,14.9-1.3,30-2,45.2-2s30.2.7,45,1.9c8.3,11.9,16.4,24.6,24.2,38,7.6,13.1,14.5,26.4,20.8,39.8-6.3,13.4-13.2,26.8-20.7,39.9zm32.3-13c5.4,13.4,10,26.8,13.8,39.8-13.1,3.2-26.9,5.9-41.2,8,4.9-7.7,9.8-15.6,14.4-23.7,4.6-8,8.9-16.1,13-24.1zm-101.4,106.7c-9.3-9.6-18.6-20.3-27.8-32,9,.4,18.2.7,27.5.7s18.7-.2,27.8-.7c-9,11.7-18.3,22.4-27.5,32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9,3.7-12.9,8.3-26.2,13.5-39.5,4.1,8,8.4,16,13.1,24s9.5,15.8,14.4,23.4zm73.9-208.1c9.3,9.6,18.6,20.3,27.8,32-9-.4-18.2-.7-27.5-.7s-18.7.2-27.8.7c9-11.7,18.3-22.4,27.5-32zm-74,58.9c-4.9,7.7-9.8,15.6-14.4,23.7-4.6,8-8.9,16-13,24-5.4-13.4-10-26.8-13.8-39.8,13.1-3.1,26.9-5.8,41.2-7.9zm-90.5,125.2c-35.4-15.1-58.3-34.9-58.3-50.6s22.9-35.6,58.3-50.6c8.6-3.7,18-7,27.7-10.1,5.7,19.6,13.2,40,22.5,60.9-9.2,20.8-16.6,41.1-22.2,60.6-9.9-3.1-19.3-6.5-28-10.2zm53.8,142.9c-13.6-7.8-19.5-37.5-14.9-75.7,1.1-9.4,2.9-19.3,5.1-29.4,19.6,4.8,41,8.5,63.5,10.9,13.5,18.5,27.5,35.3,41.6,50-32.6,30.3-63.2,46.9-84,46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7,38.2-1.1,67.9-14.6,75.8-3,1.8-6.9,2.6-11.5,2.6-20.7,0-51.4-16.5-84-46.6,14-14.7,28-31.4,41.3-49.9,22.6-2.4,44-6.1,63.6-11,2.3,10.1,4.1,19.8,5.2,29.1zm38.5-66.7c-8.6,3.7-18,7-27.7,10.1-5.7-19.6-13.2-40-22.5-60.9,9.2-20.8,16.6-41.1,22.2-60.6,9.9,3.1,19.3,6.5,28.1,10.2,35.4,15.1,58.3,34.9,58.3,50.6,0,15.7-23,35.6-58.4,50.6zm-264.9-268.7z"/> 18 - <circle class="st1" cx="420.9" cy="296.5" r="45.7"/> 19 - <path class="st1" d="M520.5,78.1"/> 20 - </g> 21 - <circle class="st0" cx="420.8" cy="296.6" r="43"/> 22 - <path class="st1" d="M466.1,296.6c0,25-20.2,45.2-45.2,45.2s-45.2-20.2-45.2-45.2,20.2-45.2,45.2-45.2,45.2,20.2,45.2,45.2ZM386,295.6v-6.3c0-1.1,1.2-5.1,1.8-6.2,1-1.9,2.9-3.5,4.6-4.7l-3.4-3.4c4-3.6,9.4-3.7,13.7-.7,1.9-4.7,6.6-7.1,11.6-6.7l-.8,4.2c5.9.2,13.1,4.1,13.1,10.8s0,.5-.7.7c-1.7.3-3.4-.4-5-.6s-1.2-.4-1.2.3,2.5,4.1,3,5.5,1,3.5.8,5.3c-5.6-.8-10.5-3.2-14.8-6.7.3,2.6,4.1,21.7,5.3,21.9s.8-.6,1-1.1,1.3-6.3,1.3-6.7c0-1-1.7-1.8-2.2-2.8-1.2-2.7,1.3-4.7,3.7-3.3s5.2,6.2,7.5,7.3,13,1.4,14.8,3.3-2.9,4.6-1.5,7.6c6.7-2.6,13.5-3.3,20.6-2.5,3.1-9.7,3.1-20.3-.9-29.8-7.3,0-14.7-3.6-17.2-10.8-2.5-7.2-.7-8.6-1.3-9.3-.8-1-6.3.6-7.4-1.5s.3-1.1-.2-1.4-1.9-.6-2.6-.8c-26-6.4-51.3,15.7-49.7,42.1,0,1.6,1.6,10.3,2.4,11.1s4.8,0,6.3,0,3.7.3,5,.5c2.9.4,7.2,2.4,9.4,2.5s2.4-.8,2.7-2.4c.4-2.6.5-7.4.5-10.1s-1-7.8-1.3-11.6c-.9-.2-.7,0-.9.5-.7,1.3-1.1,3.2-1.9,4.8s-5.2,8.7-5.7,9-.7-.5-.8-.8c-1.6-3.5-2-7.9-1.9-11.8-.9-1-5.4,4.9-6.7,5.3l-.8-.4v-.3h-.2ZM455.6,276.4c1.1-1.2-6-8.9-7.2-10-3-2.7-5.4-4.5-3.5,1.4s5.7,7.8,10.6,8.5h.1ZM410.9,270.1c-.4-.5-6.1,2.9-5.5,4.6,1.9-1.3,5.9-1.7,5.5-4.6ZM400.4,276.4c-.3-2.4-6.3-2.7-7.2-1s1.6,1.4,1.9,1.4c1.8.3,3.5-.6,5.2-.4h.1ZM411.3,276.8c3.8,1.3,6.6,3.6,10.9,3.7s0-3-1.2-3.9c-2.2-1.7-5.1-2.4-7.8-2.4s-1.6-.3-1.4.4c2.8.6,7.3.7,8.4,3.8-2.3-.3-3.9-1.6-6.2-2s-2.5-.5-2.6.3h0ZM420.6,290.3c-.8-5.1-5.7-10.8-10.9-11.6s-1.3-.4-.8.5,4.7,3.2,5.7,4,4.5,4.2,2.1,3.8-8.4-7.8-9.4-6.7c.2.9,1.1,1.9,1.7,2.7,3,3.8,6.9,6.8,11.8,7.4h-.2ZM395.3,279.8c-5,1.1-6.9,6.3-6.7,11,.7.8,5-3.8,5.4-4.5s2.7-4.6,1.1-4-2.9,4.4-4.2,4.6.2-2.1.4-2.5c1.1-1.6,2.9-3.1,4-4.6h0ZM400.4,281.5c-.4-.5-2,1.3-2.3,1.7-2.9,3.9-2.6,10.2-1.5,14.8.8.2.8-.3,1.2-.7,3-3.8,5.5-10.5,4.5-15.4-2.1,3.1-3.1,7.3-3.6,11h-1.3c0-4,1.9-7.7,3-11.4h0ZM426.9,305.9c0-1.7-1.7-1.4-2.5-1.9s-1.3-1.9-3-1.4c1.3,2.1,3,3.2,5.5,3.4h0ZM417.2,308.5c7.6.7,5.5-1.9,1.4-5.5-1.3-.3-1.5,4.5-1.4,5.5ZM437,309.7c-3.5-.3-7.8-2-11.2-2.1s-1.3,0-1.9.7c4,1.3,8.4,1.7,12.1,4l1-2.5h0ZM420.5,312.8c-7.3,0-15.1,3.7-20.4,8.8s-4.8,5.3-4.8,6.2c0,1.8,8.6,6.2,10.5,6.8,12.1,4.8,27.5,3.5,38.2-4.2s3.1-2.7,0-6.2c-5.7-6.6-14.7-11.4-23.4-11.3h-.1ZM398.7,316.9c-1.4-1.4-5-1.9-7-2.1s-5.3-.3-6.9.6l13.9,1.4h0ZM456.9,314.8h-7.4c-.9,0-4.9,1.1-6,1.6s-.8.6,0,.5c2.4,0,5.1-1,7.6-1.3s3.5.2,5.1,0,1.3-.3.6-.8h0Z"/> 23 - <path class="st0" d="M386,295.6l.8.4c1.3-.3,5.8-6.2,6.7-5.3,0,3.9.3,8.3,1.9,11.8s0,1.2.8.8,5.1-7.8,5.7-9,1.3-3.5,1.9-4.8,0-.7.9-.5c.3,3.8,1.2,7.8,1.3,11.6s0,7.5-.5,10.1-1.1,2.4-2.7,2.4-6.5-2.1-9.4-2.5-3.7-.5-5-.5-5.4,1.1-6.3,0-2.2-9.5-2.4-11.1c-1.5-26.4,23.7-48.5,49.7-42.1s2.2.4,2.6.8,0,1,.2,1.4c1.1,2,6.5.5,7.4,1.5s.4,6.9,1.3,9.3c2.5,7.2,10,10.9,17.2,10.8,4,9.4,4,20.1.9,29.8-7.2-.7-13.9,0-20.6,2.5-1.3-3.1,4.1-5.1,1.5-7.6s-11.8-1.9-14.8-3.3-5.4-6.1-7.5-7.3-4.9.6-3.7,3.3,2.1,1.8,2.2,2.8-1,6.2-1.3,6.7-.3,1.3-1,1.1c-1.1-.3-5-19.3-5.3-21.9,4.3,3.5,9.2,5.9,14.8,6.7.2-1.9-.3-3.5-.8-5.3s-3-5.1-3-5.5c0-.8.9-.3,1.2-.3,1.6,0,3.3.8,5,.6s.7.3.7-.7c0-6.6-7.2-10.6-13.1-10.8l.8-4.2c-5.1-.3-9.6,2-11.6,6.7-4.3-3-9.8-3-13.7.7l3.4,3.4c-1.8,1.3-3.5,2.8-4.6,4.7s-1.8,5.1-1.8,6.2v6.6h.2ZM431.6,265c7.8,2.1,8.7-3.5.2-1.3l-.2,1.3ZM432.4,270.9c.3.6,6.4-.4,5.8-2.3s-4.6.6-5.7.6l-.2,1.7h.1ZM434.5,276c.8,1.2,5.7-1.8,5.5-2.7-.4-1.9-6.6,1.2-5.5,2.7ZM442.9,276.4c-.9-.9-5,2.8-4.6,4,.6,2.4,5.7-3,4.6-4ZM445.1,279.9c-.3.2-3.1,4.6-1.5,5s3.5-3.4,3.5-4-1.3-1.3-2-.9h0ZM448.9,287.4c2.1.8,3.8-5.1,2.3-5.5-1.9-.6-2.6,5.1-2.3,5.5ZM457.3,288.6c.5-1.7,1.1-4.7-1-5.5-1,.3-.6,3.9-.6,4.8l.3.5,1.3.2h0Z"/> 24 - <path class="st0" d="M455.6,276.4c-5-.8-9.1-3.6-10.6-8.5s.5-4,3.5-1.4,8.3,8.7,7.2,10h-.1Z"/> 25 - <path class="st0" d="M420.6,290.3c-4.9-.6-8.9-3.6-11.8-7.4s-1.5-1.8-1.7-2.7c1-1,8.5,6.6,9.4,6.7,2.4.4-1.8-3.5-2.1-3.8-1-.8-5.4-3.5-5.7-4-.4-.8.5-.5.8-.5,5.2.8,10.1,6.6,10.9,11.6h.2Z"/> 26 - <path class="st0" d="M400.4,281.5c-1.1,3.7-3,7.3-3,11.4h1.3c.5-3.7,1.5-7.8,3.6-11,1,4.8-1.5,11.6-4.5,15.4s-.4.8-1.2.7c-1.1-4.5-1.3-10.8,1.5-14.8s1.9-2.2,2.3-1.7h0Z"/> 27 - <path class="st0" d="M411.3,276.8c0-.8,2.1-.4,2.6-.3,2.4.4,4,1.7,6.2,2-1.2-3.1-5.7-3.2-8.4-3.8,0-.8.9-.4,1.4-.4,2.8,0,5.6.7,7.8,2.4,2.2,1.7,4,4,1.2,3.9-4.3,0-7.1-2.4-10.9-3.7h0Z"/> 28 - <path class="st0" d="M395.3,279.8c-1.1,1.6-3,3-4,4.6s-1.9,2.8-.4,2.5,2.8-4,4.2-4.6-.9,3.6-1.1,4c-.4.7-4.7,5.2-5.4,4.5-.2-4.6,1.8-9.9,6.7-11h0Z"/> 29 - <path class="st0" d="M437,309.7l-1,2.5c-3.6-2.3-8-2.8-12.1-4,.5-.7,1.1-.7,1.9-.7,3.4,0,7.8,1.8,11.2,2.1h0Z"/> 30 - <path class="st0" d="M417.2,308.5c0-1,0-5.8,1.4-5.5,4,3.5,6.1,6.2-1.4,5.5Z"/> 31 - <path class="st0" d="M400.4,276.4c-1.8-.3-3.5.7-5.2.4s-2.3-.8-1.9-1.4c.8-1.6,6.9-1.4,7.2,1h-.1Z"/> 32 - <path class="st0" d="M410.9,270.1c.4,3-3.6,3.3-5.5,4.6-.6-1.8,5-5.1,5.5-4.6Z"/> 33 - <path class="st0" d="M426.9,305.9c-2.5-.2-4.1-1.3-5.5-3.4,1.7-.4,2,.8,3,1.4s2.6.3,2.5,1.9h0Z"/> 34 - <path class="st1" d="M432.4,270.9l.2-1.7c1.1,0,5.1-2.2,5.7-.6s-5.5,2.9-5.8,2.3h-.1Z"/> 35 - <path class="st1" d="M431.6,265l.2-1.3c8.4-2.1,7.7,3.4-.2,1.3Z"/> 36 - <path class="st1" d="M434.5,276c-1.1-1.5,5.1-4.6,5.5-2.7s-4.6,4-5.5,2.7Z"/> 37 - <path class="st1" d="M442.9,276.4c1.1,1.1-4,6.4-4.6,4s3.7-4.9,4.6-4Z"/> 38 - <path class="st1" d="M445.1,279.9c.7-.4,2.1,0,2,.9s-2.4,4.4-3.5,4,1.3-4.8,1.5-5h0Z"/> 39 - <path class="st1" d="M448.9,287.4c-.3-.3.4-6.1,2.3-5.5,1.4.4-.2,6.2-2.3,5.5Z"/> 40 - <path class="st1" d="M457.3,288.6l-1.3-.2-.3-.5c0-.9-.4-4.6.6-4.8,2.1.8,1.5,3.8,1,5.5h0Z"/> 41 - <path class="st0" d="M420.5,312.8c8.9,0,17.9,4.7,23.4,11.3,5.6,6.6,3.8,3.5,0,6.2-10.7,7.7-26.1,9-38.2,4.2-1.9-.8-10.5-5.1-10.5-6.8s4-5.3,4.8-6.2c5.3-5,13.1-8.6,20.4-8.8h.1Z"/> 42 - <path class="st0" d="M398.7,316.9l-13.9-1.4c1.7-1,5-.8,6.9-.6s5.6.7,7,2.1h0Z"/> 43 - <path class="st0" d="M456.9,314.8c.7.5,0,.8-.6.8-1.6.2-3.5-.2-5.1,0-2.4.3-5.2,1.2-7.6,1.3s-1.1,0,0-.5,5.1-1.6,6-1.6h7.4,0Z"/> 44 - </svg>
+2 -2
src/routes/__root.tsx
··· 4 4 import { TanStackRouterDevtoolsPanel } from "@tanstack/react-router-devtools"; 5 5 import type { QueryClient } from "@tanstack/react-query"; 6 6 7 - import { Header } from "~/components/header"; 7 + import { Header } from "~/chapters/header"; 8 8 9 - import appCss from "~/styles/global.css?url"; 9 + import appCss from "~/design-system/styles/global.css?url"; 10 10 11 11 interface MyRouterContext { 12 12 queryClient: QueryClient;
+2 -2
src/routes/_chapters.tsx
··· 1 1 import { createFileRoute, Outlet, useMatches } from "@tanstack/react-router"; 2 2 3 - import { ChapterPager } from "~/components/chapter-navigation"; 4 - import { SectionHeader } from "~/components/console/section-header"; 3 + import { ChapterPager } from "~/chapters/chapter-navigation"; 4 + import { SectionHeader } from "~/chapters/section-header"; 5 5 6 6 export const Route = createFileRoute("/_chapters")({ 7 7 staticData: {
+5 -5
src/routes/_chapters/basic.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 import { useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 - import { ConsoleCard } from "~/components/console/console-card"; 4 + import { ConsoleCard } from "~/demos/components/console-card"; 5 5 import { 6 6 PokedexPagination, 7 7 PokedexTableResults, 8 8 PokedexTableSection, 9 - } from "~/components/tables/pokedex-table-section"; 10 - import { getPokemonListQueryFn, getPokemonListQueryKey } from "~/utils/pokemon"; 11 - import { getStrategyArticle } from "~/server/strategy-article.functions"; 12 - import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 9 + } from "~/demos/components/pokedex-table-section"; 10 + import { getPokemonListQueryFn, getPokemonListQueryKey } from "~/demos/query/pokemon-query"; 11 + import { getStrategyArticle } from "~/articles/strategy-article.functions"; 12 + import { BlogTableSplitColumn } from "~/chapters/chapter-split"; 13 13 14 14 const searchParamsSchema = v.object({ 15 15 offset: v.optional(v.number(), 0),
+9 -6
src/routes/_chapters/debounced-preload-filters.tsx
··· 2 2 import { useDebouncedCallback } from "@tanstack/react-pacer"; 3 3 import { queryOptions, useQueryClient, useSuspenseQuery } from "@tanstack/react-query"; 4 4 import * as v from "valibot"; 5 - import { FilterForm } from "~/components/filter-form"; 6 - import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 7 - import { ConsoleCard } from "~/components/console/console-card"; 5 + import { FilterForm } from "~/demos/components/filter-form"; 6 + import { BlogTableSplitColumn } from "~/chapters/chapter-split"; 7 + import { ConsoleCard } from "~/demos/components/console-card"; 8 8 import { 9 9 PokedexPagination, 10 10 PokedexTableResults, 11 11 PokedexTableSection, 12 - } from "~/components/tables/pokedex-table-section"; 13 - import { getFilteredPokemonListQueryKey, getFilteredPokemonListQueryFn } from "~/utils/pokemon"; 14 - import { getStrategyArticle } from "~/server/strategy-article.functions"; 12 + } from "~/demos/components/pokedex-table-section"; 13 + import { 14 + getFilteredPokemonListQueryKey, 15 + getFilteredPokemonListQueryFn, 16 + } from "~/demos/query/pokemon-query"; 17 + import { getStrategyArticle } from "~/articles/strategy-article.functions"; 15 18 16 19 const searchParamsSchema = v.object({ 17 20 offset: v.optional(v.number(), 0),
+9 -6
src/routes/_chapters/filters.tsx
··· 1 1 import { createFileRoute, useRouteContext } from "@tanstack/react-router"; 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 - import { FilterForm } from "~/components/filter-form"; 5 - import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 6 - import { ConsoleCard } from "~/components/console/console-card"; 4 + import { FilterForm } from "~/demos/components/filter-form"; 5 + import { BlogTableSplitColumn } from "~/chapters/chapter-split"; 6 + import { ConsoleCard } from "~/demos/components/console-card"; 7 7 import { 8 8 PokedexPagination, 9 9 PokedexTableResults, 10 10 PokedexTableSection, 11 - } from "~/components/tables/pokedex-table-section"; 12 - import { getFilteredPokemonListQueryKey, getFilteredPokemonListQueryFn } from "~/utils/pokemon"; 13 - import { getStrategyArticle } from "~/server/strategy-article.functions"; 11 + } from "~/demos/components/pokedex-table-section"; 12 + import { 13 + getFilteredPokemonListQueryKey, 14 + getFilteredPokemonListQueryFn, 15 + } from "~/demos/query/pokemon-query"; 16 + import { getStrategyArticle } from "~/articles/strategy-article.functions"; 14 17 15 18 const searchParamsSchema = v.object({ 16 19 offset: v.optional(v.number(), 0),
+5 -5
src/routes/_chapters/intent-preloading.tsx
··· 1 1 import { createFileRoute, useRouteContext } from "@tanstack/react-router"; 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 - import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 - import { ConsoleCard } from "~/components/console/console-card"; 4 + import { BlogTableSplitColumn } from "~/chapters/chapter-split"; 5 + import { ConsoleCard } from "~/demos/components/console-card"; 6 6 import { 7 7 PokedexPagination, 8 8 PokedexTableResults, 9 9 PokedexTableSection, 10 - } from "~/components/tables/pokedex-table-section"; 11 - import { getPokemonListQueryKey, getPokemonListQueryFn } from "~/utils/pokemon"; 12 - import { getStrategyArticle } from "~/server/strategy-article.functions"; 10 + } from "~/demos/components/pokedex-table-section"; 11 + import { getPokemonListQueryKey, getPokemonListQueryFn } from "~/demos/query/pokemon-query"; 12 + import { getStrategyArticle } from "~/articles/strategy-article.functions"; 13 13 14 14 const searchParamsSchema = v.object({ 15 15 offset: v.optional(v.number(), 0),
+7 -7
src/routes/_chapters/live-query-filters.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 import { useLiveSuspenseQuery, eq, ilike, toArray } from "@tanstack/react-db"; 3 3 import * as v from "valibot"; 4 - import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 - import { ConsoleCard } from "~/components/console/console-card"; 6 - import { FilterForm } from "~/components/filter-form"; 4 + import { BlogTableSplitColumn } from "~/chapters/chapter-split"; 5 + import { ConsoleCard } from "~/demos/components/console-card"; 6 + import { FilterForm } from "~/demos/components/filter-form"; 7 7 import { 8 8 PokedexPagination, 9 9 PokedexTableResults, 10 10 PokedexTableSection, 11 - } from "~/components/tables/pokedex-table-section"; 11 + } from "~/demos/components/pokedex-table-section"; 12 12 import { 13 13 pokemonCollection, 14 14 pokemonTypesCollection, 15 15 typesCollection, 16 - } from "~/data/local/collections"; 16 + } from "~/demos/live-query/collections"; 17 17 import { 18 18 getPokemonListingQueryLimit, 19 19 normalizePokemonNameFilter, 20 20 toPokemonListing, 21 - } from "~/lib/pokemon-listing"; 22 - import { getStrategyArticle } from "~/server/strategy-article.functions"; 21 + } from "~/demos/pokemon-listing/pokemon-listing"; 22 + import { getStrategyArticle } from "~/articles/strategy-article.functions"; 23 23 24 24 const searchParamsSchema = v.object({ 25 25 offset: v.optional(v.number(), 0),
+9 -6
src/routes/_chapters/live-query.tsx
··· 1 1 import { createFileRoute } from "@tanstack/react-router"; 2 2 import { useLiveSuspenseQuery, eq, toArray } from "@tanstack/react-db"; 3 3 import * as v from "valibot"; 4 - import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 - import { ConsoleCard } from "~/components/console/console-card"; 4 + import { BlogTableSplitColumn } from "~/chapters/chapter-split"; 5 + import { ConsoleCard } from "~/demos/components/console-card"; 6 6 import { 7 7 PokedexPagination, 8 8 PokedexTableResults, 9 9 PokedexTableSection, 10 - } from "~/components/tables/pokedex-table-section"; 11 - import { getStrategyArticle } from "~/server/strategy-article.functions"; 10 + } from "~/demos/components/pokedex-table-section"; 11 + import { getStrategyArticle } from "~/articles/strategy-article.functions"; 12 12 import { 13 13 pokemonCollection, 14 14 typesCollection, 15 15 pokemonTypesCollection, 16 - } from "~/data/local/collections"; 17 - import { getPokemonListingQueryLimit, toPokemonListing } from "~/lib/pokemon-listing"; 16 + } from "~/demos/live-query/collections"; 17 + import { 18 + getPokemonListingQueryLimit, 19 + toPokemonListing, 20 + } from "~/demos/pokemon-listing/pokemon-listing"; 18 21 19 22 const searchParamsSchema = v.object({ 20 23 offset: v.optional(v.number(), 0),
+5 -5
src/routes/_chapters/pagination.tsx
··· 1 1 import { createFileRoute, useRouteContext } from "@tanstack/react-router"; 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 - import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 - import { ConsoleCard } from "~/components/console/console-card"; 4 + import { BlogTableSplitColumn } from "~/chapters/chapter-split"; 5 + import { ConsoleCard } from "~/demos/components/console-card"; 6 6 import { 7 7 PokedexPagination, 8 8 PokedexTableResults, 9 9 PokedexTableSection, 10 - } from "~/components/tables/pokedex-table-section"; 11 - import { getPokemonListQueryKey, getPokemonListQueryFn } from "~/utils/pokemon"; 12 - import { getStrategyArticle } from "~/server/strategy-article.functions"; 10 + } from "~/demos/components/pokedex-table-section"; 11 + import { getPokemonListQueryKey, getPokemonListQueryFn } from "~/demos/query/pokemon-query"; 12 + import { getStrategyArticle } from "~/articles/strategy-article.functions"; 13 13 14 14 const searchParamsSchema = v.object({ 15 15 offset: v.optional(v.number(), 0),
+5 -5
src/routes/_chapters/preloading.tsx
··· 1 1 import { createFileRoute, useRouteContext } from "@tanstack/react-router"; 2 2 import { queryOptions, useSuspenseQuery } from "@tanstack/react-query"; 3 3 import * as v from "valibot"; 4 - import { BlogTableSplitColumn } from "~/components/blog-table-split-column"; 5 - import { ConsoleCard } from "~/components/console/console-card"; 4 + import { BlogTableSplitColumn } from "~/chapters/chapter-split"; 5 + import { ConsoleCard } from "~/demos/components/console-card"; 6 6 import { 7 7 PokedexPagination, 8 8 PokedexTableResults, 9 9 PokedexTableSection, 10 - } from "~/components/tables/pokedex-table-section"; 11 - import { getPokemonListQueryKey, getPokemonListQueryFn } from "~/utils/pokemon"; 12 - import { getStrategyArticle } from "~/server/strategy-article.functions"; 10 + } from "~/demos/components/pokedex-table-section"; 11 + import { getPokemonListQueryKey, getPokemonListQueryFn } from "~/demos/query/pokemon-query"; 12 + import { getStrategyArticle } from "~/articles/strategy-article.functions"; 13 13 14 14 const searchParamsSchema = v.object({ 15 15 offset: v.optional(v.number(), 0),
+3 -139
src/routes/index.tsx
··· 1 - import { createFileRoute, Link } from "@tanstack/react-router"; 2 - import { createServerFn } from "@tanstack/react-start"; 3 - import { renderServerComponent } from "@tanstack/react-start/rsc"; 4 - import { StatusDot, StatusDotWithLabel } from "~/components/console/status-dot"; 5 - import { chapterGroups } from "~/lib/chapters"; 1 + import { createFileRoute } from "@tanstack/react-router"; 2 + import { LandingPage, getLandingPage } from "~/landing/landing-page"; 6 3 7 4 export const Route = createFileRoute("/")({ 8 5 loader: async () => { ··· 12 9 component: LandingPageComponent, 13 10 }); 14 11 15 - const getLandingPage = createServerFn({ method: "GET" }).handler(async () => { 16 - return renderServerComponent(<LandingPageDocument />); 17 - }); 18 - 19 12 function LandingPageComponent() { 20 13 const { landingPage } = Route.useLoaderData(); 21 - return <>{landingPage}</>; 22 - } 23 - 24 - function LandingPageDocument() { 25 - return ( 26 - <main className="min-h-screen flex flex-col bg-(--bg-primary)"> 27 - <section className="border-b border-(--border-default) bg-(--bg-secondary)"> 28 - <div className="mx-auto grid max-w-6xl gap-10 px-6 py-12 lg:grid-cols-[minmax(0,0.78fr)_minmax(20rem,0.42fr)] lg:items-end"> 29 - <div> 30 - <div className="flex items-center gap-3 mb-8"> 31 - <StatusDot status="cached" /> 32 - <span className="text-sm font-mono uppercase text-(--text-muted)"> 33 - Interactive technical book 34 - </span> 35 - </div> 36 - <h1 className="max-w-3xl text-4xl font-semibold text-(--text-primary) mb-5 leading-[1.05] sm:text-5xl"> 37 - Prefetching Patterns 38 - </h1> 39 - <p className="text-base text-(--text-secondary) max-w-2xl leading-relaxed"> 40 - A chaptered learning lab for comparing no preloading, route preloading, intent 41 - preloading, search-param driven data, and synced local collections. 42 - </p> 43 - </div> 44 - 45 - <aside className="toc-status" aria-label="Reading status legend"> 46 - <StatusDot status="cached" /> 47 - <span className="text-xs font-mono uppercase text-(--text-muted)"> 48 - Read in order. Each chapter keeps the console beside the explanation. 49 - </span> 50 - <div className="mt-5 grid gap-3 text-sm font-mono text-(--text-muted)"> 51 - <StatusDotWithLabel status="cached" label="Cached route data" /> 52 - <StatusDotWithLabel status="fetching" label="Fetching in progress" /> 53 - <StatusDotWithLabel status="idle" label="Idle route" /> 54 - </div> 55 - </aside> 56 - </div> 57 - </section> 58 - 59 - <section className="mx-auto w-full max-w-6xl px-6 py-12"> 60 - <h2 className="mb-8 border-b border-(--border-default) pb-4 text-xl font-semibold text-(--text-primary)"> 61 - Table of contents 62 - </h2> 63 - 64 - <div className="grid gap-10"> 65 - {chapterGroups.map((group) => ( 66 - <section key={group.label} className="toc-group" aria-labelledby={`toc-${group.label}`}> 67 - <div className="toc-group__heading"> 68 - <h3 id={`toc-${group.label}`} className="toc-group__title"> 69 - {group.label} 70 - </h3> 71 - <p className="toc-group__description">{group.description}</p> 72 - </div> 73 - 74 - <ol className="toc-list"> 75 - {group.chapters.map((chapter, chapterIndex) => ( 76 - <li key={chapter.to}> 77 - <Link 78 - to={chapter.to} 79 - preload={chapterIndex === 0 ? false : "intent"} 80 - className="toc-link group focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color)" 81 - > 82 - <span className="toc-link__number">{chapter.number}</span> 83 - <span className="toc-link__body"> 84 - <span className="toc-link__title">{chapter.title}</span> 85 - <span className="toc-link__summary">{chapter.summary}</span> 86 - <span className="toc-link__tags" aria-label="Concepts"> 87 - {chapter.tags.map((tag) => ( 88 - <span key={tag} className="toc-link__tag"> 89 - {tag} 90 - </span> 91 - ))} 92 - </span> 93 - </span> 94 - <span className="toc-link__open" aria-hidden="true"> 95 - &gt; 96 - </span> 97 - </Link> 98 - </li> 99 - ))} 100 - </ol> 101 - </section> 102 - ))} 103 - </div> 104 - 105 - <div className="mt-12 border-t border-(--border-default) pt-6"> 106 - <Link 107 - to="/basic" 108 - className="inline-flex items-center gap-2 border border-(--accent-default) bg-(--accent-surface) px-4 py-3 font-mono text-sm font-semibold text-(--text-primary) transition-colors duration-fast hover:bg-(--accent-subtle) focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color)" 109 - > 110 - Start reading 111 - <span aria-hidden="true">&gt;</span> 112 - </Link> 113 - </div> 114 - </section> 115 - 116 - <footer className="border-t border-(--border-default) bg-(--bg-card) mt-auto"> 117 - <div className="max-w-6xl mx-auto px-6 py-6 text-sm font-mono text-(--text-muted)"> 118 - <p> 119 - Built with{" "} 120 - <a 121 - href="https://tanstack.com/router/latest" 122 - target="_blank" 123 - rel="noopener noreferrer" 124 - className="text-(--accent-default) hover:text-(--accent-hover) hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-(--bg-card) transition-colors duration-fast" 125 - > 126 - TanStack Router 127 - </a> 128 - {" · "} 129 - <a 130 - href="https://tanstack.com/query/latest" 131 - target="_blank" 132 - rel="noopener noreferrer" 133 - className="text-(--accent-default) hover:text-(--accent-hover) hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-(--bg-card) transition-colors duration-fast" 134 - > 135 - TanStack Query 136 - </a> 137 - {" · "} 138 - <a 139 - href="https://tanstack.com/start/latest" 140 - target="_blank" 141 - rel="noopener noreferrer" 142 - className="text-(--accent-default) hover:text-(--accent-hover) hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-(--ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-(--bg-card) transition-colors duration-fast" 143 - > 144 - TanStack Start 145 - </a> 146 - </p> 147 - </div> 148 - </footer> 149 - </main> 150 - ); 14 + return <LandingPage landingPage={landingPage} />; 151 15 }
+1 -1
src/server/pokemon.functions.ts src/demos/server/pokemon.functions.ts
··· 5 5 getPokemonListingQueryLimit, 6 6 normalizePokemonNameFilter, 7 7 toPokemonListing, 8 - } from "~/lib/pokemon-listing"; 8 + } from "~/demos/pokemon-listing/pokemon-listing"; 9 9 10 10 const PokemonListParamsSchema = v.object({ 11 11 offset: v.optional(v.number()),
+2 -2
src/server/strategy-article.functions.tsx src/articles/strategy-article.functions.tsx
··· 1 1 import { createServerFn } from "@tanstack/react-start"; 2 2 import { renderServerComponent } from "@tanstack/react-start/rsc"; 3 3 import * as v from "valibot"; 4 - import { StrategyArticle } from "~/components/strategy-article"; 5 - import { strategyContent } from "~/content/strategies.server"; 4 + import { StrategyArticle } from "~/articles/strategy-article"; 5 + import { strategyContent } from "~/articles/strategies.server"; 6 6 7 7 export const getStrategyArticle = createServerFn({ method: "GET" }) 8 8 .inputValidator(v.object({ title: v.string(), slug: v.string() }))
src/styles/global.css src/design-system/styles/global.css
src/styles/tokens.css src/design-system/styles/tokens.css
src/utils/markdown.ts src/articles/markdown.ts
+4 -1
src/utils/pokemon.ts src/demos/query/pokemon-query.ts
··· 1 1 import type { QueryFunctionContext } from "@tanstack/react-query"; 2 - import { getServerPokemonList, getServerFilteredPokemonList } from "~/server/pokemon.functions"; 2 + import { 3 + getServerPokemonList, 4 + getServerFilteredPokemonList, 5 + } from "~/demos/server/pokemon.functions"; 3 6 4 7 export const getPokemonListQueryKey = (location: string, offset: number) => { 5 8 return ["pokemon-list", location, { offset }] as const;