# Extending the Tutorial System

Guide for adding new features to the Tutorial Engine and Step Builder.

---

## Part 1: Adding a New Engine Extension

Engine extensions are self-contained plugins that hook into the tutorial lifecycle without modifying `TutorialEngine.js`.

### Step 1: Create the Extension File

Create a file in `tutorials/extensions/YourExtension.js`:

```js
export const YourExtension = {
    name: 'yourFeature',

    // Called once when engine.use() registers the plugin
    install(engine) {
        // Store engine ref if needed
        this.engine = engine;
    },

    // Called BEFORE each step renders. Return false to skip the step.
    beforeStep(step, index, engine) {
        if (!step.yourProperty) return; // no-op if feature not used
        // Your logic here
        // return false; // to skip step
    },

    // Called AFTER the typewriter finishes typing
    afterTyping(step, isLast, engine) {
        // Good place to add interactive elements, start observers, etc.
    },

    // Called when the tutorial DOM is first built
    onBuildDOM(engine) {},

    // Called when animations/state are cleaned up between steps
    onCleanup(engine) {
        // IMPORTANT: clean up any DOM, listeners, or observers here
    },

    // Called when the tutorial closes
    onClose(engine) {
        // Final cleanup
    },
};
```

### Step 2: Register the Extension

In `tutorials/TutorialEngine.js`, add the import at the top and register in the constructor:

```js
import { YourExtension } from './extensions/YourExtension.js';

// In constructor, after the existing .use() calls:
this.use(YourExtension);
```

### Step 3: Add Config Defaults (if needed)

If your feature needs configurable options, add defaults to `tutorials/TutorialConfig.js`:

```js
static DEFAULTS = {
    // ... existing defaults ...
    yourFeatureColor: '#ff0000',
    yourFeatureEnabled: true,
};
```

Read them in your extension via `engine.config.get('yourFeatureColor')`.

### Available Hooks

| Hook | Arguments | Return | When |
|------|-----------|--------|------|
| `install` | `(engine)` | — | Once on `engine.use()` |
| `beforeStep` | `(step, index, engine)` | `false` to skip | Before step renders |
| `afterTyping` | `(step, isLast, engine)` | — | After typewriter finishes |
| `onBuildDOM` | `(engine)` | — | After initial DOM construction |
| `onCleanup` | `(engine)` | — | Between step transitions |
| `onClose` | `(engine)` | — | Tutorial closes |

### Example: The HideIf Extension

```js
// tutorials/extensions/HideIfExtension.js
export const HideIfExtension = {
    name: 'hideIf',
    beforeStep(step, index, engine) {
        if (!step.hideIf) return;
        if (engine.config.get('devMode')) return;
        if (document.querySelector(step.hideIf)) return false;
    },
};
```

Registered in TutorialEngine constructor: `this.use(HideIfExtension);`

---

## Part 2: Adding a New Step Builder Section (Card)

Step builder cards are child Vue components that edit step properties.

### Step 1: Create the Card Component

Create a file in `components/step-builder/cards/SbYourCard.vue`:

```vue
<template>
    <div class="sb-card" :class="{ 'sb-card--open': sb.cards.yourCard }">
        <div class="sb-card-head" @click="sb.toggle('yourCard')">
            <span>YOUR CARD</span>
            <span class="sb-chevron">{{ sb.cards.yourCard ? '&#9660;' : '&#9654;' }}</span>
        </div>
        <div v-show="sb.cards.yourCard" class="sb-card-body">
            <!-- Your card content here -->
            <div class="sb-row">
                <label class="sb-lbl">Your Field</label>
                <input :value="sb.step.yourField || ''"
                       @input="sb.setStepProp('yourField', $event.target.value)"
                       class="sb-input"
                       placeholder="value">
            </div>
        </div>
    </div>
</template>
<script>
export default {
    inject: ['sb'],
    methods: {
        // Card-specific methods go here — NOT in the parent
        // Access parent: this.sb.state, this.sb.step, this.sb.engine
        // Trigger preview: this.sb.previewNow()
        // Set step property: this.sb.setStepProp(key, value)
        // Pick element: this.sb.pickField('yourField')
        // Vue reactivity: this.$set(this.sb.step, 'key', value)
        //                  this.$delete(this.sb.step, 'key')
    },
};
</script>
```

### Step 2: Register in the Parent

In `components/VlTutorialStepBuilder.vue`:

1. **Import:**
```js
import SbYourCard from './step-builder/cards/SbYourCard.vue';
```

2. **Register:**
```js
components: {
    // ... existing components ...
    SbYourCard,
},
```

3. **Add to cards data:**
```js
cards: {
    // ... existing cards ...
    yourCard: false,  // false = collapsed by default
},
```

4. **Add to template** (inside the `.sb-detail` div):
```html
<sb-your-card />
```

### Step 3: Add Styles (if needed)

Append any new CSS classes to `tutorials/tutorial.scss`. All styles are global with `sb-` prefix.

```scss
// ─── Your Card ───
.sb-your-special-input { /* ... */ }
```

Do NOT add `<style>` sections inside Vue components.

### Communication Pattern

All children access the parent via `inject: ['sb']`:

