Skip to content

Add HOCON playground with interactive tutorials#5

Merged
BrainRTP merged 107 commits intomainfrom
feature/play-ground
May 4, 2026
Merged

Add HOCON playground with interactive tutorials#5
BrainRTP merged 107 commits intomainfrom
feature/play-ground

Conversation

@BrainRTP
Copy link
Copy Markdown
Collaborator

@BrainRTP BrainRTP commented May 4, 2026

New in-browser HOCON editor at /playground/ with autocomplete, live diagnostics, and a resolved-JSON preview. Runs entirely in the browser, no backend.
Tutorial mode walks through 28 lessons covering values, items, actions, templates, and conditions. Lesson links jump to the matching docs page.
EN/RU UI, lesson content, and diagnostic messages. Share a config via URL, recent edits are kept in local history per mode.

BrainRTP added 30 commits May 1, 2026 17:56
BrainRTP and others added 28 commits May 3, 2026 11:32
…lick types)

Four lessons closing out actions topic. Each builds on the previous so
the user grows a working click handler from "just send a message" to
"different left/right reactions".

- 16-click-message: click { actions { message } } - introduces the
  three-level nesting (click -> actions -> action-type) with the
  simplest possible action
- 17-click-command: swap message for command - same nesting, different
  action type. Mentions @p selector and the long-form { cmd, as }
  variant for player-side execution
- 18-multiple-actions: stack multiple action types in one actions block;
  notes the gotcha that since actions is an object you can't have two
  `message` keys (point users at the bulk action for that)
- 19-click-types: split click.actions into click.left.actions /
  click.right.actions; classic shop pattern (left=buy, right=inspect)

Each starter carries the previous lesson's state forward so the
incremental build is visible in the editor. Shape-checks assert the
specific path the lesson teaches; users are free to leave or remove
prior content as they like.

15-count-cooldown -> 16-click-message. 19-click-types is the new tail
until batch 6 (templates).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
When the same path is assigned twice and both old and new values are
objects, the resolver now merges them recursively instead of overwriting.
Matches Lightbend HOCON's documented behavior:

  defaults { a = 1 }
  defaults { b = 2 }
  ->  { defaults: { a: 1, b: 2 } }

Last-wins still applies for any non-object collision (scalar over scalar,
scalar over object, object over scalar) - same as before.

Localized to setPath() in resolve.ts + a new private mergeTrees() helper.
8 tests in resolve.test.ts cover: simple two-block merge, three-block,
field-overlap last-wins inside the merge, recursive nested merge, both
non-object overwrite directions, scalar dup unchanged, dotted-key merge.

Self-contained change - revert via `git revert HEAD` if it causes any
regression in real configs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`a += [x]` now concatenates `[x]` onto an existing array at `a` (or
creates `[x]` if `a` is unset), instead of silently overwriting. Matches
the HOCON spec's desugaring `a += b == a = ${?a} [b]` for the literal-
array case.

Wired e.append from the parser through buildTree -> setPath. The append
branch handles three cases:
- existing is an array Node: concat items
- existing is undefined: treat as first assignment
- existing is something else: fall back to overwrite (predictable, even
  if HOCON spec would error)

Substitution-valued appends (`items += ${shared}`) take the fall-back
path; supporting them needs deferred resolution and isn't required for
the templates lessons. Documented inline.

6 new tests cover the common scenarios + regression on plain `=`.
Self-contained: revert via `git revert HEAD` if needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`{ slot = 0, ${defaults} }` now merges the resolved defaults object's
fields into the enclosing object instead of erroring on the substitution
token. Foundational for the templates topic's DRY patterns.

Implementation:
- types.ts: Entry gains optional `spread?: boolean`. When true, `value`
  is a substitution Node and `path` is `[]`.
- parser.ts: parseEntry detects `substitution` token at entry position
  (where a key path would otherwise start), emits a spread Entry.
