@basictech/react 0.8.0-beta.1 → 0.8.0-beta.3

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basictech/react",
3
- "version": "0.8.0-beta.1",
3
+ "version": "0.8.0-beta.3",
4
4
  "description": "",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -12,11 +12,13 @@
12
12
  "scripts": {
13
13
  "build": "tsup",
14
14
  "dev": "tsup --watch",
15
+ "prepublishOnly": "npm run build",
15
16
  "test": "echo \"Error: no test specified\" && exit 1"
16
17
  },
17
18
  "author": "",
18
19
  "license": "ISC",
19
20
  "dependencies": {
21
+ "semver": "^7.7.2",
20
22
  "ajv": "^8.17.1",
21
23
  "dexie": "^4.2.1",
22
24
  "dexie-observable": "^4.0.1-beta.13",
package/readme.md CHANGED
@@ -99,6 +99,7 @@ Root provider component. Must wrap your entire app.
99
99
  schema={schema} // Required: Your Basic schema
100
100
  debug={false} // Optional: Enable console logging
101
101
  dbMode="sync" // Optional: "sync" (default) or "remote"
102
+ devToolbar={false} // Optional: Floating dev status bar (localhost / dev / debug)
102
103
  />
103
104
  ```
104
105
 
@@ -109,6 +110,7 @@ Root provider component. Must wrap your entire app.
109
110
  | `schema` | `object` | required | Schema with `project_id` and `tables` |
110
111
  | `debug` | `boolean` | `false` | Enable debug logging |
111
112
  | `dbMode` | `"sync" \| "remote"` | `"sync"` | Database mode |
113
+ | `devToolbar` | `boolean` | `false` | Show the Basic dev toolbar (only when `localhost`, `NODE_ENV === "development"`, or `debug={true}`) |
112
114
 
113
115
  #### Database Modes
114
116
 
@@ -139,6 +141,10 @@ const {
139
141
  db, // Database instance
140
142
  dbStatus, // DBStatus - see below
141
143
  dbMode, // "sync" | "remote"
144
+
145
+ // Dev / schema snapshot (for custom tooling)
146
+ devInfo, // BasicSchemaDevInfo | null — local vs remote schema status
147
+ refreshSchemaStatus, // () => Promise<void> — re-fetch schema status from API
142
148
  } = useBasic()
143
149
  ```
144
150
 
@@ -160,6 +166,33 @@ Import the enum for comparisons: `import { useBasic, DBStatus } from '@basictech
160
166
 
161
167
  ---
162
168
 
169
+ ### Development toolbar
170
+
171
+ A small floating bar (similar in spirit to Next.js dev indicators) shows **auth**, **database mode**, **sync status**, and **schema vs server** health. It is **opt-in** and only appears in development: `localhost` / `127.0.0.1` / `.local`, `NODE_ENV === "development"`, or when `debug` is `true` on the provider or on the standalone component.
172
+
173
+ **Option A — provider flag**
174
+
175
+ ```tsx
176
+ <BasicProvider schema={schema} devToolbar debug>
177
+ <App />
178
+ </BasicProvider>
179
+ ```
180
+
181
+ **Option B — place the component yourself** (must be under `BasicProvider`; respects the same visibility rules, or pass `debug` to force):
182
+
183
+ ```tsx
184
+ import { BasicDevToolbar } from '@basictech/react'
185
+
186
+ <BasicProvider schema={schema}>
187
+ <App />
188
+ <BasicDevToolbar />
189
+ </BasicProvider>
190
+ ```
191
+
192
+ The expanded panel includes **Refresh schema** (re-runs the remote schema check) and **Copy debug info** (JSON snapshot **without** raw access tokens). You can also read `devInfo` and call `refreshSchemaStatus()` from `useBasic()` for your own UI.
193
+
194
+ ---
195
+
163
196
  ### `useQuery()`
164
197
 
165
198
  Live query hook - automatically re-renders when data changes.
@@ -1,165 +1,75 @@
1
- import React, { createContext, useContext, useEffect, useState, useRef } from 'react'
1
+ import React, { useCallback, useEffect, useRef, useState, Suspense, lazy } from 'react'
2
2
 
3
3
  import { BasicSync, initDexieExtensions } from './sync'
4
4
  import { RemoteDB, DBMode, BasicDB } from './core/db'
5
5
  import { AuthManager } from './core/auth/AuthManager'
6
- import type { Token, User, AuthResult, GetTokenOptions, PdsEndpoints } from './core/auth/AuthManager'
6
+ import type { User, AuthResult, GetTokenOptions, PdsEndpoints } from './core/auth/AuthManager'
7
7
 
8
8
  import { log } from './config'
9
9
  import { version as currentVersion } from '../package.json'
10
10
  import { createVersionUpdater } from './updater/versionUpdater'
11
11
  import { getMigrations } from './updater/updateMigrations'
12
- import { BasicStorage, LocalStorageAdapter, STORAGE_KEYS } from './utils/storage'
12
+ import { BasicStorage, LocalStorageAdapter } from './utils/storage'
13
13
  import { isDevelopment, checkForNewVersion, getSyncStatus } from './utils/network'
14
14
  import { validateAndCheckSchema } from './utils/schema'
15
+ import { BasicContext, DBStatus, noDb, type BasicSchemaDevInfo } from './context'
16
+
17
+ const BasicDevToolbar = lazy(() =>
18
+ import('./dev/BasicDevToolbar').then((m) => ({ default: m.BasicDevToolbar }))
19
+ )
15
20
 
16
21
  export type { BasicStorage, LocalStorageAdapter } from './utils/storage'
17
22
  export type { DBMode, BasicDB, Collection } from './core/db'
18
- export type { Token, User, AuthResult, GetTokenOptions, PdsEndpoints }
23
+ export type { Token, User, AuthResult, GetTokenOptions, PdsEndpoints } from './core/auth/AuthManager'
24
+ export type { BasicContextType, BasicSchemaDevInfo } from './context'
25
+ export { DBStatus, useBasic, BasicContext } from './context'
19
26
 
20
27
  export type AuthConfig = {
21
- scopes?: string | string[];
28
+ scopes?: string | string[]
22
29
  /** @deprecated Use pds_url instead */
23
- server_url?: string;
30
+ server_url?: string
24
31
  /** PDS URL for auth and data (default: https://pds.basic.id) */
25
- pds_url?: string;
32
+ pds_url?: string
26
33
  /** Admin server URL for connect reporting (default: https://api.basic.tech) */
27
- admin_url?: string;
28
- ws_url?: string;
34
+ admin_url?: string
35
+ ws_url?: string
29
36
  }
30
37
 
31
38
  export type BasicProviderProps = {
32
- children: React.ReactNode;
33
- /**
34
- * @deprecated Project ID is now extracted from schema.project_id.
39
+ children: React.ReactNode
40
+ /**
41
+ * @deprecated Project ID is now extracted from schema.project_id.
35
42
  * This prop is kept for backward compatibility but can be omitted.
36
43
  */
37
- project_id?: string;
44
+ project_id?: string
38
45
  /** The Basic schema object containing project_id and table definitions */
39
- schema?: any;
40
- debug?: boolean;
41
- storage?: BasicStorage;
42
- auth?: AuthConfig;
46
+ schema?: any
47
+ debug?: boolean
48
+ storage?: BasicStorage
49
+ auth?: AuthConfig
43
50
  /**
44
51
  * Database mode - determines which implementation is used
45
52
  * - 'sync': Uses Dexie + WebSocket for local-first sync (default)
46
53
  * - 'remote': Uses REST API calls directly to server
47
54
  */
48
- dbMode?: DBMode;
55
+ dbMode?: DBMode
56
+ /** Show floating dev toolbar (localhost, NODE_ENV=development, or debug=true). */
57
+ devToolbar?: boolean
49
58
  }
50
59
 
51
60
  const DEFAULT_AUTH_CONFIG = {
52
61
  scopes: 'profile,email,app:admin',
53
62
  pds_url: 'https://pds.basic.id',
54
63
  admin_url: 'https://api.basic.tech',
55
- ws_url: 'wss://pds.basic.id/ws'
64
+ ws_url: 'wss://pds.basic.id/ws',
56
65
  } as const
57
66
 
58
-
59
- export enum DBStatus {
60
- LOADING = "LOADING",
61
- OFFLINE = "OFFLINE",
62
- CONNECTING = "CONNECTING",
63
- ONLINE = "ONLINE",
64
- SYNCING = "SYNCING",
65
- ERROR = "ERROR",
66
- /** Sync reported an error but will retry (e.g. expired token). Used for status code 4 from dexie-syncable. */
67
- ERROR_WILL_RETRY = "ERROR_WILL_RETRY",
68
- /** Token expired; the SDK is refreshing and will reconnect automatically. */
69
- ERROR_TOKEN_EXPIRED = "ERROR_TOKEN_EXPIRED"
70
- }
71
-
72
- /**
73
- * Context type for useBasic hook
74
- */
75
- export type BasicContextType = {
76
- // Auth state
77
- isReady: boolean;
78
- isSignedIn: boolean;
79
- user: User | null;
80
- /** The user's DID (Decentralized Identifier), extracted from the access token `sub` claim */
81
- did: string | null;
82
- /** Space-separated scope string from the access token */
83
- scope: string | null;
84
- /** Check if a specific scope is granted (e.g., hasScope('profile')) */
85
- hasScope: (scope: string) => boolean;
86
- /** Returns scopes that were requested but not granted in the current token */
87
- missingScopes: () => string[];
88
-
89
- // Auth actions (new camelCase naming)
90
- signIn: () => Promise<void>;
91
- signInWithHandle: (handle: string) => Promise<void>;
92
- signOut: () => Promise<void>;
93
- signInWithCode: (code: string, state?: string) => Promise<AuthResult>;
94
-
95
- // Token management
96
- getToken: (options?: GetTokenOptions) => Promise<string>;
97
- getSignInUrl: (redirectUri?: string) => Promise<string>;
98
-
99
- // DB access
100
- db: BasicDB;
101
- dbStatus: DBStatus;
102
- dbMode: DBMode;
103
-
104
- // Legacy aliases (deprecated - will be removed in future version)
105
- /** @deprecated Use isReady instead */
106
- isAuthReady: boolean;
107
- /** @deprecated Use signIn instead */
108
- signin: () => Promise<void>;
109
- /** @deprecated Use signOut instead */
110
- signout: () => Promise<void>;
111
- /** @deprecated Use signInWithCode instead */
112
- signinWithCode: (code: string, state?: string) => Promise<AuthResult>;
113
- /** @deprecated Use getSignInUrl instead */
114
- getSignInLink: (redirectUri?: string) => Promise<string>;
115
- }
116
-
117
- const noDb: BasicDB = {
118
- collection: () => {
119
- throw new Error('no basicdb found - initialization failed. double check your schema.')
120
- }
121
- }
122
-
123
- export const BasicContext = createContext<BasicContextType>({
124
- // Auth state
125
- isReady: false,
126
- isSignedIn: false,
127
- user: null,
128
- did: null,
129
- scope: null,
130
- hasScope: () => false,
131
- missingScopes: () => [],
132
-
133
- // Auth actions
134
- signIn: () => Promise.resolve(),
135
- signInWithHandle: () => Promise.resolve(),
136
- signOut: () => Promise.resolve(),
137
- signInWithCode: () => Promise.resolve({ success: false }),
138
-
139
- // Token management
140
- getToken: (_options?: GetTokenOptions) => Promise.reject(new Error('no token')),
141
- getSignInUrl: () => Promise.resolve(""),
142
-
143
- // DB access
144
- db: noDb,
145
- dbStatus: DBStatus.LOADING,
146
- dbMode: 'sync',
147
-
148
- // Legacy aliases
149
- isAuthReady: false,
150
- signin: () => Promise.resolve(),
151
- signout: () => Promise.resolve(),
152
- signinWithCode: () => Promise.resolve({ success: false }),
153
- getSignInLink: () => Promise.resolve("")
154
- });
155
-
156
67
  type ErrorObject = {
157
- code: string;
158
- title: string;
159
- message: string;
68
+ code: string
69
+ title: string
70
+ message: string
160
71
  }
161
72
 
162
- // Tracks the subset of AuthManager state that React effects depend on.
163
73
  type AuthSnapshot = {
164
74
  isSignedIn: boolean
165
75
  hasToken: boolean
@@ -187,11 +97,11 @@ export function BasicProvider({
187
97
  debug = false,
188
98
  storage,
189
99
  auth,
190
- dbMode = 'sync'
100
+ dbMode = 'sync',
101
+ devToolbar = false,
191
102
  }: BasicProviderProps) {
192
103
  const project_id = schema?.project_id || project_id_prop
193
104
 
194
- // Merge auth config with defaults (server_url is deprecated in favor of pds_url)
195
105
  if (auth?.server_url && !auth?.pds_url) {
196
106
  log('Warning: auth.server_url is deprecated, use auth.pds_url instead')
197
107
  }
@@ -199,7 +109,7 @@ export function BasicProvider({
199
109
  scopes: auth?.scopes || DEFAULT_AUTH_CONFIG.scopes,
200
110
  pds_url: auth?.pds_url || auth?.server_url || DEFAULT_AUTH_CONFIG.pds_url,
201
111
  admin_url: auth?.admin_url || DEFAULT_AUTH_CONFIG.admin_url,
202
- ws_url: auth?.ws_url || DEFAULT_AUTH_CONFIG.ws_url
112
+ ws_url: auth?.ws_url || DEFAULT_AUTH_CONFIG.ws_url,
203
113
  }
204
114
 
205
115
  const scopesString = Array.isArray(authConfig.scopes)
@@ -209,7 +119,9 @@ export function BasicProvider({
209
119
  const storageRef = useRef<BasicStorage>(storage || new LocalStorageAdapter())
210
120
  const storageAdapter = storageRef.current
211
121
 
212
- // --- AuthManager (stable instance held in a ref) ---
122
+ const schemaRef = useRef(schema)
123
+ schemaRef.current = schema
124
+
213
125
  const [authState, setAuthState] = useState<AuthSnapshot>({
214
126
  isSignedIn: false,
215
127
  hasToken: false,
@@ -234,19 +146,56 @@ export function BasicProvider({
234
146
  )
235
147
  }
236
148
 
237
- // --- DB state (stays in React) ---
238
149
  const syncRef = useRef<BasicSync | null>(null)
239
150
  const remoteDbRef = useRef<RemoteDB | null>(null)
240
151
  const [shouldConnect, setShouldConnect] = useState(false)
241
152
  const [dbStatus, setDbStatus] = useState<DBStatus>(DBStatus.OFFLINE)
242
153
  const [isReady, setIsReady] = useState(false)
243
154
  const [error, setError] = useState<ErrorObject | null>(null)
155
+ const [schemaDevInfo, setSchemaDevInfo] = useState<BasicSchemaDevInfo | null>(null)
244
156
 
245
157
  const isDevMode = () => isDevelopment(debug)
246
158
 
247
- // --- Mount: version updater + auth init + DB init ---
159
+ const refreshSchemaStatus = useCallback(async () => {
160
+ const s = schemaRef.current
161
+ if (!s) {
162
+ setSchemaDevInfo(
163
+ project_id
164
+ ? {
165
+ projectId: project_id,
166
+ localVersion: undefined,
167
+ status: 'no_schema',
168
+ valid: false,
169
+ lastCheckedAt: Date.now(),
170
+ }
171
+ : null,
172
+ )
173
+ return
174
+ }
175
+ const result = await validateAndCheckSchema(s)
176
+ if (!result.isValid) {
177
+ const errText =
178
+ result.errors?.map((e: { message?: string }) => e.message || '').join('; ') || 'invalid'
179
+ setSchemaDevInfo({
180
+ projectId: s.project_id ?? null,
181
+ localVersion: s.version,
182
+ status: 'invalid',
183
+ valid: false,
184
+ lastCheckedAt: Date.now(),
185
+ error: errText,
186
+ })
187
+ return
188
+ }
189
+ setSchemaDevInfo({
190
+ projectId: s.project_id ?? null,
191
+ localVersion: s.version,
192
+ status: result.schemaStatus.status ?? 'unknown',
193
+ valid: result.schemaStatus.valid,
194
+ lastCheckedAt: Date.now(),
195
+ })
196
+ }, [project_id])
197
+
248
198
  useEffect(() => {
249
- // Version updater (SDK migration, not auth-related)
250
199
  const runVersionUpdater = async () => {
251
200
  try {
252
201
  const versionUpdater = createVersionUpdater(storageAdapter, currentVersion, getMigrations())
@@ -268,7 +217,6 @@ export function BasicProvider({
268
217
  return authRef.current.setupNetworkListeners()
269
218
  }, [])
270
219
 
271
- // --- DB init (separate mount effect) ---
272
220
  useEffect(() => {
273
221
  async function initSyncDb(options: { shouldConnect: boolean }) {
274
222
  if (!syncRef.current) {
@@ -276,7 +224,7 @@ export function BasicProvider({
276
224
 
277
225
  await initDexieExtensions()
278
226
 
279
- syncRef.current = new BasicSync('basicdb', { schema: schema });
227
+ syncRef.current = new BasicSync('basicdb', { schema: schema })
280
228
 
281
229
  syncRef.current.syncable.on('statusChanged', (status: number) => {
282
230
  const newStatus = getSyncStatus(status) as DBStatus
@@ -304,7 +252,8 @@ export function BasicProvider({
304
252
  setError({
305
253
  code: 'missing_project_id',
306
254
  title: 'Project ID Required',
307
- message: 'Remote mode requires a project_id. Provide it via schema.project_id or the project_id prop.'
255
+ message:
256
+ 'Remote mode requires a project_id. Provide it via schema.project_id or the project_id prop.',
308
257
  })
309
258
  setIsReady(true)
310
259
  return
@@ -319,8 +268,12 @@ export function BasicProvider({
319
268
  debug: debug,
320
269
  onAuthError: (error) => {
321
270
  log('RemoteDB auth error:', error)
271
+ if (error.errorType === 'forbidden') {
272
+ log('403 Forbidden - user lacks required scope, not signing out')
273
+ return
274
+ }
322
275
  handleSignOut()
323
- }
276
+ },
324
277
  })
325
278
  setDbStatus(DBStatus.ONLINE)
326
279
  setIsReady(true)
@@ -333,19 +286,35 @@ export function BasicProvider({
333
286
  if (!result.isValid) {
334
287
  let errorMessage = ''
335
288
  if (result.errors) {
336
- result.errors.forEach((error: any, index: number) => {
337
- errorMessage += `${index + 1}: ${error.message} - at ${error.instancePath}\n`
289
+ result.errors.forEach((err: any, index: number) => {
290
+ errorMessage += `${index + 1}: ${err.message} - at ${err.instancePath}\n`
338
291
  })
339
292
  }
293
+ setSchemaDevInfo({
294
+ projectId: schema?.project_id ?? null,
295
+ localVersion: schema?.version,
296
+ status: 'invalid',
297
+ valid: false,
298
+ lastCheckedAt: Date.now(),
299
+ error: errorMessage.trim() || undefined,
300
+ })
340
301
  setError({
341
302
  code: 'schema_invalid',
342
303
  title: 'Basic Schema is invalid!',
343
- message: errorMessage
304
+ message: errorMessage,
344
305
  })
345
306
  setIsReady(true)
346
307
  return null
347
308
  }
348
309
 
310
+ setSchemaDevInfo({
311
+ projectId: schema?.project_id ?? null,
312
+ localVersion: schema?.version,
313
+ status: result.schemaStatus.status ?? 'unknown',
314
+ valid: result.schemaStatus.valid,
315
+ lastCheckedAt: Date.now(),
316
+ })
317
+
349
318
  if (dbMode === 'remote') {
350
319
  initRemoteDb()
351
320
  } else {
@@ -367,30 +336,40 @@ export function BasicProvider({
367
336
  if (schema) {
368
337
  checkSchema()
369
338
  } else {
339
+ setSchemaDevInfo(
340
+ project_id
341
+ ? {
342
+ projectId: project_id,
343
+ localVersion: undefined,
344
+ status: 'no_schema',
345
+ valid: false,
346
+ lastCheckedAt: Date.now(),
347
+ }
348
+ : null,
349
+ )
370
350
  if (dbMode === 'remote' && project_id) {
371
351
  initRemoteDb()
372
352
  } else {
373
353
  setIsReady(true)
374
354
  }
375
355
  }
376
- }, []);
356
+ }, [])
377
357
 
378
- // --- Connect sync DB when auth is ready ---
379
358
  useEffect(() => {
380
359
  if (authState.hasToken && syncRef.current && authState.isSignedIn && shouldConnect) {
381
360
  log('connecting to db...')
382
361
 
383
- syncRef.current?.connect({
384
- getToken: (opts?: GetTokenOptions) => authRef.current.getToken(opts),
385
- ws_url: authConfig.ws_url
386
- })
362
+ syncRef.current
363
+ ?.connect({
364
+ getToken: (opts?: GetTokenOptions) => authRef.current.getToken(opts),
365
+ ws_url: authConfig.ws_url,
366
+ })
387
367
  .catch((e: any) => {
388
368
  log('error connecting to db', e)
389
369
  })
390
370
  }
391
371
  }, [authState.isSignedIn, authState.hasToken, shouldConnect])
392
372
 
393
- // --- Sign out (auth cleanup + sync teardown) ---
394
373
  const handleSignOut = async () => {
395
374
  await authRef.current.signOut()
396
375
  if (syncRef.current) {
@@ -405,7 +384,6 @@ export function BasicProvider({
405
384
  }
406
385
  }
407
386
 
408
- // --- Sign in wrappers (add dev-mode error display) ---
409
387
  const handleSignIn = async () => {
410
388
  try {
411
389
  await authRef.current.signIn()
@@ -414,7 +392,8 @@ export function BasicProvider({
414
392
  setError({
415
393
  code: 'signin_error',
416
394
  title: 'Sign-in Failed',
417
- message: (error as Error).message || 'An error occurred during sign-in. Please try again.'
395
+ message:
396
+ (error as Error).message || 'An error occurred during sign-in. Please try again.',
418
397
  })
419
398
  }
420
399
  throw error
@@ -429,14 +408,14 @@ export function BasicProvider({
429
408
  setError({
430
409
  code: 'signin_error',
431
410
  title: 'Sign-in Failed',
432
- message: (error as Error).message || 'An error occurred during sign-in. Please try again.'
411
+ message:
412
+ (error as Error).message || 'An error occurred during sign-in. Please try again.',
433
413
  })
434
414
  }
435
415
  throw error
436
416
  }
437
417
  }
438
418
 
439
- // --- DB accessor ---
440
419
  const getCurrentDb = (): BasicDB => {
441
420
  if (dbMode === 'remote') {
442
421
  return remoteDbRef.current || noDb
@@ -444,33 +423,30 @@ export function BasicProvider({
444
423
  return syncRef.current || noDb
445
424
  }
446
425
 
447
- // --- Context value ---
448
- const contextValue: BasicContextType = {
449
- // Auth state
426
+ const contextValue = {
450
427
  isReady: authState.isAuthReady,
451
428
  isSignedIn: authState.isSignedIn,
452
429
  user: authState.user,
453
430
  did: authState.did,
454
431
  scope: authState.tokenScope,
455
- hasScope: (scope: string) => authRef.current.hasScope(scope),
432
+ hasScope: (s: string) => authRef.current.hasScope(s),
456
433
  missingScopes: () => authRef.current.missingScopes(),
457
434
 
458
- // Auth actions
459
435
  signIn: handleSignIn,
460
436
  signInWithHandle: handleSignInWithHandle,
461
437
  signOut: handleSignOut,
462
438
  signInWithCode: (code: string, state?: string) => authRef.current.signInWithCode(code, state),
463
439
 
464
- // Token management
465
440
  getToken: (opts?: GetTokenOptions) => authRef.current.getToken(opts),
466
441
  getSignInUrl: (redirectUri?: string) => authRef.current.getSignInUrl(redirectUri),
467
442
 
468
- // DB access
469
443
  db: getCurrentDb(),
470
444
  dbStatus,
471
445
  dbMode,
472
446
 
473
- // Legacy aliases (deprecated)
447
+ devInfo: schemaDevInfo,
448
+ refreshSchemaStatus,
449
+
474
450
  isAuthReady: authState.isAuthReady,
475
451
  signin: handleSignIn,
476
452
  signout: handleSignOut,
@@ -481,33 +457,37 @@ export function BasicProvider({
481
457
  return (
482
458
  <BasicContext.Provider value={contextValue}>
483
459
  {error && isDevMode() && <ErrorDisplay error={error} />}
460
+ {devToolbar && isDevMode() && (
461
+ <Suspense fallback={null}>
462
+ <BasicDevToolbar debug={debug} />
463
+ </Suspense>
464
+ )}
484
465
  {isReady && children}
485
466
  </BasicContext.Provider>
486
467
  )
487
468
  }
488
469
 
489
470
  function ErrorDisplay({ error }: { error: ErrorObject }) {
490
- return <div style={{
491
- position: 'absolute',
492
- top: 20,
493
- left: 20,
494
- color: 'black',
495
- backgroundColor: '#f8d7da',
496
- border: '1px solid #f5c6cb',
497
- borderRadius: '4px',
498
- padding: '20px',
499
- maxWidth: '400px',
500
- margin: '20px auto',
501
- boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
502
- fontFamily: 'monospace',
503
- }}>
504
- <h3 style={{ fontSize: '0.8rem', opacity: 0.8 }}>code: {error.code}</h3>
505
- <h1 style={{ fontSize: '1.2rem', lineHeight: '1.5' }}>{error.title}</h1>
506
- <p>{error.message}</p>
507
- </div>
508
- }
509
-
510
-
511
- export function useBasic() {
512
- return useContext(BasicContext);
471
+ return (
472
+ <div
473
+ style={{
474
+ position: 'absolute',
475
+ top: 20,
476
+ left: 20,
477
+ color: 'black',
478
+ backgroundColor: '#f8d7da',
479
+ border: '1px solid #f5c6cb',
480
+ borderRadius: '4px',
481
+ padding: '20px',
482
+ maxWidth: '400px',
483
+ margin: '20px auto',
484
+ boxShadow: '0 2px 4px rgba(0, 0, 0, 0.1)',
485
+ fontFamily: 'monospace',
486
+ }}
487
+ >
488
+ <h3 style={{ fontSize: '0.8rem', opacity: 0.8 }}>code: {error.code}</h3>
489
+ <h1 style={{ fontSize: '1.2rem', lineHeight: 1.5 }}>{error.title}</h1>
490
+ <p>{error.message}</p>
491
+ </div>
492
+ )
513
493
  }