Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
666bc29
feat(directives): registry + fence-aware directive parser (openspec: …
jung-thomas Nov 5, 2026
0005256
fix(directives): enforce label.required + tighten nested-container as…
jung-thomas Nov 5, 2026
de1eac3
feat(directives): expandDirectives rendering + param escaping
jung-thomas Nov 5, 2026
93cae8f
fix(directives): C1 render contract (structured output + safe escapin…
jung-thomas Nov 5, 2026
13dff0a
fix(directives): C2 injection test + NEW-I1 real depth guard threading
jung-thomas Nov 5, 2026
36b01d5
feat(directives): renderDirectivesAsText plain-text fallback
jung-thomas Nov 5, 2026
48aaf33
feat(directives): wire expandDirectives into compose pipeline
jung-thomas Nov 5, 2026
67128a7
feat(directives): slug:line in DirectiveParseError + tut-echo fixture…
jung-thomas Nov 5, 2026
598340a
feat(directives): MCP/AI read paths use plain-text directive fallback
jung-thomas Nov 5, 2026
4a3111a
feat(directives): directive-unknown lint rule
jung-thomas Nov 5, 2026
f53e553
feat(directives): build:directive-schema registry export
jung-thomas Nov 5, 2026
72f798c
test(directives): registry conformance + empty-registry corpus regres…
jung-thomas Nov 5, 2026
32bdeef
docs(directives): author grammar reference + pipeline gotcha
jung-thomas Nov 5, 2026
f39e0a5
chore(directives): check off completed openspec tasks
jung-thomas Nov 5, 2026
86a8650
fix(directives): empty-registry unclosed-container safety + single-qu…
jung-thomas Nov 5, 2026
e4ac2a0
docs(plan): directive-grammar implementation plan
jung-thomas Nov 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions docs/authors/writing-tutorials.md
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,93 @@ Key behaviors:

Full worked walkthrough (self-demonstrating): **[Use Variables to Personalize Your Tutorials](https://developers.sap.com/tutorials/use-variables-in-tutorials)** (source: `sap-tutorials/meta-tutorials`).

### 3.8 Directives ★ NEW

Directives are a generic structured-content syntax for tutorials. They allow you to mark up passages that will be handled by specialized components, added to the platform in follow-up features. The directive system ships with **no concrete directives yet** — it is the foundation only. Examples here use hypothetical directive names to show the syntax; they will not render as live components until those features land.

Directives have three forms:

**Inline directives** (mid-sentence):

```markdown
This feature is available in `:menu[Production]{edition="prod"}` only.
```

A directive spans from the opening `:` through the closing `}`. It has a **name** (kebab-case), an optional **label** in brackets, and optional **attributes** in braces.

**Leaf directives** (standalone, block-level):

```markdown
Some text above.

::code-sample[Download the SDK]{url="https://example.com/sdk.zip" filename="sdk.zip"}

More text below.
```

Leaf directives are alone on a line, prefixed with `::`, and render as a self-contained block.

**Container directives** (wrapping other content):

```markdown
:::details[Click to expand]
This content is hidden until the reader clicks the heading.
More paragraphs and **markdown** all work here.
:::
```

Container directives open with `:::` and close with `:::`. Content between them is Markdown, rendered normally. Nesting uses extra colons — an outer container uses more colons than its inner content:

```markdown
::::compare
Outer container (four colons).

:::figure[Screenshot A]{align="left"}
Inner figure (three colons).
:::

:::figure[Screenshot B]{align="right"}
Another inner figure.
:::
::::
```

**Attribute syntax:**

- **Quoted values:** `key="value"` or `key='value'` — use double or single quotes around values with spaces.
- **Unquoted values:** `key=value` (no spaces in the value itself).
- **Bare flags:** `open` → treated as `open=true`.
- **CSS classes:** `.classname` — shorthand for `class="classname"`. The directive's implementation controls which classes are allowed (you will get a build warning if you use a class not in its allowlist).
- **Element IDs:** `#id` — shorthand for `id="id"`.
- **Duplicate keys:** an error; each key must appear once.

Examples:

```markdown
:::panel[Expandable section]{open .highlight #my-panel}

This is a container with `open=true`, class `highlight`, and id `my-panel`.

:::

:code[JavaScript]{lang="js" theme='dark'}

:::details[Learn more]{class="advanced"}
For advanced users only.
:::
```

**Escaping and safety:**

- Attribute values are **always HTML-escaped** by the framework — you never need to escape manually, and HTML injection through attributes is not possible.
- Directives inside code fences, inline code, indented code blocks, HTML blocks, or YAML frontmatter are **never interpreted** — they render literally as text. This keeps your Markdown safe when it happens to contain colons and braces that look like a directive but aren't.

**Unknown directive names:**

If you use a directive name that doesn't exist (e.g. `:menu[...]` before the `menu` feature ships), the text is left as-is (rendered literally) and you'll see a **lint warning** `directive-unknown` during the build. This warning never stops a build — it's informational only. The intent is to make it safe to write Markdown that *coincidentally* looks like `:something[...]` without risking a hard failure.

When a concrete directive is added to the platform, the warning will stop and the directive will render as a styled component.

---

## 4. The author workflow today
Expand Down
2 changes: 2 additions & 0 deletions docs/developers/reference/tutorials-ims-gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ Cross-references:
- **`CAP_BASE_URL` env var** — Used by `scripts/parsers/cap.ts` and migration scripts. Defaults to `http://localhost:4004`.
- **Node.js >= 20 required** — Build scripts use native `fetch` (no polyfill).
- **Slug fields** — `Missions.slug` and `CompletionPaths.slug` must be populated for the build pipeline to generate mission/group pages. Run `node scripts/migrate-reference-data.js populate-slugs` after data import.
- **Directive pre-pass pipeline position** — The directive grammar parser (`scripts/parsers/directives.ts`) runs in `scripts/parsers/compose.ts` AFTER variable substitution (`wrapTokens`) and BEFORE option-block conversion (`convertOptionBlocks`). This means `{{var:x}}` tokens inside directive labels work (variables are already expanded), and directives can wrap option blocks. The framework reserves Hugo shortcode namespace `tut-*` for directive output; any new directive implementation must NOT generate a shortcode with that prefix or it will collide with the framework.
- **Empty directive registry byte-identical invariant** — When no concrete directives are registered (the foundation-only state), a tutorial with no directive markup parses and renders byte-identical to the pre-directive version. This is enforced by a corpus regression test (`test/unit/directives.test.js`). Before registering a new directive, ensure it ships with: a summary in `DirectiveRegistry`, a `toText()` plain-text fallback, full developer docs, and a regression suite (the conformance test will fail loudly if these are missing). Handoff: [docs/developers/reference/directive-grammar.md](directive-grammar.md).

## Directory layout

Expand Down
Loading
Loading