- resolve.ts: buildTree stashes spreads on a Symbol-keyed slot of the
  Tree (so Object.keys() doesn't see them). resolveSubsDeep evaluates
  spreads FIRST, Object.assigns each into the output, then processes
  own keys - so explicit fields override spread fields on collision.
- scope-ranges.ts: walkEntries skips spread entries (they don't add a
  key path to index).
- unknown-keys.ts: already early-skips empty-path entries, no change.

Semantic note: HOCON spec uses strict declaration-order for merge
precedence. We use "spreads-first, own-keys-win" because the typical
templates intent ("apply defaults, then override") works regardless of
where the user puts `${ref}` in the literal. Documented inline.

7 tests: simple merge, override-wins, items-array DRY, multiple spreads
compose, spread of non-object is no-op, spread of unknown ref is
graceful, no spurious parse errors.

Touches 4 files - revert via `git revert HEAD` cleanly removes the
feature without affecting the merge or += commits before it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three lessons that exercise the parser features added in the three
preceding commits.

- 20-include: regex check (`include "..."`), explicit caveat in the
  intro that the playground warns because there's no real file system;
  syntax check still passes
- 21-array-append: builds on lesson 10's two-item menu pattern but uses
  `+=` to append; shape-check verifies BOTH items survive
- 22-object-merge: two `defaults { ... }` blocks merging into one
  resolved object; cross-references the same rule firing on `include`
  for the base+override pattern

19-click-types -> 20-include. 22-object-merge is the new tail until
batch 7 (defaults-merge / optional-subs / placeholders).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… placeholders)

Three lessons closing out the templates topic.

- 23-defaults-merge: classic DRY pattern - define `defaults { ... }` and
  spread `${defaults}` into each item via the inline-spread feature
  (commit aeb73db). Shape-check verifies both items inherit material AND
  the second item's own `name` overrides the default (own-keys-win).
