Blocks

A Block is a meaningful section of a Source, separated by a --- line. It keeps one idea, example, or task step together and can have its own identity, data, and layout.

The methodology connects readable files into paths. Blocks apply that attention to the sequence within a page.

In Player, Blocks and Beats choreograph learning and focus. A Block frames a topic or activity; Beats guide attention through its text, media, and interactions. Player derives Beats from normal content, and components can provide explicit steps such as books or chess moves.

Headings organize the text within a Block. Add a separator when the reader moves to a new topic or activity.

Choose a useful boundary

A good Block makes sense as a unit: an explanation with its example, a task with its instructions and expected result, or a question with the context needed to answer it.

  • Guides: give each substantial step a Block. Keep its command, explanation, and expected output together.
  • References: group related syntax and examples. Start a new Block for a new concept or contract; keep short notes and related links with their topic.
  • Lessons: separate the explanation, worked example, and practice activity when each is a distinct part of the learning sequence.
  • Component sources: keep each component's explanation, implementation fences, and usage example in its defining Block. Put a full composition example in a separate Block at the end.

Use enough context to make each Block understandable. A short page may need only one Block. Split a long Block where the reader's task changes, rather than at a fixed word count or every paragraph.

Write a sequence

This page has an introduction, an example, and a practice activity. The example keeps its explanation and code together. The practice Block has a stable id so another page can link to it as lesson.md#practice.

Block IDs and heading IDs share the page’s fragment namespace. Block IDs take priority: a heading named “Practice” inside a Block with id: practice receives practice-1. Repeated headings also receive an available numbered suffix. Explicit HTML IDs must also be unique. Do not repeat id="practice" on a heading inside that Block; the Block already supplies the fragment target. Compilation and lint diagnose collisions in compiled Block HTML and identify both elements and their owning Blocks.

# Your first function

A function names a reusable operation. We'll write one, then change it.

---

## Greet a reader

This function returns a greeting using the name you pass in.

```js
function greet(name) {
  return `Hello, ${name}!`
}

greet("Ada") // "Hello, Ada!"
```

---

<!-- id: practice -->

## Try another name

Call `greet` with your own name. The returned greeting should include it.

Blocks and Player flow

Player moves through Beats within each Block: headings, paragraphs, list items, code, and other meaningful elements. A Block can contain several Beats. Interactive components can define their own Beats, such as positions in a chess replay.

Focus guides attention through those Beats. Slides uses Blocks as presentation sections, with Beats providing the steps inside each section. A heading alone adds structure within the current Block; --- starts the next section.

After a substantial edit, read the page normally, then try Focus and Slides. Check that each section has enough context, related code and explanation stay together, and long sections have useful stopping points. Keep large tables and code examples intact when readers need to examine them as a whole.

Try the Player example to see the same Source in reading, Focus, and Slides modes.

Identity, data, and layout

Place Block topmatter in an HTML comment at the start of a Block:

---

<!--
id: key-idea
layout: center
-->

## One idea at a time

A Block gives related content a shared identity and layout.

Keep a first Block's metadata comment before link definitions such as [@layout] and [@origin]. Definitions placed above the comment make it ordinary body content, so its id and type are not Block metadata. For example:

<!-- id: overview
 type: summary -->

[@layout]: ./site.layout.md
[@origin]: ./template.page.md

# My page

Keep Source-wide definitions in an introductory Block that save Actions do not replace. Definitions at the end of a file belong to its last Block's body. tx.source(id).updateBlock(blockId, { body }) replaces that entire body, including its definitions. A data-only update preserves the body; a body replacement must retain any definitions it still needs. Updating Source frontmatter with data() re-serializes the frontmatter, so YAML comments and flow-list formatting are not preserved as an editing contract.

Use explicit ids for Blocks that other pages link to, include, or reference for progress. Keep those ids stable as the content moves or changes. Positional ids are useful for ordinary rendering but change when earlier Blocks are inserted.

