create-fluxstack 1.20.0 → 1.21.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.
@@ -1,206 +1,151 @@
1
- // 🔥 CounterDemo - Contador isolado e compartilhado
2
-
3
- import { useMemo } from 'react'
4
- import { Live } from '@/core/client'
5
- import { LiveCounter } from '@server/live/LiveCounter'
6
- import { LiveLocalCounter } from '@server/live/LiveLocalCounter'
7
-
8
- export function CounterDemo() {
9
- const isolatedRoom = useMemo(() => {
10
- if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
11
- return `local-${crypto.randomUUID()}`
12
- }
13
- return `local-${Math.random().toString(36).slice(2)}`
14
- }, [])
15
-
16
- const sharedCounter = Live.use(LiveCounter, {
17
- room: 'global-counter',
18
- initialState: LiveCounter.defaultState
19
- })
20
-
21
- const isolatedCounter = Live.use(LiveCounter, {
22
- room: isolatedRoom,
23
- initialState: LiveCounter.defaultState,
24
- persistState: false
25
- })
26
-
27
- const localCounter = Live.use(LiveLocalCounter, {
28
- initialState: LiveLocalCounter.defaultState,
29
- persistState: false
30
- })
31
-
32
- const renderCounter = (
33
- title: string,
34
- description: string,
35
- counter: ReturnType<typeof Live.use>
36
- ) => {
37
- const handleIncrement = async () => {
38
- await counter.increment()
39
- }
40
-
41
- const handleDecrement = async () => {
42
- await counter.decrement()
43
- }
44
-
45
- const handleReset = async () => {
46
- await counter.reset()
47
- }
48
-
49
- return (
50
- <div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-5 sm:p-8 max-w-md w-full flex flex-col">
51
- <h2 className="text-xl sm:text-2xl font-bold text-white mb-2 text-center">
52
- {title}
53
- </h2>
54
-
55
- <p className="text-gray-400 text-xs sm:text-sm text-center mb-4 sm:mb-6">
56
- {description}
57
- </p>
58
-
59
- <div className="flex flex-wrap justify-center gap-2 sm:gap-4 mb-4 sm:mb-6">
60
- <div className={`flex items-center gap-2 px-3 py-1 rounded-full text-xs ${
61
- counter.$connected
62
- ? 'bg-emerald-500/20 text-emerald-300'
63
- : 'bg-red-500/20 text-red-300'
64
- }`}>
65
- <div className={`w-2 h-2 rounded-full ${
66
- counter.$connected ? 'bg-emerald-400' : 'bg-red-400'
67
- }`} />
68
- {counter.$connected ? 'Conectado' : 'Desconectado'}
69
- </div>
70
-
71
- <div className="flex items-center gap-2 px-3 py-1 rounded-full text-xs bg-blue-500/20 text-blue-300">
72
- <span>👥</span>
73
- {counter.$state.connectedUsers} usuário(s)
74
- </div>
75
- </div>
76
-
77
- <div className="text-center mb-6 sm:mb-8 flex-1 flex flex-col justify-center">
78
- <div className="text-6xl sm:text-8xl font-bold bg-theme-gradient bg-clip-text text-transparent">
79
- {counter.$state.count}
80
- </div>
81
-
82
- {counter.$state.lastUpdatedBy && (
83
- <p className="text-gray-500 text-sm mt-2">
84
- Última atualização: {counter.$state.lastUpdatedBy}
85
- </p>
86
- )}
87
- </div>
88
-
89
- <div className="flex gap-4 justify-center">
90
- <button
91
- onClick={handleDecrement}
92
- disabled={counter.$loading}
93
- className="w-14 h-14 flex items-center justify-center text-3xl btn-complement disabled:opacity-50"
94
- >
95
-
96
- </button>
97
-
98
- <button
99
- onClick={handleReset}
100
- disabled={counter.$loading}
101
- className="px-6 h-14 flex items-center justify-center text-sm btn-theme-outline disabled:opacity-50"
102
- >
103
- Reset
104
- </button>
105
-
106
- <button
107
- onClick={handleIncrement}
108
- disabled={counter.$loading}
109
- className="w-14 h-14 flex items-center justify-center text-3xl btn-accent disabled:opacity-50"
110
- >
111
- +
112
- </button>
113
- </div>
114
-
115
- {counter.$loading && (
116
- <div className="flex justify-center mt-4">
117
- <div className="w-5 h-5 border-2 border-theme border-t-transparent rounded-full animate-spin" />
118
- </div>
119
- )}
120
-
121
- <div className="mt-8 pt-6 border-t border-white/10">
122
- <p className="text-gray-500 text-xs text-center">
123
- Usando <code className="text-theme">Room Events</code>
124
- </p>
125
- </div>
126
- </div>
127
- )
128
- }
129
-
130
- const renderLocalCounter = () => {
131
- const handleIncrement = async () => {
132
- await localCounter.increment()
133
- }
134
- const handleDecrement = async () => {
135
- await localCounter.decrement()
136
- }
137
- const handleReset = async () => {
138
- await localCounter.reset()
139
- }
140
-
141
- return (
142
- <div className="bg-white/5 backdrop-blur-sm border border-white/10 rounded-2xl p-5 sm:p-8 max-w-md w-full flex flex-col">
143
- <h2 className="text-xl sm:text-2xl font-bold text-white mb-2 text-center">
144
- Contador Local (sem Room)
145
- </h2>
146
- <p className="text-gray-400 text-xs sm:text-sm text-center mb-4 sm:mb-6">
147
- Estado local do componente, sem eventos de sala.
148
- </p>
149
-
150
- <div className="text-center mb-6 sm:mb-8 flex-1 flex flex-col justify-center">
151
- <div className="text-6xl sm:text-8xl font-bold bg-gradient-to-r from-amber-400 via-orange-400 to-rose-400 bg-clip-text text-transparent">
152
- {localCounter.$state.count}
153
- </div>
154
- </div>
155
-
156
- <div className="flex gap-4 justify-center">
157
- <button
158
- onClick={handleDecrement}
159
- disabled={localCounter.$loading}
160
- className="w-14 h-14 flex items-center justify-center text-3xl btn-complement disabled:opacity-50"
161
- >
162
-
163
- </button>
164
-
165
- <button
166
- onClick={handleReset}
167
- disabled={localCounter.$loading}
168
- className="px-6 h-14 flex items-center justify-center text-sm btn-theme-outline disabled:opacity-50"
169
- >
170
- Reset
171
- </button>
172
-
173
- <button
174
- onClick={handleIncrement}
175
- disabled={localCounter.$loading}
176
- className="w-14 h-14 flex items-center justify-center text-3xl btn-accent disabled:opacity-50"
177
- >
178
- +
179
- </button>
180
- </div>
181
-
182
- {localCounter.$loading && (
183
- <div className="flex justify-center mt-4">
184
- <div className="w-5 h-5 border-2 border-theme border-t-transparent rounded-full animate-spin" />
185
- </div>
186
- )}
187
- </div>
188
- )
189
- }
190
-
191
- return (
192
- <div className="flex flex-col lg:flex-row gap-4 sm:gap-6 items-stretch justify-center">
193
- {renderLocalCounter()}
194
- {renderCounter(
195
- 'Contador Isolado',
196
- 'Cada aba tem seu próprio valor (room único).',
197
- isolatedCounter
198
- )}
199
- {renderCounter(
200
- 'Contador Compartilhado',
201
- 'Abra em várias abas - todos veem o mesmo valor!',
202
- sharedCounter
203
- )}
204
- </div>
205
- )
206
- }
1
+ import { useMemo } from 'react'
2
+ import { Live } from '@/core/client'
3
+ import { LiveCounter } from '@server/live/LiveCounter'
4
+ import { LiveLocalCounter } from '@server/live/LiveLocalCounter'
5
+ import { FaMinus, FaPlus, FaRotateRight, FaUsers } from 'react-icons/fa6'
6
+
7
+ type CounterProxy = ReturnType<typeof Live.use>
8
+
9
+ function ConnectionPill({ connected }: { connected: boolean }) {
10
+ return (
11
+ <span className={`inline-flex items-center gap-2 rounded-full border px-2.5 py-1 text-xs ${
12
+ connected
13
+ ? 'border-emerald-400/25 bg-emerald-400/10 text-emerald-200'
14
+ : 'border-red-400/25 bg-red-400/10 text-red-200'
15
+ }`}>
16
+ <span className={`h-1.5 w-1.5 rounded-full ${connected ? 'bg-emerald-300' : 'bg-red-300'}`} />
17
+ {connected ? 'Connected' : 'Offline'}
18
+ </span>
19
+ )
20
+ }
21
+
22
+ function CounterCard({
23
+ title,
24
+ description,
25
+ mode,
26
+ counter,
27
+ accent = 'theme',
28
+ }: {
29
+ title: string
30
+ description: string
31
+ mode: string
32
+ counter: CounterProxy
33
+ accent?: 'theme' | 'warm'
34
+ }) {
35
+ const valueClass = accent === 'warm'
36
+ ? 'bg-gradient-to-r from-amber-300 via-orange-300 to-rose-300'
37
+ : 'bg-theme-gradient'
38
+
39
+ return (
40
+ <article className="flex min-h-[430px] w-full flex-col rounded-lg border border-white/10 bg-[#07070b]/85 p-5 shadow-2xl shadow-black/20">
41
+ <div className="flex items-start justify-between gap-4">
42
+ <div>
43
+ <span className="rounded-full border border-white/10 bg-white/[0.03] px-2.5 py-1 text-xs text-gray-400">
44
+ {mode}
45
+ </span>
46
+ <h2 className="mt-4 text-xl font-semibold tracking-tight text-white">{title}</h2>
47
+ <p className="mt-2 text-sm leading-6 text-gray-500">{description}</p>
48
+ </div>
49
+ {'$connected' in counter && <ConnectionPill connected={counter.$connected} />}
50
+ </div>
51
+
52
+ <div className="flex flex-1 items-center justify-center py-8">
53
+ <div className={`bg-clip-text text-7xl font-semibold tabular-nums tracking-tight text-transparent sm:text-8xl ${valueClass}`}>
54
+ {counter.$state.count}
55
+ </div>
56
+ </div>
57
+
58
+ {'connectedUsers' in counter.$state && (
59
+ <div className="mb-5 flex items-center justify-between rounded-lg border border-white/10 bg-white/[0.025] px-3 py-2 text-xs text-gray-400">
60
+ <span className="inline-flex items-center gap-2">
61
+ <FaUsers className="text-theme" />
62
+ Users in room
63
+ </span>
64
+ <span className="font-mono text-white">{counter.$state.connectedUsers}</span>
65
+ </div>
66
+ )}
67
+
68
+ {counter.$state.lastUpdatedBy && (
69
+ <p className="mb-5 truncate text-xs text-gray-500">
70
+ Last update: <span className="text-gray-300">{counter.$state.lastUpdatedBy}</span>
71
+ </p>
72
+ )}
73
+
74
+ <div className="grid grid-cols-[56px_1fr_56px] gap-3">
75
+ <button
76
+ onClick={() => counter.decrement()}
77
+ disabled={counter.$loading}
78
+ className="flex h-12 items-center justify-center rounded-lg border border-white/10 bg-white/[0.03] text-gray-200 transition hover:bg-white/[0.07] disabled:opacity-50"
79
+ aria-label={`Decrease ${title}`}
80
+ >
81
+ <FaMinus />
82
+ </button>
83
+ <button
84
+ onClick={() => counter.reset()}
85
+ disabled={counter.$loading}
86
+ className="inline-flex h-12 items-center justify-center gap-2 rounded-lg border border-theme-active bg-theme-muted px-4 text-sm font-semibold text-theme transition hover:shadow-theme disabled:opacity-50"
87
+ >
88
+ <FaRotateRight className="h-3.5 w-3.5" />
89
+ Reset
90
+ </button>
91
+ <button
92
+ onClick={() => counter.increment()}
93
+ disabled={counter.$loading}
94
+ className="flex h-12 items-center justify-center rounded-lg bg-white text-black transition hover:bg-gray-200 disabled:opacity-50"
95
+ aria-label={`Increase ${title}`}
96
+ >
97
+ <FaPlus />
98
+ </button>
99
+ </div>
100
+ </article>
101
+ )
102
+ }
103
+
104
+ export function CounterDemo() {
105
+ const isolatedRoom = useMemo(() => {
106
+ if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
107
+ return `local-${crypto.randomUUID()}`
108
+ }
109
+ return `local-${Math.random().toString(36).slice(2)}`
110
+ }, [])
111
+
112
+ const localCounter = Live.use(LiveLocalCounter, {
113
+ initialState: LiveLocalCounter.defaultState,
114
+ persistState: false,
115
+ })
116
+
117
+ const isolatedCounter = Live.use(LiveCounter, {
118
+ room: isolatedRoom,
119
+ initialState: LiveCounter.defaultState,
120
+ persistState: false,
121
+ })
122
+
123
+ const sharedCounter = Live.use(LiveCounter, {
124
+ room: 'global-counter',
125
+ initialState: LiveCounter.defaultState,
126
+ })
127
+
128
+ return (
129
+ <div className="grid w-full gap-4 lg:grid-cols-3">
130
+ <CounterCard
131
+ title="Local state"
132
+ description="A component-local counter for quick state updates without a shared room."
133
+ mode="No room"
134
+ counter={localCounter}
135
+ accent="warm"
136
+ />
137
+ <CounterCard
138
+ title="Isolated room"
139
+ description="A private room per tab. The server owns the state, but the session is isolated."
140
+ mode="Private room"
141
+ counter={isolatedCounter}
142
+ />
143
+ <CounterCard
144
+ title="Shared room"
145
+ description="One global room. Open another tab and every client sees the same counter."
146
+ mode="Global room"
147
+ counter={sharedCounter}
148
+ />
149
+ </div>
150
+ )
151
+ }
@@ -1,119 +1,140 @@
1
- // 🔥 FormDemo - Exemplo de Live Component
2
- import { Live } from '@/core/client'
3
- import { LiveForm } from '@server/live/LiveForm'
4
-
5
- export function FormDemo() {
6
- // ✨ Usa defaultState do backend automaticamente
7
- const form = Live.use(LiveForm)
8
-
9
- // Sucesso
10
- if (form.submitted) {
11
- return (
12
- <div className="p-4 sm:p-6 bg-green-500/20 border border-green-500/30 rounded-xl text-center w-full max-w-xl mx-auto">
13
- <div className="text-4xl mb-3">✅</div>
14
- <h2 className="text-xl font-bold text-white mb-2">Enviado!</h2>
15
- <p className="text-gray-300">Obrigado, <span className="text-green-400">{form.name}</span>!</p>
16
- <p className="text-gray-400 text-sm mt-2">
17
- Enviado em: {form.submittedAt ? new Date(form.submittedAt).toLocaleString() : '-'}
18
- </p>
19
- <button
20
- onClick={() => form.reset()}
21
- className="mt-4 btn-theme"
22
- >
23
- Novo Formulário
24
- </button>
25
- </div>
26
- )
27
- }
28
-
29
- return (
30
- <div className="p-4 sm:p-6 bg-white/10 backdrop-blur-sm border border-white/20 rounded-xl w-full max-w-xl mx-auto">
31
- <div className="flex items-center justify-between mb-6">
32
- <h2 className="text-xl font-bold text-white">Live Form</h2>
33
- <span className={`px-3 py-1 rounded-full text-xs ${
34
- form.$connected ? 'bg-green-500/20 text-green-300' : 'bg-red-500/20 text-red-300'
35
- }`}>
36
- {form.$connected ? '🟢 Conectado' : '🔴 Desconectado'}
37
- </span>
38
- </div>
39
-
40
- <div className="space-y-4">
41
- {/* Nome - sync on blur */}
42
- <div>
43
- <label className="block text-gray-300 text-sm mb-1">
44
- Nome <span className="text-theme text-xs">(sync: blur)</span>
45
- </label>
46
- <input
47
- {...form.$field('name', { syncOn: 'blur' })}
48
- placeholder="Seu nome"
49
- className="w-full input-theme"
50
- />
51
- </div>
52
-
53
- {/* Email - sync on change with debounce */}
54
- <div>
55
- <label className="block text-gray-300 text-sm mb-1">
56
- Email <span className="text-blue-400 text-xs">(sync: 500ms)</span>
57
- </label>
58
- <input
59
- {...form.$field('email', { syncOn: 'change', debounce: 500 })}
60
- type="email"
61
- placeholder="seu@email.com"
62
- className="w-full input-theme"
63
- />
64
- </div>
65
-
66
- {/* Mensagem - sync on blur */}
67
- <div>
68
- <label className="block text-gray-300 text-sm mb-1">
69
- Mensagem <span className="text-orange-400 text-xs">(sync: blur)</span>
70
- </label>
71
- <textarea
72
- {...form.$field('message', { syncOn: 'blur' })}
73
- rows={3}
74
- placeholder="Sua mensagem..."
75
- className="w-full input-theme resize-none"
76
- />
77
- </div>
78
-
79
- {/* Botões */}
80
- <div className="flex gap-2">
81
- <button
82
- onClick={async () => {
83
- try {
84
- await form.$sync()
85
- await form.submit()
86
- } catch (err: any) {
87
- alert(err.message || 'Erro ao enviar')
88
- }
89
- }}
90
- disabled={!form.$connected || form.$loading}
91
- className="flex-1 px-4 py-3 bg-theme-gradient text-white rounded-lg font-medium hover:shadow-theme transition-all disabled:opacity-50"
92
- >
93
- {form.$loading ? 'Enviando...' : 'Enviar'}
94
- </button>
95
- <button
96
- onClick={() => form.reset()}
97
- className="px-4 py-3 btn-theme-ghost"
98
- >
99
- Limpar
100
- </button>
101
- </div>
102
- </div>
103
-
104
- {/* Legenda */}
105
- <div className="mt-4 p-3 bg-white/5 rounded-lg text-xs text-gray-400 space-y-1">
106
- <p><span className="text-theme">blur:</span> Sincroniza ao sair do campo</p>
107
- <p><span className="text-blue-400">500ms:</span> Sincroniza 500ms após parar de digitar</p>
108
- </div>
109
-
110
- {/* Debug */}
111
- <details className="mt-4">
112
- <summary className="text-gray-400 text-sm cursor-pointer">Debug State (servidor)</summary>
113
- <pre className="mt-2 p-3 bg-black/40 rounded-lg text-xs text-green-400 overflow-auto">
114
- {JSON.stringify(form.$state, null, 2)}
115
- </pre>
116
- </details>
117
- </div>
118
- )
119
- }
1
+ import { Live } from '@/core/client'
2
+ import { LiveForm } from '@server/live/LiveForm'
3
+ import { FaCheck, FaCode, FaEnvelope, FaRegMessage, FaUser } from 'react-icons/fa6'
4
+
5
+ function FieldHint({ children }: { children: string }) {
6
+ return (
7
+ <span className="rounded-full border border-white/10 bg-white/[0.03] px-2 py-0.5 text-[11px] text-gray-500">
8
+ {children}
9
+ </span>
10
+ )
11
+ }
12
+
13
+ export function FormDemo() {
14
+ const form = Live.use(LiveForm)
15
+
16
+ if (form.submitted) {
17
+ return (
18
+ <div className="w-full max-w-2xl rounded-lg border border-emerald-400/20 bg-emerald-400/10 p-6 text-center shadow-2xl shadow-black/20">
19
+ <div className="mx-auto mb-5 flex h-12 w-12 items-center justify-center rounded-lg bg-emerald-400 text-black">
20
+ <FaCheck />
21
+ </div>
22
+ <h2 className="text-2xl font-semibold text-white">Message received</h2>
23
+ <p className="mt-2 text-sm leading-6 text-gray-300">
24
+ Thanks, <span className="text-emerald-200">{form.name || 'there'}</span>. The server state was updated and submitted.
25
+ </p>
26
+ <p className="mt-3 text-xs text-gray-500">
27
+ Submitted at {form.submittedAt ? new Date(form.submittedAt).toLocaleString() : '-'}
28
+ </p>
29
+ <button onClick={() => form.reset()} className="mt-6 h-11 rounded-lg bg-white px-5 text-sm font-semibold text-black transition hover:bg-gray-200">
30
+ Start another form
31
+ </button>
32
+ </div>
33
+ )
34
+ }
35
+
36
+ return (
37
+ <div className="grid w-full max-w-5xl gap-4 lg:grid-cols-[1fr_360px]">
38
+ <section className="rounded-lg border border-white/10 bg-[#07070b]/85 p-5 shadow-2xl shadow-black/20 sm:p-6">
39
+ <div className="mb-6 flex flex-col gap-3 sm:flex-row sm:items-start sm:justify-between">
40
+ <div>
41
+ <h2 className="text-2xl font-semibold tracking-tight text-white">Contact workflow</h2>
42
+ <p className="mt-2 text-sm leading-6 text-gray-500">
43
+ Fields sync with the server using different strategies so the UI stays responsive.
44
+ </p>
45
+ </div>
46
+ <span className={`inline-flex w-fit items-center gap-2 rounded-full border px-3 py-1 text-xs ${
47
+ form.$connected
48
+ ? 'border-emerald-400/25 bg-emerald-400/10 text-emerald-200'
49
+ : 'border-red-400/25 bg-red-400/10 text-red-200'
50
+ }`}>
51
+ <span className={`h-1.5 w-1.5 rounded-full ${form.$connected ? 'bg-emerald-300' : 'bg-red-300'}`} />
52
+ {form.$connected ? 'Connected' : 'Offline'}
53
+ </span>
54
+ </div>
55
+
56
+ <div className="space-y-4">
57
+ <label className="block">
58
+ <div className="mb-2 flex items-center justify-between gap-3">
59
+ <span className="inline-flex items-center gap-2 text-sm font-medium text-gray-200">
60
+ <FaUser className="text-theme" />
61
+ Name
62
+ </span>
63
+ <FieldHint>sync on blur</FieldHint>
64
+ </div>
65
+ <input
66
+ {...form.$field('name', { syncOn: 'blur' })}
67
+ placeholder="Ada Lovelace"
68
+ className="w-full input-theme"
69
+ />
70
+ </label>
71
+
72
+ <label className="block">
73
+ <div className="mb-2 flex items-center justify-between gap-3">
74
+ <span className="inline-flex items-center gap-2 text-sm font-medium text-gray-200">
75
+ <FaEnvelope className="text-theme-secondary" />
76
+ Email
77
+ </span>
78
+ <FieldHint>debounce 500ms</FieldHint>
79
+ </div>
80
+ <input
81
+ {...form.$field('email', { syncOn: 'change', debounce: 500 })}
82
+ type="email"
83
+ placeholder="ada@company.dev"
84
+ className="w-full input-theme"
85
+ />
86
+ </label>
87
+
88
+ <label className="block">
89
+ <div className="mb-2 flex items-center justify-between gap-3">
90
+ <span className="inline-flex items-center gap-2 text-sm font-medium text-gray-200">
91
+ <FaRegMessage className="text-theme" />
92
+ Message
93
+ </span>
94
+ <FieldHint>sync on blur</FieldHint>
95
+ </div>
96
+ <textarea
97
+ {...form.$field('message', { syncOn: 'blur' })}
98
+ rows={5}
99
+ placeholder="Tell us what you want to build..."
100
+ className="w-full resize-none input-theme"
101
+ />
102
+ </label>
103
+
104
+ <div className="flex flex-col gap-3 pt-2 sm:flex-row">
105
+ <button
106
+ onClick={async () => {
107
+ try {
108
+ await form.$sync()
109
+ await form.submit()
110
+ } catch (err: any) {
111
+ alert(err.message || 'Submit failed')
112
+ }
113
+ }}
114
+ disabled={!form.$connected || form.$loading}
115
+ className="h-11 flex-1 rounded-lg bg-white px-5 text-sm font-semibold text-black transition hover:bg-gray-200 disabled:opacity-50"
116
+ >
117
+ {form.$loading ? 'Sending...' : 'Submit'}
118
+ </button>
119
+ <button
120
+ onClick={() => form.reset()}
121
+ className="h-11 rounded-lg border border-white/10 bg-white/[0.03] px-5 text-sm font-semibold text-white transition hover:bg-white/[0.06]"
122
+ >
123
+ Clear
124
+ </button>
125
+ </div>
126
+ </div>
127
+ </section>
128
+
129
+ <aside className="rounded-lg border border-white/10 bg-black/30 p-5">
130
+ <div className="mb-4 flex items-center gap-2 text-sm font-semibold text-white">
131
+ <FaCode className="text-theme" />
132
+ Server state
133
+ </div>
134
+ <pre className="max-h-[460px] overflow-auto rounded-lg border border-white/10 bg-black/50 p-4 text-xs leading-6 text-emerald-300">
135
+ <code>{JSON.stringify(form.$state, null, 2)}</code>
136
+ </pre>
137
+ </aside>
138
+ </div>
139
+ )
140
+ }