- 24-optional-subs: `${?var}` form silently absent when missing.
  Regex-checked because the lesson teaches the LITERAL form, not just
  the resolved value (writing `subtitle = null` would also resolve to
  null but wouldn't teach the feature).
- 25-placeholders: contrasts `${var}` (HOCON parse-time) with `%name%`
  (PlaceholderAPI runtime); shape `matches` checks the literal text
  survives into the resolved value (the parser doesn't touch it).

22-object-merge -> 23-defaults-merge. 25-placeholders is the new tail
until batch 8 (conditions: rules + bindings).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Final lesson batch. Closes the curriculum at 29 lessons across 6 topics.

- 26-rules-permission: introduces `rules { ... }` as a click-gating
  block sibling of `actions`; lists the most common rule kinds
  (permission, money, levels, gamemode, world). Notes AND-semantics for
  multi-rule blocks.
- 27-bindings: per-player visual variants via `bindings = [{ rules,
  props }]`. Cross-references the previous lesson on rule structure;
  introduces "first match wins" tier patterns.
- 28-rules-world: rules also work at item level (gates visibility,
  not actions). Combines a world rule with the inherited click handler
  from earlier; mentions OR-via-bindings and the if/js rules for
  advanced cases.

25-placeholders -> 26-rules-permission. 28-rules-world is the final
lesson (next: null).

The full 29-lesson tutorial: 8 basics + 4 menu + 4 items + 4 actions +
6 templates + 3 conditions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lessons can now reference external sources (HOCON spec, Bukkit docs,
PlaceholderAPI registry) as proper hyperlinks instead of bare URLs.

Implementation:
- renderInline pulls code spans out via SOH/STX-delimited placeholders
  before running other transforms, then restores them with re-escaped
  contents at the end. This is necessary for ordering: the link regex
  would otherwise match `[text](url)` patterns *inside* code spans where
  the user wants the literal markdown form to display.
- Only http/https URLs render as anchors; anything else (javascript:,
  relative paths, etc.) collapses to href="#" so untrusted lesson
  contributors can't inject XSS.
- Anchors get target="_blank" rel="noopener noreferrer" so the target
  page can't reach back into the playground via window.opener.

5 new tests cover: http link, https link, javascript: neutralised,
relative path neutralised, and `[text](url)` inside backticks staying
literal. 333/333 total.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…values

The lesson previously implied durations were a generic standard. They're
HOCON's own format, defined in the spec under "Duration format" - not
ISO 8601 and not Java's Duration.parse syntax.

Updated the intro (en + ru) to:
- name the format ("HOCON's own duration format") with a link to the
  authoritative section in the lightbend/config spec
- clarify that suffixes are lowercase and case-sensitive
- list the supported short suffixes (ns/us/ms/s/m/h/d) since they're
  what users will type 95% of the time

Uses the markdown link support added in the previous commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r default

The browser-default `<a>` color (vivid #0000EE / purple visited) jars
against the rest of the muted dark palette. Switched to GitHub-style
sky blue for dark theme (#79c0ff) and the standard medium blue for
light theme (#0969da). Underline is dotted-feel via reduced opacity
text-decoration-color; becomes solid on hover so links stay visibly
clickable without screaming.

Scoped to .pg-tutorial a so the rest of the chrome (header, panels)
keeps its own conventions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The goal field in a lesson was injected as a raw text node, so backticks
in the goal showed up as literal characters instead of <code> spans:

  "Цель: Добавь три ключа: \`size\` (число 3), \`cooldown\` (длительность 5s)"

is what the user actually saw, instead of the intended formatted line.

Fix: export renderInlineMd from markdown.ts (same code/bold/link pipeline
as the paragraph form, just without the <p> wrap), and use it in
TutorialPanel.buildHead's goal block via innerHTML on a span.

The new helper is sandboxed to the same XSS protections as renderMd
(http(s) link allowlist, html escape on every text segment), so feeding
user content through innerHTML stays safe.

4 new tests cover code/bold/link/escape via the new entry point.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both lessons asked the user to write custom keys (closeOnClick, cooldown,
tags, meta) at the menu-root level, which triggered "Unknown key in
menu-root scope" warnings the moment the user reached the goal. The
warnings made the lesson feel broken even when the goal was satisfied.

03-values: drop the boolean primitive from the goal (no top-level bool
key exists in the AbstractMenus menu-root schema). Goal now teaches
number + duration with `size` and `updateInterval` - both real menu-root
fields. The intro still teaches all three primitives and points at where
booleans actually live (firework, bindings, potion settings).

04-lists-vs-objects: swap the synthetic `tags` / `meta` for the real
menu-root keys `metaList` (list) and `activators` (object with `command`
inside). Both are valid AbstractMenus configuration; the lesson keeps
its pedagogical point about list-vs-object syntax without producing
warnings.

en + ru intros, goals, asserts, and hints all updated. 337/337 tests
pass.

Note: 5 other lessons (05/06/07/22/23) still trigger menu-root warnings
because they intentionally define custom variables (`defaults`,
`itemName`, `closeAction`). That's a separate issue - solving it
properly needs a substitution-aware unknown-key check, not per-lesson
edits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lesson intros and hints already used triple-backtick fences for
multi-line code samples (e.g. the verbose-vs-dotted comparison in
05-dotted-keys), but renderMd had no handler for them - the fences
showed up as literal characters and the contents got rendered as plain
paragraphs (with backticks visible).

Implementation:
- Pre-stash fenced regions before paragraph splitting via an ETX (0x03)
  sentinel built with String.fromCharCode (keeps the source file plain
  ASCII instead of relying on raw control bytes that some editors mangle).
- Each fence becomes a single token regardless of how many blank lines
  it contains, so multi-paragraph code samples survive the split intact.
- Filter empty blocks introduced by the stash padding so we don't emit
  spurious <p></p> around fences.
- Same FENCE_MARK trick applied to the existing inline-code STASH_OPEN /
  STASH_CLOSE constants for consistency.
- Fence contents are HTML-escaped; bold/code/link transforms do NOT run
  inside, so a code sample showing markdown syntax stays literal.
- Optional language tag after opening fence (```hocon, ```ts) is parsed
  and ignored - room for a syntax-highlighting upgrade later.

CSS: .pg-tutorial pre / pre code styled separately from inline code -
its own padding, scroll on overflow, no rounded pill background on the
inner code element.

6 new tests cover: single fence, language tag, blank-line preservation,
HTML escaping, paragraph+fence+paragraph ordering, no inline transforms
inside fences.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous version asked the user to write `name = ${itemName}` at the
top level, where `name` is not a recognised menu-root key - it's an item
field. Result: a spurious "Unknown key `name` in menu-root scope" warning
even after the goal was satisfied correctly.

Restructured so the substitution is consumed inside an item:

  itemName = "Diamond Sword"
  items = [
    { slot = 4, material = DIAMOND_SWORD, name = ${itemName} }
  ]

Now `name` lives in the item scope where it's catalog-known. The custom
`itemName` at menu-root still warns (intentional substitution variable;
the broader fix for that warning class needs a substitution-aware
unknown-key check, separate work).

Both en + ru intros, goals, asserts, and hints updated. 84/84 tutorial
tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Catalog KeyDef.description was hardcoded English, so users on the ru
locale saw English text in autocomplete tooltips and hover-docs even
when the rest of the UI was Russian.

- catalog/i18n.ts: describeKeyDef(def) helper that looks up
  `<scope>.<name>` in a per-locale dictionary; falls back to the
  canonical English description if the key isn't translated. Auto-loads
  catalog/translations/<lang>.ts via import.meta.glob - dropping a new
  file there registers the locale (same convention as the UI i18n).
- catalog/translations/ru.ts: seeded ~70 high-frequency keys covering
  menu-root + item + click + actions + rules + binding + activators.
  Partial translation is fine; the helper falls back per-key.
- cm/hover-docs.ts and cm/catalog-source.ts swapped def.description for
  describeKeyDef(def). Two single-line changes.
- 4 tests: en default, ru hits seeded key, ru falls back on unseeded,
  switch-back-to-en works.

Community PRs adding new locales just need to add a translations file +
optionally add the locale code to the language selector's LANG_LABELS.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same class of fix as lesson 06: the previous version assigned `click =
${closeAction}` at the top level, where `click` is not a recognised
menu-root key (it's an item field), so the user got an "Unknown key
`click` in menu-root scope" warning even after satisfying the goal.

Restructured to use a real shop close-button shape:

  closeBtnAction { closeMenu = true }
  items = [
    { slot = 8, material = BARRIER, name = "&cClose"
      click { actions = ${closeBtnAction} }
    }
  ]

Now `click` lives in the item scope where it's catalog-known, and the
referenced object is `actions`-shaped (matching click.actions, also
catalog-known) - resolves without warnings on the recognised keys.
The custom `closeBtnAction` at menu-root still warns; that's the same
substitution-variable issue the previous fix flagged for the broader
substitution-aware unknown-key check.

Bonus: the new shape is closer to a real menu config than the synthetic
`{ close = true }` had been - users coming from real configs will
recognise the pattern.

Both en + ru intros, goals, asserts, and hints updated. 84/84 tutorial
tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…nown

Both lessons used a made-up key purely to demonstrate a HOCON syntactic
form, but the synthetic key tripped the unknown-key validator and
produced a confusing warning at the menu-root level.

- 02-separators: `meta` -> `closeActions`. Both keys are objects and the
  pedagogical point (drop the `=` before `{`) is identical, but
  `closeActions` is a real menu-root key and renders without warning.
- 24-optional-subs: `subtitle` -> `title`. The lesson teaches `${?var}`,
  which only requires the LHS to be an assignable key. Switching to
  `title` (catalog-known menu-root field) clears the warning while
  keeping the demo intact. Adjusted the intro and template-include
  example to use `customTitle` consistently.

en + ru both updated. 347/347 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
\`defaults\` is a community convention rather than a hard AbstractMenus
field, but it's the canonical name for "shared item template I substitute
into each item via \${defaults}". The DRY-pattern lessons (05-dotted-keys,
22-object-merge, 23-defaults-merge) all use it and were getting spurious
"Unknown key \`defaults\` in menu-root scope" warnings every time the
user reached the goal.

- catalog/keys.ts: added entry with valueType: 'object' and
  childrenScope: 'item' so its children also validate against item-scope
  (matching how it's actually used as an item template).
- catalog/translations/ru.ts: matching ru description.

Audit after this + the lesson 02/24 swap commit: 5 of the 7 lessons that
previously warned now resolve cleanly. Only 06 and 07 still warn, and
intentionally - they teach the user to define a *custom* substitution
variable (itemName, closeBtnAction); the proper fix is a separate
substitution-aware unknown-key check that excludes any key referenced
from \${name} elsewhere in the doc.

347/347 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lesson intros and hints carry inline HOCON examples in triple-backtick
fences, but they rendered as flat text - everything in one foreground
colour, no contrast between keys/strings/operators. Hard to read at a
glance, especially next to the editor which IS highlighted.

- highlight-hocon.ts: thin wrapper around the existing cm/hocon-tokenizer.
  Walks each line, emits HTML where every non-null token is wrapped in
  `<span class="cm-tk-${type}">`. Always HTML-escapes; safe for innerHTML.
- markdown.ts: when a fenced block has no lang or `hocon` lang, send the
  content through highlightHocon; otherwise plain escape (so ```bash etc.
  fall through cleanly). Lang is captured per-fence via an updated stash
  regex.
- playground.css: respec the .cm-tk-* palette under `.pg-tutorial pre`
  so the same colours apply outside the editor's `.cm-editor` scope.
  Both dark + light themes covered.
- 5 new highlight-hocon tests: classes appear, html escapes, comments and
  substitutions get their own classes, multi-line preserved.
- markdown tests adjusted: tests that probe structural rendering now use
  ```text to opt out of highlighting; new tests verify hocon and explicit
  `hocon` lang produce the expected token classes; ```bash falls through.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mass reformat of every lesson's intro fenced blocks, hints, and starter
templates so any HOCON object with two or more keys lands on multiple
lines, one entry per line. One-key objects keep the compact inline form.
Mixed cases (some inline, some on own lines) are normalized to one-per-
line to remove the visual jaggedness.

Done via a one-shot Python script (kept as a scratch in /tmp, not
committed): walks each .json, splits inner content by top-level commas /
newlines / semicolons (skipping strings, ${...} substitutions, and
#//-comments), expands when count >= 2, then re-anchors every line via
a port of formatHocon's bracket-depth indenter so previously partial
formatting comes out consistent.

24 lesson files touched. The transform is idempotent: re-running the
script produces no further diff. Manual spot-checks on the trickier
lessons (07-variables-object, 23-defaults-merge, 17-click-command,
12-lore, 28-rules-world) confirm comments inside fences are preserved
verbatim and nested array indentation is correct.

354/354 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The intro showed a code example with `\${closeBtnAction}` then said
"When `\${name}` resolves to an object..." - the `\${name}` placeholder
was never introduced in this lesson and reads as if a different variable
is being discussed. Renamed both the en and ru sentence to refer to
`\${closeBtnAction}` directly so the prose tracks the example.

Lessons 06 and 24 also use `\${name}` but as the FIRST mention of the
substitution syntax (introducing the pattern itself before any concrete
example), so they're left as-is.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lessons now live as 01-introduction.json through 29-rules-world.json,
matching the 1-based "Lesson N / Total" counter the user sees in the
tutorial UI. Previously the file IDs ran 00..28 while the position
counter ran 1..29, so users referencing lesson "11" in conversation
were actually on file 10-slots.json - confusing.

Mass rename via a one-shot Python script (kept in /tmp): for each .json
in lessons/, increments the leading 2-digit number, rewrites `id` and
`next` fields, remaps any cross-references in intro/hints text
(02-separators -> 03-separators, 22-object-merge -> 23-object-merge),
and updates lessons.test.ts + TutorialController.test.ts hardcoded IDs.

Anyone with progress saved under old IDs lands on the first lesson via
the existing stale-id fallback in TutorialController.enter() (commit
b5e52c4) - no broken state.

Obsidian spec also updated to match.

361/361 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…opup

Two related polish items.

1. Markdown image syntax `![alt](url)` in lesson intros and hints renders
   to an `<img loading="lazy">`. URL allowlist accepts http(s) absolute
   plus same-origin paths starting with `/` (so static assets in
   public/img/... can be referenced); javascript:, data:, and bare
   relatives all collapse to no output (or `#` for links). Image
   substitution runs before the link substitution to avoid `![alt]
   (url)` matching the link regex with a stray leading `!`.

   playground.css: .pg-tutorial img gets max-width: 100%, lazy load,
   subtle border so images sit cleanly inside the panel.

2. Lesson 11-slots intro (en + ru) now embeds /docs/img/items_slots_id.png
   right under the ascii grid - a real visual reference for the slot
   numbering instead of just text.

3. The "All lessons" popup now prefixes each row with its 1-based
   global position ("1. Introduction", "11. Slot positions", ...). Helps
   users locate themselves at a glance and matches the "Lesson N / Total"
   counter shown in the panel header.

5 new tests cover image rendering / safety + the numbering prefix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lessons can now reference the docs wiki via `[label](wiki:concept)`.
At render time the concept is resolved against a registry and the
active locale, producing a real `/docs/<lang>/.../#anchor` href.
Russian anchors are percent-encoded for href correctness.

- tutorial/wiki-links.ts: registry seeded with 11 concepts (click,
  slot, items, item-format, actions, rules, variables, placeholders,
  activators, hocon, templates). Per-locale URLs; missing locale falls
  back to en. Adding more concepts is one entry per item.
- markdown.ts: link substitution detects the `wiki:` scheme, calls
  resolveWikiUrl, falls back to `#` for unknown concepts (so the
  broken link is visible). Same `target="_blank" rel="noopener
  noreferrer"` as every other lesson link - all playground links open
  in a new tab so the tutorial state isn't lost.
- 8 new tests cover registry resolution + the markdown integration.
- 5 lessons get example wiki links seeded in their intros (en + ru):
  10-items-array, 11-slots, 12-name-and-material, 17-click-message,
  26-placeholders, 27-rules-permission. The pattern is the same for
  every other lesson - extend incrementally.

369/369 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Picks up where the previous wiki-links commit left off. Audited every
lesson, picked the most-relevant concept per intro, added the link.

Registry changes (wiki-links.ts):
- Fixed click anchor: `#click-handling` -> `#click-processing` (the real
  heading slug in menu-structure.md). Same for the ru anchor.
- Removed the made-up `slot` and `items` anchors that didn't exist in
  the docs; pointed them at real headings (`#buttons` for items, `#slot`
  on item-format for slot positions).
- Added new concepts wired to actual section anchors: `size` (menu
  properties), `display-rules`, `bindings`, `display` (item-format -
  name/lore/material/flags), `mechanics` (enchantments), `cooldown`
  (slot+cooldown section).

Lessons newly linked (en + ru):
- 09-size:        "menu" -> wiki:size
- 13-lore:        "lore" -> wiki:display
- 14-enchantments:"enchantments" -> wiki:mechanics
- 15-flags:       "flags" -> wiki:display
- 16-count-cooldown:"item" -> wiki:cooldown
- 18-click-command:"command action" -> wiki:actions
- 19-multiple-actions:"actions" -> wiki:actions
- 20-click-types: "left/right/middle/..." -> wiki:click
- 21-include:     "include" -> wiki:hocon
- 24-defaults-merge:"DRY trick" -> wiki:templates
- 25-optional-subs:"${name}" -> wiki:variables
- 28-bindings:    "bindings" -> wiki:bindings
- 29-rules-world: "rules" -> wiki:rules + "visibility" -> wiki:display-rules

369/369 tests pass. Anchor strings verified against the actual
section headings in src/content/docs/{en,ru}/general/menu-structure.md
and item-format.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Audited every standalone wiki page (actions/rules/variables/placeholders/
activators/hocon/templates) for sub-headings and added per-anchor
registry entries where they map naturally to lesson concepts.

Registry additions (all anchors verified against the actual headings in
src/content/docs/{en,ru}/...):
- action.message, action.command, action.sound, action.teleport,
  action.delay, action.bulk - per-action anchors. actions.md uses English
  headings in BOTH locales (## Message / ## Command), so slugs are
  identical for en and ru.
- templates.include - the "Templates in separate file" section, which
  is what the include lesson actually wants to point at.
- Pointed top-level entries at meaningful section anchors instead of
  page roots: rules -> #all-rules, placeholders -> #built-in-placeholders,
  activators -> #all-activators, hocon -> #basic-hocon-syntax,
  templates -> #templates-basics. Per-locale slugs differ for the
  Russian-headed sections.

Lessons updated:
- 17-click-message: "message", "command", "sound" each get their own
  per-action anchor (was just one wiki:actions for the whole list).
- 18-click-command: wiki:actions -> wiki:action.command.
- 21-include: wiki:hocon -> wiki:templates.include (more relevant target).

Added a sanity test in wiki-links.test.ts that scans every shipped
lesson's intro and hints for `wiki:concept` refs and asserts each one
exists in the registry. Catches typos and registry drift on the next
commit instead of in the browser.

370/370 tests pass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@BrainRTP BrainRTP merged commit 1748dd2 into main May 4, 2026
4 checks passed
@BrainRTP BrainRTP deleted the feature/play-ground branch May 4, 2026 06:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant