this repo has no description
0
fork

Configure Feed

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

fix articles

+217 -215
+39
src/articles/article-shell.server.tsx
··· 1 + import { cn } from "~/design-system/utils/cn"; 2 + import { renderMarkdown } from "~/articles/render-markdown.server"; 3 + 4 + interface ArticleShell { 5 + title: string; 6 + markdown: string; 7 + } 8 + 9 + export async function ArticleShell({ title, markdown }: ArticleShell) { 10 + const content = await renderMarkdown(markdown); 11 + 12 + const mainClassName = cn([ 13 + "space-y-5 text-sm leading-7 text-(--text-secondary)", 14 + "[&_h2]:mb-4 [&_h2]:mt-8 [&_h2]:text-xl [&_h2]:font-semibold [&_h2]:text-(--text-primary) [&_h2]:first:mt-0", 15 + "[&_h3]:mb-3 [&_h3]:mt-6 [&_h3]:text-lg [&_h3]:font-semibold [&_h3]:text-(--text-primary) first:[&_h3]:mt-0", 16 + "[&_p]:mb-5", 17 + "[&_ul]:mb-5 [&_ul]:list-disc [&_ul]:space-y-2 [&_ul]:pl-5", 18 + "[&_ol]:mb-5 [&_ol]:list-decimal [&_ol]:space-y-2 [&_ol]:pl-5", 19 + "[&_li]:text-(--text-secondary)", 20 + "[&_strong]:font-semibold [&_strong]:text-(--text-primary)", 21 + "[&_code]:rounded [&_code]:bg-(--bg-primary) [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs", 22 + "[&_pre]:mb-5 [&_pre]:overflow-x-auto [&_pre]:rounded [&_pre]:bg-(--bg-primary) [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-xs", 23 + "[&_a]:text-(--accent-default) [&_a]:underline [&_a]:decoration-(--accent-default)/30 [&_a]:underline-offset-2 hover:[&_a]:decoration-(--accent-default)", 24 + "[&_blockquote]:mb-5 [&_blockquote]:border-l-2 [&_blockquote]:border-(--accent-default) [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:text-(--text-muted)", 25 + "[&_hr]:my-8 [&_hr]:border-(--border-default)", 26 + ]); 27 + 28 + return ( 29 + <article className="border border-(--border-default) bg-(--bg-secondary) p-6 md:p-8 xl:sticky xl:top-20"> 30 + <h2 className="mb-6 text-3xl font-semibold uppercase leading-tight text-(--text-primary) md:text-4xl"> 31 + {title} 32 + </h2> 33 + <div className={mainClassName}>{content}</div> 34 + <div className="mt-8 border-t border-(--border-default) pt-5 font-mono text-xs uppercase text-(--text-muted)"> 35 + Strategy notes / walkthrough 36 + </div> 37 + </article> 38 + ); 39 + }
+29
src/articles/article.functions.tsx
··· 1 + import { createServerFn } from "@tanstack/react-start"; 2 + import { renderServerComponent } from "@tanstack/react-start/rsc"; 3 + import * as v from "valibot"; 4 + import { ArticleShell } from "~/articles/article-shell.server"; 5 + 6 + const articlesContent: Record<string, () => Promise<string>> = { 7 + basic: () => import("./content/basic.md?raw").then((m) => m.default), 8 + preloading: () => import("./content/preloading.md?raw").then((m) => m.default), 9 + "intent-preloading": () => import("./content/intent-preloading.md?raw").then((m) => m.default), 10 + pagination: () => import("./content/pagination.md?raw").then((m) => m.default), 11 + filters: () => import("./content/filters.md?raw").then((m) => m.default), 12 + "debounced-preload-filters": () => 13 + import("./content/debounced-preload-filters.md?raw").then((m) => m.default), 14 + "live-query": () => import("./content/live-query.md?raw").then((m) => m.default), 15 + "live-query-filters": () => import("./content/live-query-filters.md?raw").then((m) => m.default), 16 + }; 17 + 18 + export const getArticle = createServerFn({ method: "GET" }) 19 + .inputValidator(v.object({ title: v.string(), slug: v.string() })) 20 + .handler(async ({ data }) => { 21 + const markdown = await (articlesContent[data.slug]?.() ?? 22 + Promise.resolve("Content coming soon.")); 23 + 24 + const Renderable = await renderServerComponent( 25 + <ArticleShell title={data.title} markdown={markdown} />, 26 + ); 27 + 28 + return { Renderable }; 29 + });
+20
src/articles/content/basic.md
··· 1 + ## No prefetching (baseline) 2 + 3 + This is the baseline approach — no prefetching whatsoever. Every page navigation triggers a fresh network request, and the user sees loading skeletons while data is fetched on demand. 4 + 5 + ### How it works 6 + 7 + 1. The user navigates to the page 8 + 2. React Query fires the fetch request from the component 9 + 3. A loading skeleton renders while the query resolves 10 + 4. Data arrives and the table renders with results 11 + 12 + ### Trade-offs 13 + 14 + - **Latency**: Every navigation incurs a full server round-trip 15 + - **UX**: Loading skeletons are visible on every page visit 16 + - **Simplicity**: The implementation is the easiest to understand and maintain 17 + 18 + ### When to use 19 + 20 + This pattern works for low-traffic pages, internal tools, or anywhere the performance overhead of a round-trip is acceptable. It is the simplest possible starting point before adding any prefetching strategy.
+19
src/articles/content/debounced-preload-filters.md
··· 1 + ## Debounced filter prefetch 2 + 3 + Debounced preloading extends filter-based prefetching by initiating fetches on every keystroke — not just on form submission. A debounce window ensures the server is not overwhelmed by rapid-fire requests. 4 + 5 + ### How it works 6 + 7 + 1. The user types in the filter input 8 + 2. Each keystroke updates the search params after a short debounce delay 9 + 3. React Query fires a prefetch for the new parameter values 10 + 4. Results update incrementally as the user types 11 + 5. The user sees live-filtered results without needing to press submit 12 + 13 + ### Debounce strategy 14 + 15 + The debounce delay balances responsiveness with server load. A 300–400ms window prevents unnecessary intermediate requests while still feeling snappy to the user. TanStack Pacer provides the debouncing primitive. 16 + 17 + ### When to use 18 + 19 + Ideal for search-as-you-type interfaces against reasonably sized or indexed datasets. Pair with server-side indexing for larger collections.
+16
src/articles/content/filters.md
··· 1 + ## Submitted filter prefetch 2 + 3 + Filter-based prefetching improves search experiences by preloading results when a filter form is submitted. Instead of waiting for the filter to be applied after submission, data is already being fetched as the form processes. 4 + 5 + ### How it works 6 + 7 + 1. The user enters filter criteria in the form fields 8 + 2. The form is submitted, updating search parameters in the URL 9 + 3. React Query prefetches results for the new parameter set 10 + 4. Results render with minimal loading time 11 + 12 + ### Key considerations 13 + 14 + - **URL-driven state**: Filters are stored in search params, making them shareable and bookmarkable 15 + - **Cache management**: Each filter combination creates a unique cache entry with its own `staleTime` 16 + - **Type safety**: Valibot validates search params at the route boundary, ensuring the filter state is always valid
+18
src/articles/content/intent-preloading.md
··· 1 + ## Intent-based preloading 2 + 3 + Intent preloading takes route-level prefetching further by triggering data fetches on hover and focus events. This creates a near-instant experience — data begins loading the moment the user shows intent to navigate. 4 + 5 + ### How it works 6 + 7 + 1. The user hovers over or focuses a navigation link 8 + 2. The `prefetch="intent"` directive triggers the route's loader 9 + 3. Data is fetched and cached before the click completes 10 + 4. On click, the page renders immediately with zero loading time 11 + 12 + ### Why intent matters 13 + 14 + Unlike viewport-based preloading which can trigger fetches for links the user may never click, intent preloading only fires when the user explicitly shows interest in a link. This balances performance gains with bandwidth efficiency. 15 + 16 + ### Configuration 17 + 18 + Set `prefetch="intent"` on `Link` components or configure it as the default for specific routes in your router configuration.
+18
src/articles/content/live-query-filters.md
··· 1 + ## Reactive filtered live search 2 + 3 + Combining live queries with client-side filtering creates a real-time search experience. The full dataset stays in sync with the server while the client filters and displays results reactively as the user types. 4 + 5 + ### How it works 6 + 7 + 1. A live query fetches the full dataset and keeps it in sync 8 + 2. Client-side filtering narrows results based on user input 9 + 3. Results update immediately with every keystroke 10 + 4. Underlying database changes are reflected in real time 11 + 12 + ### Architecture 13 + 14 + Electric SQL handles the server-to-client sync layer, while TanStack DB collections manage live query subscriptions. Client-side filtering provides instant feedback without additional network requests, and URL search params keep the filter state shareable. 15 + 16 + ### Best for 17 + 18 + Dashboards, monitoring interfaces, multi-user collaboration tools, and any application where data freshness takes priority over raw query throughput.
+20
src/articles/content/live-query.md
··· 1 + ## Electric SQL synced collection 2 + 3 + Live queries use Electric SQL to keep client-side data in sync with the server database in real time. Instead of polling or manual refetching, database changes are pushed to connected clients automatically. 4 + 5 + ### How it works 6 + 7 + 1. The client establishes a sync connection to the Electric SQL server 8 + 2. A live query is defined using the TanStack DB collection API 9 + 3. When database records change, updates stream to connected clients 10 + 4. The UI re-renders automatically with fresh data 11 + 12 + ### Benefits 13 + 14 + - **Real-time updates**: Changes appear immediately without manual refresh 15 + - **Offline support**: Local data remains available during connectivity interruptions 16 + - **Multi-user**: All connected clients see each other's changes in real time 17 + 18 + ### Trade-offs 19 + 20 + Live queries require the Electric SQL sync infrastructure. They introduce additional complexity compared to simple REST fetching but are the right choice for collaborative or real-time applications where data freshness matters.
+18
src/articles/content/pagination.md
··· 1 + ## Viewport pagination preload 2 + 3 + Pagination preloading uses the viewport to determine which pages to prefetch. When "next" or "previous" pagination links enter the viewport, the corresponding page's data is preloaded before the user clicks. 4 + 5 + ### How it works 6 + 7 + 1. Pagination links render with `prefetch="viewport"` 8 + 2. When a link enters the viewport, the router triggers a prefetch 9 + 3. Data is cached before the user clicks the navigation button 10 + 4. Pagination navigation feels instant 11 + 12 + ### Why viewport preloading for pagination? 13 + 14 + Pagination is a natural fit for viewport-based prefetching. The next and previous buttons are always visible at the bottom of the page, so prefetching them is almost guaranteed to be useful. It strikes an ideal balance between eager and lazy loading. 15 + 16 + ### Optimization 17 + 18 + Combine with `staleTime` to avoid redundant refetches as the user pages back and forth through the data set.
+20
src/articles/content/preloading.md
··· 1 + ## Route-level prefetch 2 + 3 + Route-level prefetching improves perceived performance by initiating data fetches before the user reaches the page. When a link enters the viewport or receives focus, TanStack Router fires the route's `loader` function to pre-warm the query cache. 4 + 5 + ### How it works 6 + 7 + 1. TanStack Router detects that a link is about to be visited 8 + 2. The route's `loader` calls `prefetchQuery` on the query client 9 + 3. Data is fetched and cached in React Query's cache 10 + 4. When the user clicks, the page renders instantly from cache 11 + 12 + ### Trade-offs 13 + 14 + - **Bandwidth**: May fetch data the user never actually views 15 + - **Freshness**: Cached data needs an appropriate `staleTime` 16 + - **Complexity**: Requires defining loaders and query options per route 17 + 18 + ### Best practices 19 + 20 + Set an appropriate `staleTime` to balance data freshness with cache hits. Use `prefetchQuery` for speculative fetches — it won't throw if the query fails, making it safe for preloading.
src/articles/markdown.ts src/articles/render-markdown.server.ts
-158
src/articles/strategies.server.ts
··· 1 - export const strategyContent: Record<string, string> = { 2 - basic: `## No prefetching (baseline) 3 - 4 - This is the baseline approach — no prefetching whatsoever. Every page navigation triggers a fresh network request, and the user sees loading skeletons while data is fetched on demand. 5 - 6 - ### How it works 7 - 8 - 1. The user navigates to the page 9 - 2. React Query fires the fetch request from the component 10 - 3. A loading skeleton renders while the query resolves 11 - 4. Data arrives and the table renders with results 12 - 13 - ### Trade-offs 14 - 15 - - **Latency**: Every navigation incurs a full server round-trip 16 - - **UX**: Loading skeletons are visible on every page visit 17 - - **Simplicity**: The implementation is the easiest to understand and maintain 18 - 19 - ### When to use 20 - 21 - This pattern works for low-traffic pages, internal tools, or anywhere the performance overhead of a round-trip is acceptable. It is the simplest possible starting point before adding any prefetching strategy.`, 22 - 23 - preloading: `## Route-level prefetch 24 - 25 - Route-level prefetching improves perceived performance by initiating data fetches before the user reaches the page. When a link enters the viewport or receives focus, TanStack Router fires the route's \`loader\` function to pre-warm the query cache. 26 - 27 - ### How it works 28 - 29 - 1. TanStack Router detects that a link is about to be visited 30 - 2. The route's \`loader\` calls \`prefetchQuery\` on the query client 31 - 3. Data is fetched and cached in React Query's cache 32 - 4. When the user clicks, the page renders instantly from cache 33 - 34 - ### Trade-offs 35 - 36 - - **Bandwidth**: May fetch data the user never actually views 37 - - **Freshness**: Cached data needs an appropriate \`staleTime\` 38 - - **Complexity**: Requires defining loaders and query options per route 39 - 40 - ### Best practices 41 - 42 - Set an appropriate \`staleTime\` to balance data freshness with cache hits. Use \`prefetchQuery\` for speculative fetches — it won't throw if the query fails, making it safe for preloading.`, 43 - 44 - "intent-preloading": `## Intent-based preloading 45 - 46 - Intent preloading takes route-level prefetching further by triggering data fetches on hover and focus events. This creates a near-instant experience — data begins loading the moment the user shows intent to navigate. 47 - 48 - ### How it works 49 - 50 - 1. The user hovers over or focuses a navigation link 51 - 2. The \`prefetch="intent"\` directive triggers the route's loader 52 - 3. Data is fetched and cached before the click completes 53 - 4. On click, the page renders immediately with zero loading time 54 - 55 - ### Why intent matters 56 - 57 - Unlike viewport-based preloading which can trigger fetches for links the user may never click, intent preloading only fires when the user explicitly shows interest in a link. This balances performance gains with bandwidth efficiency. 58 - 59 - ### Configuration 60 - 61 - Set \`prefetch="intent"\` on \`Link\` components or configure it as the default for specific routes in your router configuration.`, 62 - 63 - pagination: `## Viewport pagination preload 64 - 65 - Pagination preloading uses the viewport to determine which pages to prefetch. When "next" or "previous" pagination links enter the viewport, the corresponding page's data is preloaded before the user clicks. 66 - 67 - ### How it works 68 - 69 - 1. Pagination links render with \`prefetch="viewport"\` 70 - 2. When a link enters the viewport, the router triggers a prefetch 71 - 3. Data is cached before the user clicks the navigation button 72 - 4. Pagination navigation feels instant 73 - 74 - ### Why viewport preloading for pagination? 75 - 76 - Pagination is a natural fit for viewport-based prefetching. The next and previous buttons are always visible at the bottom of the page, so prefetching them is almost guaranteed to be useful. It strikes an ideal balance between eager and lazy loading. 77 - 78 - ### Optimization 79 - 80 - Combine with \`staleTime\` to avoid redundant refetches as the user pages back and forth through the data set.`, 81 - 82 - filters: `## Submitted filter prefetch 83 - 84 - Filter-based prefetching improves search experiences by preloading results when a filter form is submitted. Instead of waiting for the filter to be applied after submission, data is already being fetched as the form processes. 85 - 86 - ### How it works 87 - 88 - 1. The user enters filter criteria in the form fields 89 - 2. The form is submitted, updating search parameters in the URL 90 - 3. React Query prefetches results for the new parameter set 91 - 4. Results render with minimal loading time 92 - 93 - ### Key considerations 94 - 95 - - **URL-driven state**: Filters are stored in search params, making them shareable and bookmarkable 96 - - **Cache management**: Each filter combination creates a unique cache entry with its own \`staleTime\` 97 - - **Type safety**: Valibot validates search params at the route boundary, ensuring the filter state is always valid`, 98 - 99 - "debounced-preload-filters": `## Debounced filter prefetch 100 - 101 - Debounced preloading extends filter-based prefetching by initiating fetches on every keystroke — not just on form submission. A debounce window ensures the server is not overwhelmed by rapid-fire requests. 102 - 103 - ### How it works 104 - 105 - 1. The user types in the filter input 106 - 2. Each keystroke updates the search params after a short debounce delay 107 - 3. React Query fires a prefetch for the new parameter values 108 - 4. Results update incrementally as the user types 109 - 5. The user sees live-filtered results without needing to press submit 110 - 111 - ### Debounce strategy 112 - 113 - The debounce delay balances responsiveness with server load. A 300–400ms window prevents unnecessary intermediate requests while still feeling snappy to the user. TanStack Pacer provides the debouncing primitive. 114 - 115 - ### When to use 116 - 117 - Ideal for search-as-you-type interfaces against reasonably sized or indexed datasets. Pair with server-side indexing for larger collections.`, 118 - 119 - "live-query": `## Electric SQL synced collection 120 - 121 - Live queries use Electric SQL to keep client-side data in sync with the server database in real time. Instead of polling or manual refetching, database changes are pushed to connected clients automatically. 122 - 123 - ### How it works 124 - 125 - 1. The client establishes a sync connection to the Electric SQL server 126 - 2. A live query is defined using the TanStack DB collection API 127 - 3. When database records change, updates stream to connected clients 128 - 4. The UI re-renders automatically with fresh data 129 - 130 - ### Benefits 131 - 132 - - **Real-time updates**: Changes appear immediately without manual refresh 133 - - **Offline support**: Local data remains available during connectivity interruptions 134 - - **Multi-user**: All connected clients see each other's changes in real time 135 - 136 - ### Trade-offs 137 - 138 - Live queries require the Electric SQL sync infrastructure. They introduce additional complexity compared to simple REST fetching but are the right choice for collaborative or real-time applications where data freshness matters.`, 139 - 140 - "live-query-filters": `## Reactive filtered live search 141 - 142 - Combining live queries with client-side filtering creates a real-time search experience. The full dataset stays in sync with the server while the client filters and displays results reactively as the user types. 143 - 144 - ### How it works 145 - 146 - 1. A live query fetches the full dataset and keeps it in sync 147 - 2. Client-side filtering narrows results based on user input 148 - 3. Results update immediately with every keystroke 149 - 4. Underlying database changes are reflected in real time 150 - 151 - ### Architecture 152 - 153 - Electric SQL handles the server-to-client sync layer, while TanStack DB collections manage live query subscriptions. Client-side filtering provides instant feedback without additional network requests, and URL search params keep the filter state shareable. 154 - 155 - ### Best for 156 - 157 - Dashboards, monitoring interfaces, multi-user collaboration tools, and any application where data freshness takes priority over raw query throughput.`, 158 - };
-17
src/articles/strategy-article.functions.tsx
··· 1 - import { createServerFn } from "@tanstack/react-start"; 2 - import { renderServerComponent } from "@tanstack/react-start/rsc"; 3 - import * as v from "valibot"; 4 - import { StrategyArticle } from "~/articles/strategy-article"; 5 - import { strategyContent } from "~/articles/strategies.server"; 6 - 7 - export const getStrategyArticle = createServerFn({ method: "GET" }) 8 - .inputValidator(v.object({ title: v.string(), slug: v.string() })) 9 - .handler(async ({ data }) => { 10 - const markdown = strategyContent[data.slug] ?? "Content coming soon."; 11 - 12 - const Renderable = await renderServerComponent( 13 - <StrategyArticle title={data.title} markdown={markdown} />, 14 - ); 15 - 16 - return { Renderable }; 17 - });
-40
src/articles/strategy-article.tsx
··· 1 - import { renderMarkdown } from "~/articles/markdown"; 2 - 3 - interface StrategyArticleProps { 4 - title: string; 5 - markdown: string; 6 - } 7 - 8 - export async function StrategyArticle({ title, markdown }: StrategyArticleProps) { 9 - const content = await renderMarkdown(markdown); 10 - 11 - return ( 12 - <article className="border border-(--border-default) bg-(--bg-secondary) p-6 md:p-8 xl:sticky xl:top-20"> 13 - <h2 className="mb-6 text-3xl font-semibold uppercase leading-tight text-(--text-primary) md:text-4xl"> 14 - {title} 15 - </h2> 16 - <div 17 - className={[ 18 - "space-y-5 text-sm leading-7 text-(--text-secondary)", 19 - "[&_h2]:mb-4 [&_h2]:mt-8 [&_h2]:text-xl [&_h2]:font-semibold [&_h2]:text-(--text-primary) [&_h2]:first:mt-0", 20 - "[&_h3]:mb-3 [&_h3]:mt-6 [&_h3]:text-lg [&_h3]:font-semibold [&_h3]:text-(--text-primary) first:[&_h3]:mt-0", 21 - "[&_p]:mb-5", 22 - "[&_ul]:mb-5 [&_ul]:list-disc [&_ul]:space-y-2 [&_ul]:pl-5", 23 - "[&_ol]:mb-5 [&_ol]:list-decimal [&_ol]:space-y-2 [&_ol]:pl-5", 24 - "[&_li]:text-(--text-secondary)", 25 - "[&_strong]:font-semibold [&_strong]:text-(--text-primary)", 26 - "[&_code]:rounded [&_code]:bg-(--bg-primary) [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs", 27 - "[&_pre]:mb-5 [&_pre]:overflow-x-auto [&_pre]:rounded [&_pre]:bg-(--bg-primary) [&_pre]:p-4 [&_pre]:font-mono [&_pre]:text-xs", 28 - "[&_a]:text-(--accent-default) [&_a]:underline [&_a]:decoration-(--accent-default)/30 [&_a]:underline-offset-2 hover:[&_a]:decoration-(--accent-default)", 29 - "[&_blockquote]:mb-5 [&_blockquote]:border-l-2 [&_blockquote]:border-(--accent-default) [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:text-(--text-muted)", 30 - "[&_hr]:my-8 [&_hr]:border-(--border-default)", 31 - ].join(" ")} 32 - > 33 - {content} 34 - </div> 35 - <div className="mt-8 border-t border-(--border-default) pt-5 font-mono text-xs uppercase text-(--text-muted)"> 36 - Strategy notes / walkthrough 37 - </div> 38 - </article> 39 - ); 40 - }