@fias/arche-sdk 1.6.1 → 1.8.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.
- package/dist/cli/create-plugin.js +0 -0
- package/dist/hooks.d.ts +24 -11
- package/dist/hooks.d.ts.map +1 -1
- package/dist/hooks.js +142 -34
- package/dist/hooks.js.map +1 -1
- package/dist/hooks.test.js +190 -96
- package/dist/hooks.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.mjs +92 -20
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +1 -1
- package/dist/themes/fonts.d.ts +1 -1
- package/dist/themes/fonts.js +1 -1
- package/dist/themes/index.d.ts +1 -1
- package/dist/themes/index.js +1 -1
- package/dist/types.d.ts +20 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -4
- package/templates/default/AGENTS.md +104 -6
- package/templates/default/CLAUDE.md +104 -6
- package/templates/multi-step/README.md +13 -0
- package/templates/multi-step/fias-plugin.json +11 -0
- package/templates/multi-step/index.html +12 -0
- package/templates/multi-step/package.json +26 -0
- package/templates/multi-step/src/App.tsx +38 -0
- package/templates/multi-step/src/context/AppContext.tsx +36 -0
- package/templates/multi-step/src/index.tsx +12 -0
- package/templates/multi-step/src/steps/step-one/StepOne.tsx +43 -0
- package/templates/multi-step/src/steps/step-two/StepTwo.tsx +40 -0
- package/templates/multi-step/tsconfig.json +17 -0
- package/templates/multi-step/vite.config.ts +18 -0
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This project is a FIAS platform plugin — a React application that runs in a sandboxed iframe within the FIAS marketplace. This file provides the context AI coding assistants need to build, test, and submit plugins effectively.
|
|
4
4
|
|
|
5
|
+
For other AI tool instruction files, see `AGENTS.md` (identical content).
|
|
6
|
+
|
|
5
7
|
## Project Structure
|
|
6
8
|
|
|
7
9
|
```
|
|
@@ -181,13 +183,17 @@ function AISummarizer() {
|
|
|
181
183
|
});
|
|
182
184
|
}
|
|
183
185
|
|
|
186
|
+
// streamingText holds the accumulated tokens during the call AND continues to
|
|
187
|
+
// hold the full text after the call completes — it is NOT cleared. result.output
|
|
188
|
+
// also contains the full text once invoke() resolves. Render ONE slot only —
|
|
189
|
+
// streamingText while loading, result.output afterwards. Showing both renders
|
|
190
|
+
// the response twice.
|
|
184
191
|
return (
|
|
185
192
|
<div>
|
|
186
193
|
<button onClick={() => summarize('...')} disabled={isLoading}>
|
|
187
194
|
Summarize
|
|
188
195
|
</button>
|
|
189
|
-
{isLoading
|
|
190
|
-
{result && <p>{result.output}</p>}
|
|
196
|
+
<p>{isLoading ? streamingText : result?.output}</p>
|
|
191
197
|
{error && <p>Error: {error.message}</p>}
|
|
192
198
|
</div>
|
|
193
199
|
);
|
|
@@ -247,12 +253,22 @@ navigateTo('/settings');
|
|
|
247
253
|
|
|
248
254
|
**Returns:** `StepNavigationApi`
|
|
249
255
|
|
|
256
|
+
Call **once** in the top-level App component. Share `currentStep` and `setCurrentStep` with step components via React context — do not call `useStepNavigation` from step components (each call creates an independent state).
|
|
257
|
+
|
|
250
258
|
```tsx
|
|
251
259
|
import { useStepNavigation } from '@fias/arche-sdk';
|
|
252
260
|
|
|
261
|
+
// Without persistence — currentStep resets on preview rebuild
|
|
253
262
|
const { currentStep, setCurrentStep } = useStepNavigation('step-1');
|
|
263
|
+
|
|
264
|
+
// With persistence — currentStep survives preview rebuilds (SDK ≥ 1.7.0)
|
|
265
|
+
const { currentStep, setCurrentStep } = useStepNavigation('step-1', {
|
|
266
|
+
persistKey: 'currentStep',
|
|
267
|
+
});
|
|
254
268
|
```
|
|
255
269
|
|
|
270
|
+
**Do NOT wrap `currentStep` in `usePersistentState`.** Two independent step-state sources create a bidirectional `useEffect` sync loop that spams `storage_write` and flashes the UI between steps. `persistKey` is the only correct way to persist the current step.
|
|
271
|
+
|
|
256
272
|
### `usePersistentState()` — Auto-saving state
|
|
257
273
|
|
|
258
274
|
**Permission:** `storage:sandbox`
|
|
@@ -266,6 +282,12 @@ const [count, setCount] = usePersistentState<number>('counter', 0);
|
|
|
266
282
|
// Automatically persists to storage on change
|
|
267
283
|
```
|
|
268
284
|
|
|
285
|
+
Use for domain data (form inputs, selections, AI results). **Do not use for `currentStep`** — use `useStepNavigation({ persistKey })` instead.
|
|
286
|
+
|
|
287
|
+
**Writes are debounced (SDK ≥ 1.8.0).** The in-memory value updates synchronously on every setter call, but the underlying `storage_write` is coalesced with a 250 ms trailing-edge debounce and a 1 s max-wait, and flushed on unmount. This makes the hook safe to call from `requestAnimationFrame`, `mousemove`, and other high-frequency handlers — bursts no longer trip the `storage_write` rate limit.
|
|
288
|
+
|
|
289
|
+
Trade-off: reading the same key via `useFiasStorage().readFile('__state/foo')` immediately after a `setValue` can see a stale value for up to ~250 ms. Read through the hook instead of bypassing it.
|
|
290
|
+
|
|
269
291
|
### `fias` — Imperative utilities
|
|
270
292
|
|
|
271
293
|
```tsx
|
|
@@ -336,6 +358,8 @@ These are hard limits enforced by the platform. Code that violates these will fa
|
|
|
336
358
|
- `storage_read`: 300/minute
|
|
337
359
|
- `storage_list`, `storage_delete`: 60/minute
|
|
338
360
|
|
|
361
|
+
A separate **runaway-loop detector** also fires if any method is called >50 times in 5 s and blocks that method for 10 s. Errors include the target for diagnostics, e.g. `Runaway loop detected: "storage_write" (path: __state/gameState) called 51 times in 5s.` — if you see this, look at the identified path and debounce the updates at the source.
|
|
362
|
+
|
|
339
363
|
### Security Rules (enforced during review)
|
|
340
364
|
|
|
341
365
|
- No `eval()`, `Function()`, `innerHTML`, or dynamic code execution
|
|
@@ -416,6 +440,84 @@ npm run submit
|
|
|
416
440
|
# First listing: 5000 credits ($50). Re-submissions: 100 credits ($1).
|
|
417
441
|
```
|
|
418
442
|
|
|
443
|
+
## Common Pitfalls
|
|
444
|
+
|
|
445
|
+
These are real bugs that have shipped from AI-generated plugins. Avoid them.
|
|
446
|
+
|
|
447
|
+
### Don't navigate from a `useEffect` that watches derived state
|
|
448
|
+
|
|
449
|
+
If a step component derives a value from context state and uses `useEffect` to redirect when that derived value is missing, the redirect can fire mid-update and yank the user away from a working screen. Common shape:
|
|
450
|
+
|
|
451
|
+
```tsx
|
|
452
|
+
// ❌ BUG: redirects whenever conversations changes between renders
|
|
453
|
+
const currentConversation = conversations.find((c) => c.id === currentConversationId);
|
|
454
|
+
|
|
455
|
+
useEffect(() => {
|
|
456
|
+
if (!currentConversation) {
|
|
457
|
+
setCurrentStep('list');
|
|
458
|
+
}
|
|
459
|
+
}, [currentConversation, setCurrentStep]); // fires on every conversations change
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
`currentConversation` is a _derived_ value — its reference changes every time `conversations` changes. The effect re-runs and may redirect during a state transition (e.g., right after the user sends a message and the component is mid-update). The user reports "I sent a message and got bounced back" or "the response never showed up".
|
|
463
|
+
|
|
464
|
+
```tsx
|
|
465
|
+
// ✅ FIX: gate on the *id* (which is durable) and render a loading state when
|
|
466
|
+
// the conversation can't be found yet — let the user navigate back manually
|
|
467
|
+
// instead of forcing it.
|
|
468
|
+
useEffect(() => {
|
|
469
|
+
if (!currentConversationId) setCurrentStep('list');
|
|
470
|
+
}, [currentConversationId, setCurrentStep]);
|
|
471
|
+
|
|
472
|
+
if (!currentConversation) return <div>Loading conversation…</div>;
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
### Use functional updaters when an `await` sits between reads and writes
|
|
476
|
+
|
|
477
|
+
When you read state, `await` something, then write back, the closure captures the _stale_ state. Use the functional form so the setter receives the latest value at the time of the update:
|
|
478
|
+
|
|
479
|
+
```tsx
|
|
480
|
+
// ❌ BUG: `messages` here is the snapshot from before await
|
|
481
|
+
const messages = currentConversation.messages;
|
|
482
|
+
const response = await invoke({ entityId, input });
|
|
483
|
+
setConversations(
|
|
484
|
+
conversations.map((c) => (c.id === id ? { ...c, messages: [...messages, response] } : c)),
|
|
485
|
+
);
|
|
486
|
+
|
|
487
|
+
// ✅ FIX: functional updater reads fresh state at update time
|
|
488
|
+
await invoke({ entityId, input });
|
|
489
|
+
setConversations((prev) =>
|
|
490
|
+
prev.map((c) => (c.id === id ? { ...c, messages: [...c.messages, response] } : c)),
|
|
491
|
+
);
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
This matters most for `usePersistentState` on lists: the user can fire many updates quickly, and lost-update bugs are common with snapshot-style updates.
|
|
495
|
+
|
|
496
|
+
### Render `streamingText` OR `result.output` — never both
|
|
497
|
+
|
|
498
|
+
Already covered in the `useEntityInvocation` section above, but worth repeating: `streamingText` is _not_ cleared after the call completes — it keeps holding the full final text. Showing `{streamingText && ...}` AND `{result && ...}` in separate JSX nodes prints the response twice as soon as `result` arrives. Use one slot: `{isLoading ? streamingText : result?.output}`.
|
|
499
|
+
|
|
500
|
+
### Verify visually when the user reports a UI bug
|
|
501
|
+
|
|
502
|
+
If a user says "I don't see the response" or "the layout's broken", don't rely solely on `console.log` and `useFiasStorage` reads. Inspect the actual rendered output. The platform exposes `get_preview_screenshot` to AI builders for exactly this reason.
|
|
503
|
+
|
|
504
|
+
### Don't persist per-frame state
|
|
505
|
+
|
|
506
|
+
For state that updates every frame (game position, drag coordinates, animated values), use plain `useState` — not `usePersistentState`. Persisting every ball-position tick writes unbounded data to durable storage and doesn't survive reloads in any useful way anyway (the ball will be somewhere different when the player resumes).
|
|
507
|
+
|
|
508
|
+
Hook-level debouncing (SDK ≥ 1.8.0) makes `usePersistentState` safe under bursts, but you still shouldn't persist what you'll discard on reload. Keep ephemeral state in `useState` and persist only meaningful checkpoints (final score, selected difficulty, high-score list).
|
|
509
|
+
|
|
510
|
+
```tsx
|
|
511
|
+
// ❌ Persisting ball position every frame — meaningless on reload
|
|
512
|
+
const [gameState, setGameState] = usePersistentState<GameState>('gameState', initial);
|
|
513
|
+
requestAnimationFrame(() => setGameState(advance(gameState)));
|
|
514
|
+
|
|
515
|
+
// ✅ Ephemeral for live gameplay, persisted for results
|
|
516
|
+
const [gameState, setGameState] = useState<GameState>(initial);
|
|
517
|
+
const [highScores, setHighScores] = usePersistentState<Score[]>('highScores', []);
|
|
518
|
+
// persist highScores only when a run completes
|
|
519
|
+
```
|
|
520
|
+
|
|
419
521
|
## Common Patterns
|
|
420
522
|
|
|
421
523
|
### Theme-Aware Card Component
|
|
@@ -491,7 +593,3 @@ function Settings() {
|
|
|
491
593
|
);
|
|
492
594
|
}
|
|
493
595
|
```
|
|
494
|
-
|
|
495
|
-
## See also
|
|
496
|
-
|
|
497
|
-
`AGENTS.md` — equivalent guide for non-Claude AI tools in this project.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Multi-Step FIAS Plugin Template
|
|
2
|
+
|
|
3
|
+
Canonical pattern for multi-step plugins. The important bits live in:
|
|
4
|
+
|
|
5
|
+
- `src/App.tsx` — calls `useStepNavigation('step-one', { persistKey: 'currentStep' })` **once**. Single source of truth for the current step.
|
|
6
|
+
- `src/context/AppContext.tsx` — exposes `currentStep` and `setCurrentStep` to step components, plus any domain data via `usePersistentState`.
|
|
7
|
+
- `src/steps/<step-id>/<StepName>.tsx` — step components read/write via context. They do not call `useStepNavigation` themselves.
|
|
8
|
+
|
|
9
|
+
## Why not `usePersistentState('currentStep', ...)`?
|
|
10
|
+
|
|
11
|
+
That used to look tempting, but it creates two independent step-state sources (one from `useStepNavigation`, one from `usePersistentState`). Any `useEffect` that syncs them turns into a bidirectional ping-pong loop that spams `storage_write` and flashes the UI between steps.
|
|
12
|
+
|
|
13
|
+
Since SDK 1.7.0, `useStepNavigation` handles persistence directly via `{ persistKey }`. That is the only correct way to persist step state. Domain data (form inputs, AI-generated content, user selections) can still use `usePersistentState` — just not `currentStep`.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A multi-step FIAS plugin arche",
|
|
5
|
+
"main": "src/index.tsx",
|
|
6
|
+
"archeType": "tool",
|
|
7
|
+
"tags": [],
|
|
8
|
+
"pricing": { "model": "free", "currency": "usd" },
|
|
9
|
+
"permissions": ["theme:read", "storage:sandbox"],
|
|
10
|
+
"sdk": "^1.7.0"
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>FIAS Plugin</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/index.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"start": "vite & sleep 2 && fias-dev",
|
|
7
|
+
"dev": "vite",
|
|
8
|
+
"build": "vite build",
|
|
9
|
+
"validate": "tsc --noEmit",
|
|
10
|
+
"dev:harness": "fias-dev",
|
|
11
|
+
"submit": "fias-dev submit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@fias/arche-sdk": "^1.7.0",
|
|
15
|
+
"react": "^19.0.0",
|
|
16
|
+
"react-dom": "^19.0.0"
|
|
17
|
+
},
|
|
18
|
+
"devDependencies": {
|
|
19
|
+
"@fias/plugin-dev-harness": "^1.0.0",
|
|
20
|
+
"@types/react": "^19.0.0",
|
|
21
|
+
"@types/react-dom": "^19.0.0",
|
|
22
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
23
|
+
"typescript": "^5.3.3",
|
|
24
|
+
"vite": "^6.0.0"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useFiasTheme, useStepNavigation } from '@fias/arche-sdk';
|
|
2
|
+
import { AppProvider } from './context/AppContext';
|
|
3
|
+
import { StepOne } from './steps/step-one/StepOne';
|
|
4
|
+
import { StepTwo } from './steps/step-two/StepTwo';
|
|
5
|
+
|
|
6
|
+
function AppContent() {
|
|
7
|
+
const theme = useFiasTheme();
|
|
8
|
+
|
|
9
|
+
// Single source of truth for step navigation.
|
|
10
|
+
// persistKey makes currentStep survive preview rebuilds via bridge storage.
|
|
11
|
+
// Do NOT also wrap currentStep in usePersistentState — two sources create
|
|
12
|
+
// a bidirectional useEffect sync loop that spams storage and flashes the UI.
|
|
13
|
+
const { currentStep, setCurrentStep } = useStepNavigation('step-one', {
|
|
14
|
+
persistKey: 'currentStep',
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
if (!theme) return null;
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<AppProvider currentStep={currentStep} setCurrentStep={setCurrentStep}>
|
|
21
|
+
<div
|
|
22
|
+
style={{
|
|
23
|
+
minHeight: '100vh',
|
|
24
|
+
backgroundColor: theme.colors.background,
|
|
25
|
+
color: theme.colors.text,
|
|
26
|
+
fontFamily: theme.fonts.body,
|
|
27
|
+
padding: theme.spacing.lg,
|
|
28
|
+
}}
|
|
29
|
+
>
|
|
30
|
+
{currentStep === 'step-two' ? <StepTwo /> : <StepOne />}
|
|
31
|
+
</div>
|
|
32
|
+
</AppProvider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function App() {
|
|
37
|
+
return <AppContent />;
|
|
38
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createContext, useContext, ReactNode } from 'react';
|
|
2
|
+
import { usePersistentState } from '@fias/arche-sdk';
|
|
3
|
+
|
|
4
|
+
interface AppContextValue {
|
|
5
|
+
currentStep: string | null;
|
|
6
|
+
setCurrentStep: (step: string | null) => void;
|
|
7
|
+
// Domain data — safe to persist with usePersistentState.
|
|
8
|
+
notes: string;
|
|
9
|
+
setNotes: (value: string) => void;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const AppContext = createContext<AppContextValue | null>(null);
|
|
13
|
+
|
|
14
|
+
interface AppProviderProps {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
currentStep: string | null;
|
|
17
|
+
setCurrentStep: (step: string | null) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function AppProvider({ children, currentStep, setCurrentStep }: AppProviderProps) {
|
|
21
|
+
// Step navigation comes in from useStepNavigation (in App.tsx) — the single
|
|
22
|
+
// source of truth. Only domain data lives in usePersistentState here.
|
|
23
|
+
const [notes, setNotes] = usePersistentState<string>('notes', '');
|
|
24
|
+
|
|
25
|
+
return (
|
|
26
|
+
<AppContext.Provider value={{ currentStep, setCurrentStep, notes, setNotes }}>
|
|
27
|
+
{children}
|
|
28
|
+
</AppContext.Provider>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function useAppContext(): AppContextValue {
|
|
33
|
+
const ctx = useContext(AppContext);
|
|
34
|
+
if (!ctx) throw new Error('useAppContext must be used within AppProvider');
|
|
35
|
+
return ctx;
|
|
36
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import ReactDOM from 'react-dom/client';
|
|
3
|
+
import { FiasProvider } from '@fias/arche-sdk';
|
|
4
|
+
import { App } from './App';
|
|
5
|
+
|
|
6
|
+
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
7
|
+
<React.StrictMode>
|
|
8
|
+
<FiasProvider>
|
|
9
|
+
<App />
|
|
10
|
+
</FiasProvider>
|
|
11
|
+
</React.StrictMode>,
|
|
12
|
+
);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { useFiasTheme } from '@fias/arche-sdk';
|
|
2
|
+
import { useAppContext } from '../../context/AppContext';
|
|
3
|
+
|
|
4
|
+
export function StepOne() {
|
|
5
|
+
const theme = useFiasTheme();
|
|
6
|
+
const { notes, setNotes, setCurrentStep } = useAppContext();
|
|
7
|
+
|
|
8
|
+
if (!theme) return null;
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div>
|
|
12
|
+
<h1 style={{ fontFamily: theme.fonts.heading }}>Step 1 — Notes</h1>
|
|
13
|
+
<textarea
|
|
14
|
+
value={notes}
|
|
15
|
+
onChange={(e) => setNotes(e.target.value)}
|
|
16
|
+
rows={6}
|
|
17
|
+
style={{
|
|
18
|
+
width: '100%',
|
|
19
|
+
padding: theme.spacing.sm,
|
|
20
|
+
backgroundColor: theme.colors.surface,
|
|
21
|
+
color: theme.colors.text,
|
|
22
|
+
border: `${theme.components.borderWidth} solid ${theme.colors.border}`,
|
|
23
|
+
borderRadius: theme.components.inputRadius,
|
|
24
|
+
}}
|
|
25
|
+
/>
|
|
26
|
+
<button
|
|
27
|
+
onClick={() => setCurrentStep('step-two')}
|
|
28
|
+
disabled={notes.trim().length === 0}
|
|
29
|
+
style={{
|
|
30
|
+
marginTop: theme.spacing.md,
|
|
31
|
+
padding: `${theme.spacing.sm} ${theme.spacing.lg}`,
|
|
32
|
+
backgroundColor: theme.colors.primary,
|
|
33
|
+
color: theme.colors.primaryText,
|
|
34
|
+
border: 'none',
|
|
35
|
+
borderRadius: theme.components.buttonRadius,
|
|
36
|
+
cursor: notes.trim().length === 0 ? 'not-allowed' : 'pointer',
|
|
37
|
+
}}
|
|
38
|
+
>
|
|
39
|
+
Next
|
|
40
|
+
</button>
|
|
41
|
+
</div>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { useFiasTheme } from '@fias/arche-sdk';
|
|
2
|
+
import { useAppContext } from '../../context/AppContext';
|
|
3
|
+
|
|
4
|
+
export function StepTwo() {
|
|
5
|
+
const theme = useFiasTheme();
|
|
6
|
+
const { notes, setCurrentStep } = useAppContext();
|
|
7
|
+
|
|
8
|
+
if (!theme) return null;
|
|
9
|
+
|
|
10
|
+
return (
|
|
11
|
+
<div>
|
|
12
|
+
<h1 style={{ fontFamily: theme.fonts.heading }}>Step 2 — Review</h1>
|
|
13
|
+
<pre
|
|
14
|
+
style={{
|
|
15
|
+
padding: theme.spacing.md,
|
|
16
|
+
backgroundColor: theme.colors.surface,
|
|
17
|
+
color: theme.colors.text,
|
|
18
|
+
borderRadius: theme.components.cardRadius,
|
|
19
|
+
whiteSpace: 'pre-wrap',
|
|
20
|
+
}}
|
|
21
|
+
>
|
|
22
|
+
{notes}
|
|
23
|
+
</pre>
|
|
24
|
+
<button
|
|
25
|
+
onClick={() => setCurrentStep('step-one')}
|
|
26
|
+
style={{
|
|
27
|
+
marginTop: theme.spacing.md,
|
|
28
|
+
padding: `${theme.spacing.sm} ${theme.spacing.lg}`,
|
|
29
|
+
backgroundColor: theme.colors.surface,
|
|
30
|
+
color: theme.colors.text,
|
|
31
|
+
border: `${theme.components.borderWidth} solid ${theme.colors.border}`,
|
|
32
|
+
borderRadius: theme.components.buttonRadius,
|
|
33
|
+
cursor: 'pointer',
|
|
34
|
+
}}
|
|
35
|
+
>
|
|
36
|
+
Back
|
|
37
|
+
</button>
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"noEmit": true
|
|
15
|
+
},
|
|
16
|
+
"include": ["src"]
|
|
17
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
|
|
4
|
+
export default defineConfig({
|
|
5
|
+
plugins: [react()],
|
|
6
|
+
build: {
|
|
7
|
+
outDir: 'dist',
|
|
8
|
+
rollupOptions: {
|
|
9
|
+
input: 'index.html',
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
server: {
|
|
13
|
+
port: 3100,
|
|
14
|
+
cors: true,
|
|
15
|
+
headers: { 'Access-Control-Allow-Origin': '*' },
|
|
16
|
+
hmr: { host: 'localhost' },
|
|
17
|
+
},
|
|
18
|
+
});
|