| Access | Code |
|--------|------|
| Current step object | `this.sb.step` |
| All steps array | `this.sb.steps` |
| Current index | `this.sb.idx` |
| State manager (undo/redo) | `this.sb.state` |
| Engine instance | `this.sb.engine` |
| Engine config | `this.sb.engine.config.get('key')` |
| Trigger preview | `this.sb.previewNow()` |
| Set step property | `this.sb.setStepProp('key', 'value')` |
| Pick element | `this.sb.pickField('propName')` |
| Copy to clipboard | `this.sb._copy(text)` |
| Toggle card | `this.sb.toggle('cardName')` |
| Card open state | `this.sb.cards.cardName` |

### Vue 2 Reactivity Rules

When adding new properties to the step object, you MUST use `$set`/`$delete`:

```js
// Adding a new property to a step
this.$set(this.sb.step, 'newProperty', value);

// Removing a property
this.$delete(this.sb.step, 'newProperty');

// Modifying existing properties is fine
this.sb.step.html = 'new value';

// Nested objects need $set too
this.$set(this.sb.step, 'nested', {});
this.$set(this.sb.step.nested, 'child', value);
```

---

## Part 3: Adding a Structural Component (Non-Card)

For components that aren't cards (like timeline, graph view, history drawer):

### Step 1: Create the Component

Create in `components/step-builder/SbYourComponent.vue`:

```vue
<template>
    <div class="sb-your-component">
        <!-- your template -->
    </div>
</template>
<script>
export default {
    inject: ['sb'],
    // Your data, computed, methods
};
</script>
```

### Step 2: Register and Place in Parent

Same import/register process as cards, but place the tag outside `.sb-detail`:

```html
<template>
    <div v-show="visible" class="sb" ...>
        <div class="sb-resize" ...></div>
        <sb-history-drawer />     <!-- structural: before header -->
        <sb-header />
        <sb-step-list />
        <sb-context-menu />
        <div class="sb-detail">
            <!-- cards go here -->
        </div>
        <sb-timeline />           <!-- structural: after detail -->
        <sb-your-component />     <!-- structural: place where it makes sense -->
        <sb-path-preview />
        <sb-graph-view />
    </div>
</template>
```

---

## Part 4: Adding a New Step Property (End-to-End)

When a new feature needs both engine handling and builder editing:

### 1. Engine side

If the property needs runtime behavior, create an extension (Part 1).
If it's just a config value, add to TutorialConfig defaults (Part 1, Step 3).

### 2. Step Builder side

Add a field in the appropriate existing card, or create a new card (Part 2).

### 3. Code generator

If the new property should appear in JSON export, check `tutorials/step-builder/utils/code-generator.js`. The `generateStepJson()` function strips default values — add your property to the clean-up logic if needed.

### 4. Update step schema

Document the new property in the step object schema (see `docs/plans/` for the current schema).

---

## File Structure Reference

```
tutorials/
├── TutorialEngine.js          # Core orchestrator + plugin system
├── TutorialConfig.js           # Config defaults
├── tutorial.scss               # ALL styles (engine + builder)
├── index.js                    # Exports
│
├── modules/                    # Engine modules (one per feature)
│   ├── Overlay.js
│   ├── Highlight.js
│   ├── Bubble.js
│   ├── Avatar.js
│   ├── Cursor.js
│   ├── Typewriter.js
│   ├── HoverForcer.js
│   ├── Scroll.js
│   ├── AutoNext.js
│   ├── Options.js
│   └── Mobile.js
│
├── extensions/                 # Engine plugins (one per feature)
│   ├── HideIfExtension.js
│   └── LinkNextExtension.js
│
├── step-builder/
│   ├── StepBuilderState.js     # State management (undo/redo, CRUD)
│   ├── step-builder-bridge.js  # Kompo form bridge (legacy)
│   └── utils/
│       ├── code-generator.js
│       ├── element-picker.js
│       └── bezier-editor.js
│
└── utils/
    ├── selectors.js
    ├── svg.js
    └── stability.js

components/
├── VlTutorialStepBuilder.vue   # Parent shell (state, shortcuts, provide)
└── step-builder/
    ├── SbHeader.vue
    ├── SbStepList.vue
    ├── SbHistoryDrawer.vue
    ├── SbContextMenu.vue
    ├── SbTimeline.vue
    ├── SbPathPreview.vue
    ├── SbGraphView.vue
    └── cards/
        ├── SbContentCard.vue
        ├── SbPositionCard.vue
        ├── SbCursorCard.vue
        ├── SbHighlightCard.vue
        ├── SbHoverCard.vue
        ├── SbScrollCard.vue
        ├── SbOptionsCard.vue
        ├── SbActionsCard.vue
        ├── SbAdvanceCard.vue
        ├── SbConditionsCard.vue
        ├── SbOutputCard.vue
        └── SbGlobalOptsCard.vue
```

---

## Checklist: New Feature

- [ ] Engine extension created in `tutorials/extensions/`
- [ ] Extension registered in `TutorialEngine.js` constructor
- [ ] Config defaults added to `TutorialConfig.js` (if needed)
- [ ] Builder card/component created in `components/step-builder/`
- [ ] Component imported and registered in parent `VlTutorialStepBuilder.vue`
- [ ] Card key added to `cards` data in parent
- [ ] Component tag added to parent template
- [ ] Styles appended to `tutorials/tutorial.scss`
- [ ] Code generator updated (if new step property)
