Teach non-coders programming by bridging Excel spreadsheets β functional programming.
and Docx experience -> markdown, html.
People already understand "B column = A column + 3". That IS a pure function.
"Drag the formula down every row" IS map. "SUM(A:A)" IS reduce. "Show rows where A > 5" IS filter.
The app makes this connection explicit: left-top is an interactive spreadsheet, left-bottom shows the equivalent code, and the AI tutor on the right helps explain.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β [Pure Functions] [Map] [Filter] [Reduce] β Topic Bar β
β [Ch.1 Cells] [Ch.2 Formulas] [Ch.3 Compose] β Chapter Tabs β
βββββββββββββββββββββββββββββββββββ¬βββββββββββββββββββββββββββββββββ€
β β β
β Interactive Spreadsheet β AI Chat Panel β
β βββββ¬ββββ¬ββββ¬ββββ β β
β β β A β B β C β β π€ Welcome! Try typing 3 β
β β 1 β 1 β 4 β β β in cell A1... β
β β 2 β 2 β 5 β β β β
β β 3 β 3 β 6 β β β π€ What does =A1+3 mean? β
β βββββ΄ββββ΄ββββ΄ββββ β β
β β π€ Great question! When you β
β βββββββββββββββββββββββββββ β write =A1+3, you're β
β β // equivalent code β β creating a pure function β
β β const B = A.map( β β that... β
β β x => x + 3 β β β
β β ) β β ββββββββββββββββββββββββββ β
β β [JSβΎ] [βΆRun] β β β Ask anything... β β
β βββββββββββββββββββββββββββ β ββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββ΄βββββββββββββββββββββββββββββββββ€
β Footer β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
- Left top: Interactive Excel-like grid (editable cells, formulas)
- Left bottom: Code editor (JS/Python/SQL) with run button
- Right: AI chat session (Gemini, decoupled for swapping)
- Top: Topic bar + chapter tabs
- Ch 1 β Cells & Values: Types (numbers, text), entering data
- Ch 2 β Simple Formulas:
=A1+3βconst add3 = (x) => x + 3β pure function concept - Ch 3 β Referencing:
=A1+B1β function composition, multiple inputs
- Ch 1 β Column Formulas: Drag formula down =
A.map(x => x + 3) - Ch 2 β Transformations: String ops, type conversion across columns
- Ch 1 β Conditions:
=IF(A1>5,...)β.filter(x => x > 5) - Ch 2 β SQL WHERE: Same data, SQL syntax
SELECT * FROM data WHERE A > 5
- Ch 1 β Aggregation:
=SUM(A:A),=COUNT(...),=AVERAGE(...)β.reduce() - Ch 2 β Building Reduce: Step-by-step accumulator concept
src/
βββ lib/
β βββ backend/
β β βββ llm/
β β βββ types.ts # LLMProvider interface, ChatMessage
β β βββ gemini.ts # Google Gemini implementation
β β βββ provider.ts # Factory + active provider state
β β βββ index.ts # Re-exports
β β
β βββ tutorial/
β β βββ content/
β β β βββ types.ts # Topic, Chapter, Lesson types
β β β βββ index.ts # Content registry (all topics)
β β β βββ pure-functions.ts # Topic 1 chapters
β β β βββ map.ts # Topic 2 chapters
β β β βββ filter.ts # Topic 3 chapters
β β β βββ reduce.ts # Topic 4 chapters
β β β
β β βββ engine/
β β βββ spreadsheet.ts # Spreadsheet data model + formula eval
β β βββ executor.ts # Code runner (JS in-browser, Python via Pyodide, SQL via sql.js)
β β βββ bridge.ts # Table β Code linking (select col β function param)
β β
β βββ components/
β β βββ TopicBar.svelte # Horizontal topic navigation
β β βββ ChapterTabs.svelte # Tab strip for chapters
β β βββ Spreadsheet.svelte # Interactive Excel-like grid
β β βββ CodeEditor.svelte # Code editor + language selector + run
β β βββ ChatPanel.svelte # AI chat interface
β β βββ SettingsModal.svelte # API key input, preferences
β β
β βββ stores/
β βββ tutorial.svelte.ts # Current topic/chapter state (Svelte 5 runes)
β βββ settings.svelte.ts # API key, preferences (localStorage-backed)
β
βββ routes/
β βββ +layout.svelte # Shell: topic bar, footer
β βββ +page.svelte # Main 3-panel layout
β
βββ app.css # Tailwind import
Decoupled design β swap Gemini for OpenAI/Anthropic by implementing the interface:
// types.ts
interface ChatMessage {
role: 'user' | 'assistant' | 'system';
content: string;
}
interface LessonContext {
topic: string;
chapter: string;
tableData: CellData[][];
currentCode: string;
language: 'js' | 'python' | 'sql';
}
interface LLMProvider {
name: string;
sendMessage(messages: ChatMessage[], context?: LessonContext): Promise<string>;
}// gemini.ts β first implementation
class GeminiProvider implements LLMProvider {
name = 'Gemini';
constructor(private apiKey: string) {}
async sendMessage(messages, context?) {
// Prepend system prompt with lesson context
// Call Gemini API (generativelanguage.googleapis.com)
// Return text response
}
}- API key stored in
localStorage, entered via SettingsModal - System prompt includes current lesson context so AI gives relevant help
- Provider is created/swapped at runtime via
provider.ts
Pure TypeScript, no UI:
type CellValue = string | number | null;
type CellData = {
raw: string; // what user typed (e.g. "=A1+3")
computed: CellValue; // evaluated result (e.g. 4)
formula: boolean; // starts with "="
};
class SpreadsheetModel {
cells: CellData[][]; // [row][col]
setCellRaw(row: number, col: number, raw: string): void;
evaluate(): void; // recalculate all formulas
getColumnValues(col: number): CellValue[];
toArray(): CellValue[][]; // plain values for code execution
}Formula evaluation:
- Parse
=A1+3style references - Support basic ops:
+,-,*,/ - Support basic functions:
SUM(),COUNT(),AVERAGE(),IF() - Dependency-order evaluation (topological sort on cell refs)
interface ExecutionResult {
output: string; // stdout
returnValue: unknown; // last expression value
error: string | null;
tableUpdate?: CellValue[][]; // if code returns array, update table
}
async function executeJS(
code: string,
inputs: Record<string, CellValue[]>
): Promise<ExecutionResult>;
async function executePython(
code: string,
inputs: Record<string, CellValue[]>
): Promise<ExecutionResult>;
async function executeSQL(code: string, tableData: CellValue[][]): Promise<ExecutionResult>;- JS:
new Function()in try/catch β inputs injected as variables (e.g.const A = [1,2,3]) - Python: Pyodide (loaded from CDN on first use, lazy)
- SQL: sql.js (SQLite WASM, loaded from CDN on first use, lazy)
- Table β Code bridge: selected columns become named inputs; return arrays update table columns
Each chapter defines:
interface Chapter {
id: string;
title: string;
instruction: string; // markdown lesson text (shown in chat as system intro)
initialTable: CellValue[][]; // starting spreadsheet data
initialCode: string; // starter code in editor
language: 'js' | 'python' | 'sql';
columnBindings: Record<string, number>; // e.g. { A: 0, B: 1 } β which cols are inputs
hints: string[]; // progressive hints for AI tutor
validation?: (table: CellValue[][], output: string) => boolean; // check if user solved it
}
interface Topic {
id: string;
title: string;
icon: string;
chapters: Chapter[];
}All components are UI-only, calling lib functions:
- TopicBar: Renders topic buttons, highlights active, emits
ontopicchange - ChapterTabs: Renders chapter tabs for active topic, emits
onchapterchange - Spreadsheet: Renders grid from
SpreadsheetModel, handles cell editing, emits changes - CodeEditor:
<textarea>with monospace font (upgrade to CodeMirror later), language dropdown, Run button - ChatPanel: Message list + input, calls
LLMProvider.sendMessage(), auto-includes lesson context
| Package | Purpose | Size |
|---|---|---|
sql.js |
SQLite in WASM for SQL execution | ~1MB WASM (CDN) |
codemirror + @codemirror/lang-javascript + @codemirror/lang-python + @codemirror/lang-sql |
Code editor | ~150KB |
- Pyodide: Loaded from CDN (
cdn.jsdelivr.net/pyodide/), no npm install needed β ~6MB WASM loaded lazily on first Python execution - sql.js: WASM loaded from CDN lazily on first SQL execution
- Remove unused deps:
luxon,uuid
- Clean up: remove old checker code, update package.json name
- Create layout: TopicBar, ChapterTabs, 3-panel grid
- Build SpreadsheetModel + Spreadsheet component (basic grid, formula eval)
- Build CodeEditor + JS executor (textarea, run button, output)
- Build table β code bridge (column β variable binding)
- Build LLM types + Gemini provider + ChatPanel
- Build settings (API key input in localStorage)
- Create Topic 1 content (Pure Functions, 3 chapters)
- Wire it all together on +page.svelte
- Verify:
npm run lint+npm run build
- Add Pyodide lazy loader
- Add sql.js lazy loader
- Add Topic 2-4 content
- CodeMirror upgrade from textarea
- Progress tracking (localStorage)
- Mobile responsive layout
- More topics