SyntaxMeaning
--- with blank lines around itStarts a new Block. Separators inside code fences are examples, not boundaries.
<!-- id: x -->One-line topmatter at the start of a Block.
<!-- YAML map -->Multi-line topmatter. The first line must be a key: pair.
TopmatterMeaning
idBlock id, addressed as file.md#id. Defaults to its position.
typeBlock type for queries. Defaults to block.
layoutcenter, wide, media-start, or media-end.
stickytrue keeps the opening Block visible while reading; defaults to false.
any otherAuthored data, available to blocks queries as where fields.

Frontmatter at the beginning of a Source describes the whole page; its --- delimiters are not Block boundaries. Heading ids stay unique across the Source. pmx lint reports invalid topmatter YAML and unknown layouts.

A media-start or media-end Block places a standalone image, video, iframe, or figure beside its prose. Wrap an illustrated component and its caption in figure to use the same layout. The media spans the rendered prose rows, so spacing adapts when you add or remove paragraphs.

The scroll storytelling example combines this layout with a literate SVG component and progressive entrances.

Groups and displays

Consecutive Blocks can share a group. Put display on its first Block to choose an installed Plugin's presentation:

<!--
group: install
display: tabs
title: Bun
icon: terminal
-->

## Install with Bun

Run `bun install`.

---

<!-- group: install -->

## npm

Run `npm install`.

Install Tabs with bun run pmx plugins add tabs. The two Blocks become panels labelled Bun and npm. The first opens by default. Each panel keeps its authored heading, content, and layout. A group's name is local to its Source; an unmarked Block or different name ends the run. Use a new name for another group.

Only the first member declares display. Move that declaration if you move or delete the first member. Without a display, the group stays a normal sequence. pmx lint reports interrupted names, misplaced displays, and unavailable displays. Malformed groups remain readable while editing.

Display labels use Block metadata: title overrides the first heading. Block description and image likewise override their first paragraph and image. These values do not inherit from Source frontmatter. Tabs accepts an optional shared icon name and requires a title or heading for each member.

Tabs supports arrow keys, Home/End, and ordinary keyboard focus. Switching panels preserves mounted content. Explicit Block IDs keep selection stable across live reordering. A Block or heading link opens the containing panel before scrolling. Tab clicks do not add history entries or save personal state.

Print and no-JavaScript reading show the complete sequence. Focus and Slides present every member as a normal Block. A whole-Source include preserves groups inside its own boundary; a single-Block include stays standalone. Included Block and Markdown-heading IDs are scoped to the occurrence so repeated groups do not collide.

Plugins can contribute other Block displays. Grouping does not impose Tabs' selection or Player behavior on other displays.

Sticky opening Block

Keep a compact Source header visible while the rest of the document scrolls:

<!-- sticky: true -->

# Changes log

Changes to PathMX, most recent first.

---

## Latest update

The entry goes here.

The whole opening Block stays beneath the site header, within the Source’s column. Links to later Blocks and headings leave room for it. Keep the opening content brief, especially for phones; it does not collapse into a smaller title. sticky composes with layout and accepts only true or false. Only the opening Block may opt in. Included Blocks do not bring sticky behavior into another Source. Focus, Slides, and print present it as an ordinary Block.

Conditional Blocks

Use when: in Block topmatter to make a Block depend on another Block's output. References use $block-id.output-name; each listed condition must match.

id: preparation
when:
  $approach.mode: { in: [solo, together] }
  $rating.score: { gte: 4 }

Scalar values mean equality: $approach.mode: solo is equivalent to $approach.mode: { eq: solo }.

ComparisonMeaning
eq: valueEqual to the scalar value
in: [value, value]Equal to one of the listed scalar values
contains: valueArray includes this scalar value
containsAny: [value, value]Array includes at least one listed scalar value
lt: number / lte: numberLess than / less than or equal to the number
gt: number / gte: numberGreater than / greater than or equal to the number

Each comparison object contains one operator. in lists must be nonempty. An empty containsAny list never matches. Array comparisons use strict scalar equality and do not match scalar outputs; in still compares a scalar against alternatives. Numbers, strings and booleans retain their types: the string "4" does not satisfy gte: 4. Numeric operands must be finite. Missing output never matches; an explicit null can match scalar equality or eq: null.

An inactive Block contributes no rendered content, active outputs or completion requirements. Its saved answers remain available if the Block becomes active again. Circular Block dependencies are rejected.