--- name: graphnet-create description: > Convert source material (article, lecture notes, textbook chapter, video transcript, or any topic the user names) into a GraphLMS **course** learning graph in lean JSON v3 format. Identifies atomic concepts, builds prerequisite edges, writes multiple-choice quizzes with misconception traps. Outputs a JSON file ready to import into a project of type "Курс" via "Импорт JSON" in the project settings panel. Use when the user asks to "сделать курс", "сделать обучающий граф", "разбить материал", "сделать урок", "build a learning graph", or hands you content to atomize for teaching. For personal wikis (read-only, no quizzes, topical organization) use the `graphnet-wiki` skill instead. --- # GraphLMS Content Builder — Course Mode Turn educational source material into a graph of small, learnable units that GraphLMS (а PWA + Telegram Mini App for learning via knowledge graphs) can import as a **course**. > **Project type:** import the produced JSON into a project created with type **«Курс»**. > A separate skill (`graphnet-wiki`) handles wiki-type projects (no quizzes, no scoring, > topical browsing). The JSON wire format is the same — but a course leans on quizzes > + prerequisite edges, while a wiki ignores quizzes and treats edges as related-to links. ## Mental model GraphLMS treats learning as **traversing a directed graph of atomic ideas**: - A **graph** is one topic. Multiple graphs make a **project** (e.g. "Линейная алгебра", "Эконометрика для социологов"). Студенты учатся внутри проекта. A project is either a **course** (this skill) or a **wiki** (`graphnet-wiki` skill). - A **node** is **one atomic idea**, learnable in 2–5 minutes of focused attention. - An **edge** A → B means *"you need to understand A before B is learnable"* (prerequisite). Direction is the only semantic — there are no edge "types"; the picker and progress rules look only at direction. - Each node carries **content** (markdown / images / audio / files) and exactly one **quiz** (multiple choice, one correct, optional misconception trap). - Студент видит одну ноду в момент. Правильный ответ помечает ноду `mastered` и открывает следующие. Неправильный — `failed` (можно повторить). Попадание в **misconception trap** — `misconception`, что **блокирует** все ноды ниже по графу: студент несёт системно ложное представление, его нельзя пускать дальше пока не исправит. The smaller and more atomic the nodes, the better the system works — it's easier to diagnose what a student doesn't understand when each node is one tight idea. ## Output format — lean JSON v3 The lean format is deliberately minimal: only content, no display state. The app fills in positions / timestamps / view settings on import. ### Single graph ```json { "version": 3, "name": "Topic name", "defaultLayout": "force", "nodes": [ /* see below */ ], "edges": [ /* see below */ ] } ``` ### Project bundle (multi-graph) ```json { "kind": "project", "version": 2, "name": "Project name", "exportedAt": "2026-04-30T10:00:00Z", "graphs": [ /* lean graph objects */ ] } ``` ### Node ```json { "id": "limits-intro", "label": "Что такое предел", "sections": [ { "type": "text", "markdown": "Предел f(x) при x → a — это значение, к которому стремится f(x), когда x приближается к a." }, { "type": "image", "svg": "..." }, { "type": "image", "name": "limit-diagram.png", "caption": "График функции с пределом" } ], "quiz": { "question": "Чему равен lim(x→2) x²?", "options": ["2", "4", "0", "x²"], "correctIndex": 1, "misconceptionIndex": 3 }, "tags": ["calculus", "limits"] } ``` **Required:** `id`, `label`. Everything else is optional. **`id` rules:** kebab-case, ASCII, unique within the graph. Used for edge references and URL fragments. Keep stable — changing an id breaks any cross-graph `portalTo` links. > **About `color` on a node:** the lean schema accepts a per-node `color` field, but the > live UI ignores it. Node fill color is now driven by **tags** (per-project Tag entities > with hex colors) — see "Tags & colors" below. Don't emit `color` on nodes. ### Edge ```json { "source": "limits-intro", "target": "limits-properties" } ``` **Required:** `source`, `target` (must reference existing node `id`s in the same graph). **Optional:** `label` (rare, only if a relation needs naming for the reader). > **Don't emit a `type` field on edges.** The schema has it as a legacy holdover but no > behaviour depends on it; setting it just adds visual noise (icons on the canvas) without > changing how the picker, scoring, or progress works. ### Section types | Type | Fields | Use for | |---|---|---| | `text` | `markdown` | Prose, formulas (TeX in markdown), lists, code blocks | | `image` | `svg` OR `name` OR `fileId` (+ `caption?`) | Diagrams, photos. SVG inline (preferred for AI), raster by filename | | `audio` | `name` OR `fileId` (+ `title?`) | Lectures, pronunciation samples | | `media` | `embedUrl?` (YouTube/VK) OR `embedHtml?` | Embedded video / iframe | | `file` | `name` OR `fileId` + `fileName` + `fileSize` | Downloadable PDFs, datasets | For raster/audio/file: emit `name` referencing a filename. The user uploads these files via "Загрузить медиа" in project settings, with the same name. **Names are unique per project** — collisions fail the upload. ### Tags & colors Tags in GraphLMS are now **first-class entities scoped to the project**: each tag has a `label` and a `color` (hex). The first tag on a node drives its **fill color** on the canvas in author and wiki views (in course-mode learner view, status colors win, so tag color is hidden during a study session). The lean JSON v3 file format still carries tags as **a string array per node** (`"tags": ["calculus", "limits"]`). The Tag entities themselves are not in the JSON — they live in PocketBase, scoped to the project. After import, the user creates Tag records with colors via the node editor (NodePanel → "Теги" → "Создать тег"). The strings on imported nodes do **not** auto-link to created Tag records yet — that's a known v1 limitation; the user manually re-tags imported nodes if they want colored chips. **What this means for your output:** - Emit tags as strings, as before: `"tags": ["category", "subcategory"]`. - Use 1–3 tags per node. The **first** tag is the "primary" — it'll drive the node's fill color, so put the most informative category first. - Across a graph, prefer ~3–8 distinct tag labels — fewer is better for color legibility. - In your **final user message**, include a "Suggested tag palette" section listing each unique tag label with a recommended hex color. Pick colors that match the domain semantics where possible (e.g. red for warnings, blue for fundamentals, green for applied). Avoid clashing colors and white/near-black on default theme. Example: `- "calculus" → #3b82f6`. The user copies the colors when creating the Tag entities in the editor. ### Quiz ```json { "question": "Plain text question", "options": ["A", "B", "C", "D"], "correctIndex": 2, "misconceptionIndex": 0 } ``` - `options`: 3–5 strings. 4 is the sweet spot. - `correctIndex`: 0-based index of the right answer. - `misconceptionIndex`: 0-based index of the trap. Optional. Must differ from `correctIndex`. ## How to decompose any content 1. **Read the whole source first.** Do not start chopping until you have the full picture and can identify what the student needs to walk away with. 2. **List candidate concepts.** Brainstorm ~20–50 ideas. Don't filter yet. 3. **Atomize.** A node is atomic when: - A reasonably motivated student can absorb it in **2–5 minutes**. - You can write **one** quiz question that distinguishes mastery from confusion. - Removing any sentence from the explanation breaks understanding. Symptoms of bad atomicity: - **Too coarse:** "Calculus" — split into Limits / Derivatives / Integrals / etc. - **Too fine:** separate nodes for "+" and "−" — merge into "Arithmetic operations". - **Mixed concerns:** "Vectors and matrices" — split. - **Two distinct techniques bundled** (e.g. "Acronyms and acrostics") — split into separate nodes unless you can justify why they share one quiz question. 4. **Order by prerequisites.** For each pair (A, B) ask: *"If a student doesn't know A, does B make sense?"* If no → A → B edge. Be conservative: too many edges is better than too few; the picker uses them to recommend the next node. 5. **Check for cycles.** A → B → A makes the graph unlearnable. Sort topologically; if you can't, you have a cycle. Break it by either (a) merging the cycle into one node or (b) inverting one edge. 6. **Sanity-check coverage.** The graph should let a student starting from zero reach every concept by following edges. Identify root nodes (no incoming edges) — these are entry points. 7. **Domain-expert pass.** Before writing JSON, ask yourself: *"If a domain expert read this graph as 'introductory ', what 1–3 fundamental concepts would they say are missing?"* Add them, or list them in the final message to the user as known gaps. ## How to write quiz questions Each node's quiz should **discriminate mastery** — getting it right means the student *understands*, not just memorized. ### Patterns that work - **Apply to a concrete example.** "If f(x) = 3x², what is f'(x)?" - **Distinguish from a similar concept.** "Median vs mean — which is robust to outliers?" - **Predict an outcome.** "What happens to inflation if the central bank raises rates?" - **Spot the error.** "Find the bug in this proof / code / argument." ### Anti-patterns — strict bans - ❌ **Pure recall.** If your question can be rephrased as "Which definition / statement is true?" or "What is the definition of X?" — rewrite it as application or distinguishing. Recognising the definition isn't understanding. - ❌ **Yes/no questions** — only 2 effective options, low signal. - ❌ **Trick wording / negation chains** — tests reading, not the concept. ### Distractor quality bar For each wrong option, ask: *"Could a real, motivated student plausibly choose this believing it's correct?"* If no → it's a filler, replace it. Filler examples to avoid (all real outputs from a previous run): - ❌ "Запомнить только цифру 8 — она важнее" (no one would think this) - ❌ "Использовать образ цифры 28 как единого символа" (nonsense) - ❌ "Чтобы фраза была написана на иностранном языке" (silly) - ❌ "Чтобы каждое слово было существительным" (random) A 4-option quiz with 2 silly distractors is effectively a 2-option quiz. **Aim for 1 correct + 1 misconception trap (when applicable) + 2 plausibly-wrong options.** ### The misconception trap (`misconceptionIndex`) This is the **most powerful pedagogical feature** of GraphLMS. Use it deliberately. A misconception trap is a wrong option that represents a **common, systematic student error** — a *predictable wrong mental model*. Selecting it doesn't just mark the node failed; it sets `misconception` status, which **blocks all downstream nodes** because the student is now operating on a wrong premise that will pollute everything they learn next. #### Specificity gate — run before setting `misconceptionIndex` Write down, in **one sentence**: *"A student who selects this option believes that ___."* If you can't fill in that blank with a specific wrong belief — **don't set `misconceptionIndex`**. Leave the option as a regular distractor (causes `failed`, which is retryable, not blocking). Concrete examples: | Question | Trap option | Wrong belief filled in | Set trap? | |---|---|---|---| | "lim(x→0) sin(x)/x = ?" | "0" | *"0 / 0 = 0"* | ✅ yes | | "Pearson r = 0.8 means..." | "X causes Y" | *"correlation implies causation"* | ✅ yes | | "What makes an image memorable?" | "shorter is better" | (can't name a wrong belief) | ❌ no | | "Loci method's advantage?" | "doesn't need imagery" | (no one believes this) | ❌ no | #### Trap-theme diversification Across a graph, **vary the trap themes**. If 5 quizzes all use "passive repetition is enough" as the trap, students learn to avoid that option pattern after the first node and the trap stops diagnosing anything. **Hard rule:** No single misconception theme should be the trap in more than ~30% of the graph's quizzes. Before emitting JSON, scan the traps you've set and ask: *"How many distinct wrong beliefs am I testing here?"* If the answer is fewer than half the trap count, rewrite some traps or drop them to plain distractors. ## Media handling - **SVG diagrams**: write inline in `sections[].svg`. AI-friendly, no upload needed. **Use them.** A graph with ≥10 nodes and zero visual sections is leaving signal on the table — at least 1–2 nodes should have SVG diagrams (timeline, decision tree, state diagram, simple chart). For procedural / spatial / structural concepts, a diagram is often clearer than 200 words of prose. - **Raster images, audio, files**: emit `sections[].name` referencing a filename. The user uploads matching files via "Загрузить медиа" in the project settings panel before/after import. Names must be unique within the project. - **Embeds (YouTube, etc.)**: use `{ "type": "media", "embedUrl": "https://youtu.be/..." }`. - **Plain text with formatting**: `{ "type": "text", "markdown": "..." }`. ## Working flow When invoked, follow these steps: 1. **Clarify.** Ask the user (skip questions where the answer is obvious from context): - Source material? (paste / file path / URL / topic name to research) - Target audience? (beginner / intermediate / expert) — affects atomicity - Project name? Or single-graph? - Language? (default: detect from source; Russian if unclear) - Output path? (default: `./graphnet-output/.json`) 2. **Read the source thoroughly.** If it's a file, read it. If it's a URL, fetch it. If it's a topic, lean on what you know — but be honest about gaps. 3. **Sketch the graph.** In a scratch list, write candidate node labels. Map prerequisites between them. **Show this sketch to the user before writing JSON** so they can correct the structure before you spend tokens on content. 4. **Validate the sketch:** - Atomic? (each node 2–5 min, one quiz question fits, no two-techniques-in-one) - Acyclic? (topologically sortable) - Complete? (domain-expert pass — see decomposition step 7) - Right granularity? (5–30 nodes per graph; split into multiple if larger) 5. **Write content + quiz for each node.** Keep markdown short and direct (3–7 sentences plus optional formula/list). For formulas, use TeX-style notation in markdown (the renderer is lightweight — keep it simple: `*x²*` style or unicode super/subscripts for basic cases). Plan SVG diagrams for at least 1–2 nodes if the topic has visual structure. 6. **Emit the JSON file** at the chosen path. Use `Write` tool, not console output. 7. **Tell the user the next steps:** - Create the project as **«Курс»** (the wiki type would hide quizzes and the scoring picker — different skill). - Open the project's settings → Импорт JSON → select the file - List any media filenames referenced via `name` that they need to upload via "Загрузить медиа" — names must match exactly - **Suggested tag palette:** for every distinct tag label you used, propose a hex color. The user creates these via NodePanel → "Теги" → "Создать тег" and re-tags imported nodes if they want colored chips on the canvas. - Mention any nodes where you weren't confident about the misconception trap so they can review - List any domain-expert gaps you noticed but chose not to add ## Quality checklist (run before writing the file) Structure: - [ ] Every node has `id` and `label` - [ ] All node `id`s are unique - [ ] Every edge's `source` and `target` reference an existing node `id` - [ ] No edges have a `type` field - [ ] No circular dependencies (topologically sortable) - [ ] Total nodes per graph: 5–30. Larger → split into a project bundle - [ ] No node combines two distinct techniques without justification Quizzes: - [ ] Each quiz has 3–5 options, `correctIndex` in bounds - [ ] Each `misconceptionIndex` (if set) is in bounds and ≠ `correctIndex` - [ ] No quiz is pure recall — each tests application / distinguishing / prediction - [ ] No silly / filler distractors — every wrong option is plausibly choosable - [ ] For every trap set, you can name the specific wrong belief in one sentence - [ ] Trap themes are diversified — no single theme dominates >30% of traps Content: - [ ] Content language matches the source / user request - [ ] Markdown is concise and well-formatted (no raw HTML in `text` sections) - [ ] At least 1–2 nodes have SVG / visual sections if the topic warrants it Tags: - [ ] Each node has 1–3 tags; primary (first) tag is the most informative category - [ ] Total distinct tag labels in the graph is ≤ 8 (color legibility) - [ ] No `color` field on nodes (deprecated; tag colors win) - [ ] Final user message includes a "Suggested tag palette" with hex colors ## Compact full example A two-node graph teaching introductory derivatives: ```json { "version": 3, "name": "Производные — основы", "defaultLayout": "force", "nodes": [ { "id": "rate-of-change", "label": "Скорость изменения", "sections": [ { "type": "text", "markdown": "**Скорость изменения** функции — насколько быстро меняется её выход при изменении входа. Если f(t) — пройденный путь, то скорость — это отношение Δf/Δt за маленький промежуток времени." } ], "quiz": { "question": "Машина едет 60 км/ч. Что описывает это число?", "options": [ "Положение машины в момент времени", "Насколько быстро машина проезжает расстояние", "Изменение скорости — ускорение", "Среднюю скорость за поездку" ], "correctIndex": 1, "misconceptionIndex": 3 }, "tags": ["calculus", "fundamentals"] }, { "id": "derivative-def", "label": "Определение производной", "sections": [ { "type": "text", "markdown": "Производная — это **мгновенная** скорость изменения. Формально:\n\nf'(x) = lim(h→0) (f(x+h) − f(x)) / h\n\nЭто предел отношения Δf/Δh, когда промежуток Δh стягивается в ноль." } ], "quiz": { "question": "Производная функции f(x) = 5x² в точке x = 3 равна:", "options": ["5", "30", "10", "15"], "correctIndex": 1, "misconceptionIndex": 0 }, "tags": ["calculus", "fundamentals"] } ], "edges": [ { "source": "rate-of-change", "target": "derivative-def" } ] } ``` In the second quiz, the trap `"5"` represents the wrong belief *"the derivative of a constant times x² is the constant itself"* (forgetting the power rule). The third option `"10"` is plausibly-wrong (computed at x = 1 by mistake). The fourth `"15"` is plausibly-wrong (multiplication of 5 × 3 without applying the rule). All three wrong options are choosable; only the first is a **systematic** error worth marking as a misconception. ## Reference The full schema and validators live in `src/entities/graph/model/fileSchema.ts` (`parseLeanGraphFile`, `parseLeanProjectFile`). Cross-check there if you hit a validation error after import.