polen 0.11.0-next.11 → 0.11.0-next.12

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 (111) hide show
  1. package/build/api/iso/schema/$$.d.ts +1 -0
  2. package/build/api/iso/schema/$$.d.ts.map +1 -1
  3. package/build/api/iso/schema/$$.js +1 -0
  4. package/build/api/iso/schema/$$.js.map +1 -1
  5. package/build/api/iso/schema/routing.d.ts +34 -0
  6. package/build/api/iso/schema/routing.d.ts.map +1 -1
  7. package/build/api/iso/schema/routing.js +58 -0
  8. package/build/api/iso/schema/routing.js.map +1 -1
  9. package/build/api/iso/schema/validation.d.ts +32 -0
  10. package/build/api/iso/schema/validation.d.ts.map +1 -0
  11. package/build/api/iso/schema/validation.js +101 -0
  12. package/build/api/iso/schema/validation.js.map +1 -0
  13. package/build/template/components/ArgumentList.d.ts +3 -4
  14. package/build/template/components/ArgumentList.d.ts.map +1 -1
  15. package/build/template/components/ArgumentList.js.map +1 -1
  16. package/build/template/components/Changelog.js +2 -2
  17. package/build/template/components/Changelog.js.map +1 -1
  18. package/build/template/components/DeprecationReason.d.ts +2 -2
  19. package/build/template/components/DeprecationReason.d.ts.map +1 -1
  20. package/build/template/components/DeprecationReason.js.map +1 -1
  21. package/build/template/components/Description.d.ts +2 -2
  22. package/build/template/components/Description.d.ts.map +1 -1
  23. package/build/template/components/Description.js.map +1 -1
  24. package/build/template/components/Field.d.ts +3 -4
  25. package/build/template/components/Field.d.ts.map +1 -1
  26. package/build/template/components/Field.js.map +1 -1
  27. package/build/template/components/FieldListSection.d.ts +3 -4
  28. package/build/template/components/FieldListSection.d.ts.map +1 -1
  29. package/build/template/components/FieldListSection.js.map +1 -1
  30. package/build/template/components/MissingSchema.d.ts +2 -1
  31. package/build/template/components/MissingSchema.d.ts.map +1 -1
  32. package/build/template/components/MissingSchema.js.map +1 -1
  33. package/build/template/components/ReferenceLink.d.ts +9 -11
  34. package/build/template/components/ReferenceLink.d.ts.map +1 -1
  35. package/build/template/components/ReferenceLink.js.map +1 -1
  36. package/build/template/components/ToastContainer.d.ts +3 -0
  37. package/build/template/components/ToastContainer.d.ts.map +1 -0
  38. package/build/template/components/ToastContainer.js +44 -0
  39. package/build/template/components/ToastContainer.js.map +1 -0
  40. package/build/template/components/ToastItem.d.ts +7 -0
  41. package/build/template/components/ToastItem.d.ts.map +1 -0
  42. package/build/template/components/ToastItem.js +48 -0
  43. package/build/template/components/ToastItem.js.map +1 -0
  44. package/build/template/components/TypeAnnotation.d.ts +4 -5
  45. package/build/template/components/TypeAnnotation.d.ts.map +1 -1
  46. package/build/template/components/TypeAnnotation.js.map +1 -1
  47. package/build/template/components/VersionPicker.d.ts +8 -0
  48. package/build/template/components/VersionPicker.d.ts.map +1 -0
  49. package/build/template/components/VersionPicker.js +66 -0
  50. package/build/template/components/VersionPicker.js.map +1 -0
  51. package/build/template/hooks/useReferencePath.d.ts +9 -0
  52. package/build/template/hooks/useReferencePath.d.ts.map +1 -0
  53. package/build/template/hooks/useReferencePath.js +18 -0
  54. package/build/template/hooks/useReferencePath.js.map +1 -0
  55. package/build/template/routes/reference.d.ts +24 -8
  56. package/build/template/routes/reference.d.ts.map +1 -1
  57. package/build/template/routes/reference.js +47 -53
  58. package/build/template/routes/reference.js.map +1 -1
  59. package/build/template/routes/root.d.ts +1 -0
  60. package/build/template/routes/root.d.ts.map +1 -1
  61. package/build/template/routes/root.js +16 -5
  62. package/build/template/routes/root.js.map +1 -1
  63. package/build/template/stores/$$.d.ts +13 -0
  64. package/build/template/stores/$$.d.ts.map +1 -0
  65. package/build/template/stores/$$.js +5 -0
  66. package/build/template/stores/$$.js.map +1 -0
  67. package/build/template/stores/$.d.ts +2 -0
  68. package/build/template/stores/$.d.ts.map +1 -0
  69. package/build/template/stores/$.js +2 -0
  70. package/build/template/stores/$.js.map +1 -0
  71. package/build/template/stores/schema.d.ts +40 -0
  72. package/build/template/stores/schema.d.ts.map +1 -0
  73. package/build/template/stores/schema.js +36 -0
  74. package/build/template/stores/schema.js.map +1 -0
  75. package/build/template/stores/toast.d.ts +103 -0
  76. package/build/template/stores/toast.d.ts.map +1 -0
  77. package/build/template/stores/toast.js +105 -0
  78. package/build/template/stores/toast.js.map +1 -0
  79. package/build/template/utils/try-with-toast.d.ts +9 -0
  80. package/build/template/utils/try-with-toast.d.ts.map +1 -0
  81. package/build/template/utils/try-with-toast.js +37 -0
  82. package/build/template/utils/try-with-toast.js.map +1 -0
  83. package/package.json +2 -1
  84. package/src/api/iso/schema/$$.ts +1 -0
  85. package/src/api/iso/schema/routing.ts +89 -0
  86. package/src/api/iso/schema/validation.ts +136 -0
  87. package/src/template/components/ArgumentList.tsx +2 -6
  88. package/src/template/components/Changelog.tsx +2 -2
  89. package/src/template/components/DeprecationReason.tsx +2 -2
  90. package/src/template/components/Description.tsx +2 -2
  91. package/src/template/components/Field.tsx +2 -6
  92. package/src/template/components/FieldListSection.tsx +2 -6
  93. package/src/template/components/MissingSchema.tsx +3 -1
  94. package/src/template/components/ReferenceLink.tsx +9 -11
  95. package/src/template/components/ToastContainer.tsx +67 -0
  96. package/src/template/components/ToastItem.tsx +119 -0
  97. package/src/template/components/TypeAnnotation.tsx +2 -6
  98. package/src/template/components/VersionPicker.tsx +94 -0
  99. package/src/template/hooks/useReferencePath.ts +20 -0
  100. package/src/template/routes/reference.tsx +49 -63
  101. package/src/template/routes/root.tsx +29 -6
  102. package/src/template/stores/$$.ts +15 -0
  103. package/src/template/stores/$.ts +1 -0
  104. package/src/template/stores/schema.ts +52 -0
  105. package/src/template/stores/toast.ts +153 -0
  106. package/src/template/utils/try-with-toast.ts +41 -0
  107. package/build/template/components/VersionSelector.d.ts +0 -8
  108. package/build/template/components/VersionSelector.d.ts.map +0 -1
  109. package/build/template/components/VersionSelector.js +0 -22
  110. package/build/template/components/VersionSelector.js.map +0 -1
  111. package/src/template/components/VersionSelector.tsx +0 -42
