@dzhechkov/keysarium 1.3.0 → 1.5.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/README.md +70 -10
- package/bin/cli.js +0 -0
- package/package.json +5 -3
- package/src/utils.js +1 -1
- package/templates/.claude/commands/analyst-manual.md +38 -0
- package/templates/.claude/commands/brain-export.md +2 -0
- package/templates/.claude/commands/bto-benchmark.md +302 -0
- package/templates/.claude/commands/casarium.md +55 -4
- package/templates/.claude/commands/feature-adr.md +8 -0
- package/templates/.claude/commands/harvest.md +28 -46
- package/templates/.claude/rules/trust-tiers.md +6 -0
- package/templates/.claude/shards/feature-adr.shard.md +64 -10
- package/templates/.claude/shards/phase-ai-factory.shard.md +109 -0
- package/templates/.claude/skills/ai-factory-mapper/README.md +90 -0
- package/templates/.claude/skills/ai-factory-mapper/SKILL.md +309 -0
- package/templates/.claude/skills/ai-factory-mapper/assets/report-template.md +123 -0
- package/templates/.claude/skills/ai-factory-mapper/references/catalog.md +230 -0
- package/templates/.claude/skills/ai-factory-mapper/references/coverage-formula.md +219 -0
- package/templates/.claude/skills/ai-factory-mapper/references/decomposition-checklist.md +183 -0
- package/templates/.claude/skills/ai-factory-mapper/references/pattern-library.md +312 -0
- package/templates/.claude/skills/ai-factory-mapper/scripts/build_docx_report.js +423 -0
- package/templates/.claude/skills/analyst-manual-full/SKILL.md +286 -0
- package/templates/.claude/skills/edu-site-generator/SKILL.md +254 -0
- package/templates/.claude/skills/edu-site-generator/examples/sample-course-structure.md +116 -0
- package/templates/.claude/skills/edu-site-generator/modules/00-content-analysis.md +84 -0
- package/templates/.claude/skills/edu-site-generator/modules/01-course-structure.md +75 -0
- package/templates/.claude/skills/edu-site-generator/modules/02-data-generation.md +186 -0
- package/templates/.claude/skills/edu-site-generator/modules/03-scaffold.md +249 -0
- package/templates/.claude/skills/edu-site-generator/modules/04-components.md +130 -0
- package/templates/.claude/skills/edu-site-generator/modules/05-gamification.md +163 -0
- package/templates/.claude/skills/edu-site-generator/modules/06-deploy.md +88 -0
- package/templates/.claude/skills/edu-site-generator/modules/07-verification.md +97 -0
- package/templates/.claude/skills/edu-site-generator/references/component-templates.md +436 -0
- package/templates/.claude/skills/edu-site-generator/references/data-schemas.md +164 -0
- package/templates/.claude/skills/edu-site-generator/references/exercise-catalog.md +110 -0
- package/templates/.claude/skills/edu-site-generator/references/tech-stack.md +69 -0
- package/templates/.claude/skills/feature-adr/SKILL.md +245 -59
- package/templates/.claude/skills/feature-adr/modules/03-adr.md +43 -0
- package/templates/.claude/skills/feature-adr/modules/03.5-ideation-swarm.md +180 -0
- package/templates/.claude/skills/feature-adr/modules/06-implementation-plan.md +56 -7
- package/templates/.claude/skills/feature-adr/modules/08-qe.md +87 -23
- package/templates/.claude/skills/feature-adr/modules/09-fleet-qe.md +305 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/README.md +143 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/brutal-honesty-review.md +240 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/chaos-engineering-resilience.md +163 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/code-goal-planner.md +446 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/enterprise-integration-testing.md +111 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/mutation-testing.md +236 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/performance-testing.md +315 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/qcsd-ideation-swarm.md +126 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/qcsd-production-swarm.md +108 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/qe-coverage-analysis.md +193 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/qe-requirements-validation.md +254 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/regression-testing.md +233 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/risk-based-testing.md +212 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/security-testing.md +311 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/shift-left-testing.md +231 -0
- package/templates/.claude/skills/feature-adr/references/agentic-qe/tdd-london-chicago.md +249 -0
- package/templates/.claude/skills/knowledge-extractor/SKILL.md +254 -0
- package/templates/.claude/skills/knowledge-extractor/modules/01-extract.md +188 -0
- package/templates/.claude/skills/knowledge-extractor/modules/02-classify.md +99 -0
- package/templates/.claude/skills/knowledge-extractor/modules/03-gate.md +150 -0
- package/templates/.claude/skills/knowledge-extractor/modules/04-integrate.md +159 -0
- package/templates/.claude/skills/knowledge-extractor/references/artifact-categories.md +100 -0
- package/templates/.claude/skills/knowledge-extractor/references/maturity-model.md +94 -0
- package/templates/.claude/skills/knowledge-extractor/references/quality-gates.md +98 -0
- package/templates/.claude/skills/knowledge-extractor/templates/artifact-card.md +63 -0
- package/templates/.claude/skills/knowledge-extractor/templates/harvest-report.md +102 -0
- package/templates/.claude/skills/transcript-site-generator/SKILL.md +287 -0
- package/templates/.claude/skills/transcript-site-generator/examples/sample-text-only-site.md +123 -0
- package/templates/.claude/skills/transcript-site-generator/examples/sample-transcript-site.md +134 -0
- package/templates/.claude/skills/transcript-site-generator/modules/00-input-analysis.md +132 -0
- package/templates/.claude/skills/transcript-site-generator/modules/01-content-parsing.md +117 -0
- package/templates/.claude/skills/transcript-site-generator/modules/02-site-generation.md +293 -0
- package/templates/.claude/skills/transcript-site-generator/modules/03-interactivity.md +406 -0
- package/templates/.claude/skills/transcript-site-generator/modules/04-deploy.md +130 -0
- package/templates/.claude/skills/transcript-site-generator/modules/05-verification.md +155 -0
- package/templates/.claude/skills/transcript-site-generator/references/component-catalog.md +86 -0
- package/templates/.claude/skills/transcript-site-generator/references/data-schemas.md +123 -0
- package/templates/.claude/skills/transcript-site-generator/references/tech-stack.md +132 -0
- package/templates/CLAUDE.md +30 -9
- package/templates/docs/continuous-learning-guide.md +622 -0
- package/templates/docs/edu-site-generator/01-deployment-guide.md +911 -0
- package/templates/docs/edu-site-generator/02-admin-guide.md +901 -0
- package/templates/docs/edu-site-generator/03-user-guide.md +1008 -0
- package/templates/docs/edu-site-generator/04-infrastructure-requirements.md +489 -0
- package/templates/docs/edu-site-generator/05-architecture.md +902 -0
- package/templates/docs/edu-site-generator/06-user-flows.md +759 -0
- package/templates/docs/improvement-directions.md +8 -4
- package/templates/docs/keysarium-core/02-functionality.md +3 -1
- package/templates/docs/skills-bto/01-deployment-guide.md +1 -1
- package/templates/docs/skills-bto/02-admin-guide.md +4 -1
- package/templates/docs/skills-bto/03-user-guide.md +4 -3
- package/templates/docs/skills-bto/04-infrastructure-requirements.md +2 -1
- package/templates/docs/skills-bto/05-architecture.md +11 -11
- package/templates/docs/skills-bto/06-user-flows.md +2 -1
- package/templates/docs/transcript-site-generator/01-deployment-guide.md +1042 -0
- package/templates/docs/transcript-site-generator/02-admin-guide.md +918 -0
- package/templates/docs/transcript-site-generator/03-user-guide.md +925 -0
- package/templates/docs/transcript-site-generator/04-infrastructure-requirements.md +482 -0
- package/templates/docs/transcript-site-generator/05-architecture.md +1068 -0
- package/templates/docs/transcript-site-generator/06-user-flows.md +855 -0
- package/templates/lib/dream-engine.md +1 -1
- package/templates/lib/memory-protocol.md +6 -2
- package/templates/lib/reward-tracker.md +6 -3
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
# Component Templates Reference
|
|
2
|
+
|
|
3
|
+
Code templates for all components. Use these as the basis when generating components in Step 4.
|
|
4
|
+
|
|
5
|
+
## Layout Components
|
|
6
|
+
|
|
7
|
+
### SectionLayout.jsx
|
|
8
|
+
```jsx
|
|
9
|
+
import { useAppStore } from '../../store/useAppStore'
|
|
10
|
+
|
|
11
|
+
export default function SectionLayout({ id, icon, title, description, theory, children }) {
|
|
12
|
+
const completed = useAppStore((s) => s.completedSections[id])
|
|
13
|
+
const score = useAppStore((s) => s.sectionScores[id])
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<section id={`section-${id}`} className="py-12 border-t border-border">
|
|
17
|
+
<div className="max-w-3xl mx-auto px-4">
|
|
18
|
+
<div className="flex items-start gap-3 mb-4">
|
|
19
|
+
<span className="text-2xl">{icon}</span>
|
|
20
|
+
<div className="flex-1">
|
|
21
|
+
<h2 className="text-xl font-bold text-text">{title}</h2>
|
|
22
|
+
<p className="text-text-muted text-sm mt-1">{description}</p>
|
|
23
|
+
</div>
|
|
24
|
+
{completed && (
|
|
25
|
+
<span className="px-3 py-1 bg-success-500/10 text-success-600 dark:text-success-400 text-xs font-medium rounded-full">
|
|
26
|
+
{score}%
|
|
27
|
+
</span>
|
|
28
|
+
)}
|
|
29
|
+
</div>
|
|
30
|
+
|
|
31
|
+
{theory && (
|
|
32
|
+
<div className="mb-8 text-text-secondary text-sm leading-relaxed whitespace-pre-line">
|
|
33
|
+
{theory}
|
|
34
|
+
</div>
|
|
35
|
+
)}
|
|
36
|
+
|
|
37
|
+
{!completed && children}
|
|
38
|
+
{completed && (
|
|
39
|
+
<p className="text-sm text-success-600 dark:text-success-400 font-medium">
|
|
40
|
+
Section completed with {score}% score
|
|
41
|
+
</p>
|
|
42
|
+
)}
|
|
43
|
+
</div>
|
|
44
|
+
</section>
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Header.jsx
|
|
50
|
+
```jsx
|
|
51
|
+
import { useAppStore } from '../../store/useAppStore'
|
|
52
|
+
import { useProgress } from '../../hooks/useProgress'
|
|
53
|
+
|
|
54
|
+
export default function Header() {
|
|
55
|
+
const { percentage, totalScore } = useProgress()
|
|
56
|
+
const toggleNav = useAppStore((s) => s.toggleNav)
|
|
57
|
+
const darkMode = useAppStore((s) => s.darkMode)
|
|
58
|
+
const toggleDarkMode = useAppStore((s) => s.toggleDarkMode)
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<header className="sticky top-0 z-50 bg-card/80 backdrop-blur-sm border-b border-border">
|
|
62
|
+
<div className="max-w-6xl mx-auto px-4 h-14 flex items-center justify-between">
|
|
63
|
+
<div className="flex items-center gap-3">
|
|
64
|
+
<button onClick={toggleNav} className="md:hidden p-2">
|
|
65
|
+
{/* hamburger icon */}
|
|
66
|
+
</button>
|
|
67
|
+
<a href="#/" className="font-bold text-text no-underline">
|
|
68
|
+
{/* Course title */}
|
|
69
|
+
</a>
|
|
70
|
+
</div>
|
|
71
|
+
<div className="flex items-center gap-4">
|
|
72
|
+
<span className="text-sm text-text-muted">{totalScore} pts</span>
|
|
73
|
+
<span className="text-sm text-text-muted">{percentage}%</span>
|
|
74
|
+
<button onClick={toggleDarkMode}>
|
|
75
|
+
{/* sun/moon icon */}
|
|
76
|
+
</button>
|
|
77
|
+
<a href="#/final-test" className="px-3 py-1.5 bg-primary-500 text-white text-sm rounded-lg">
|
|
78
|
+
Final Test
|
|
79
|
+
</a>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</header>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Navigation.jsx
|
|
88
|
+
|
|
89
|
+
**CRITICAL:** Use `scrollIntoView()` via buttons, NOT `<a href="#section-...">`:
|
|
90
|
+
```jsx
|
|
91
|
+
function scrollTo(id) {
|
|
92
|
+
document.getElementById(`section-${id}`)?.scrollIntoView({ behavior: 'smooth' })
|
|
93
|
+
// Close nav on mobile
|
|
94
|
+
setNavOpen(false)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Render:
|
|
98
|
+
<button onClick={() => scrollTo(section.id)}>
|
|
99
|
+
{section.icon} {section.shortTitle}
|
|
100
|
+
</button>
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Flashcard.jsx
|
|
104
|
+
```jsx
|
|
105
|
+
import { useState } from 'react'
|
|
106
|
+
|
|
107
|
+
export default function Flashcard({ cards, onComplete }) {
|
|
108
|
+
// Toggle state: tracks which side is showing (true = back, false = front)
|
|
109
|
+
const [flipped, setFlipped] = useState({})
|
|
110
|
+
// Viewed state: tracks which cards have been seen at least once (for completion)
|
|
111
|
+
const [viewed, setViewed] = useState({})
|
|
112
|
+
|
|
113
|
+
function handleFlip(index) {
|
|
114
|
+
// Toggle the card back and forth
|
|
115
|
+
setFlipped(prev => ({ ...prev, [index]: !prev[index] }))
|
|
116
|
+
|
|
117
|
+
// Track first-time views for completion scoring
|
|
118
|
+
if (!viewed[index]) {
|
|
119
|
+
const nextViewed = { ...viewed, [index]: true }
|
|
120
|
+
setViewed(nextViewed)
|
|
121
|
+
if (Object.keys(nextViewed).length === cards.length) {
|
|
122
|
+
onComplete(100)
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<div className="grid gap-4 sm:grid-cols-2">
|
|
129
|
+
{cards.map((card, i) => (
|
|
130
|
+
<button
|
|
131
|
+
key={i}
|
|
132
|
+
onClick={() => handleFlip(i)}
|
|
133
|
+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); handleFlip(i) } }}
|
|
134
|
+
aria-label={flipped[i] ? 'Flip card to front' : 'Flip card to back'}
|
|
135
|
+
className={`p-6 rounded-xl border-2 min-h-[120px] transition-all duration-300 text-left cursor-pointer ${
|
|
136
|
+
flipped[i]
|
|
137
|
+
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
|
|
138
|
+
: 'border-border hover:border-primary-400'
|
|
139
|
+
}`}
|
|
140
|
+
>
|
|
141
|
+
<p className={`text-sm ${flipped[i] ? 'text-primary-700 dark:text-primary-300' : 'text-text font-medium'}`}>
|
|
142
|
+
{flipped[i] ? card.back : card.front}
|
|
143
|
+
</p>
|
|
144
|
+
<p className="text-xs text-text-muted mt-2">
|
|
145
|
+
{viewed[i] ? '✓ Viewed — click to flip' : 'Click to flip'}
|
|
146
|
+
</p>
|
|
147
|
+
</button>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### CommandBuilder.jsx
|
|
155
|
+
```jsx
|
|
156
|
+
import { useState } from 'react'
|
|
157
|
+
|
|
158
|
+
export default function CommandBuilder({ instruction, parts, correctCommand, hints, onComplete }) {
|
|
159
|
+
const [built, setBuilt] = useState([])
|
|
160
|
+
const [checked, setChecked] = useState(false)
|
|
161
|
+
const [correct, setCorrect] = useState(false)
|
|
162
|
+
const [hintIndex, setHintIndex] = useState(0)
|
|
163
|
+
|
|
164
|
+
const availableParts = parts.filter((_, i) => !built.includes(i))
|
|
165
|
+
const builtCommand = built.map((i) => parts[i]).join('')
|
|
166
|
+
|
|
167
|
+
function handleAdd(index) {
|
|
168
|
+
if (checked) return
|
|
169
|
+
setBuilt([...built, index])
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function handleRemove(pos) {
|
|
173
|
+
if (checked) return
|
|
174
|
+
setBuilt(built.filter((_, i) => i !== pos))
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function handleCheck() {
|
|
178
|
+
if (builtCommand === correctCommand) {
|
|
179
|
+
setCorrect(true)
|
|
180
|
+
setChecked(true)
|
|
181
|
+
onComplete(100)
|
|
182
|
+
} else {
|
|
183
|
+
setHintIndex((i) => Math.min(i + 1, hints.length - 1))
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function handleReset() {
|
|
188
|
+
setBuilt([])
|
|
189
|
+
setChecked(false)
|
|
190
|
+
setCorrect(false)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return (
|
|
194
|
+
<div className="space-y-4">
|
|
195
|
+
<p className="text-sm text-text-secondary">{instruction}</p>
|
|
196
|
+
|
|
197
|
+
<div className="bg-bg-secondary rounded-lg p-3 font-mono text-sm min-h-[44px]">
|
|
198
|
+
<span className="text-text-muted">$ </span>
|
|
199
|
+
{built.map((partIndex, pos) => (
|
|
200
|
+
<button key={pos} onClick={() => handleRemove(pos)}
|
|
201
|
+
className="text-primary-600 dark:text-primary-400 hover:line-through">
|
|
202
|
+
{parts[partIndex]}
|
|
203
|
+
</button>
|
|
204
|
+
))}
|
|
205
|
+
{built.length === 0 && <span className="text-text-muted">click parts below...</span>}
|
|
206
|
+
</div>
|
|
207
|
+
|
|
208
|
+
<div className="flex flex-wrap gap-2">
|
|
209
|
+
{availableParts.map((part, i) => {
|
|
210
|
+
const originalIndex = parts.indexOf(part)
|
|
211
|
+
return (
|
|
212
|
+
<button key={i} onClick={() => handleAdd(originalIndex)}
|
|
213
|
+
className="px-3 py-1.5 bg-card border border-border rounded-lg text-sm hover:border-primary-400">
|
|
214
|
+
{part}
|
|
215
|
+
</button>
|
|
216
|
+
)
|
|
217
|
+
})}
|
|
218
|
+
</div>
|
|
219
|
+
|
|
220
|
+
{hintIndex > 0 && !correct && (
|
|
221
|
+
<p className="text-sm text-amber-600 dark:text-amber-400 bg-amber-50 dark:bg-amber-900/20 rounded-lg p-3">
|
|
222
|
+
{hints[hintIndex - 1]}
|
|
223
|
+
</p>
|
|
224
|
+
)}
|
|
225
|
+
|
|
226
|
+
{correct && (
|
|
227
|
+
<p className="text-sm text-success-600 dark:text-success-400 bg-success-500/10 rounded-lg p-3">
|
|
228
|
+
Correct!
|
|
229
|
+
</p>
|
|
230
|
+
)}
|
|
231
|
+
|
|
232
|
+
<div className="flex gap-2">
|
|
233
|
+
{!checked && built.length > 0 && (
|
|
234
|
+
<button onClick={handleCheck}
|
|
235
|
+
className="px-6 py-2 bg-primary-500 text-white rounded-lg hover:bg-primary-600">
|
|
236
|
+
Check
|
|
237
|
+
</button>
|
|
238
|
+
)}
|
|
239
|
+
{built.length > 0 && !correct && (
|
|
240
|
+
<button onClick={handleReset}
|
|
241
|
+
className="px-6 py-2 border border-border text-text-secondary rounded-lg hover:bg-bg-hover">
|
|
242
|
+
Reset
|
|
243
|
+
</button>
|
|
244
|
+
)}
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
)
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### App.jsx Template
|
|
252
|
+
```jsx
|
|
253
|
+
import { HashRouter, Routes, Route } from 'react-router-dom'
|
|
254
|
+
import Header from './components/layout/Header'
|
|
255
|
+
import Footer from './components/layout/Footer'
|
|
256
|
+
import ProgressBar from './components/layout/ProgressBar'
|
|
257
|
+
import Toast from './components/common/Toast'
|
|
258
|
+
import HomePage from './pages/HomePage'
|
|
259
|
+
import FinalTestPage from './pages/FinalTestPage'
|
|
260
|
+
import { useAchievements } from './hooks/useAchievements'
|
|
261
|
+
|
|
262
|
+
function AppContent() {
|
|
263
|
+
useAchievements()
|
|
264
|
+
return (
|
|
265
|
+
<div className="min-h-screen flex flex-col bg-surface">
|
|
266
|
+
<Header />
|
|
267
|
+
<ProgressBar />
|
|
268
|
+
<div className="flex-1">
|
|
269
|
+
<Routes>
|
|
270
|
+
<Route path="/" element={<HomePage />} />
|
|
271
|
+
<Route path="/final-test" element={<FinalTestPage />} />
|
|
272
|
+
</Routes>
|
|
273
|
+
</div>
|
|
274
|
+
<Footer />
|
|
275
|
+
<Toast />
|
|
276
|
+
</div>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export default function App() {
|
|
281
|
+
return (
|
|
282
|
+
<HashRouter>
|
|
283
|
+
<AppContent />
|
|
284
|
+
</HashRouter>
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Toast.jsx Template
|
|
290
|
+
```jsx
|
|
291
|
+
import { useEffect } from 'react'
|
|
292
|
+
import { useAppStore } from '../../store/useAppStore'
|
|
293
|
+
|
|
294
|
+
export default function Toast() {
|
|
295
|
+
const toast = useAppStore((s) => s.toast)
|
|
296
|
+
const clearToast = useAppStore((s) => s.clearToast)
|
|
297
|
+
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
if (toast) {
|
|
300
|
+
const timer = setTimeout(clearToast, 4000)
|
|
301
|
+
return () => clearTimeout(timer)
|
|
302
|
+
}
|
|
303
|
+
}, [toast])
|
|
304
|
+
|
|
305
|
+
if (!toast) return null
|
|
306
|
+
|
|
307
|
+
return (
|
|
308
|
+
<div className="fixed top-20 right-4 z-50 animate-fade-in-up">
|
|
309
|
+
<div className="bg-card border border-border rounded-xl shadow-lg p-4 max-w-sm">
|
|
310
|
+
<div className="flex items-start gap-3">
|
|
311
|
+
<span className="text-2xl">{toast.icon}</span>
|
|
312
|
+
<div>
|
|
313
|
+
<p className="font-semibold text-text text-sm">{toast.title}</p>
|
|
314
|
+
<p className="text-xs text-text-muted mt-1">{toast.description}</p>
|
|
315
|
+
</div>
|
|
316
|
+
</div>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
)
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## Interactive Components
|
|
324
|
+
|
|
325
|
+
### Quiz.jsx — CRITICAL SCORING PATTERN
|
|
326
|
+
```jsx
|
|
327
|
+
// handleSelect increments correctCount
|
|
328
|
+
function handleSelect(index) {
|
|
329
|
+
if (selected !== null) return
|
|
330
|
+
setSelected(index)
|
|
331
|
+
if (index === question.correctAnswer) {
|
|
332
|
+
setCorrectCount((c) => c + 1) // Already counted here
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// handleNext uses correctCount DIRECTLY (no +1 compensation)
|
|
337
|
+
function handleNext() {
|
|
338
|
+
if (currentIndex < questions.length - 1) {
|
|
339
|
+
setCurrentIndex((i) => i + 1)
|
|
340
|
+
setSelected(null)
|
|
341
|
+
} else {
|
|
342
|
+
setFinished(true)
|
|
343
|
+
const score = Math.round((correctCount / questions.length) * 100) // NOT correctCount + 1
|
|
344
|
+
onComplete(score)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Finished view also uses correctCount directly
|
|
349
|
+
if (finished) {
|
|
350
|
+
const finalCorrect = correctCount // NOT correctCount + (selected === ...)
|
|
351
|
+
// ...
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### ScenarioGame.jsx — SAME SCORING PATTERN
|
|
356
|
+
```jsx
|
|
357
|
+
// handleSelect increments score
|
|
358
|
+
function handleSelect(option) {
|
|
359
|
+
if (selectedOption) return
|
|
360
|
+
setSelectedOption(option)
|
|
361
|
+
if (option.result === 'positive') {
|
|
362
|
+
setScore((s) => s + 1) // Already counted here
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// handleNext uses score DIRECTLY
|
|
367
|
+
function handleNext() {
|
|
368
|
+
// ...
|
|
369
|
+
setFinished(true)
|
|
370
|
+
const finalScore = score // NOT score + (selectedOption?.result === 'positive' ? 1 : 0)
|
|
371
|
+
const percentage = Math.round((finalScore / steps.length) * 100)
|
|
372
|
+
onComplete(percentage)
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
### DragOrder.jsx — Shuffle Pattern
|
|
377
|
+
```jsx
|
|
378
|
+
// Fisher-Yates shuffle on mount
|
|
379
|
+
const [items, setItems] = useState(() => {
|
|
380
|
+
const shuffled = [...initialItems]
|
|
381
|
+
for (let i = shuffled.length - 1; i > 0; i--) {
|
|
382
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
383
|
+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]
|
|
384
|
+
}
|
|
385
|
+
return shuffled
|
|
386
|
+
})
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
### MatchingExercise.jsx — Shuffle Right Side
|
|
390
|
+
```jsx
|
|
391
|
+
const [shuffledRight] = useState(() => {
|
|
392
|
+
const arr = items.map(i => i.right)
|
|
393
|
+
// Fisher-Yates shuffle
|
|
394
|
+
for (let i = arr.length - 1; i > 0; i--) {
|
|
395
|
+
const j = Math.floor(Math.random() * (i + 1));
|
|
396
|
+
[arr[i], arr[j]] = [arr[j], arr[i]]
|
|
397
|
+
}
|
|
398
|
+
return arr
|
|
399
|
+
})
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
## Section Component Template
|
|
403
|
+
|
|
404
|
+
```jsx
|
|
405
|
+
import SectionLayout from '../layout/SectionLayout'
|
|
406
|
+
import Quiz from '../interactive/Quiz' // or appropriate component
|
|
407
|
+
import { useAppStore } from '../../store/useAppStore'
|
|
408
|
+
import { SECTIONS } from '../../data/sections'
|
|
409
|
+
import { quizQuestions } from '../../data/quizQuestions' // or exercises
|
|
410
|
+
|
|
411
|
+
const section = SECTIONS[0] // adjust index
|
|
412
|
+
|
|
413
|
+
export default function Section1() {
|
|
414
|
+
const markComplete = useAppStore((s) => s.markSectionComplete)
|
|
415
|
+
|
|
416
|
+
return (
|
|
417
|
+
<SectionLayout {...section} theory={section.theory}>
|
|
418
|
+
<Quiz
|
|
419
|
+
questions={quizQuestions[section.id]}
|
|
420
|
+
onComplete={(score) => markComplete(section.id, score)}
|
|
421
|
+
/>
|
|
422
|
+
</SectionLayout>
|
|
423
|
+
)
|
|
424
|
+
}
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
## Component-to-Data Mapping
|
|
428
|
+
|
|
429
|
+
| Component | Data Source | Props |
|
|
430
|
+
|-----------|-----------|-------|
|
|
431
|
+
| Quiz | `quizQuestions[sectionId]` | `questions, onComplete` |
|
|
432
|
+
| Flashcard | `flashcardData[sectionId]` | `cards, onComplete` |
|
|
433
|
+
| MatchingExercise | `matchingData[sectionId]` | `items, onComplete` |
|
|
434
|
+
| DragOrder | `dragOrderData[sectionId]` or `orderingData[sectionId]` | `instruction, items, correctOrder, onComplete` |
|
|
435
|
+
| CommandBuilder | `commandBuilderData[sectionId]` | `instruction, parts, correctCommand, hints, onComplete` |
|
|
436
|
+
| ScenarioGame | `simulationData[sectionId]` or `scenarioData[sectionId]` | `title, scenario, steps, onComplete` |
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Data Schemas Reference
|
|
2
|
+
|
|
3
|
+
All data files live in `src/data/`. Content is data-driven — never hardcode text in components.
|
|
4
|
+
|
|
5
|
+
## sections.js
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
// Section definition
|
|
9
|
+
interface Section {
|
|
10
|
+
id: string // kebab-case, unique, 2-4 words
|
|
11
|
+
order: number // 1-based sequential
|
|
12
|
+
title: string // "N. Full Title" (with order prefix)
|
|
13
|
+
shortTitle: string // 2-3 words for sidebar
|
|
14
|
+
description: string // One-line summary
|
|
15
|
+
icon: string // Single emoji
|
|
16
|
+
interactiveType: InteractiveType
|
|
17
|
+
theory: string // Multi-paragraph, use \n\n for breaks, **bold** for emphasis
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type InteractiveType =
|
|
21
|
+
| 'quiz' // -> Quiz.jsx
|
|
22
|
+
| 'flashcards' // -> Flashcard.jsx
|
|
23
|
+
| 'matching' // -> MatchingExercise.jsx
|
|
24
|
+
| 'drag-and-drop' // -> DragOrder.jsx
|
|
25
|
+
| 'ordering' // -> DragOrder.jsx (alias)
|
|
26
|
+
| 'builder' // -> CommandBuilder.jsx
|
|
27
|
+
| 'simulation' // -> ScenarioGame.jsx
|
|
28
|
+
| 'scenario' // -> ScenarioGame.jsx (alias)
|
|
29
|
+
|
|
30
|
+
// Exports
|
|
31
|
+
export const SECTIONS: Section[]
|
|
32
|
+
export const TOTAL_SECTIONS: number // = SECTIONS.length
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## quizQuestions.js
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// Per-section quiz questions
|
|
39
|
+
interface QuizQuestion {
|
|
40
|
+
id: string // unique, e.g. "section-id-q1"
|
|
41
|
+
question: string // The question text
|
|
42
|
+
options: string[] // 4 options (array of 4 strings)
|
|
43
|
+
correctAnswer: number // Index (0-3) of the correct option
|
|
44
|
+
explanation: string // Why the correct answer is correct
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Final test question (one per section)
|
|
48
|
+
interface FinalTestQuestion {
|
|
49
|
+
id: string // unique, e.g. "final-section-id"
|
|
50
|
+
sectionId: string // Must match a section.id
|
|
51
|
+
question: string
|
|
52
|
+
options: string[] // 4 options
|
|
53
|
+
correctAnswer: number // Index (0-3)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Exports
|
|
57
|
+
export const quizQuestions: Record<string, QuizQuestion[]>
|
|
58
|
+
export const finalTestQuestions: FinalTestQuestion[]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## exercises.js
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
// Flashcard data
|
|
65
|
+
interface FlashcardItem {
|
|
66
|
+
front: string // Question/term side
|
|
67
|
+
back: string // Answer/definition side
|
|
68
|
+
}
|
|
69
|
+
export const flashcardData: Record<string, FlashcardItem[]>
|
|
70
|
+
// 4-8 cards per section
|
|
71
|
+
|
|
72
|
+
// Matching data
|
|
73
|
+
interface MatchingItem {
|
|
74
|
+
left: string // Left side (stays in order)
|
|
75
|
+
right: string // Right side (shuffled)
|
|
76
|
+
}
|
|
77
|
+
export const matchingData: Record<string, MatchingItem[]>
|
|
78
|
+
// 4-6 pairs per section
|
|
79
|
+
|
|
80
|
+
// Drag order data
|
|
81
|
+
interface DragOrderItem {
|
|
82
|
+
id: string // Unique within this exercise
|
|
83
|
+
label: string // Display text
|
|
84
|
+
}
|
|
85
|
+
interface DragOrderExercise {
|
|
86
|
+
instruction: string // What to arrange
|
|
87
|
+
items: DragOrderItem[] // Items to order
|
|
88
|
+
correctOrder: string[] // Array of item IDs in correct order
|
|
89
|
+
}
|
|
90
|
+
export const dragOrderData: Record<string, DragOrderExercise>
|
|
91
|
+
export const orderingData: Record<string, DragOrderExercise> // alias
|
|
92
|
+
|
|
93
|
+
// Command builder data
|
|
94
|
+
interface CommandBuilderExercise {
|
|
95
|
+
instruction: string // What command to build
|
|
96
|
+
parts: string[] // Command parts (including spaces)
|
|
97
|
+
correctCommand: string // Expected assembled command
|
|
98
|
+
hints: string[] // Progressive hints
|
|
99
|
+
}
|
|
100
|
+
export const commandBuilderData: Record<string, CommandBuilderExercise>
|
|
101
|
+
|
|
102
|
+
// Scenario/Simulation data
|
|
103
|
+
interface ScenarioOption {
|
|
104
|
+
id: string // e.g. 'a', 'b', 'c'
|
|
105
|
+
text: string // Option display text
|
|
106
|
+
result: 'positive' | 'neutral' | 'negative'
|
|
107
|
+
feedback: string // Feedback after selection
|
|
108
|
+
}
|
|
109
|
+
interface ScenarioStep {
|
|
110
|
+
id: string // e.g. 'step1'
|
|
111
|
+
description: string // Situation description
|
|
112
|
+
options: ScenarioOption[] // 2-3 options
|
|
113
|
+
}
|
|
114
|
+
interface ScenarioExercise {
|
|
115
|
+
title: string
|
|
116
|
+
scenario: string // Context/setup text
|
|
117
|
+
steps: ScenarioStep[] // 3-5 steps
|
|
118
|
+
}
|
|
119
|
+
export const simulationData: Record<string, ScenarioExercise>
|
|
120
|
+
export const scenarioData: Record<string, ScenarioExercise> // alias
|
|
121
|
+
|
|
122
|
+
// FAQ data
|
|
123
|
+
interface FAQItem {
|
|
124
|
+
question: string
|
|
125
|
+
answer: string
|
|
126
|
+
}
|
|
127
|
+
export const faqData: FAQItem[]
|
|
128
|
+
// 5-8 items about the course topic
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## achievements.js
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
interface Achievement {
|
|
135
|
+
id: string // unique kebab-case
|
|
136
|
+
title: string // Display name (localized)
|
|
137
|
+
description: string // What was accomplished
|
|
138
|
+
icon: string // Single emoji
|
|
139
|
+
condition: (state: StoreState) => boolean // Unlock condition
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// StoreState shape:
|
|
143
|
+
interface StoreState {
|
|
144
|
+
completedSections: Record<string, boolean>
|
|
145
|
+
sectionScores: Record<string, number>
|
|
146
|
+
finalTestScore: number | null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export const ACHIEVEMENTS: Achievement[]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Standard Achievement Set
|
|
153
|
+
|
|
154
|
+
Every course should include these base achievements:
|
|
155
|
+
|
|
156
|
+
| ID | Condition | Description |
|
|
157
|
+
|----|-----------|-------------|
|
|
158
|
+
| `first-step` | 1+ section completed | Starting the journey |
|
|
159
|
+
| `halfway` | N/2+ sections completed | Halfway through |
|
|
160
|
+
| `perfectionist` | Any section with score 100 | Perfect score |
|
|
161
|
+
| `full-course` | All N sections completed | Course complete |
|
|
162
|
+
| `test-passed` | Final test score >= 70 | Test passed |
|
|
163
|
+
|
|
164
|
+
Plus 3-5 topic-specific achievements based on section groupings.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Exercise Type Catalog
|
|
2
|
+
|
|
3
|
+
## 6 Interactive Exercise Types
|
|
4
|
+
|
|
5
|
+
### 1. Quiz (`interactiveType: 'quiz'`)
|
|
6
|
+
|
|
7
|
+
**Best for:** Factual knowledge, best practices, conceptual understanding
|
|
8
|
+
**Content type:** Statements that can be verified as true/false or selected from options
|
|
9
|
+
|
|
10
|
+
**When to use:**
|
|
11
|
+
- Testing recall of facts, definitions, rules
|
|
12
|
+
- Multiple valid-looking answers exist (good distractors)
|
|
13
|
+
- Explanations add learning value
|
|
14
|
+
|
|
15
|
+
**Data needed:** 2-4 questions with 4 options each, correct answer index, explanation
|
|
16
|
+
|
|
17
|
+
**UX:** Step through questions one at a time, immediate feedback per question, final score.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
### 2. Flashcards (`interactiveType: 'flashcards'`)
|
|
22
|
+
|
|
23
|
+
**Best for:** Vocabulary, definitions, term-concept pairs
|
|
24
|
+
**Content type:** Two-sided information (question/answer, term/definition)
|
|
25
|
+
|
|
26
|
+
**When to use:**
|
|
27
|
+
- Memorization is the goal
|
|
28
|
+
- Content is naturally paired (term <-> definition)
|
|
29
|
+
- Self-paced review is valuable
|
|
30
|
+
|
|
31
|
+
**Data needed:** 4-8 front/back card pairs
|
|
32
|
+
|
|
33
|
+
**UX:** Click/tap to toggle — cards flip back and forth freely. Tracks which cards have been viewed at least once. 100% completion on viewing all. Keyboard accessible (Enter/Space).
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
### 3. Matching Exercise (`interactiveType: 'matching'`)
|
|
38
|
+
|
|
39
|
+
**Best for:** Associations, mappings, relationships
|
|
40
|
+
**Content type:** Items that belong together in pairs
|
|
41
|
+
|
|
42
|
+
**When to use:**
|
|
43
|
+
- Concepts map to descriptions, tools to purposes, causes to effects
|
|
44
|
+
- Visual connection reinforces learning
|
|
45
|
+
- 4-6 distinct pairs available
|
|
46
|
+
|
|
47
|
+
**Data needed:** 4-6 left/right pairs
|
|
48
|
+
|
|
49
|
+
**UX:** Left column fixed, right column shuffled. Click left then right to match. Green on correct, shake on wrong.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
### 4. Drag-to-Order (`interactiveType: 'drag-and-drop'` or `'ordering'`)
|
|
54
|
+
|
|
55
|
+
**Best for:** Sequential processes, priority ordering, step-by-step procedures
|
|
56
|
+
**Content type:** Items with a definitive correct order
|
|
57
|
+
|
|
58
|
+
**When to use:**
|
|
59
|
+
- Steps in a process must be in specific sequence
|
|
60
|
+
- Priority or importance ordering matters
|
|
61
|
+
- Understanding workflow/pipeline order
|
|
62
|
+
|
|
63
|
+
**Data needed:** 4-8 items with IDs, labels, and correct order array
|
|
64
|
+
|
|
65
|
+
**UX:** Drag items to reorder, "Check" button to verify, retry on wrong. Uses @dnd-kit.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
### 5. Command Builder (`interactiveType: 'builder'`)
|
|
70
|
+
|
|
71
|
+
**Best for:** CLI commands, API calls, code syntax
|
|
72
|
+
**Content type:** Structured text that must be assembled correctly
|
|
73
|
+
|
|
74
|
+
**When to use:**
|
|
75
|
+
- Teaching command-line tools or API usage
|
|
76
|
+
- Syntax matters (order of flags, arguments)
|
|
77
|
+
- Progressive hints guide the learner
|
|
78
|
+
|
|
79
|
+
**Data needed:** Command parts array, correct assembled command, hints array
|
|
80
|
+
|
|
81
|
+
**UX:** Click parts to build command (shown as terminal-style), check button, progressive hints on failure.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
### 6. Scenario Game (`interactiveType: 'simulation'` or `'scenario'`)
|
|
86
|
+
|
|
87
|
+
**Best for:** Decision-making, workflows, applied knowledge
|
|
88
|
+
**Content type:** Multi-step situations requiring judgment
|
|
89
|
+
|
|
90
|
+
**When to use:**
|
|
91
|
+
- Teaching decision-making in real-world contexts
|
|
92
|
+
- Multiple valid approaches with different outcomes
|
|
93
|
+
- Feedback on choices is more valuable than binary right/wrong
|
|
94
|
+
|
|
95
|
+
**Data needed:** Scenario description, 3-5 steps each with 2-3 options (positive/neutral/negative)
|
|
96
|
+
|
|
97
|
+
**UX:** Read scenario, step through decisions, see feedback per choice, final score = positive choices / total steps.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Exercise Distribution Guidelines
|
|
102
|
+
|
|
103
|
+
| Section Count | Recommended Mix |
|
|
104
|
+
|---------------|----------------|
|
|
105
|
+
| 3-5 | 1 quiz, 1 flashcard, 1 matching/drag |
|
|
106
|
+
| 6-9 | 2 quiz, 1 flashcard, 1 matching, 1 drag, 1 scenario |
|
|
107
|
+
| 10-15 | 3 quiz, 2 flashcard, 2 matching, 2 drag, 1 builder, 2 scenario |
|
|
108
|
+
| 16-20 | Balanced across all 6 types |
|
|
109
|
+
|
|
110
|
+
**Rule:** Never have 3+ consecutive sections with the same exercise type.
|