Branching Course Editor
Reference Manual — v6.0
What is it? The Branching Course Editor is a visual, node-based authoring tool for creating interactive, non-linear learning experiences. Authors design courses as directed graphs where each node represents a content screen, a decision point, a quiz, or a flow-control mechanism. Connections between nodes define the paths learners can take, enabling deterministic, probabilistic, conditional, and gated branching.
The editor runs entirely in the browser as a single-page application. Courses are exported as portable JSON or YAML files and can be played back in the companion Course Player.
Key Concepts
| Concept | Description |
|---|---|
| Node | A single screen or logic point in the course. Each node has a type (content, decision, assessment, etc.), a title, optional body content, and a position on the canvas. |
| Connection | A directed edge from one node to another. Connections have a type (default, conditional, weighted, random), an optional label, and may carry conditions or weights. |
| Attribute | A named numeric variable that tracks learner progress (e.g., knowledge, confidence). Attributes are defined at the course level and modified by attribute effects on individual nodes. |
| Page | A self-contained sub-graph within a course. Multi-page courses group related nodes together and use page_link nodes to navigate between pages. |
| Layout | Each node can have a custom visual layout made of content blocks (headings, text, images, callouts, quizzes). The layout editor controls how nodes appear to learners at runtime. |
| Start Node | The designated entry point of the course. Exactly one node per page should be marked as the start node. |
System Requirements
- Browser: Any modern browser — Chrome 90+, Firefox 90+, Safari 15+, Edge 90+
- JavaScript: Must be enabled
- Screen: Minimum 360px width (mobile-optimized). Desktop experience recommended at 1280px+.
- Network: CDN access required for Tailwind CSS, js-yaml, marked.js, and KaTeX. No backend server is required for core editing (server features are optional).
- Storage: LocalStorage used for auto-save and settings persistence
2. Interface Reference
2.1 Canvas
The canvas is the central work area where nodes are placed and connections are drawn. It is an infinite, pannable, zoomable surface with a dot-grid background.
| Feature | Interaction |
|---|---|
| Grid | 40px dot grid (configurable). Provides visual alignment reference. Can be hidden via settings. |
| Pan | Middle-click drag, Alt+click drag, Space+drag, or two-finger trackpad scroll. |
| Zoom | Ctrl/Cmd+scroll wheel or pinch gesture. Zoom range configurable (default 10% to 500%). Zoom percentage displayed in the menu bar. |
| Fit All | Click the fit button in the zoom controls, or use View > Fit all nodes. Automatically adjusts pan and zoom to show all nodes. |
| Select | Click a node to select it. Click empty canvas to deselect. Ctrl/Cmd+click to add/remove nodes from multi-selection. |
| Rectangle Select | Click and drag on empty canvas to draw a selection rectangle. All nodes within the rectangle are selected. |
| Empty Canvas Hint | When no nodes exist, a centered hint with instructions is displayed. |
2.2 Toolbar & Menu Bar
The desktop interface uses a macOS-style menu bar across the top of the editor. Below the menu bar is a pages tab bar and a floating quick-add toolbar on the canvas.
Menu Bar
| Menu | Items |
|---|---|
| File | Course Database, Load from server, Save to server, New page, Import file, Export YAML, Export JSON, Print diagram, Clear canvas |
| Add Node | Content, Decision, Assessment, End, Random, Weighted, Condition, Gate, Page Link. All items are draggable to the canvas. |
| View | Fit all nodes, Auto-arrange nodes, Read/Storyboard view, Horizontal layout, Vertical layout |
| Themes | Domain theme configuration |
| Settings | Dark mode, System themes (Default, Midnight, Ocean, Warm, Slate, Forest, Rose, Violet), All settings... |
Quick-Add Toolbar
A floating pill-shaped toolbar centered at the top of the canvas. Contains the most common node types as colored buttons. Each button can be clicked to add at the center of the viewport, or dragged directly to a position on the canvas. The toolbar can be collapsed to a small "Add" stub.
Quick-add buttons: Content Decision Quiz Condition Page Link End
Additionally, the toolbar includes toggle buttons for Link Drag and Downstream Drag physics modes, and a Node Repulsion toggle.
Zoom Controls
Located at the right end of the menu bar: zoom out (−), zoom percentage display, zoom in (+), and fit-all (⊡) buttons.
2.3 Properties Panel
The right sidebar (default 288px wide) shows contextual property editors. It is organized into tabs:
| Tab | Contents |
|---|---|
| Properties | When a node is selected: node ID (read-only), type selector, title, content (Markdown), options (for decision), questions (for assessment), condition/default_target (for condition/gate), target_page/target_node (for page_link), passing score (for assessment), success/failure targets (for assessment), attribute effects, media attachments, "Set as Start" button. When a connection is selected: from/to node IDs, type, label, condition, weight. When nothing is selected: course title, description, version, tags, metadata. |
| Code | Raw JSON/YAML view of the selected node, connection, or entire course data. Syntax-highlighted and read-only preview with copy button. |
| Attributes | Course-level attribute definitions. Each attribute has: name, description, default value, min, max. Below definitions: a Progression Map showing which nodes affect each attribute with visual bar charts. |
| Settings | All editor configuration options organized into categories. See Section 9: Editor Settings. |
2.4 Mobile Interface
On screens narrower than 768px, the interface adapts:
- Menu bar collapses to a simple title bar with the course name input.
- Properties panel becomes a bottom drawer (60vh height) that slides up from the bottom. A drag handle at the top allows manual dismiss. A backdrop overlay appears behind the drawer.
- Three FABs (Floating Action Buttons) replace the desktop toolbar, stacked at the bottom-right:
- Properties FAB (blue, bottom) — opens the properties drawer
- Add Node FAB (green, middle) — opens a bottom sheet with all node types
- More FAB (gray, top) — opens a bottom sheet with File actions (export, import, clear, etc.)
- Bottom sheets appear with a drag handle and list items. The Add Node sheet lists all node types with icons. The More sheet provides export/import/server actions.
- Touch-friendly connection points are enlarged to 24px diameter for easier tapping.
- Nodes are slightly narrower (185px instead of 220px).
2.5 Keyboard Shortcuts
| Shortcut | Action |
|---|---|
| Escape | Cancel current connection, deselect all nodes and connections |
| Delete / Backspace | Delete selected node(s) or connection (with confirmation if enabled) |
| Space (hold) | Enter pan mode — click and drag to pan the canvas |
| Ctrl+click / Cmd+click | Toggle node in multi-selection |
| Ctrl+scroll / Cmd+scroll | Zoom in/out at cursor position |
| Ctrl+S | Save to server |
| Ctrl+E | Export YAML |
| Ctrl+K | Focus search (in this manual) |
| Alt+click drag | Pan the canvas |
| Middle-click drag | Pan the canvas |
3. Node Types Reference
The editor supports 11 node types. Each type serves a distinct purpose in branching course design.
Content Content Node
Purpose: Displays information to the learner. The most basic and commonly used node type. Content is displayed as Markdown with support for LaTeX math, tables, and images.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | auto-generated | Unique identifier |
type | string | "content" | Fixed type identifier |
title | string | "New Content" | Display title |
content | string | "" | Markdown body text |
position | object | {x:0, y:0} | Canvas position {x, y} |
isStart | boolean | false | Whether this is the start node |
attribute_effects | array | [] | Attribute modifications applied when visited |
attachments | array | [] | Media file attachments |
Connection Rules
- Outgoing: Exactly 0 or 1 (single output type). The connection is always
default. - Incoming: Unlimited
Validation
- Warning if more than 1 outgoing connection
- No outgoing connection is valid (can be a dead end, though not recommended)
Player Behavior
Renders the Markdown content with a "Next" or "Continue" button that follows the single outgoing default connection. Attribute effects are applied on node entry. If a layout is defined, it renders the layout blocks instead of raw Markdown.
Example JSON
JSON { "id": "intro", "type": "content", "title": "Introduction", "content": "Welcome to the course! This is **Markdown** content.\n\nYou can use:\n- Lists\n- `code`\n- Math: $E = mc^2$", "position": { "x": 100, "y": 100 }, "attribute_effects": [ { "attr_id": "knowledge", "delta": 5, "when": "always" } ] }
Decision Decision Node
Purpose: Presents the learner with explicit choices. Each option maps to an outgoing connection. The learner's selection determines which path is taken.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | auto-generated | Unique identifier |
type | string | "decision" | Fixed type identifier |
title | string | "New Decision" | Display title / question |
content | string | "" | Markdown body text above the options |
options | array | [{id:"opt-a", label:"Option A"}, {id:"opt-b", label:"Option B"}] | Choice options. Each has id, label, and optional description. |
position | object | {x:0, y:0} | Canvas position |
attribute_effects | array | [] | Attribute modifications |
Connection Rules
- Outgoing: 1 or more (multi-output). Each outgoing connection should have a label matching an option label. Connections use
conditionaltype. - Incoming: Unlimited
Player Behavior
Renders content followed by a list of clickable choice buttons (labeled A, B, C...). When the learner selects an option, the player finds the outgoing connection whose label matches the option label and navigates to that node. If no match is found, the first connection is used as a fallback.
Example JSON
JSON { "id": "fork-1", "type": "decision", "title": "Which path?", "content": "Choose your direction:", "options": [ { "id": "opt-a", "label": "Beginner Path", "description": "Start from basics" }, { "id": "opt-b", "label": "Advanced Path", "description": "Jump to the deep end" } ], "position": { "x": 300, "y": 100 } }
Assessment Assessment Node
Purpose: Tests learner knowledge with one or more questions. Routes to different paths based on whether the learner passes or fails (score vs. passing threshold).
Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | auto-generated | Unique identifier |
type | string | "assessment" | Fixed type identifier |
title | string | "New Assessment" | Display title |
content | string | "" | Instructions shown before questions |
questions | array | [] | Array of question objects |
passing_score | number | 80 | Percentage required to pass (0-100) |
success_target | string | "" | Node ID to navigate to on pass |
failure_target | string | "" | Node ID to navigate to on fail |
attribute_effects | array | [] | Effects with when: "always", "pass", or "fail" |
Question Types
| Type | Properties |
|---|---|
multiple-choice | prompt, options (array of {id, label, correct:bool}), points |
true-false | prompt, correct_answer (boolean), points |
short-answer | prompt, correct_answer (string), points |
numeric | prompt, correct_answer (number), tolerance, points |
Connection Rules
- Outgoing: Typically 2 (pass and fail paths) specified via
success_targetandfailure_target. Can also use standard connections. - Incoming: Unlimited
Player Behavior
All questions are presented at once. The learner answers each, then clicks "Submit". The player calculates the percentage score, compares to passing_score, shows a pass/fail result with score breakdown, then navigates to success_target or failure_target. Attribute effects with when: "pass" or when: "fail" only apply for the corresponding outcome.
Example JSON
JSON { "id": "quiz-1", "type": "assessment", "title": "Knowledge Check", "passing_score": 80, "questions": [ { "id": "q1", "type": "multiple-choice", "prompt": "What is 2+2?", "options": [ { "id": "a", "label": "3" }, { "id": "b", "label": "4", "correct": true }, { "id": "c", "label": "5" } ], "points": 10 } ], "success_target": "module-2", "failure_target": "review-1", "position": { "x": 600, "y": 100 } }
Example Example Node
Purpose: Displays a worked example, demonstration, or case study. Functionally similar to a content node but visually distinct to indicate its pedagogical role. Domain themes can automatically flavor example content.
Properties
Same as Content node with type: "example".
Connection Rules
- Outgoing: 0 or 1 (
defaultconnection). Same as content. - Incoming: Unlimited
Player Behavior
Renders identically to content with a green "Example" badge. In the player, a distinct badge style (green background) distinguishes it from standard content. When a domain theme is set, the layout editor can auto-generate domain-specific example content.
Example JSON
JSON { "id": "ex-1", "type": "example", "title": "Worked Example: Quadratic Formula", "content": "Solve $x^2 + 5x + 6 = 0$...", "position": { "x": 200, "y": 300 } }
Practice Practice Node
Purpose: Provides an interactive practice exercise or hands-on activity. Like example, this is functionally a content node with distinct visual identity for instructional design clarity.
Properties
Same as Content node with type: "practice".
Connection Rules
- Outgoing: 0 or 1 (
defaultconnection) - Incoming: Unlimited
Player Behavior
Renders with an orange "Practice" badge. Content is displayed as Markdown with a "Continue" button.
Example JSON
JSON { "id": "practice-1", "type": "practice", "title": "Try It Yourself", "content": "Using the formula above, solve the following problems...", "position": { "x": 400, "y": 300 } }
Random Random Node
Purpose: Introduces controlled randomness. The runtime picks one of the outgoing connections completely at random (uniform probability). Useful for randomized practice sets, unpredictable narrative branches, or A/B testing scenarios.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | auto-generated | Unique identifier |
type | string | "random" | Fixed type identifier |
title | string | "New Random" | Display title |
content | string | "" | Optional text shown before branching |
position | object | {x:0, y:0} | Canvas position |
Connection Rules
- Outgoing: 2 or more recommended. All connections use
randomtype. Each has equal probability. - Incoming: Unlimited
Player Behavior
Briefly displays the node content (if any), then automatically picks one outgoing connection uniformly at random and navigates to its target. The learner does not make a choice — the system decides.
Example JSON
JSON { "id": "random-event", "type": "random", "title": "Random Encounter", "content": "Something unexpected happens...", "position": { "x": 400, "y": 200 } }
Weighted Weighted Node
Purpose: Similar to random, but each outgoing connection carries a probability weight (0-100). The runtime samples proportionally. Useful for scenarios where some outcomes are more likely than others.
Properties
Same base properties as Random. Weights are stored on the connections, not the node itself.
Connection Rules
- Outgoing: 2 or more recommended. All connections use
weightedtype and must include aweightfield (0-100). Weights should sum to 100; if they do not, the runtime normalizes automatically. - Incoming: Unlimited
Validation
- Warning if weights do not sum to 100
- Warning if any connection is missing a weight
Player Behavior
Displays content briefly, then samples one outgoing connection proportionally to its weight. Probability chips are shown on the node in the editor to visualize the distribution.
Example JSON
JSON { "id": "weighted-fork", "type": "weighted", "title": "Scenario Draw", "content": "The outcome depends on probability.", "position": { "x": 500, "y": 200 } } // Connections from this node: // { from: "weighted-fork", to: "easy-path", type: "weighted", label: "Easy", weight: 70 } // { from: "weighted-fork", to: "hard-path", type: "weighted", label: "Hard", weight: 30 }
Condition Condition Node
Purpose: Evaluates a runtime expression and routes the learner to the matching outgoing connection. Works like a programmatic switch/case statement. Used for adaptive routing based on scores, attributes, or custom variables.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | auto-generated | Unique identifier |
type | string | "condition" | Fixed type identifier |
title | string | "New Condition" | Display title |
condition | string | "" | The variable or expression to evaluate |
default_target | string | "" | Fallback node ID if no condition matches |
position | object | {x:0, y:0} | Canvas position |
Connection Rules
- Outgoing: 1 or more. All connections use
conditionaltype. Each connection'slabelcarries the condition expression that must evaluate to true for that path to be taken. Connections are evaluated in order; the first match wins. - Incoming: Unlimited
Player Behavior
Evaluates each outgoing connection's condition expression against the current state (score, attributes, variables). Navigates to the first matching connection. If none match, navigates to default_target. See Expression Language Reference for syntax.
Example JSON
JSON { "id": "score-router", "type": "condition", "title": "Score Router", "condition": "score", "default_target": "review-1", "position": { "x": 700, "y": 150 } }
Gate Gate Node
Purpose: Blocks the learner at this point until a condition becomes true. Acts as a prerequisite check or conditional barrier. Once the condition is satisfied, the learner proceeds.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | auto-generated | Unique identifier |
type | string | "gate" | Fixed type identifier |
title | string | "New Gate" | Display title |
content | string | "" | Message shown while gate is locked |
condition | string | "" | Expression that must be true to pass. Empty string = always pass. |
default_target | string | "" | Node ID to proceed to when gate opens |
position | object | {x:0, y:0} | Canvas position |
Connection Rules
- Outgoing: Exactly 0 or 1 (single output type). Uses
defaultconnection ordefault_target. - Incoming: Unlimited
Player Behavior
When a learner arrives at a gate node, the condition is evaluated. If true, the learner passes through immediately. If false, the gate displays the content/message and a locked state. The runtime periodically re-evaluates the condition. A gate with an empty condition always passes immediately.
Example JSON
JSON { "id": "prereq-gate", "type": "gate", "title": "Prerequisite Check", "content": "Complete Module 1 before continuing.", "condition": "completed_modules >= 1", "default_target": "module-2-intro", "position": { "x": 800, "y": 150 } }
Page Link Page Link Node
Purpose: Navigates to another page in a multi-page course. Acts as a portal between sub-graphs. The target can be a specific page and optionally a specific node within that page.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | auto-generated | Unique identifier |
type | string | "page_link" | Fixed type identifier |
title | string | "New Page Link" | Display title |
target_page | string | "" | ID of the target page |
target_node | string | "" | Optional: specific node ID within the target page |
position | object | {x:0, y:0} | Canvas position |
Connection Rules
- Outgoing: 0 or 1 (single output type). The outgoing connection is a fallback; the primary navigation is via
target_page. - Incoming: Unlimited
Player Behavior
When reached, the player switches to the target page and navigates to either the target_node or the start node of that page. Learner state (attributes, variables, score) is preserved across page transitions.
Example JSON
JSON { "id": "go-to-page-2", "type": "page_link", "title": "Continue to Module 2", "target_page": "page-2", "target_node": "module-2-intro", "position": { "x": 1000, "y": 200 } }
End End Node
Purpose: Marks the terminal point of a course path. No outgoing connections. Displays a completion screen with final statistics.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
id | string | auto-generated | Unique identifier |
type | string | "end" | Fixed type identifier |
title | string | "New End" | Display title |
content | string | "" | Optional completion message |
attribute_effects | array | [] | Final attribute modifications |
position | object | {x:0, y:0} | Canvas position |
Connection Rules
- Outgoing: Exactly 0 (zero output type). Any outgoing connections produce a validation error.
- Incoming: Unlimited (but at least 1 recommended)
Player Behavior
Displays a completion screen with a trophy icon, the node title, optional content, and a statistics grid showing: final score, nodes visited, questions answered, and final attribute values. A "Restart" button allows the learner to begin the course again.
Example JSON
JSON { "id": "finish", "type": "end", "title": "Course Complete", "content": "Congratulations! You have completed the course.", "position": { "x": 1200, "y": 150 } }
Node Type Summary
| Type | Color | Icon | Max Out | Conn Type | Has Options | Has Condition |
|---|---|---|---|---|---|---|
content | Blue | 📄 | 1 | default | No | No |
decision | Amber | 🔀 | Unlimited | conditional | Yes | No |
assessment | Emerald | 📝 | 2* | default | No | No |
example | Lime | 📚 | 1 | default | No | No |
practice | Orange | 🔧 | 1 | default | No | No |
random | Violet | 🎲 | Unlimited | random | No | No |
weighted | Orange | ⚖️ | Unlimited | weighted | No | No |
condition | Cyan | ⚡ | Unlimited | conditional | No | Yes |
gate | Rose | 🚧 | 1 | default | No | Yes |
page_link | Purple | 📑 | 1 | default | No | No |
end | Gray | 🏁 | 0 | N/A | No | No |
* Assessment routing uses success_target / failure_target properties rather than standard connections.
4. Connection Reference
Connection Properties
| Property | Type | Required | Description |
|---|---|---|---|
id | string | Auto | Unique connection identifier (auto-generated: conn-{timestamp}) |
from | string | Yes | Source node ID |
to | string | Yes | Target node ID |
type | string | Yes | Connection type: default, conditional, weighted, or random |
label | string | No | Display label on the edge. For decision nodes, matches option labels. For condition nodes, carries the condition expression. |
condition | string | No | Condition expression (for conditional type) |
weight | number | No* | Probability weight 0-100 (required for weighted type) |
Connection Types
| Type | Used With | Behavior | Extra Fields |
|---|---|---|---|
default | content, gate, page_link, end, assessment | Taken automatically when the source node completes. Unconditional. | None |
conditional | decision, condition | Taken only when the label/condition evaluates to true. For decision nodes, the label matches the selected option. For condition nodes, the label is an expression. | label, condition |
weighted | weighted | Sampled by probability. The runtime generates a random number and selects a connection proportional to its weight. | weight (0-100), label |
random | random | One connection is selected uniformly at random. All connections have equal probability. | label |
Creating Connections
- Hover over a node to reveal connection points — small circles on the left (input) and right (output) sides of the node.
- Click and drag from an output point (blue circle, right side).
- A temporary line follows your cursor. Release on another node's input point (left side) to complete the connection.
- Press Escape to cancel an in-progress connection.
Editing: Click a connection line to select it (turns red). Edit its properties in the Properties panel. Click elsewhere to deselect.
Deleting: Select a connection and press Delete/Backspace, or use the delete button in the properties panel.
Connection Validation Rules
- Self-connections (same source and target) are rejected
- Duplicate connections (same from/to pair) are rejected
- Connections into end nodes are rejected (end nodes are terminal)
- Single-output node types (content, gate, page_link) warn when more than one outgoing connection exists
- Connection type is automatically inferred from the source node type (condition → conditional, weighted → weighted, random → random, others → default)
5. Attribute System Reference
Attributes are named numeric variables that represent learner characteristics (knowledge, confidence, skill level, etc.). They are defined at the course level, modified by node effects, displayed in the player's HUD, and can be used in condition/gate expressions.
Attribute Definitions
Defined in the Attributes tab of the properties panel. Each attribute has:
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier (auto-generated: attr-{timestamp}) |
name | string | Display name (e.g., "Courage", "Knowledge") |
description | string | Optional description of what this attribute represents |
default | number | Starting value (default: 0) |
min | number|null | Minimum value (null = no minimum) |
max | number|null | Maximum value (null = no maximum) |
Attribute Effects
Individual nodes can modify attributes when the learner visits them. Effects are configured in the Properties tab when a node is selected.
| Field | Type | Description |
|---|---|---|
attr_id | string | ID of the attribute to modify |
delta | number | Amount to add (positive) or subtract (negative) |
when | string | "always", "pass", or "fail". For assessment nodes, "pass"/"fail" triggers only apply for the corresponding outcome. |
Progression Map
The Attributes tab displays a visual Progression Map showing which nodes affect each attribute, with bar charts indicating the magnitude and direction of each effect. Click a node in the map to select it and jump to its properties.
Using Attributes in Expressions
Attributes are available as variables in condition and gate expressions. Reference them by their name (lowercase, underscores for spaces):
Expression // In a condition node's outgoing connection: knowledge >= 50 // In a gate node's condition: confidence > 30 && skill_level >= 2 // Compound conditions: (knowledge >= 80 || experience >= 5) && attempts < 3
Player HUD Display
When a course has attributes defined, the player shows an Attribute Bar below the header. Each attribute appears as a pill with an icon, name, and current value. When an attribute changes, a delta indicator (+N / -N) flashes with a green (increase) or red (decrease) animation.
Built-in icon mappings:
| Attribute Name | Icon |
|---|---|
| knowledge | 📚 |
| confidence | 💪 |
| experience | ⭐ |
| skill_level | 🎯 |
| creativity | 🎨 |
| critical_thinking | 🧠 |
| intuition | 🔮 |
| wisdom | 🦉 |
| speed | ⚡ |
| accuracy | 🎯 |
| research | 🔬 |
| practical | 🔧 |
| communication | 💬 |
| leadership | 👑 |
| teamwork | 🤝 |
| default | 📊 |
6. Layout Editor Reference
Each node can have a custom visual layout that controls how it appears to learners at runtime. The layout editor is a full-screen modal with a block-based WYSIWYG canvas.
Opening the Layout Editor
Select a node, then click the "Edit Layout" button in the Properties panel. The editor opens as a full-screen overlay with three areas:
- Block Palette (left sidebar, 192px) — lists all available block types. Click or drag to add.
- Page Canvas (center, 900px default) — a positioned canvas with a dot-grid background where blocks are placed.
- Header Bar (top) — shows node name, canvas width presets, template menu, and close/save buttons.
Block Types
| Block | Icon | Default Size | Properties |
|---|---|---|---|
| heading | 🔤 | 660 x 90 | level (1-6), text (string), align (left/center/right) |
| text | 📝 | 640 x 200 | content (Markdown string), align (left/center/right) |
| callout | 💡 | 640 x 140 | style (info/warning/success), content (Markdown string) |
| image | 🖼️ | 500 x 320 | url (image URL), alt (alt text), caption (optional). Supports upload via media endpoint. |
| video | 🎬 | 640 x 160 | url (embed URL or direct video URL), caption (optional). YouTube/Vimeo embeds auto-detected. |
| code | 💻 | 660 x 240 | language (string, e.g. "python", "javascript"), content (code string). Syntax highlighting via marked.js code blocks. |
| columns | ⬜ | 780 x 240 | columns (array of 2 column objects). Each column can contain text content. Renders as a side-by-side grid. |
| divider | ── | 640 x 48 | No properties. Renders as a horizontal line separator. |
| quiz_question | ❓ | 660 x 340 | question (string), choices (array of strings), answer_idx (number, 0-based correct choice index), explanation (optional string shown after answer) |
| nav_button | ▶ | 260 x 80 | label (button text, e.g. "Next"), style (primary/secondary), _optId (optional: binds to a decision option for routing) |
Positioning and Sizing
Each block has absolute positioning on the canvas with properties x, y, w (width), and h (height). Blocks can be:
- Moved by dragging the header bar (cursor: move)
- Resized by dragging the resize handle in the bottom-right corner
- Precisely positioned using the geometry bar at the bottom of each block (X, Y, W, H numeric inputs)
- Aligned using alignment buttons (left/center/right) in the geometry bar
Canvas Width Presets
The header bar provides width presets for the page canvas: common options include 640px (mobile), 900px (default), and wider options for desktop-first designs.
Templates
The layout editor includes pre-built templates accessible from the header menu:
| Template | Contents |
|---|---|
| Lesson Intro | Heading (H1), info callout (learning objectives), text block, primary nav button |
| Content Section | Heading (H2), text block, image, divider |
| Quiz Section | Heading (H2), quiz_question block, primary nav button ("Submit") |
| Two-Column | Heading (H2), columns block with side-by-side content |
Auto-Generated Layouts
When opening the layout editor for a node that has no layout, the editor auto-generates one based on the node type:
- Content/Example/Practice: Heading + text block with the node's content
- Decision: Heading + nav_button for each option
- Assessment: Heading + quiz_question for each question + submit button
- Condition/Gate: Info/warning callout with condition details
- End: Centered "The End" heading
- Random/Weighted: Callout explaining the branching behavior
- Page Link: Callout explaining the page navigation
7. Course File Format Specification
Courses are stored as JSON or YAML files. The format supports both single-page and multi-page structures.
Top-Level Structure
| Field | Type | Required | Description |
|---|---|---|---|
course | object | Yes | Course metadata |
nodes | array | Yes* | Node list (single-page format) |
connections | array | Yes* | Connection list (single-page format) |
pages | array | Yes* | Page list (multi-page format) |
* Either nodes+connections (single-page) or pages (multi-page) must be present.
Course Metadata Object
| Field | Type | Default | Description |
|---|---|---|---|
id | string | auto | Unique course identifier |
title | string | "Untitled Course" | Course title |
description | string | "" | Course description |
version | string | "1.0.0" | Semantic version string |
tags | array | [] | String tags for categorization |
metadata | object | {} | Free-form metadata: author, created, estimated_time, etc. |
attributes | array | [] | Attribute definitions (see Section 5) |
themes | array | [] | Visual theme definitions |
default_theme_id | string | "theme-scholar" | Active theme identifier |
Single-Page Format
JSON { "course": { "id": "my-course", "title": "My Course", "version": "1.0.0", "attributes": [] }, "nodes": [ { "id": "start", "type": "content", "title": "Start", "content": "...", "position": {"x":100,"y":100} }, { "id": "end", "type": "end", "title": "Done", "position": {"x":400,"y":100} } ], "connections": [ { "from": "start", "to": "end", "type": "default" } ] }
Multi-Page Format
JSON { "course": { "id": "multi-page-course", "title": "Multi-Page Course", "version": "1.0.0" }, "pages": [ { "id": "page-1", "title": "Module 1", "nodes": [ /* ... */ ], "connections": [ /* ... */ ] }, { "id": "page-2", "title": "Module 2", "nodes": [ /* ... */ ], "connections": [ /* ... */ ] } ] }
Complete Example: Mixed Branching Course
YAML course: id: "adventure-1" title: "The Forest Adventure" version: "1.0.0" attributes: - id: courage name: Courage default: 0 min: 0 max: 100 nodes: - id: "start" type: "content" title: "Forest Edge" content: "You stand at the forest edge." position: {x: 80, y: 200} - id: "fork" type: "decision" title: "Which way?" options: - {id: left, label: "Go left"} - {id: right, label: "Go right"} position: {x: 320, y: 200} - id: "encounter" type: "weighted" title: "Random Encounter" position: {x: 800, y: 200} - id: "quiz" type: "assessment" title: "Quick Check" passing_score: 70 success_target: "win" failure_target: "retry" position: {x: 1280, y: 200} - id: "retry" type: "gate" title: "Retry Gate" condition: "attempts < 3" default_target: "quiz" position: {x: 1280, y: 380} - id: "win" type: "end" title: "Victory" position: {x: 1520, y: 200} connections: - {from: "start", to: "fork", type: "default"} - {from: "fork", to: "encounter", type: "conditional", label: "Go left"} - {from: "encounter", to: "quiz", type: "weighted", label: "Friendly", weight: 70} - {from: "retry", to: "quiz", type: "default"}
8. Import & Export
Exporting
| Format | How | Details |
|---|---|---|
| YAML | File > Export YAML | Produces a .yaml file using js-yaml. Human-readable, ideal for version control. |
| JSON | File > Export JSON | Produces a .json file. Indentation controlled by the prettyJson setting (default: 2-space indent). Set to false for compact output. |
Export Options (controlled by settings)
prettyJson— When true, JSON is pretty-printed with 2-space indentation. When false, compact single-line output.exportMeta— When false, themetadatafield is stripped from the exported course object.
Export Behavior
- Single-page courses export with
nodesandconnectionsat the top level (backward-compatible format). - Multi-page courses export with a
pagesarray, each containing its ownnodesandconnections. - Connection types are automatically inferred from the source node type during export.
- Only populated optional fields are included (e.g.,
attribute_effectsis only present if non-empty).
Importing
How: File > Import file, or the mobile More sheet "Import" button. Accepts .json, .yaml, and .yml files.
Format Detection
- Files ending in
.jsonare parsed as JSON - Files ending in
.yamlor.ymlare parsed as YAML
Import Handling
- The file must contain either
nodes(single-page) orpages(multi-page) at the top level. - Course metadata is extracted from the
courseobject. - Connection IDs are regenerated to avoid collisions.
- Missing positions default to
{x:100, y:100}. - Auto-layout: If all nodes share the same position (indicating they lack stored positions), the tree layout algorithm automatically arranges them.
- If
autoFitis enabled, the view fits all nodes after import.
Legacy Format Support
Unknown node types encountered during import (e.g., "lesson", "slide", "info") are automatically mapped to the content type.
File > Print diagram opens the browser's print dialog. The canvas is rendered without the grid, toolbar, sidebar, and status bar. Nodes retain their visual styling. Uses landscape orientation by default.
9. Editor Settings Reference
All settings are persisted to localStorage. Access via Settings menu > All settings, or the Settings tab in the properties panel.
Appearance
| Setting | Type | Default | Description |
|---|---|---|---|
showGrid | boolean | true | Show the dot-grid background on the canvas |
gridSize | number | 40 | Grid spacing in pixels |
nodeShadows | boolean | true | Show box shadows on nodes |
animations | boolean | true | Enable CSS transitions and animations |
fontSize | number | 13 | Base font size in pixels |
sidebarWidth | number | 288 | Properties panel width in pixels |
Canvas
| Setting | Type | Default | Description |
|---|---|---|---|
snapToGrid | boolean | false | Snap node positions to grid |
zoomSpeed | number | 0.10 | Zoom step per scroll increment |
zoomMin | number | 0.10 | Minimum zoom level (10%) |
zoomMax | number | 5 | Maximum zoom level (500%) |
autoFit | boolean | true | Auto-fit view after import/load |
Nodes
| Setting | Type | Default | Description |
|---|---|---|---|
defaultNodeType | string | "content" | Default type for new nodes |
nodeWidth | number | 220 | Node width in pixels |
showTypeLabels | boolean | true | Show type label badge on nodes |
showContentPreview | boolean | true | Show content preview text on nodes |
showOptionChips | boolean | true | Show option chips on decision nodes |
showNodeIds | boolean | false | Display node IDs on the canvas |
nodeRadius | number | 8 | Node border radius in pixels |
compactNodes | boolean | false | Use compact node rendering |
Connections
| Setting | Type | Default | Description |
|---|---|---|---|
connStyle | string | "curve" | Connection line style (curve or straight) |
arrowStyle | string | "arrow" | Arrow head style |
connColor | string | "default" | Connection color scheme |
connHover | boolean | true | Highlight connections on hover |
showConnLabels | boolean | true | Show labels on connection edges |
edgeThickness | number | 2 | Connection line thickness in pixels |
Behavior
| Setting | Type | Default | Description |
|---|---|---|---|
selectOnAdd | boolean | true | Automatically select newly added nodes |
confirmDelete | boolean | true | Show confirmation dialog before deleting nodes/connections |
confirmClear | boolean | true | Show confirmation before clearing the canvas |
propsOnSelect | boolean | true | Auto-switch to Properties tab when a node is selected |
layoutDir | string | "horizontal" | Auto-arrange layout direction (horizontal or vertical) |
Physics
| Setting | Type | Default | Description |
|---|---|---|---|
nodeRepulsion | boolean | true | Enable node repulsion physics (nodes push away from each other to avoid overlap) |
linkedDrag | boolean | true | When dragging a node, connected nodes are pulled along as if connected by elastic ropes |
downstreamDrag | boolean | false | When dragging a node, all downstream (connected-forward) nodes move rigidly with it |
Export
| Setting | Type | Default | Description |
|---|---|---|---|
exportFormat | string | "yaml" | Default export format |
prettyJson | boolean | true | Pretty-print JSON exports with 2-space indentation |
exportMeta | boolean | true | Include metadata in exports |
API / Server
| Setting | Type | Default | Description |
|---|---|---|---|
apiBaseUrl | string | "http://localhost:8001" | Base URL for the course API server |
autoLoad | boolean | false | Auto-load last course from localStorage on startup |
showServerErrors | boolean | true | Display server error messages |
autoSave | boolean | true | Auto-save to server periodically |
autoSaveInterval | number | 30 | Auto-save interval in seconds |
saveIndicator | boolean | true | Show save toast notifications |
Rich Content
| Setting | Type | Default | Description |
|---|---|---|---|
renderMarkdown | boolean | true | Render Markdown in node content previews |
renderLatex | boolean | true | Render LaTeX math expressions |
renderTables | boolean | true | Render Markdown tables |
allowImages | boolean | true | Allow inline images in content |
Accessibility
| Setting | Type | Default | Description |
|---|---|---|---|
highContrast | boolean | false | Enable high-contrast mode |
focusRing | boolean | false | Show visible focus rings on interactive elements |
reduceMotion | boolean | false | Disable animations for motion sensitivity |
10. Player Reference
The Course Player renders branching courses for learners. It supports file upload, URL loading, and a built-in course catalog with server integration.
State Variables
The player maintains these state variables throughout a session:
| Variable | Type | Description |
|---|---|---|
nodeId | string | Current node ID |
score | number | Accumulated score (from assessments) |
history | array | Ordered list of visited nodes: [{id, title}] |
vars | object | Custom variables set by set_variables blocks on nodes |
attrs | object | Current attribute values (from attribute_effects) |
visited | Set | Set of all node IDs that have been visited |
quizAnswers | object | Map of node ID to quiz response data |
Built-in Variables Available in Expressions
| Variable | Type | Description |
|---|---|---|
score | number | Current accumulated score |
completed_modules | number | Count of visited content-type nodes |
attempts | number | Total number of unique nodes visited |
questions_answered | number | Number of quiz questions answered |
All custom vars | varies | Any variable set via set_variables |
All attrs | number | Current value of each defined attribute |
Runtime Behavior by Node Type
| Node Type | Player Rendering | Navigation |
|---|---|---|
content | Markdown content with "Next" button | Follows single default connection |
example | Same as content, green badge | Follows single default connection |
practice | Same as content, orange badge | Follows single default connection |
decision | Content + choice buttons | Follows connection matching selected option |
assessment | Quiz questions + submit button | Routes to success_target or failure_target based on score |
random | Brief content display (auto-advance) | Randomly selects one outgoing connection |
weighted | Brief content display (auto-advance) | Weighted random selection of outgoing connection |
condition | Transparent (auto-advance) | First matching condition expression, or default_target |
gate | Shows locked message if condition is false | Proceeds when condition becomes true |
page_link | Transparent (auto-advance) | Switches to target page |
end | Completion screen with stats | None (terminal). Restart button available. |
Attribute HUD
When a course defines attributes, the player displays an attribute bar below the header. Each attribute shows as a pill with icon, name, and current value. Changes animate with flash effects (green pulse for increases, red pulse for decreases) and show delta indicators.
History / Breadcrumb Trail
A breadcrumb trail at the top of the content area shows the path taken: each visited node's title is listed, separated by arrows. The current node is highlighted. A floating history FAB opens a slide-out panel listing all visited nodes.
Restart Behavior
Clicking the restart FAB (or the restart button on the end screen) resets all state: score, history, vars, attrs, visited, and quizAnswers return to their initial values. The player navigates back to the start node.
11. Validation Rules
The editor continuously validates the course and shows issue counts in the status bar. Nodes with issues display a warning badge.
Node-Type Specific Rules
| Rule | Severity | Description |
|---|---|---|
| Single-output overflow | Warning | Content, gate, and page_link nodes can only have 1 outgoing connection. Additional connections trigger this warning. |
| End node has outgoing | Error | End nodes must be terminal. Any outgoing connections are invalid. |
| No start node | Warning | If the course has nodes but none are marked as the start node, a course-level warning appears. |
Connection Rules
| Rule | Behavior |
|---|---|
| Self-connection | Silently rejected (connection is not created) |
| Duplicate connection | Silently rejected |
| Connection to end node | Rejected with hint message |
| Excess connection on single-output | Allowed but warning displayed |
Course-Level Rules
- No start node: At least one node per page should be designated as the start node
- Orphan nodes: Nodes with no incoming or outgoing connections may indicate incomplete wiring (not currently flagged as errors, but visible on the canvas)
- Unreachable nodes: Nodes that cannot be reached from the start node (not currently auto-detected)
Status Bar
The bottom status bar displays: node count, connection count, and issue count. Clicking the issue indicator can reveal details about each validation problem with the option to navigate to the affected node.
12. Common Patterns & Recipes
Linear Progression
The simplest pattern. Chain content nodes in sequence.
Content A → Content B → Content C → End
Use: Tutorials, presentations, guided walkthroughs.
Binary Choice
A decision node with exactly two options leading to different paths.
Content → Decision (A / B) → Path A → End
→ Path B → End
Use: Choose-your-own-adventure, A/B scenarios, beginner vs. advanced tracks.
Multi-Path Scenario
A decision with 3+ options, each leading to unique content, eventually converging back to a common node.
Decision (A/B/C) → Path A →
→ Path B → Merge Point → Continue
→ Path C →
Use: Role-play scenarios, case studies with multiple approaches.
Assessment with Remediation Loop
Assessment routes failures back through review content, then re-tests. A gate limits retry attempts.
Content → Assessment -(pass)→ Next Module
-(fail)→ Review Content → Gate (attempts < 3) → Assessment
Use: Certification courses, mastery learning, skill verification.
Probabilistic Outcomes
A weighted node distributes learners across paths by probability.
Content → Weighted (70%/30%) → Easy Scenario (70%)
→ Hard Scenario (30%)
Use: Simulated real-world variance, randomized case studies, gamification.
Attribute-Gated Progression
Use attributes and gate/condition nodes to create adaptive paths based on accumulated learner traits.
Content (+5 knowledge) → Content (+5 knowledge) → Gate (knowledge ≥ 10) → Advanced Topic
Use: Adaptive learning paths, RPG-style skill trees, prerequisite enforcement.
Multi-Page Course
Organize a large course into pages (chapters/modules). Use page_link nodes to navigate between them.
Page 1: Intro → Content → Page Link (to Page 2) Page 2: Content → Assessment → Page Link (to Page 3) Page 3: Content → End
Use: Large courses, multi-chapter textbooks, modular curricula.
Adaptive Difficulty
Combine assessments with condition nodes to route learners based on their performance.
Assessment → Condition (score) -(score ≥ 90)→ Advanced Track
-(score ≥ 60)→ Standard Track
-(score < 60)→ Remediation Track
Use: Differentiated instruction, placement testing, scaffolded difficulty.
13. Expression Language Reference
Expressions are used in condition nodes (outgoing connection labels) and gate nodes (condition field). They are evaluated at runtime using JavaScript semantics.
Operators
| Operator | Meaning | Example |
|---|---|---|
== | Equality (loose) | path == "scenic" |
=== | Strict equality | score === 100 |
!= | Inequality | attempts != 0 |
< | Less than | score < 50 |
<= | Less than or equal | knowledge <= 30 |
> | Greater than | confidence > 80 |
>= | Greater than or equal | score >= 90 |
&& | Logical AND | score >= 70 && attempts < 3 |
|| | Logical OR | knowledge > 50 || experience > 5 |
! | Logical NOT | !visited_scenic |
+ - * / | Arithmetic | score + bonus >= 100 |
% | Modulo | attempts % 2 == 0 |
() | Grouping | (a || b) && c |
Available Variables
| Source | Variables |
|---|---|
| Built-in | score, completed_modules, attempts, questions_answered |
| Custom vars | Any key set via set_variables on nodes (e.g., path, visited_scenic) |
| Attributes | All defined attributes by name (e.g., knowledge, confidence, courage) |
Example Expressions
Expressions // Simple threshold score >= 80 // Attribute check knowledge >= 50 && confidence > 20 // Custom variable check path == "scenic" && visited_scenic // Compound with grouping (score >= 90 || (knowledge >= 80 && experience > 3)) && attempts < 5 // Arithmetic in condition score + bonus >= 100 // Negation !completed_intro
Evaluation Order
- All current state variables are collected:
vars+attrs+ built-in variables. - The expression string is evaluated as a JavaScript expression with variables injected as function parameters.
- The result is coerced to a boolean.
- If evaluation throws an error (e.g., undefined variable), the result defaults to
false.
new Function(). Avoid injecting user-controlled strings into expressions in production environments. For authored courses, this is safe as the course author controls all expression content.
14. Troubleshooting & FAQ
"My connections are not showing"
- Ensure you are dragging from the output port (right side, blue circle) to the input port (left side) of another node.
- Self-connections (same source and target) are silently rejected.
- Duplicate connections (same from/to pair already exists) are silently rejected.
- Connections into
endnodes are blocked. - Check the zoom level — at very low zoom, thin connection lines may be hard to see.
- Verify the
showConnLabelsandedgeThicknesssettings.
"Assessment is not routing correctly"
- Verify that
success_targetandfailure_targetcontain valid node IDs that exist in the course. - Check
passing_score— it is a percentage (0-100). A score of 80 means the learner needs 80% correct. - Ensure your questions have
correct: truemarked on the right option, orcorrect_answerset for true-false questions. - If using attribute effects with
when: "pass"/"fail", make sure thewhenfield is set correctly.
"Gate node is stuck / learner cannot proceed"
- Check the gate's
conditionexpression. Use the expression reference to ensure syntax is correct. - Verify that the variables referenced in the condition are actually being set by earlier nodes (via
set_variablesorattribute_effects). - An empty condition always passes immediately. If the gate should be open, clear the condition field.
- Ensure
default_targetpoints to a valid node ID. - Check that the gate has an outgoing connection or a
default_target.
"Attributes not updating in the player"
- Attributes must be defined at the course level (Attributes tab) before they can be referenced in effects.
- Attribute effects on nodes must reference the correct
attr_idmatching a defined attribute. - Check the
deltavalue — a delta of 0 has no effect. - For assessment nodes, check the
whenfield — effects withwhen: "pass"only apply when the learner passes. - Verify that the learner actually visits the node (check history in the player).
"Import failed"
- Ensure the file is valid JSON or YAML. Use a validator tool to check syntax.
- The file must contain either a
nodesarray (single-page) or apagesarray (multi-page) at the top level. - The
courseobject is optional but recommended. If missing, defaults are applied. - Check for trailing commas (invalid in JSON) or incorrect YAML indentation.
- If the file was exported from an older version, unknown node types are mapped to
contentautomatically.
"Nodes overlap / cluttered layout"
- Use View > Auto-arrange nodes to apply the tree layout algorithm.
- Enable Node Repulsion in settings or the quick toolbar to have nodes push apart automatically.
- Use View > Fit all nodes to adjust zoom and pan to see all nodes.
- Try switching between horizontal and vertical layout directions.
"Weighted node probabilities seem wrong"
- Verify that all outgoing connections from the weighted node have a
weightvalue set. - Weights should ideally sum to 100. If they do not, the runtime normalizes them proportionally.
- Remember: probability does not guarantee specific outcomes in small sample sizes. A 70/30 split may not produce exactly 7 and 3 results in 10 runs.
"Condition node always takes the default path"
- Check that each outgoing connection's label contains a valid expression.
- Connections are evaluated in order; ensure the most specific conditions come first.
- Undefined variables evaluate to
undefined, causing comparisons to fail. Make sure variables are set before the condition node is reached. - Use the Code tab to inspect the raw condition expressions.
"Auto-save is not working"
- Auto-save requires a running API server at the configured
apiBaseUrl. - Check that
autoSaveis enabled in settings. - Verify the server is accessible (check browser DevTools console for CORS or network errors).
- The course must have been saved to the server at least once (via File > Save to server) before auto-save activates.
Branching Course Editor Reference Manual — Version 6.0
Last updated: March 2026
Tutorial · Open Editor · Course Player · Course Database