@@ -1,14 +1,10 @@
1
+ import type { React } from '#dep/react/index'
1
2
  import { Grafaid } from '#lib/grafaid/index'
2
3
  import { Box, Heading } from '@radix-ui/themes'
3
4
  import type { GraphQLNamedType } from 'graphql'
4
- import type { FC } from 'react'
5
5
  import { FieldList } from './FieldList.js'
6
6
 
7
- export interface Props {
8
- data: GraphQLNamedType
9
- }
10
-
11
- export const FieldListSection: FC<Props> = ({ data }) => {
7
+ export const FieldListSection: React.FC<{ data: GraphQLNamedType }> = ({ data }) => {
12
8
  if (!Grafaid.Schema.TypesLike.isFielded(data)) return null
13
9
 
14
10
  const fields = Grafaid.Schema.NodesLike.getFields(data)
@@ -1,3 +1,5 @@
1
- export const MissingSchema = () => {
1
+ import type { React } from '#dep/react/index'
2
+
3
+ export const MissingSchema: React.FC = () => {
2
4
  return <div>No content to show. There is no schema to work with.</div>
3
5
  }
@@ -1,17 +1,8 @@
1
1
  import { Api } from '#api/iso'
2
- import type { ReactNode } from 'react'
2
+ import type { React } from '#dep/react/index'
3
3
  import { useVersionPath } from '../hooks/useVersionPath.js'
4
4
  import { Link } from './Link.js'
5
5
 
6
- interface Props {
7
- /** The GraphQL type name */
8
- type: string
9
- /** Optional field name for field-specific links */
10
- field?: string
11
- /** Link content */
12
- children: ReactNode
13
- }
14
-
15
6
  /**
16
7
  * Link component for GraphQL schema references that preserves version context
17
8
  *
@@ -19,7 +10,14 @@ interface Props {
19
10
  * <ReferenceLink type="User">User</ReferenceLink>
20
11
  * <ReferenceLink type="User" field="name">User.name</ReferenceLink>
21
12
  */
22
- export const ReferenceLink = ({ type, field, children }: Props) => {
13
+ export const ReferenceLink: React.FC<{
14
+ /** The GraphQL type name */
15
+ type: string
16
+ /** Optional field name for field-specific links */
17
+ field?: string
18
+ /** Link content */
19
+ children: React.ReactNode
20
+ }> = ({ type, field, children }) => {
23
21
  const versionPath = useVersionPath()
24
22
 
25
23
  const path = Api.Schema.Routing.joinSegmentsAndPaths(
@@ -0,0 +1,67 @@
1
+ import type { React } from '#dep/react/index'
2
+ import { Box, Flex } from '@radix-ui/themes'
3
+ import { useSnapshot } from 'valtio'
4
+ import { Stores } from '../stores/$.js'
5
+ import { ToastItem } from './ToastItem.js'
6
+
7
+ export const ToastContainer: React.FC = () => {
8
+ const snap = useSnapshot(Stores.Toast.store)
9
+
10
+ return (
11
+ <>
12
+ <style>
13
+ {`
14
+ @keyframes slideIn {
15
+ from {
16
+ transform: translateX(100%);
17
+ opacity: 0;
18
+ }
19
+ to {
20
+ transform: translateX(0);
21
+ opacity: 1;
22
+ }
23
+ }
24
+
25
+ @keyframes slideOut {
26
+ from {
27
+ transform: translateX(0);
28
+ opacity: 1;
29
+ }
30
+ to {
31
+ transform: translateX(100%);
32
+ opacity: 0;
33
+ }
34
+ }
35
+
36
+ @keyframes timerCountdown {
37
+ from {
38
+ width: 100%;
39
+ }
40
+ to {
41
+ width: 0%;
42
+ }
43
+ }
44
+ `}
45
+ </style>
46
+ <Box
47
+ position='fixed'
48
+ bottom='0'
49
+ right='0'
50
+ p='4'
51
+ style={{
52
+ zIndex: 9999,
53
+ pointerEvents: 'none',
54
+ }}
55
+ >
56
+ <Flex
57
+ direction='column'
58
+ gap='2'
59
+ align='end'
60
+ style={{ pointerEvents: 'auto' }}
61
+ >
62
+ {snap.toasts.map(toast => <ToastItem key={toast.id} toast={toast} />)}
63
+ </Flex>
64
+ </Box>
65
+ </>
66
+ )
67
+ }
@@ -0,0 +1,119 @@
1
+ import type { React } from '#dep/react/index'
2
+ import {
3
+ CheckCircledIcon,
4
+ Cross2Icon,
5
+ CrossCircledIcon,
6
+ ExclamationTriangleIcon,
7
+ InfoCircledIcon,
8
+ } from '@radix-ui/react-icons'
9
+ import { Box, Button, Card, Flex, IconButton, Text } from '@radix-ui/themes'
10
+ import type { TextProps } from '@radix-ui/themes'
11
+ import type { ReadonlyDeep } from 'type-fest'
12
+ import { Stores } from '../stores/$.js'
13
+
14
+ const toastVariants = {
15
+ info: {
16
+ icon: <InfoCircledIcon />,
17
+ color: 'blue',
18
+ },
19
+ success: {
20
+ icon: <CheckCircledIcon />,
21
+ color: 'green',
22
+ },
23
+ warning: {
24
+ icon: <ExclamationTriangleIcon />,
25
+ color: 'amber',
26
+ },
27
+ error: {
28
+ icon: <CrossCircledIcon />,
29
+ color: 'red',
30
+ },
31
+ } satisfies Record<Stores.Toast.Type, {
32
+ icon: React.ReactElement
33
+ color: NonNullable<TextProps['color']>
34
+ }>
35
+
36
+ export const ToastItem: React.FC<{ toast: ReadonlyDeep<Stores.Toast.Toast> }> = ({ toast }) => {
37
+ const handleClose = () => Stores.toast.remove(toast.id)
38
+ const duration = toast.duration ?? 5000
39
+ const showTimer = duration > 0
40
+ const type: Stores.Toast.Type = toast.type || 'info'
41
+
42
+ return (
43
+ <Card
44
+ size={'2'}
45
+ style={{
46
+ animation: 'slideIn 0.2s ease-out',
47
+ position: 'relative',
48
+ }}
49
+ >
50
+ <Flex gap='3' align='start' maxWidth={'400px'}>
51
+ <Text
52
+ as='div'
53
+ size='4'
54
+ color={toastVariants[type].color}
55
+ >
56
+ {toastVariants[type].icon}
57
+ </Text>
58
+ <Box style={{ flex: 1 }}>
59
+ <Flex align='baseline' gap='2' wrap='wrap'>
60
+ <Text weight='medium' size='2'>
61
+ {toast.message}
62
+ </Text>
63
+ {toast.actions.map((action) => (
64
+ <Button
65
+ id={action.label}
66
+ size='1'
67
+ variant='soft'
68
+ onClick={() => {
69
+ action.onClick()
70
+ handleClose()
71
+ }}
72
+ >
73
+ {action.label}
74
+ </Button>
75
+ ))}
76
+ </Flex>
77
+
78
+ {toast.description && (
79
+ <Text size='1' color='gray' mt='1' style={{ display: 'block' }}>
80
+ {toast.description}
81
+ </Text>
82
+ )}
83
+ </Box>
84
+
85
+ <IconButton
86
+ size='1'
87
+ variant='ghost'
88
+ color='gray'
89
+ onClick={handleClose}
90
+ style={{ flexShrink: 0 }}
91
+ >
92
+ <Cross2Icon />
93
+ </IconButton>
94
+ </Flex>
95
+
96
+ {showTimer && (
97
+ <Box
98
+ style={{
99
+ position: 'absolute',
100
+ bottom: 0,
101
+ left: 0,
102
+ right: 0,
103
+ height: '2px',
104
+ backgroundColor: `var(--${toastVariants[toast.type || 'info'].color}-a3)`,
105
+ overflow: 'hidden',
106
+ }}
107
+ >
108
+ <Box
109
+ style={{
110
+ height: '100%',
111
+ backgroundColor: `var(--${toastVariants[toast.type || 'info'].color}-a6)`,
112
+ animation: `timerCountdown ${duration}ms linear forwards`,
113
+ }}
114
+ />
115
+ </Box>
116
+ )}
117
+ </Card>
118
+ )
119
+ }
@@ -1,17 +1,13 @@
1
+ import type { React } from '#dep/react/index'
1
2
  import { Text } from '@radix-ui/themes'
2
3
  import type { GraphQLType } from 'graphql'
3
4
  import { isInputObjectType, isListType, isNamedType, isNonNullType, isScalarType } from 'graphql'
4
- import type { FC } from 'react'
5
5
  import { ReferenceLink } from './ReferenceLink.js'
6
6
 
7
- export interface Props {
8
- type: GraphQLType // Can be either GraphQLInputType or GraphQLOutputType
9
- }
10
-
11
7
  /**
12
8
  * Renders a GraphQL type recursively, with links for named types
13
9
  */
14
- export const TypeAnnotation: FC<Props> = ({ type }) => {
10
+ export const TypeAnnotation: React.FC<{ type: GraphQLType }> = ({ type }) => {
15
11
  // Handle NonNull type wrapper
16
12
  if (isNonNullType(type)) {
17
13
  return (
@@ -0,0 +1,94 @@
1
+ import { Api } from '#api/iso'
2
+ import type { React } from '#dep/react/index'
3
+ import { Select } from '@radix-ui/themes'
4
+ import { useNavigate } from 'react-router'
5
+ import { useReferencePath } from '../hooks/useReferencePath.js'
6
+ import { schemaSource } from '../sources/schema-source.js'
7
+ import { Stores } from '../stores/$.js'
8
+ import { tryWithToast } from '../utils/try-with-toast.js'
9
+
10
+ interface Props {
11
+ all: string[]
12
+ current: string
13
+ }
14
+
15
+ export const VersionPicker: React.FC<Props> = ({ all, current }) => {
16
+ const navigate = useNavigate()
17
+ const currentPath = useReferencePath()
18
+
19
+ // Don't show selector if only one version
20
+ if (all.length <= 1) {
21
+ return null
22
+ }
23
+
24
+ const handleVersionChange = async (newVersion: string) => {
25
+ const error = await tryWithToast(async () => {
26
+ // Check if current path exists in target version
27
+ const targetSchema = await schemaSource.get(newVersion)
28
+ // Find fallback path if needed
29
+ const fallbackPath = Api.Schema.Validation.findFallbackPath(targetSchema, currentPath)
30
+ // Get redirect description if path changed
31
+ const redirectDescription = Api.Schema.Validation.getRedirectDescription(
32
+ targetSchema,
33
+ currentPath,
34
+ fallbackPath,
35
+ newVersion,
36
+ )
37
+ // Create the new path
38
+ const newPath = Api.Schema.Routing.createReferencePath({
39
+ version: newVersion,
40
+ type: fallbackPath.type,
41
+ field: fallbackPath.field,
42
+ })
43
+ // Show toast notification if schema location redirect will occur
44
+ if (redirectDescription) {
45
+ Stores.Toast.store.info(redirectDescription, {
46
+ duration: 160_000,
47
+ actions: [
48
+ {
49
+ label: 'Go back',
50
+ onClick() {
51
+ navigate(-1)
52
+ },
53
+ },
54
+ {
55
+ label: 'View changelog',
56
+ onClick() {
57
+ // Navigate to changelog page
58
+ navigate('/changelog')
59
+ },
60
+ },
61
+ ],
62
+ })
63
+ }
64
+
65
+ navigate(newPath)
66
+ }, 'Failed to switch version')
67
+
68
+ // Fallback logic if error occurred
69
+ if (error) {
70
+ // Fallback to simple navigation if schema loading fails
71
+ const newPath = Api.Schema.Routing.createReferencePath({
72
+ version: newVersion,
73
+ type: currentPath.type,
74
+ field: currentPath.field,
75
+ })
76
+ navigate(newPath)
77
+ }
78
+ }
79
+
80
+ return (
81
+ <Select.Root value={current} onValueChange={handleVersionChange}>
82
+ <Select.Trigger>
83
+ {current === Api.Schema.VERSION_LATEST ? `Latest` : current}
84
+ </Select.Trigger>
85
+ <Select.Content>
86
+ {all.map(version => (
87
+ <Select.Item key={version} value={version}>
88
+ {version === Api.Schema.VERSION_LATEST ? `Latest` : version}
89
+ </Select.Item>
90
+ ))}
91
+ </Select.Content>
92
+ </Select.Root>
93
+ )
94
+ }
@@ -0,0 +1,20 @@
1
+ import { Api } from '#api/iso'
2
+ import { useLocation, useParams } from 'react-router'
3
+
4
+ /**
5
+ * Hook that returns parsed reference path parameters
6
+ *
7
+ * @throws {Error} If not currently on a reference route
8
+ * @returns Parsed reference path object
9
+ */
10
+ export const useReferencePath = (): Api.Schema.Validation.PathValidation => {
11
+ const params = useParams()
12
+ const location = useLocation()
13
+
14
+ Api.Schema.Routing.assertReferenceRoute({
15
+ pathname: location.pathname,
16
+ params,
17
+ })
18
+
19
+ return params
20
+ }
@@ -1,15 +1,16 @@
1
1
  import type { Content } from '#api/content/$'
2
2
  import { Api } from '#api/iso'
3
+ import type { React } from '#dep/react/index'
3
4
  import { GrafaidOld } from '#lib/grafaid-old/index'
4
- import { Grafaid } from '#lib/grafaid/index'
5
5
  import { route, routeIndex } from '#lib/react-router-aid/react-router-aid'
6
6
  import { createLoader, useLoaderData } from '#lib/react-router-loader/react-router-loader'
7
7
  import { Box } from '@radix-ui/themes'
8
- import { Outlet, useParams } from 'react-router'
8
+ import { neverCase } from '@wollybeard/kit/language'
9
+ import { useParams } from 'react-router'
9
10
  import { Field } from '../components/Field.js'
10
11
  import { MissingSchema } from '../components/MissingSchema.js'
11
12
  import { NamedType } from '../components/NamedType.js'
12
- import { VersionSelector } from '../components/VersionSelector.js'
13
+ import { VersionPicker } from '../components/VersionPicker.js'
13
14
  import { SidebarLayout } from '../layouts/index.js'
14
15
  import { schemaSource } from '../sources/schema-source.js'
15
16
 
@@ -29,7 +30,9 @@ export const loader = createLoader(async ({ params }) => {
29
30
  }
30
31
  })
31
32
 
32
- const RouteReferenceComponent = () => {
33
+ // Single component that handles all reference route variations
34
+ const ReferenceView = () => {
35
+ const params = useParams() as { version?: string; type?: string; field?: string }
33
36
  const data = useLoaderData<typeof loader>()
34
37
 
35
38
  if (!data.schema) {
@@ -59,91 +62,74 @@ const RouteReferenceComponent = () => {
59
62
  // Calculate basePath based on current version
60
63
  const basePath = Api.Schema.Routing.createReferenceBasePath(data.currentVersion)
61
64
 
65
+ // Determine view type and render appropriate content
66
+ const viewType = Api.Schema.Routing.getReferenceViewType({
67
+ schema: data.schema,
68
+ type: params.type,
69
+ field: params.field,
70
+ })
71
+
72
+ const content: React.ReactNode = (() => {
73
+ if (viewType === 'index') {
74
+ return <div>Select a type from the sidebar to view its documentation.</div>
75
+ } else if (viewType === 'type-missing' || viewType === 'field-missing') {
76
+ return <MissingSchema />
77
+ } else if (viewType === 'type') {
78
+ const type = data.schema.getType(params.type!)!
79
+ return <NamedType data={type} />
80
+ } else if (viewType === 'field') {
81
+ const type = data.schema.getType(params.type!)!
82
+ const fields = (type as any).getFields()
83
+ const field = fields[params.field!]
84
+ return <Field data={field} />
85
+ } else {
86
+ neverCase(viewType)
87
+ }
88
+ })()
89
+
62
90
  return (
63
91
  <SidebarLayout sidebar={sidebarItems} basePath={basePath}>
64
92
  <Box mb={`4`}>
65
- <VersionSelector
66
- availableVersions={data.availableVersions}
67
- currentVersion={data.currentVersion}
93
+ <VersionPicker
94
+ all={[...data.availableVersions]} // Convert readonly to mutable
95
+ current={data.currentVersion}
68
96
  />
69
97
  </Box>
70
- <Outlet />
98
+ {content}
71
99
  </SidebarLayout>
72
100
  )
73
101
  }
74
102
 
75
- // Shared hooks for schema data validation and retrieval
76
- const useReferenceSchema = () => {
77
- const data = useLoaderData<typeof loader>('reference')
78
- if (!data?.schema) {
79
- throw new Error('Schema not found')
80
- }
81
- return data
82
- }
83
-
84
- const useSchemaType = (typeName: string) => {
85
- const { schema } = useReferenceSchema()
86
- const type = schema.getType(typeName)
87
- if (!type) {
88
- throw new Error(`Could not find type ${typeName}`)
89
- }
90
- return type
91
- }
92
-
93
- const useSchemaField = (typeName: string, fieldName: string) => {
94
- const type = useSchemaType(typeName)
95
- if (!Grafaid.Schema.TypesLike.isFielded(type)) {
96
- throw new Error(`Type ${typeName} does not have fields`)
97
- }
98
-
99
- const fields = type.getFields()
100
- const field = fields[fieldName]
101
- if (!field) {
102
- throw new Error(`Could not find field ${fieldName} on type ${typeName}`)
103
- }
104
- return field
105
- }
106
-
107
- const RouteComponentIndex = () => {
108
- return <div>Select a type from the sidebar to view its documentation.</div>
109
- }
110
-
111
- const RouteComponentType = () => {
112
- const params = useParams() as { type: string }
113
- const type = useSchemaType(params.type)
114
- return <NamedType data={type} />
115
- }
116
-
117
- const RouteComponentTypeField = () => {
118
- const params = useParams() as { type: string; field: string }
119
- const field = useSchemaField(params.type, params.field)
120
- return <Field data={field} />
121
- }
122
-
103
+ // Define routes that handle type and field params
123
104
  const typeAndFieldRoutes = [
124
- routeIndex(RouteComponentIndex),
105
+ routeIndex({
106
+ Component: ReferenceView,
107
+ loader,
108
+ }),
125
109
  route({
126
110
  path: `:type`,
127
- Component: RouteComponentType,
111
+ Component: ReferenceView,
128
112
  errorElement: <MissingSchema />,
113
+ loader,
129
114
  children: [
130
115
  route({
131
116
  path: `:field`,
132
- Component: RouteComponentTypeField,
117
+ Component: ReferenceView,
133
118
  errorElement: <MissingSchema />,
119
+ loader,
134
120
  }),
135
121
  ],
136
122
  }),
137
123
  ]
138
124
 
139
125
  /**
140
- * Reference documentation with proper nested structure - all routes in one file
126
+ * Reference documentation routes using proper React Router patterns
127
+ * - Parent routes have no components (automatically render Outlet)
128
+ * - Leaf routes have components and loaders that always run fresh
129
+ * - Single ReferenceView component handles all variations
141
130
  */
142
131
  export const reference = route({
143
- id: 'reference',
144
132
  path: `reference`,
145
- loader,
146
- Component: RouteReferenceComponent,
147
133
  children: [
148
134
  ...typeAndFieldRoutes,
149
135
  route({
@@ -1,5 +1,7 @@
1
1
  import type { ReactRouter } from '#dep/react-router/index'
2
2
  import { route } from '#lib/react-router-aid/react-router-aid'
3
+ import { createLoader } from '#lib/react-router-loader/react-router-loader'
4
+ import type { Stores } from '#template/stores/$'
3
5
  import { Box, Flex, Theme } from '@radix-ui/themes'
4
6
  import { Link as LinkReactRouter } from 'react-router'
5
7
  import { Outlet, ScrollRestoration } from 'react-router'
@@ -11,6 +13,7 @@ import { Link as PolenLink } from '../components/Link.js'
11
13
  import { Logo } from '../components/Logo.js'
12
14
  import { NotFound } from '../components/NotFound.js'
13
15
  import { ThemeToggle } from '../components/ThemeToggle.js'
16
+ import { ToastContainer } from '../components/ToastContainer.js'
14
17
  import { ThemeProvider, useTheme } from '../contexts/ThemeContext.js'
15
18
  import { changelog } from './changelog.js'
16
19
  import { index } from './index.js'
@@ -46,7 +49,12 @@ const Layout = () => {
46
49
  style={{ color: `inherit`, textDecoration: `none` }}
47
50
  >
48
51
  <Box display={{ initial: `block`, md: `block` }}>
49
- <Logo src={logoSrc} title={templateVariables.title} height={30} showTitle={true} />
52
+ <Logo
53
+ src={logoSrc}
54
+ title={templateVariables.title}
55
+ height={30}
56
+ showTitle={true}
57
+ />
50
58
  </Box>
51
59
  </LinkReactRouter>
52
60
  <Flex direction='row' gap='4' style={{ flex: 1 }}>
@@ -61,7 +69,7 @@ const Layout = () => {
61
69
  )
62
70
 
63
71
  return (
64
- <Theme asChild appearance={appearance}>
72
+ <Theme asChild appearance={appearance} radius='none'>
65
73
  <Box
66
74
  width={{ initial: `100%`, sm: `100%`, md: `var(--container-4)` }}
67
75
  maxWidth='100vw'
@@ -72,15 +80,13 @@ const Layout = () => {
72
80
  >
73
81
  {header}
74
82
  <Outlet />
83
+ <ToastContainer />
75
84
  </Box>
76
85
  </Theme>
77
86
  )
78
87
  }
79
88
 
80
- const children: ReactRouter.RouteObject[] = [
81
- index,
82
- pages,
83
- ]
89
+ const children: ReactRouter.RouteObject[] = [index, pages]
84
90
 
85
91
  //
86
92
  //
@@ -122,8 +128,25 @@ children.push(notFoundRoute)
122
128
  //
123
129
  //
124
130
 
131
+ const storeModules = import.meta.glob('../stores/!($.*)*.ts', { eager: true }) as Record<
132
+ string,
133
+ Stores.StoreModule
134
+ >
135
+
125
136
  export const root = route({
126
137
  path: `/`,
127
138
  Component,
139
+ loader: createLoader(async () => {
140
+ // Reset all stores on SSR to prevent cross-request pollution
141
+ if (import.meta.env.SSR) {
142
+ for (const module of Object.values(storeModules)) {
143
+ if (module.store?.reset) {
144
+ module.store.reset()
145
+ }
146
+ }
147
+ }
148
+
149
+ return {}
150
+ }),
128
151
  children,
129
152
  })
@@ -0,0 +1,15 @@
1
+ export * as Schema from './schema.js'
2
+ export { store as schema } from './schema.js'
3
+
4
+ export * as Toast from './toast.js'
5
+ export { store as toast } from './toast.js'
6
+
7
+ export interface Store {
8
+ reset: () => void
9
+ set: (data?: unknown) => void
10
+ }
11
+
12
+ export interface StoreModule {
13
+ store: Store
14
+ initialState: unknown
15
+ }
@@ -0,0 +1 @@
1
+ export * as Stores from './$$.js'