@draekien/create-d9-app 0.0.0-stage → 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -2
  3. package/dist/index.js +53 -0
  4. package/index.js +11 -0
  5. package/package.json +37 -4
  6. package/templates/base/.claude/settings.json +36 -0
  7. package/templates/base/.codex/hooks.json +30 -0
  8. package/templates/base/.intent/hooks/intent-claude-gate.mjs +278 -0
  9. package/templates/base/.intent/hooks/intent-codex-gate.mjs +278 -0
  10. package/templates/base/AGENTS.md +28 -0
  11. package/templates/base/CLAUDE.md +1 -0
  12. package/templates/base/_gitignore +7 -0
  13. package/templates/base/apps/web/package.json +38 -0
  14. package/templates/base/apps/web/src/examples/form.test.tsx +10 -0
  15. package/templates/base/apps/web/src/examples/form.tsx +35 -0
  16. package/templates/base/apps/web/src/examples/greeting.ts +11 -0
  17. package/templates/base/apps/web/src/examples/hotkeys.test.tsx +7 -0
  18. package/templates/base/apps/web/src/examples/hotkeys.tsx +10 -0
  19. package/templates/base/apps/web/src/examples/query.test.tsx +18 -0
  20. package/templates/base/apps/web/src/examples/query.tsx +8 -0
  21. package/templates/base/apps/web/src/examples/table.test.tsx +7 -0
  22. package/templates/base/apps/web/src/examples/table.tsx +47 -0
  23. package/templates/base/apps/web/src/home.test.tsx +18 -0
  24. package/templates/base/apps/web/src/home.tsx +21 -0
  25. package/templates/base/apps/web/src/routeTree.gen.ts +68 -0
  26. package/templates/base/apps/web/src/router.tsx +20 -0
  27. package/templates/base/apps/web/src/routes/__root.tsx +26 -0
  28. package/templates/base/apps/web/src/routes/index.tsx +10 -0
  29. package/templates/base/apps/web/tsconfig.json +12 -0
  30. package/templates/base/apps/web/vite.config.ts +20 -0
  31. package/templates/base/package.json +28 -0
  32. package/templates/base/pnpm-workspace.yaml +7 -0
  33. package/templates/base/scripts/check-comments.ts +100 -0
  34. package/templates/base/tsconfig.json +13 -0
  35. package/templates/base/turbo.json +23 -0
  36. package/templates/tools/biome/.biome/effect-cleanup.grit +8 -0
  37. package/templates/tools/biome/.biome/handler-naming.grit +9 -0
  38. package/templates/tools/biome/.biome/no-default-props.grit +5 -0
  39. package/templates/tools/biome/.biome/no-derived-state-effect.grit +7 -0
  40. package/templates/tools/biome/.biome/no-double-assertion.grit +5 -0
  41. package/templates/tools/biome/.biome/no-fetch-in-effect.grit +6 -0
  42. package/templates/tools/biome/.biome/no-impure-render.grit +8 -0
  43. package/templates/tools/biome/.biome/no-inline-query-key.grit +7 -0
  44. package/templates/tools/biome/.biome/no-query-client-in-render.grit +7 -0
  45. package/templates/tools/biome/.biome/no-refetch-on-success.grit +7 -0
  46. package/templates/tools/biome/.biome/no-try-around-jsx.grit +9 -0
  47. package/templates/tools/biome/.biome/query-client-stale-time.grit +8 -0
  48. package/templates/tools/biome/biome.json +156 -0
  49. package/templates/tools/biome/package.json +8 -0
  50. package/templates/tools/lefthook/lefthook.yml +6 -0
  51. package/templates/tools/lefthook/package.json +5 -0
  52. package/templates/tools/shadcn/packages/ui/components.json +22 -0
  53. package/templates/tools/shadcn/packages/ui/package.json +33 -0
  54. package/templates/tools/shadcn/packages/ui/src/components/button.tsx +56 -0
  55. package/templates/tools/shadcn/packages/ui/src/lib/utils.ts +1 -0
  56. package/templates/tools/shadcn/packages/ui/src/styles/globals.css +87 -0
  57. package/templates/tools/shadcn/packages/ui/tsconfig.json +10 -0
@@ -0,0 +1,278 @@
1
+ #!/usr/bin/env node
2
+ import { appendFileSync, existsSync, mkdirSync, readFileSync } from 'node:fs'
3
+ import { execFileSync } from 'node:child_process'
4
+ import { tmpdir } from 'node:os'
5
+ import { dirname, join, relative, sep } from 'node:path'
6
+ import { createHash } from 'node:crypto'
7
+ import { performance } from 'node:perf_hooks'
8
+
9
+ const AGENT = "codex"
10
+ const CATALOG_COMMAND = "pnpm exec intent list --json --no-notices"
11
+ const LOAD_COMMAND = "pnpm exec intent load <package>#<skill>"
12
+ const EDIT_TOOLS = new Set(["Edit","Write","apply_patch"])
13
+ const GATE_DENY_REASON = "Blocked: check TanStack guidance before editing. If a listed skill matches, load it, then retry the edit."
14
+ const INTENT_INVOCATION_PATTERN = /(?:^|&&|\|\||;|\|)\s*((?:bunx\s+--no-install\s+--package\s+@tanstack\/intent\s+intent)|(?:npm\s+exec\s+--no\s+--\s+intent)|(?:yarn\s+exec\s+intent)|(?:bunx\s+@tanstack\/intent(?:@latest)?)|(?:pnpm\s+exec\s+intent)|(?:pnpm\s+dlx\s+@tanstack\/intent(?:@latest)?)|(?:npx\s+@tanstack\/intent(?:@latest)?)|(?:yarn\s+dlx\s+@tanstack\/intent(?:@latest)?)|(?:(?:[^\s|;&]*[\\/])?intent))\s+(list|load)(?:\s+([^\s|;&]+))?/i
15
+
16
+ try {
17
+ await main()
18
+ } catch {
19
+ }
20
+
21
+ process.exit(0)
22
+
23
+ async function main() {
24
+ const event = readEventFromStdin()
25
+
26
+ if (isSessionStartEvent(event)) {
27
+ const stateFile = stateFileForEvent(event)
28
+ const additionalContext = await createSessionCatalogContext(rootForEvent(event))
29
+ appendObservation(stateFile, { action: 'list', raw: CATALOG_COMMAND })
30
+ if (additionalContext) {
31
+ process.stdout.write(JSON.stringify(sessionStartOutput(additionalContext)))
32
+ }
33
+ return
34
+ }
35
+
36
+ const stateFile = stateFileForEvent(event)
37
+ const observation = observationFromEvent(event)
38
+
39
+ if (observation) {
40
+ appendObservation(stateFile, observation)
41
+ }
42
+
43
+ const toolName = event?.tool_name ?? event?.toolName
44
+ if (typeof toolName === 'string' && EDIT_TOOLS.has(toolName) && !hasIntentCheck(stateFile)) {
45
+ process.stdout.write(JSON.stringify(denyOutput()))
46
+ }
47
+ }
48
+
49
+ function readEventFromStdin() {
50
+ try {
51
+ return JSON.parse(readFileSync(0, 'utf8'))
52
+ } catch {
53
+ return {}
54
+ }
55
+ }
56
+
57
+ function isSessionStartEvent(event) {
58
+ return (event?.hook_event_name ?? event?.hookEventName) === 'SessionStart'
59
+ }
60
+
61
+ function rootForEvent(event) {
62
+ return typeof event?.cwd === 'string' && event.cwd ? event.cwd : process.cwd()
63
+ }
64
+
65
+ async function createSessionCatalogContext(root) {
66
+ try {
67
+ const start = performance.now()
68
+ const localCli = resolveLocalIntentCli(root)
69
+ const result = readIntentList(root, localCli)
70
+ const durationMs = performance.now() - start
71
+ console.error(
72
+ `[intent-${AGENT}-session-catalog] listIntentSkills found ${result.skills.length} skills from ${result.packages.length} packages in ${formatDuration(durationMs)} (packageJsonReadCount=${result.debug?.scan.packageJsonReadCount ?? 'unknown'})`,
73
+ )
74
+ return formatSessionCatalog(result, loadCommandForLocalCli(root, localCli))
75
+ } catch {
76
+ return ''
77
+ }
78
+ }
79
+
80
+ // The package-manager runner in CATALOG_COMMAND (npx, pnpm dlx, ...) resolves
81
+ // @tanstack/intent@latest against the registry on every run, which costs one
82
+ // to four seconds per session start. When the project has the package
83
+ // installed, run its CLI directly with this Node binary instead. Returns the
84
+ // CLI path and the node_modules directory it was found in, or null.
85
+ function resolveLocalIntentCli(root) {
86
+ let dir = root
87
+ let prev
88
+ while (dir !== prev) {
89
+ const nodeModulesDir = join(dir, 'node_modules')
90
+ const packageJsonPath = join(nodeModulesDir, '@tanstack', 'intent', 'package.json')
91
+ if (existsSync(packageJsonPath)) {
92
+ try {
93
+ const bin = JSON.parse(readFileSync(packageJsonPath, 'utf8')).bin
94
+ const relativeBin = typeof bin === 'string' ? bin : bin && bin.intent
95
+ if (typeof relativeBin === 'string') {
96
+ const cli = join(dirname(packageJsonPath), relativeBin)
97
+ if (existsSync(cli)) return { cli, nodeModulesDir }
98
+ }
99
+ } catch {
100
+ }
101
+ return null
102
+ }
103
+ prev = dir
104
+ dir = dirname(dir)
105
+ }
106
+ return null
107
+ }
108
+
109
+ // The load command shown to the agent. When the catalog came from a local
110
+ // install that also has a bin shim, suggest that shim (same installation, no
111
+ // registry lookup); otherwise suggest the package-manager runner.
112
+ function loadCommandForLocalCli(root, localCli) {
113
+ if (!localCli) return LOAD_COMMAND
114
+ const bin = join(localCli.nodeModulesDir, '.bin', 'intent')
115
+ if (!existsSync(bin)) return LOAD_COMMAND
116
+ return relative(root, bin).split(sep).join('/') + ' load <package>#<skill>'
117
+ }
118
+
119
+ function readIntentList(root, localCli) {
120
+ const options = {
121
+ cwd: root,
122
+ encoding: 'utf8',
123
+ env: { ...process.env, INTENT_AUDIENCE: 'agent' },
124
+ maxBuffer: 1024 * 1024,
125
+ stdio: ['ignore', 'pipe', 'pipe'],
126
+ timeout: 9000,
127
+ }
128
+ const output = localCli
129
+ ? execFileSync(process.execPath, [localCli.cli, 'list', '--json', '--no-notices'], options)
130
+ : execFileSync(CATALOG_COMMAND, { ...options, shell: true })
131
+ return JSON.parse(output)
132
+ }
133
+
134
+ function formatDuration(durationMs) {
135
+ return `${durationMs.toFixed(1)}ms`
136
+ }
137
+
138
+ function formatSessionCatalog(result, loadCommand) {
139
+ if (!Array.isArray(result.skills) || result.skills.length === 0) return ''
140
+
141
+ return [
142
+ 'TanStack Intent skills are available in this repository.',
143
+ '',
144
+ 'These are Intent skills, not native agent skills.',
145
+ 'Load a matching skill with: `' + loadCommand + '`.',
146
+ '',
147
+ 'Before substantial work, check whether one listed skill clearly matches the user task. If one clearly matches, load that full skill guidance with the Intent CLI before proceeding.',
148
+ '',
149
+ 'If no skill clearly matches, continue normally. Do not load a skill just to improve phrasing or gather nonessential context.',
150
+ '',
151
+ 'Available local Intent skills:',
152
+ formatSkillCatalog(result.skills),
153
+ formatWarnings(result),
154
+ ]
155
+ .filter(Boolean)
156
+ .join('\n')
157
+ }
158
+
159
+ function formatSkillCatalog(skills) {
160
+ return skills
161
+ .map((skill) => `- ${skill.use}: ${normalizeDescription(skill.description)}`)
162
+ .join('\n')
163
+ }
164
+
165
+ function normalizeDescription(description) {
166
+ return typeof description === 'string' ? description.replace(/\s+/g, ' ').trim() : ''
167
+ }
168
+
169
+ function formatWarnings(result) {
170
+ const warnings = [
171
+ ...(Array.isArray(result.warnings) ? result.warnings : []),
172
+ ...(Array.isArray(result.conflicts)
173
+ ? result.conflicts.map(
174
+ (conflict) =>
175
+ `Version conflict for ${conflict.packageName}; using ${conflict.chosen.version}`,
176
+ )
177
+ : []),
178
+ ]
179
+
180
+ if (warnings.length === 0) return ''
181
+ return `\nWarnings:\n${warnings.map((warning) => `- ${warning}`).join('\n')}`
182
+ }
183
+
184
+ function sessionStartOutput(additionalContext) {
185
+ if (AGENT === 'copilot') {
186
+ return { additionalContext }
187
+ }
188
+
189
+ return {
190
+ hookSpecificOutput: {
191
+ hookEventName: 'SessionStart',
192
+ additionalContext,
193
+ },
194
+ }
195
+ }
196
+
197
+ function stateFileForEvent(event) {
198
+ const sessionId = typeof event?.session_id === 'string' ? event.session_id : 'unknown'
199
+ const cwd = typeof event?.cwd === 'string' ? event.cwd : process.cwd()
200
+ const key = createHash('sha256').update(AGENT + '\0' + cwd + '\0' + sessionId).digest('hex')
201
+ return join(tmpdir(), 'tanstack-intent-hooks', key + '.jsonl')
202
+ }
203
+
204
+ function parseIntentInvocation(command) {
205
+ if (typeof command !== 'string') return undefined
206
+ const match = command.match(INTENT_INVOCATION_PATTERN)
207
+ if (!match?.[1] || !match[2]) return undefined
208
+ const action = match[2].toLowerCase()
209
+ if (action !== 'list' && action !== 'load') return undefined
210
+ const skillUse = action === 'load' ? match[3] : undefined
211
+ if (action === 'load' && !skillUse) return undefined
212
+ return action === 'load' ? { action, skillUse } : { action }
213
+ }
214
+
215
+ function observationFromEvent(event) {
216
+ if (!event || typeof event !== 'object') return undefined
217
+ const toolName = event.tool_name ?? event.toolName
218
+ const toolInput = event.tool_input ?? event.toolArgs
219
+ if (toolName !== 'Bash') return undefined
220
+ const command = typeof toolInput === 'string' ? safeCommandFromString(toolInput) : commandFromObject(toolInput)
221
+ const parsed = parseIntentInvocation(command)
222
+ if (!parsed || typeof command !== 'string') return undefined
223
+ return { action: parsed.action, skillUse: parsed.skillUse, raw: command }
224
+ }
225
+
226
+ function commandFromObject(value) {
227
+ return value && typeof value === 'object' ? value.command : undefined
228
+ }
229
+
230
+ function safeCommandFromString(value) {
231
+ try {
232
+ const command = commandFromObject(JSON.parse(value))
233
+ return typeof command === 'string' ? command : value
234
+ } catch {
235
+ return value
236
+ }
237
+ }
238
+
239
+ function appendObservation(stateFile, observation) {
240
+ try {
241
+ mkdirSync(dirname(stateFile), { recursive: true })
242
+ appendFileSync(stateFile, JSON.stringify({ ts: new Date().toISOString(), ...observation }) + '\n')
243
+ } catch {
244
+ }
245
+ }
246
+
247
+ function hasIntentCheck(stateFile) {
248
+ if (!existsSync(stateFile)) return false
249
+ try {
250
+ return readFileSync(stateFile, 'utf8')
251
+ .split('\n')
252
+ .filter(Boolean)
253
+ .some((line) => {
254
+ try {
255
+ const action = JSON.parse(line).action
256
+ return action === 'list' || action === 'load'
257
+ } catch {
258
+ return false
259
+ }
260
+ })
261
+ } catch {
262
+ return false
263
+ }
264
+ }
265
+
266
+ function denyOutput() {
267
+ if (AGENT === 'copilot') {
268
+ return { permissionDecision: 'deny', permissionDecisionReason: GATE_DENY_REASON }
269
+ }
270
+
271
+ return {
272
+ hookSpecificOutput: {
273
+ hookEventName: 'PreToolUse',
274
+ permissionDecision: 'deny',
275
+ permissionDecisionReason: GATE_DENY_REASON,
276
+ },
277
+ }
278
+ }
@@ -0,0 +1,28 @@
1
+ <!-- intent-skills:start -->
2
+ ## Skill Loading
3
+
4
+ Use the repository’s installed Intent. If it is unavailable, report the missing dependency instead of downloading a replacement.
5
+ Before editing files for a substantial task:
6
+ - Run `pnpm exec intent list` from the workspace root to see available local skills.
7
+ - If a listed skill matches the task, run `pnpm exec intent load <package>#<skill>` before changing files.
8
+ - Use the loaded `SKILL.md` guidance while making the change.
9
+ - Monorepos: when working across packages, run the skill check from the workspace root and prefer the local skill for the package being changed.
10
+ - Multiple matches: prefer the most specific local skill for the package or concern you are changing; load additional skills only when the task spans multiple packages or concerns.
11
+ <!-- intent-skills:end -->
12
+
13
+ # Project
14
+
15
+ - Turborepo monorepo managed with pnpm. Apps in `apps/`, shared packages in `packages/`.
16
+ - `apps/web`: TanStack Start on Vite, React, TypeScript, Vitest.
17
+ - Run tasks from the repo root: `pnpm build`, `pnpm check`, `pnpm typecheck`, `pnpm test`, `pnpm dev`.
18
+ - Dependency versions are exact. Never use `^`, `~` or `latest`.
19
+ - Routes live in `apps/web/src/routes`. `routeTree.gen.ts` is generated; do not edit it.
20
+
21
+ <!-- BEGIN:turborepo-agent-rules -->
22
+
23
+ # This is NOT the Turborepo you know
24
+
25
+ Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the `turbo` package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run `node -p "require.resolve('turbo/package.json')"` from a workspace that depends on `turbo`.
26
+
27
+ Read `docs/README.md` inside that installed package first, then read the relevant pages from its `docs/` directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access.
28
+ <!-- END:turborepo-agent-rules -->
@@ -0,0 +1 @@
1
+ @AGENTS.md
@@ -0,0 +1,7 @@
1
+ node_modules
2
+ dist
3
+ .turbo
4
+ .output
5
+ .nitro
6
+ .tanstack
7
+ *.log
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "web",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "vite dev",
7
+ "build": "vite build",
8
+ "typecheck": "tsc --noEmit",
9
+ "test": "vitest run"
10
+ },
11
+ "dependencies": {
12
+ "@tailwindcss/vite": "4.3.3",
13
+ "@tanstack/react-form": "1.33.5",
14
+ "@tanstack/react-hotkeys": "0.13.0",
15
+ "@tanstack/react-query": "5.104.1",
16
+ "@tanstack/react-router": "1.170.41",
17
+ "@tanstack/react-router-ssr-query": "1.167.3",
18
+ "@tanstack/react-start": "1.168.60",
19
+ "@tanstack/react-table": "9.2.8",
20
+ "@workspace/ui": "workspace:*",
21
+ "motion": "14.0.0",
22
+ "react": "19.3.0",
23
+ "react-dom": "19.3.0",
24
+ "tailwindcss": "4.3.3"
25
+ },
26
+ "devDependencies": {
27
+ "@babel/core": "8.0.7",
28
+ "@rolldown/plugin-babel": "0.2.4",
29
+ "@types/node": "22.20.5",
30
+ "@types/react": "19.3.0",
31
+ "@types/react-dom": "19.3.0",
32
+ "@vitejs/plugin-react": "6.1.2",
33
+ "babel-plugin-react-compiler": "1.0.0",
34
+ "typescript": "7.0.2",
35
+ "vite": "8.3.4",
36
+ "vitest": "5.0.3"
37
+ }
38
+ }
@@ -0,0 +1,10 @@
1
+ import { renderToString } from "react-dom/server"
2
+ import { expect, it } from "vitest"
3
+ import { FormExample } from "./form.tsx"
4
+
5
+ it("renders the name field and submit button", () => {
6
+ const html = renderToString(<FormExample />)
7
+
8
+ expect(html).toContain('name="name"')
9
+ expect(html).toContain("Submit")
10
+ })
@@ -0,0 +1,35 @@
1
+ import { useForm } from "@tanstack/react-form"
2
+ import { useState } from "react"
3
+
4
+ export function FormExample() {
5
+ const [submitted, setSubmitted] = useState("")
6
+ const form = useForm({
7
+ defaultValues: { name: "" },
8
+ onSubmit: ({ value }) => setSubmitted(value.name),
9
+ })
10
+
11
+ const handleSubmit = (event: React.SubmitEvent<HTMLFormElement>) => {
12
+ event.preventDefault()
13
+ form.handleSubmit()
14
+ }
15
+
16
+ return (
17
+ <form onSubmit={handleSubmit} className="flex flex-col items-center gap-2">
18
+ <form.Field name="name">
19
+ {(field) => (
20
+ <label className="flex gap-2">
21
+ Name
22
+ <input
23
+ name={field.name}
24
+ value={field.state.value}
25
+ onChange={(event) => field.handleChange(event.target.value)}
26
+ className="border"
27
+ />
28
+ </label>
29
+ )}
30
+ </form.Field>
31
+ <button type="submit">Submit</button>
32
+ {submitted ? <p>Submitted {submitted}</p> : null}
33
+ </form>
34
+ )
35
+ }
@@ -0,0 +1,11 @@
1
+ import { queryOptions } from "@tanstack/react-query"
2
+
3
+ export const greetingKeys = {
4
+ all: ["greeting"] as const,
5
+ detail: (name: string) => [...greetingKeys.all, name] as const,
6
+ }
7
+
8
+ export const greetingOptions = queryOptions({
9
+ queryKey: greetingKeys.detail("query"),
10
+ queryFn: () => Promise.resolve("Hello, query"),
11
+ })
@@ -0,0 +1,7 @@
1
+ import { renderToString } from "react-dom/server"
2
+ import { expect, it } from "vitest"
3
+ import { HotkeysExample } from "./hotkeys.tsx"
4
+
5
+ it("renders the hotkey hint", () => {
6
+ expect(renderToString(<HotkeysExample />)).toContain("Press Ctrl+K or Cmd+K")
7
+ })
@@ -0,0 +1,10 @@
1
+ import { useHotkey } from "@tanstack/react-hotkeys"
2
+ import { useState } from "react"
3
+
4
+ export function HotkeysExample() {
5
+ const [count, setCount] = useState(0)
6
+
7
+ useHotkey("Mod+K", () => setCount((current) => current + 1))
8
+
9
+ return <p>Press Ctrl+K or Cmd+K ({count} presses)</p>
10
+ }
@@ -0,0 +1,18 @@
1
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
2
+ import { renderToString } from "react-dom/server"
3
+ import { expect, it } from "vitest"
4
+ import { greetingOptions } from "./greeting.ts"
5
+ import { QueryExample } from "./query.tsx"
6
+
7
+ it("renders the prefetched greeting", async () => {
8
+ const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 } } })
9
+ await queryClient.prefetchQuery(greetingOptions)
10
+
11
+ const html = renderToString(
12
+ <QueryClientProvider client={queryClient}>
13
+ <QueryExample />
14
+ </QueryClientProvider>
15
+ )
16
+
17
+ expect(html).toContain("Hello, query")
18
+ })
@@ -0,0 +1,8 @@
1
+ import { useSuspenseQuery } from "@tanstack/react-query"
2
+ import { greetingOptions } from "./greeting.ts"
3
+
4
+ export function QueryExample() {
5
+ const { data } = useSuspenseQuery(greetingOptions)
6
+
7
+ return <p>{data}</p>
8
+ }
@@ -0,0 +1,7 @@
1
+ import { renderToString } from "react-dom/server"
2
+ import { expect, it } from "vitest"
3
+ import { TableExample } from "./table.tsx"
4
+
5
+ it("renders a cell value from the row data", () => {
6
+ expect(renderToString(<TableExample />)).toContain("Grace Hopper")
7
+ })
@@ -0,0 +1,47 @@
1
+ import type { ColumnDef } from "@tanstack/react-table"
2
+ import { tableFeatures, useTable } from "@tanstack/react-table"
3
+
4
+ type Person = { name: string; role: string }
5
+
6
+ const features = tableFeatures({})
7
+
8
+ const data: Array<Person> = [
9
+ { name: "Ada Lovelace", role: "Mathematician" },
10
+ { name: "Grace Hopper", role: "Admiral" },
11
+ ]
12
+
13
+ const columns: Array<ColumnDef<typeof features, Person>> = [
14
+ { accessorKey: "name", header: "Name" },
15
+ { accessorKey: "role", header: "Role" },
16
+ ]
17
+
18
+ export function TableExample() {
19
+ const table = useTable({ features, columns, data })
20
+
21
+ return (
22
+ <table>
23
+ <thead>
24
+ {table.getHeaderGroups().map((headerGroup) => (
25
+ <tr key={headerGroup.id}>
26
+ {headerGroup.headers.map((header) => (
27
+ <th key={header.id}>
28
+ <table.FlexRender header={header} />
29
+ </th>
30
+ ))}
31
+ </tr>
32
+ ))}
33
+ </thead>
34
+ <tbody>
35
+ {table.getRowModel().rows.map((row) => (
36
+ <tr key={row.id}>
37
+ {row.getAllCells().map((cell) => (
38
+ <td key={cell.id}>
39
+ <table.FlexRender cell={cell} />
40
+ </td>
41
+ ))}
42
+ </tr>
43
+ ))}
44
+ </tbody>
45
+ </table>
46
+ )
47
+ }
@@ -0,0 +1,18 @@
1
+ import { QueryClient, QueryClientProvider } from "@tanstack/react-query"
2
+ import { renderToString } from "react-dom/server"
3
+ import { expect, it } from "vitest"
4
+ import { greetingOptions } from "./examples/greeting.ts"
5
+ import { Home } from "./home.tsx"
6
+
7
+ it("renders the greeting", async () => {
8
+ const queryClient = new QueryClient({ defaultOptions: { queries: { staleTime: 60_000 } } })
9
+ await queryClient.prefetchQuery(greetingOptions)
10
+
11
+ const html = renderToString(
12
+ <QueryClientProvider client={queryClient}>
13
+ <Home />
14
+ </QueryClientProvider>
15
+ )
16
+
17
+ expect(html).toContain("Hello from your new app")
18
+ })
@@ -0,0 +1,21 @@
1
+ import { Button } from "@workspace/ui/components/button.tsx"
2
+ import { motion } from "motion/react"
3
+ import { FormExample } from "./examples/form.tsx"
4
+ import { HotkeysExample } from "./examples/hotkeys.tsx"
5
+ import { QueryExample } from "./examples/query.tsx"
6
+ import { TableExample } from "./examples/table.tsx"
7
+
8
+ export function Home() {
9
+ return (
10
+ <main className="flex min-h-screen flex-col items-center justify-center gap-6">
11
+ <h1 className="text-3xl font-semibold">Hello from your new app</h1>
12
+ <motion.div initial={{ opacity: 0, y: 12 }} animate={{ opacity: 1, y: 0 }}>
13
+ <Button>Get started</Button>
14
+ </motion.div>
15
+ <QueryExample />
16
+ <FormExample />
17
+ <TableExample />
18
+ <HotkeysExample />
19
+ </main>
20
+ )
21
+ }
@@ -0,0 +1,68 @@
1
+ /* eslint-disable */
2
+
3
+ // @ts-nocheck
4
+
5
+ // noinspection JSUnusedGlobalSymbols
6
+
7
+ // This file was automatically generated by TanStack Router.
8
+ // You should NOT make any changes in this file as it will be overwritten.
9
+ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10
+
11
+ import { Route as rootRouteImport } from './routes/__root'
12
+ import { Route as IndexRouteImport } from './routes/index'
13
+
14
+ const IndexRoute = IndexRouteImport.update({
15
+ id: '/',
16
+ path: '/',
17
+ getParentRoute: () => rootRouteImport,
18
+ } as any)
19
+
20
+ export interface FileRoutesByFullPath {
21
+ '/': typeof IndexRoute
22
+ }
23
+ export interface FileRoutesByTo {
24
+ '/': typeof IndexRoute
25
+ }
26
+ export interface FileRoutesById {
27
+ __root__: typeof rootRouteImport
28
+ '/': typeof IndexRoute
29
+ }
30
+ export interface FileRouteTypes {
31
+ fileRoutesByFullPath: FileRoutesByFullPath
32
+ fullPaths: '/'
33
+ fileRoutesByTo: FileRoutesByTo
34
+ to: '/'
35
+ id: '__root__' | '/'
36
+ fileRoutesById: FileRoutesById
37
+ }
38
+ export interface RootRouteChildren {
39
+ IndexRoute: typeof IndexRoute
40
+ }
41
+
42
+ declare module '@tanstack/react-router' {
43
+ interface FileRoutesByPath {
44
+ '/': {
45
+ id: '/'
46
+ path: '/'
47
+ fullPath: '/'
48
+ preLoaderRoute: typeof IndexRouteImport
49
+ parentRoute: typeof rootRouteImport
50
+ }
51
+ }
52
+ }
53
+
54
+ const rootRouteChildren: RootRouteChildren = {
55
+ IndexRoute: IndexRoute,
56
+ }
57
+ export const routeTree = rootRouteImport
58
+ ._addFileChildren(rootRouteChildren)
59
+ ._addFileTypes<FileRouteTypes>()
60
+
61
+ import type { getRouter } from './router.tsx'
62
+ import type { createStart } from '@tanstack/react-start'
63
+ declare module '@tanstack/react-start' {
64
+ interface Register {
65
+ ssr: true
66
+ router: Awaited<ReturnType<typeof getRouter>>
67
+ }
68
+ }
@@ -0,0 +1,20 @@
1
+ import { QueryClient } from "@tanstack/react-query"
2
+ import { createRouter } from "@tanstack/react-router"
3
+ import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query"
4
+ import { routeTree } from "./routeTree.gen.ts"
5
+
6
+ export function getRouter() {
7
+ const queryClient = new QueryClient({
8
+ defaultOptions: { queries: { staleTime: 30_000 } },
9
+ })
10
+ const router = createRouter({
11
+ routeTree,
12
+ context: { queryClient },
13
+ scrollRestoration: true,
14
+ defaultPreload: "intent",
15
+ defaultPreloadStaleTime: 0,
16
+ })
17
+
18
+ setupRouterSsrQueryIntegration({ router, queryClient })
19
+ return router
20
+ }