Skip to content

Examples

The package ships two templates: a small starter and a complete Physics map. This documentation also uses a map of the onboarding-map project. Each example is live beside the file that produces it. Open the map full screen to explore it; the download link gives you its TypeScript source.

The smallest useful starting point: two regions, a few items and two stages. This is what init copies into a new project. Replace it with your subject, or ask your agent to expand it.

StarterYour first week — a laptop, a change, and the review that merges it.
import { defineMap, docLink } from 'onboarding-map';

/*
 * A starting point: two regions, a handful of items and two stages. Replace it
 * with your own subject, or ask your coding agent to (see AGENTS.md).
 *
 * The map has two layers:
 *  - the TERRITORY: regions (domains) → groups (categories) → items.
 *    Every item is on the route ('path'), a choice not taken ('alternative'),
 *    or worth knowing it exists ('context').
 *  - the JOURNEY: stages, each Do → Observe → Read, that pass through the
 *    route's items and together build the goal.
 */

const docs = docLink('vendor');

export default defineMap({
  id: 'starter',
  version: '0.1.0',
  title: 'Your first week',
  motto: 'Do, observe, read, repeat.',
  labels: {
    intro:
      'Everything you will meet in your first week. Follow the route; the rest is territory to recognise.',
    period: 'Day',
  },

  kinds: [
    { id: 'tool', label: 'Tool', plural: 'Tools', description: 'Something you install and use.' },
    { id: 'concept', label: 'Concept', plural: 'Concepts', description: 'An idea you need to understand.' },
  ],
  periods: [{ period: 1, title: 'Getting set up', summary: 'From an empty laptop to your first change.' }],

  goal: {
    title: 'Your first change',
    statement: 'A small change of yours, reviewed and merged.',
    doneWhen: 'Your change is merged and you can explain every step it went through.',
    modules: [
      {
        id: 'workstation',
        title: 'Workstation',
        purpose: 'Everything else happens on it.',
        dependsOn: [],
        builtFrom: ['editor', 'git'],
        produces: ['An editor and git, set up and working'],
      },
      {
        id: 'change',
        title: 'Merged change',
        purpose: 'Proof that the whole loop works for you.',
        dependsOn: ['workstation'],
        relations: [{ to: 'workstation', verb: 'is made on' }],
        builtFrom: ['pull-request'],
        produces: ['A merged pull request'],
      },
    ],
  },

  domains: [
    { id: 'tools', label: 'Tools', tagline: 'What you work with', order: 0, color: '#3b82f6' },
    { id: 'process', label: 'Process', tagline: 'How work gets in', order: 1, color: '#10b981' },
  ],

  nodes: [
    {
      id: 'editors',
      label: 'Editor',
      domain: 'tools',
      kind: 'category',
      status: 'path',
      summary: 'Where you write code.',
    },
    {
      id: 'editor',
      label: 'VS Code',
      domain: 'tools',
      kind: 'tool',
      status: 'path',
      parent: 'editors',
      stage: 'setup',
      summary: 'The editor the team uses, with the extensions in the repository recommendations.',
      docs: [docs('VS Code docs', 'https://code.visualstudio.com/docs')],
    },
    {
      id: 'other-editor',
      label: 'JetBrains IDEs',
      domain: 'tools',
      kind: 'tool',
      status: 'alternative',
      alternativeTo: 'editor',
      parent: 'editors',
      summary: 'Works too; the team settings are only maintained for VS Code.',
    },
    {
      id: 'vcs',
      label: 'Version control',
      domain: 'tools',
      kind: 'category',
      status: 'path',
      summary: 'Keeping history.',
    },
    {
      id: 'git',
      label: 'Git',
      domain: 'tools',
      kind: 'tool',
      status: 'path',
      parent: 'vcs',
      stage: 'setup',
      summary: 'Records every change, so work can be shared, reviewed and undone.',
      docs: [docs('Pro Git', 'https://git-scm.com/book')],
    },
    {
      id: 'review',
      label: 'Review',
      domain: 'process',
      kind: 'category',
      status: 'path',
      summary: 'Getting work in.',
    },
    {
      id: 'pull-request',
      label: 'Pull request',
      domain: 'process',
      kind: 'concept',
      status: 'path',
      parent: 'review',
      stage: 'first-change',
      summary: 'A proposed change that others read and approve before it is merged.',
      docs: [docs('About pull requests', 'https://docs.github.com/en/pull-requests')],
    },
    {
      id: 'ci',
      label: 'CI checks',
      domain: 'process',
      kind: 'concept',
      status: 'context',
      parent: 'review',
      summary: 'Tests that run on every pull request. You will see them; you do not need to change them yet.',
    },
  ],

  edges: [{ from: 'pull-request', to: 'git', kind: 'builds-on' }],

  stages: [
    {
      id: 'setup',
      period: 1,
      order: 1,
      title: 'Set up',
      feeling: 'My laptop is ready and I know where the code lives.',
      task: 'Install the editor and git, and clone the repository.',
      delivers: ['workstation'],
      waypoints: ['editor', 'git'],
      do: [
        'Install VS Code and the recommended extensions.',
        { text: 'Clone the repository.', tip: 'git clone <url>' },
      ],
      observe: [{ text: 'What does `git log` show you?', note: true }],
      read: ['editor', 'git'],
      checkpoint: 'The project opens in your editor and `git status` is clean.',
    },
    {
      id: 'first-change',
      period: 1,
      order: 2,
      title: 'First change',
      feeling: 'I got a change reviewed and merged.',
      task: 'Make a small change and take it through review.',
      delivers: ['change'],
      waypoints: ['pull-request'],
      do: ['Fix a typo in the README on a new branch.', 'Open a pull request.'],
      observe: [{ text: 'Which checks ran on your pull request?', nodes: ['pull-request'], note: true }],
      read: ['pull-request', 'ci'],
      checkpoint: 'Your pull request is merged.',
    },
  ],
});
import { defineMap, docLink } from 'onboarding-map';

/*
 * A starting point: two regions, a handful of items and two stages. Replace it
 * with your own subject, or ask your coding agent to (see AGENTS.md).
 *
 * The map has two layers:
 *  - the TERRITORY: regions (domains) → groups (categories) → items.
 *    Every item is on the route ('path'), a choice not taken ('alternative'),
 *    or worth knowing it exists ('context').
 *  - the JOURNEY: stages, each Do → Observe → Read, that pass through the
 *    route's items and together build the goal.
 */

const docs = docLink('vendor');

export default defineMap({
  id: 'starter',
  version: '0.1.0',
  title: 'Your first week',
  motto: 'Do, observe, read, repeat.',
  labels: {
    intro:
      'Everything you will meet in your first week. Follow the route; the rest is territory to recognise.',
    period: 'Day',
  },

  kinds: [
    { id: 'tool', label: 'Tool', plural: 'Tools', description: 'Something you install and use.' },
    { id: 'concept', label: 'Concept', plural: 'Concepts', description: 'An idea you need to understand.' },
  ],
  periods: [{ period: 1, title: 'Getting set up', summary: 'From an empty laptop to your first change.' }],

  goal: {
    title: 'Your first change',
    statement: 'A small change of yours, reviewed and merged.',
    doneWhen: 'Your change is merged and you can explain every step it went through.',
    modules: [
      {
        id: 'workstation',
        title: 'Workstation',
        purpose: 'Everything else happens on it.',
        dependsOn: [],
        builtFrom: ['editor', 'git'],
        produces: ['An editor and git, set up and working'],
      },
      {
        id: 'change',
        title: 'Merged change',
        purpose: 'Proof that the whole loop works for you.',
        dependsOn: ['workstation'],
        relations: [{ to: 'workstation', verb: 'is made on' }],
        builtFrom: ['pull-request'],
        produces: ['A merged pull request'],
      },
    ],
  },

  domains: [
    { id: 'tools', label: 'Tools', tagline: 'What you work with', order: 0, color: '#3b82f6' },
    { id: 'process', label: 'Process', tagline: 'How work gets in', order: 1, color: '#10b981' },
  ],

  nodes: [
    {
      id: 'editors',
      label: 'Editor',
      domain: 'tools',
      kind: 'category',
      status: 'path',
      summary: 'Where you write code.',
    },
    {
      id: 'editor',
      label: 'VS Code',
      domain: 'tools',
      kind: 'tool',
      status: 'path',
      parent: 'editors',
      stage: 'setup',
      summary: 'The editor the team uses, with the extensions in the repository recommendations.',
      docs: [docs('VS Code docs', 'https://code.visualstudio.com/docs')],
    },
    {
      id: 'other-editor',
      label: 'JetBrains IDEs',
      domain: 'tools',
      kind: 'tool',
      status: 'alternative',
      alternativeTo: 'editor',
      parent: 'editors',
      summary: 'Works too; the team settings are only maintained for VS Code.',
    },
    {
      id: 'vcs',
      label: 'Version control',
      domain: 'tools',
      kind: 'category',
      status: 'path',
      summary: 'Keeping history.',
    },
    {
      id: 'git',
      label: 'Git',
      domain: 'tools',
      kind: 'tool',
      status: 'path',
      parent: 'vcs',
      stage: 'setup',
      summary: 'Records every change, so work can be shared, reviewed and undone.',
      docs: [docs('Pro Git', 'https://git-scm.com/book')],
    },
    {
      id: 'review',
      label: 'Review',
      domain: 'process',
      kind: 'category',
      status: 'path',
      summary: 'Getting work in.',
    },
    {
      id: 'pull-request',
      label: 'Pull request',
      domain: 'process',
      kind: 'concept',
      status: 'path',
      parent: 'review',
      stage: 'first-change',
      summary: 'A proposed change that others read and approve before it is merged.',
      docs: [docs('About pull requests', 'https://docs.github.com/en/pull-requests')],
    },
    {
      id: 'ci',
      label: 'CI checks',
      domain: 'process',
      kind: 'concept',
      status: 'context',
      parent: 'review',
      summary: 'Tests that run on every pull request. You will see them; you do not need to change them yet.',
    },
  ],

  edges: [{ from: 'pull-request', to: 'git', kind: 'builds-on' }],

  stages: [
    {
      id: 'setup',
      period: 1,
      order: 1,
      title: 'Set up',
      feeling: 'My laptop is ready and I know where the code lives.',
      task: 'Install the editor and git, and clone the repository.',
      delivers: ['workstation'],
      waypoints: ['editor', 'git'],
      do: [
        'Install VS Code and the recommended extensions.',
        { text: 'Clone the repository.', tip: 'git clone <url>' },
      ],
      observe: [{ text: 'What does `git log` show you?', note: true }],
      read: ['editor', 'git'],
      checkpoint: 'The project opens in your editor and `git status` is clean.',
    },
    {
      id: 'first-change',
      period: 1,
      order: 2,
      title: 'First change',
      feeling: 'I got a change reviewed and merged.',
      task: 'Make a small change and take it through review.',
      delivers: ['change'],
      waypoints: ['pull-request'],
      do: ['Fix a typo in the README on a new branch.', 'Open a pull request.'],
      observe: [{ text: 'Which checks ran on your pull request?', nodes: ['pull-request'], note: true }],
      read: ['pull-request', 'ci'],
      checkpoint: 'Your pull request is merged.',
    },
  ],
});

A complete example outside software. It shows how regions, route items, stages and a goal fit together for introductory mechanics.

PhysicsMotion, forces and energy, mapped for a newcomer to the subject.
import { defineMap, docLink, ref } from 'onboarding-map';

/*
 * Introductory mechanics for a first-year student: from describing motion to
 * predicting it with forces and with energy. A small, complete example of a
 * map outside software.
 */

const wiki = docLink('wikipedia', 'https://en.wikipedia.org/wiki/');

export default defineMap({
  id: 'intro-mechanics',
  version: '0.1.0',
  title: 'Introductory mechanics',
  motto: 'Predict first, then check.',
  labels: {
    intro:
      'Four weeks from describing motion to predicting it. Follow the route; the rest of the map is physics you will meet later.',
    period: 'Week',
    tip: { label: 'Worked example', hint: 'Hint' },
    aria: { map: 'Map of classical mechanics' },
    refs: { heading: 'In the textbook' },
    sources: { wikipedia: 'Wikipedia', textbook: 'Textbook' },
  },

  kinds: [
    { id: 'concept', label: 'Concept', plural: 'Concepts', description: 'A quantity or idea used to describe motion.' },
    { id: 'law', label: 'Law', plural: 'Laws', description: 'A statement about nature that experiments keep confirming.' },
    { id: 'method', label: 'Method', plural: 'Methods', description: 'A way of working out an answer.' },
  ],
  periods: [
    { period: 1, title: 'Describing motion', summary: 'Where things are, how fast, and how that changes.' },
    { period: 2, title: 'Causes of motion', summary: 'Forces, and what energy lets you skip.' },
  ],
  documents: [
    {
      id: 'openstax',
      title: 'OpenStax University Physics, Volume 1',
      date: '2016-09-19',
      audience: 'First-year university students',
      url: 'https://openstax.org/details/books/university-physics-volume-1',
    },
  ],

  goal: {
    title: 'Predicting motion',
    statement: 'You can predict how an object moves from the forces on it, and check the answer with energy.',
    doneWhen: 'Given a ball thrown off a cliff or a block sliding down a ramp, you can predict where and how fast, two ways.',
    story:
      'Motion is described with vectors that change in time. Forces change that motion, by Newton’s second law. Energy is a shortcut: when it is conserved, it gives speeds without following the motion step by step.',
    modules: [
      {
        id: 'kinematics',
        title: 'Describing motion',
        purpose: 'You can only predict what you can describe.',
        dependsOn: [],
        builtFrom: ['vectors', 'derivatives', 'velocity', 'acceleration'],
        produces: ['Position, velocity and acceleration of anything, as vectors'],
        refs: [ref('openstax')],
      },
      {
        id: 'projectiles',
        title: 'Projectiles',
        purpose: 'The first motion you can predict completely.',
        dependsOn: ['kinematics'],
        relations: [{ to: 'kinematics', verb: 'applies' }],
        builtFrom: ['constant-acceleration', 'free-fall', 'projectile-motion'],
        produces: ['Where a thrown object lands, and when'],
      },
      {
        id: 'dynamics',
        title: 'Forces',
        purpose: 'Explains why the acceleration is what it is.',
        dependsOn: ['kinematics'],
        relations: [{ to: 'kinematics', verb: 'explains' }],
        builtFrom: ['newtons-first', 'newtons-second', 'free-body-diagram', 'friction'],
        produces: ['The acceleration of an object from the forces on it'],
      },
      {
        id: 'energy',
        title: 'Energy',
        purpose: 'Answers “how fast?” without following the motion.',
        dependsOn: ['dynamics'],
        relations: [{ to: 'dynamics', verb: 'shortcuts' }],
        builtFrom: ['work', 'kinetic-energy', 'potential-energy', 'energy-conservation'],
        produces: ['Speeds from heights, and a second check on every answer'],
      },
    ],
  },

  domains: [
    { id: 'math', order: 0, label: 'Mathematical tools', shortLabel: 'Math', color: '#6B7280', tagline: 'The language mechanics is written in.' },
    { id: 'kinematics', order: 1, label: 'Kinematics', color: '#2563EB', tagline: 'Describing motion without asking why.' },
    { id: 'dynamics', order: 2, label: 'Dynamics', color: '#DC2626', tagline: 'Forces and how they change motion.' },
    { id: 'conservation', order: 3, label: 'Conservation laws', shortLabel: 'Conservation', color: '#059669', tagline: 'Quantities that stay the same, and what they let you skip.' },
    { id: 'beyond', order: 4, label: 'Beyond mechanics', shortLabel: 'Beyond', color: '#7C3AED', tagline: 'Where classical mechanics stops working.' },
  ],

  nodes: [
    // Mathematical tools
    { id: 'math-basics', label: 'Calculus and vectors', domain: 'math', kind: 'category', status: 'path', summary: 'The two tools every later idea uses.' },
    {
      id: 'vectors', label: 'Vectors', domain: 'math', kind: 'concept', status: 'path', parent: 'math-basics', stage: 'describe',
      summary: 'Quantities with a size and a direction, like a displacement of 3 m north-east. Added tip to tail, and split into x and y components.',
      docs: [wiki('Vectors', 'Euclidean_vector')], refs: [ref('openstax')],
    },
    {
      id: 'derivatives', label: 'Derivatives', domain: 'math', kind: 'method', status: 'path', parent: 'math-basics', stage: 'describe',
      summary: 'How fast one quantity changes as another does. Velocity is the derivative of position with respect to time.',
      docs: [wiki('Derivative', 'Derivative')], refs: [ref('openstax')],
    },
    {
      id: 'integrals', label: 'Integrals', domain: 'math', kind: 'method', status: 'context', parent: 'math-basics',
      summary: 'The reverse of a derivative: from a velocity over time back to the distance covered.',
    },
    {
      id: 'units', label: 'Dimensional analysis', domain: 'math', kind: 'method', status: 'context', parent: 'math-basics',
      summary: 'Checking that the units on both sides of an equation match. Catches most algebra mistakes.',
    },

    // Kinematics
    { id: 'describing', label: 'Describing motion', domain: 'kinematics', kind: 'category', status: 'path', summary: 'The quantities.' },
    {
      id: 'velocity', label: 'Velocity', domain: 'kinematics', kind: 'concept', status: 'path', parent: 'describing', stage: 'describe',
      summary: 'How fast position changes, and in which direction. Speed is its size.',
      docs: [wiki('Velocity', 'Velocity')], refs: [ref('openstax')],
    },
    {
      id: 'acceleration', label: 'Acceleration', domain: 'kinematics', kind: 'concept', status: 'path', parent: 'describing', stage: 'describe',
      summary: 'How fast velocity changes. Braking, speeding up and turning are all acceleration.',
      docs: [wiki('Acceleration', 'Acceleration')], refs: [ref('openstax')],
    },
    {
      id: 'reference-frames', label: 'Reference frames', domain: 'kinematics', kind: 'concept', status: 'context', parent: 'describing',
      summary: 'Motion is always measured relative to something. A ball dropped in a moving train falls straight down for the passenger.',
    },
    { id: 'solving', label: 'Solving motion', domain: 'kinematics', kind: 'category', status: 'path', summary: 'Getting positions and times.' },
    {
      id: 'constant-acceleration', label: 'Constant-acceleration equations', domain: 'kinematics', kind: 'method', status: 'path', parent: 'solving', stage: 'projectile',
      summary: 'Four equations linking position, velocity, acceleration and time when the acceleration does not change.',
      docs: [wiki('Equations of motion', 'Equations_of_motion')], refs: [ref('openstax')],
    },
    {
      id: 'numerical', label: 'Step-by-step simulation', domain: 'kinematics', kind: 'method', status: 'alternative', parent: 'solving', alternativeTo: 'constant-acceleration',
      summary: 'Advancing the motion in small time steps on a computer. Works for any force, but hides the pattern; we solve by hand first.',
      docs: [wiki('Numerical methods for ODEs', 'Numerical_methods_for_ordinary_differential_equations')],
    },
    {
      id: 'free-fall', label: 'Free fall', domain: 'kinematics', kind: 'concept', status: 'path', parent: 'solving', stage: 'projectile',
      summary: 'Near the ground everything falls with the same acceleration, g ≈ 9.8 m/s², if air resistance is small.',
      docs: [wiki('Free fall', 'Free_fall')], refs: [ref('openstax')],
    },
    {
      id: 'projectile-motion', label: 'Projectile motion', domain: 'kinematics', kind: 'concept', status: 'path', parent: 'solving', stage: 'projectile',
      summary: 'A thrown object moves steadily sideways while falling freely. Treating the two directions separately is the whole trick.',
      docs: [wiki('Projectile motion', 'Projectile_motion')], refs: [ref('openstax')],
    },
    {
      id: 'circular-motion', label: 'Circular motion', domain: 'kinematics', kind: 'concept', status: 'context', parent: 'solving',
      summary: 'Moving in a circle at constant speed is still acceleration, pointing to the centre.',
    },

    // Dynamics
    { id: 'newton', label: 'Newton’s laws', domain: 'dynamics', kind: 'category', status: 'path', summary: 'Why motion changes.' },
    {
      id: 'newtons-first', label: 'First law', domain: 'dynamics', kind: 'law', status: 'path', parent: 'newton', stage: 'forces',
      summary: 'Without a net force, an object keeps its velocity. Motion does not need a cause; changes in motion do.',
      docs: [wiki('Newton’s laws of motion', 'Newton%27s_laws_of_motion')], refs: [ref('openstax')],
    },
    {
      id: 'newtons-second', label: 'Second law', domain: 'dynamics', kind: 'law', status: 'path', parent: 'newton', stage: 'forces',
      summary: 'Net force equals mass times acceleration, F = ma. The bridge from forces to the kinematics you already know.',
      docs: [wiki('Newton’s laws of motion', 'Newton%27s_laws_of_motion')], refs: [ref('openstax')],
    },
    {
      id: 'newtons-third', label: 'Third law', domain: 'dynamics', kind: 'law', status: 'context', parent: 'newton',
      summary: 'Forces come in pairs: if A pushes B, B pushes A equally hard the other way.',
    },
    {
      id: 'lagrangian', label: 'Lagrangian mechanics', domain: 'dynamics', kind: 'method', status: 'alternative', parent: 'newton', alternativeTo: 'newtons-second',
      summary: 'The same physics derived from energies instead of forces. More powerful for complicated systems; forces are easier to picture first.',
      docs: [wiki('Lagrangian mechanics', 'Lagrangian_mechanics')],
    },
    { id: 'forces', label: 'Kinds of force', domain: 'dynamics', kind: 'category', status: 'path', summary: 'What pushes and pulls.' },
    {
      id: 'free-body-diagram', label: 'Free-body diagram', domain: 'dynamics', kind: 'method', status: 'path', parent: 'forces', stage: 'forces',
      summary: 'A sketch of one object with every force on it as an arrow. Almost every dynamics problem starts here.',
      docs: [wiki('Free body diagram', 'Free_body_diagram')], refs: [ref('openstax')],
    },
    {
      id: 'friction', label: 'Friction', domain: 'dynamics', kind: 'concept', status: 'path', parent: 'forces', stage: 'forces',
      summary: 'The force that resists sliding, roughly proportional to how hard the surfaces are pressed together.',
      docs: [wiki('Friction', 'Friction')], refs: [ref('openstax')],
    },
    { id: 'springs', label: 'Spring force', domain: 'dynamics', kind: 'law', status: 'context', parent: 'forces', summary: 'A spring pulls back in proportion to how far it is stretched (Hooke’s law).' },
    { id: 'drag', label: 'Air resistance', domain: 'dynamics', kind: 'concept', status: 'context', parent: 'forces', summary: 'Grows with speed; the reason a feather and a hammer fall differently on Earth.' },

    // Conservation laws
    { id: 'energy-group', label: 'Energy', domain: 'conservation', kind: 'category', status: 'path', summary: 'The quantity that is never lost.' },
    {
      id: 'work', label: 'Work', domain: 'conservation', kind: 'concept', status: 'path', parent: 'energy-group', stage: 'energy',
      summary: 'Force times distance moved along it. The way forces put energy into or take it out of an object.',
      docs: [wiki('Work', 'Work_(physics)')], refs: [ref('openstax')],
    },
    {
      id: 'kinetic-energy', label: 'Kinetic energy', domain: 'conservation', kind: 'concept', status: 'path', parent: 'energy-group', stage: 'energy',
      summary: 'Energy of motion, ½mv². Doubling the speed quadruples it.',
      docs: [wiki('Kinetic energy', 'Kinetic_energy')], refs: [ref('openstax')],
    },
    {
      id: 'potential-energy', label: 'Potential energy', domain: 'conservation', kind: 'concept', status: 'path', parent: 'energy-group', stage: 'energy',
      summary: 'Energy stored by position, like mgh for height above the ground.',
      docs: [wiki('Potential energy', 'Potential_energy')], refs: [ref('openstax')],
    },
    {
      id: 'energy-conservation', label: 'Conservation of energy', domain: 'conservation', kind: 'law', status: 'path', parent: 'energy-group', stage: 'energy',
      summary: 'Without friction, kinetic plus potential energy stays the same. With it, the missing energy has become heat.',
      docs: [wiki('Conservation of energy', 'Conservation_of_energy')], refs: [ref('openstax')],
    },
    { id: 'momentum-group', label: 'Momentum', domain: 'conservation', kind: 'category', status: 'context', summary: 'The other conserved quantity.' },
    { id: 'momentum', label: 'Momentum', domain: 'conservation', kind: 'concept', status: 'context', parent: 'momentum-group', summary: 'Mass times velocity. Conserved when no outside force acts, which makes collisions solvable.' },
    { id: 'collisions', label: 'Collisions', domain: 'conservation', kind: 'concept', status: 'context', parent: 'momentum-group', summary: 'Momentum is always conserved in a collision; kinetic energy only in elastic ones.' },
    { id: 'angular-momentum', label: 'Angular momentum', domain: 'conservation', kind: 'concept', status: 'context', parent: 'momentum-group', summary: 'Why a spinning skater speeds up when pulling in their arms.' },

    // Beyond mechanics
    { id: 'limits', label: 'Where it breaks down', domain: 'beyond', kind: 'category', status: 'context', summary: 'Newton is an approximation.' },
    { id: 'relativity', label: 'Special relativity', domain: 'beyond', kind: 'concept', status: 'context', parent: 'limits', summary: 'Near the speed of light, time and length depend on who measures them.' },
    { id: 'quantum', label: 'Quantum mechanics', domain: 'beyond', kind: 'concept', status: 'context', parent: 'limits', summary: 'At atomic sizes, position and velocity cannot both be known exactly.' },
    { id: 'chaos', label: 'Chaos', domain: 'beyond', kind: 'concept', status: 'context', parent: 'limits', summary: 'Some systems follow Newton exactly yet cannot be predicted far ahead, like a double pendulum.' },
  ],

  edges: [
    { from: 'velocity', to: 'derivatives', kind: 'is-defined-by' },
    { from: 'newtons-second', to: 'acceleration', kind: 'determines' },
    { from: 'free-fall', to: 'newtons-second', kind: 'follows-from', label: 'follows from' },
    { from: 'work', to: 'kinetic-energy', kind: 'changes' },
    { from: 'friction', to: 'energy-conservation', kind: 'turns-energy-into-heat', label: 'turns energy into heat in' },
    { from: 'integrals', to: 'constant-acceleration', kind: 'derives' },
    { from: 'momentum', to: 'newtons-third', kind: 'follows-from' },
  ],

  stages: [
    {
      id: 'describe', period: 1, order: 1,
      title: 'Describe a motion',
      feeling: 'I can describe how something moves with numbers, not words.',
      task: 'Film something moving, measure its position over time and turn that into velocity and acceleration.',
      delivers: ['kinematics'],
      waypoints: ['vectors', 'derivatives', 'velocity', 'acceleration'],
      do: [
        'Film a ball rolling across a table next to a ruler, with your phone.',
        { text: 'Step through the video and write down the position every 0.1 s.', tip: 'Most phones can play video frame by frame; at 30 fps, 0.1 s is every third frame.', tipKind: 'reveal' },
        { text: 'Work out the velocity between each pair of measurements.', nodes: ['velocity', 'derivatives'] },
      ],
      observe: [
        { text: 'Is the velocity the same everywhere? Where does it change?', note: true, nodes: ['acceleration'] },
        { text: 'What would the numbers look like for a ball rolling downhill?', note: true },
      ],
      read: ['vectors', 'derivatives', 'velocity', 'acceleration', 'reference-frames'],
      checkpoint: 'From your own table of positions you can give the ball’s velocity and say whether it accelerated.',
      refs: [ref('openstax')],
    },
    {
      id: 'projectile', period: 1, order: 2,
      title: 'Predict a throw',
      feeling: 'I predicted where something would land before it did.',
      task: 'Predict where a ball rolled off a table lands, then check.',
      delivers: ['projectiles'],
      waypoints: ['constant-acceleration', 'free-fall', 'projectile-motion'],
      do: [
        'Measure the table height and the speed of a ball rolling across it (as in stage 1).',
        { text: 'Predict how far from the table the ball lands.', tip: 'Fall time from the height: h = ½gt². Distance = speed × that time.', tipKind: 'reveal', nodes: ['projectile-motion'] },
        'Mark the spot, roll the ball, and see.',
      ],
      observe: [
        { text: 'How far off was your prediction? What could explain the difference?', note: true },
        { text: 'Did the sideways speed change during the fall?', nodes: ['projectile-motion'] },
      ],
      read: ['constant-acceleration', 'free-fall', 'projectile-motion', 'numerical', 'drag'],
      checkpoint: 'Your prediction lands within a few centimetres, and you can explain why sideways and downwards are separate.',
      refs: [ref('openstax')],
    },
    {
      id: 'forces', period: 2, order: 3,
      title: 'Find the forces',
      feeling: 'I can explain an acceleration from the forces that cause it.',
      task: 'Pull a block along a table with a spring scale and explain its motion with Newton’s second law.',
      delivers: ['dynamics'],
      waypoints: ['newtons-first', 'newtons-second', 'free-body-diagram', 'friction'],
      do: [
        'Pull a block steadily with a spring scale and note the force needed to keep it moving at constant speed.',
        { text: 'Draw the free-body diagram for the block.', nodes: ['free-body-diagram'] },
        { text: 'Pull harder and work out the acceleration you expect.', tip: 'Net force = pull − friction; a = net force / mass.', tipKind: 'reveal', nodes: ['newtons-second'] },
      ],
      observe: [
        { text: 'At constant speed, what is the net force? Why?', note: true, nodes: ['newtons-first'] },
        { text: 'Does friction change when you pull harder?', note: true, nodes: ['friction'] },
      ],
      read: ['newtons-first', 'newtons-second', 'free-body-diagram', 'friction', 'newtons-third', 'lagrangian'],
      checkpoint: 'You can draw the free-body diagram and predict the block’s acceleration from the pull, its mass and friction.',
      refs: [ref('openstax')],
    },
    {
      id: 'energy', period: 2, order: 4,
      title: 'Take the shortcut',
      feeling: 'I can get a speed from a height without following the motion.',
      task: 'Predict a ball’s speed at the bottom of a ramp using energy, and compare with a force-based answer.',
      delivers: ['energy'],
      waypoints: ['work', 'kinetic-energy', 'potential-energy', 'energy-conservation'],
      do: [
        { text: 'Predict the speed at the bottom of a ramp from its height alone.', tip: 'mgh = ½mv², so v = √(2gh). The mass cancels.', tipKind: 'reveal', nodes: ['energy-conservation'] },
        'Measure the actual speed with the filming method from stage 1.',
      ],
      observe: [
        { text: 'Why did you not need the ramp’s angle?', note: true, nodes: ['potential-energy'] },
        { text: 'Where did the missing energy go, if the measured speed is lower?', note: true, nodes: ['friction', 'work'] },
      ],
      read: ['work', 'kinetic-energy', 'potential-energy', 'energy-conservation', 'momentum'],
      checkpoint: 'You can predict the speed two ways — with forces and with energy — and explain why they differ.',
      refs: [ref('openstax')],
    },
  ],
});
import { defineMap, docLink, ref } from 'onboarding-map';

/*
 * Introductory mechanics for a first-year student: from describing motion to
 * predicting it with forces and with energy. A small, complete example of a
 * map outside software.
 */

const wiki = docLink('wikipedia', 'https://en.wikipedia.org/wiki/');

export default defineMap({
  id: 'intro-mechanics',
  version: '0.1.0',
  title: 'Introductory mechanics',
  motto: 'Predict first, then check.',
  labels: {
    intro:
      'Four weeks from describing motion to predicting it. Follow the route; the rest of the map is physics you will meet later.',
    period: 'Week',
    tip: { label: 'Worked example', hint: 'Hint' },
    aria: { map: 'Map of classical mechanics' },
    refs: { heading: 'In the textbook' },
    sources: { wikipedia: 'Wikipedia', textbook: 'Textbook' },
  },

  kinds: [
    { id: 'concept', label: 'Concept', plural: 'Concepts', description: 'A quantity or idea used to describe motion.' },
    { id: 'law', label: 'Law', plural: 'Laws', description: 'A statement about nature that experiments keep confirming.' },
    { id: 'method', label: 'Method', plural: 'Methods', description: 'A way of working out an answer.' },
  ],
  periods: [
    { period: 1, title: 'Describing motion', summary: 'Where things are, how fast, and how that changes.' },
    { period: 2, title: 'Causes of motion', summary: 'Forces, and what energy lets you skip.' },
  ],
  documents: [
    {
      id: 'openstax',
      title: 'OpenStax University Physics, Volume 1',
      date: '2016-09-19',
      audience: 'First-year university students',
      url: 'https://openstax.org/details/books/university-physics-volume-1',
    },
  ],

  goal: {
    title: 'Predicting motion',
    statement: 'You can predict how an object moves from the forces on it, and check the answer with energy.',
    doneWhen: 'Given a ball thrown off a cliff or a block sliding down a ramp, you can predict where and how fast, two ways.',
    story:
      'Motion is described with vectors that change in time. Forces change that motion, by Newton’s second law. Energy is a shortcut: when it is conserved, it gives speeds without following the motion step by step.',
    modules: [
      {
        id: 'kinematics',
        title: 'Describing motion',
        purpose: 'You can only predict what you can describe.',
        dependsOn: [],
        builtFrom: ['vectors', 'derivatives', 'velocity', 'acceleration'],
        produces: ['Position, velocity and acceleration of anything, as vectors'],
        refs: [ref('openstax')],
      },
      {
        id: 'projectiles',
        title: 'Projectiles',
        purpose: 'The first motion you can predict completely.',
        dependsOn: ['kinematics'],
        relations: [{ to: 'kinematics', verb: 'applies' }],
        builtFrom: ['constant-acceleration', 'free-fall', 'projectile-motion'],
        produces: ['Where a thrown object lands, and when'],
      },
      {
        id: 'dynamics',
        title: 'Forces',
        purpose: 'Explains why the acceleration is what it is.',
        dependsOn: ['kinematics'],
        relations: [{ to: 'kinematics', verb: 'explains' }],
        builtFrom: ['newtons-first', 'newtons-second', 'free-body-diagram', 'friction'],
        produces: ['The acceleration of an object from the forces on it'],
      },
      {
        id: 'energy',
        title: 'Energy',
        purpose: 'Answers “how fast?” without following the motion.',
        dependsOn: ['dynamics'],
        relations: [{ to: 'dynamics', verb: 'shortcuts' }],
        builtFrom: ['work', 'kinetic-energy', 'potential-energy', 'energy-conservation'],
        produces: ['Speeds from heights, and a second check on every answer'],
      },
    ],
  },

  domains: [
    { id: 'math', order: 0, label: 'Mathematical tools', shortLabel: 'Math', color: '#6B7280', tagline: 'The language mechanics is written in.' },
    { id: 'kinematics', order: 1, label: 'Kinematics', color: '#2563EB', tagline: 'Describing motion without asking why.' },
    { id: 'dynamics', order: 2, label: 'Dynamics', color: '#DC2626', tagline: 'Forces and how they change motion.' },
    { id: 'conservation', order: 3, label: 'Conservation laws', shortLabel: 'Conservation', color: '#059669', tagline: 'Quantities that stay the same, and what they let you skip.' },
    { id: 'beyond', order: 4, label: 'Beyond mechanics', shortLabel: 'Beyond', color: '#7C3AED', tagline: 'Where classical mechanics stops working.' },
  ],

  nodes: [
    // Mathematical tools
    { id: 'math-basics', label: 'Calculus and vectors', domain: 'math', kind: 'category', status: 'path', summary: 'The two tools every later idea uses.' },
    {
      id: 'vectors', label: 'Vectors', domain: 'math', kind: 'concept', status: 'path', parent: 'math-basics', stage: 'describe',
      summary: 'Quantities with a size and a direction, like a displacement of 3 m north-east. Added tip to tail, and split into x and y components.',
      docs: [wiki('Vectors', 'Euclidean_vector')], refs: [ref('openstax')],
    },
    {
      id: 'derivatives', label: 'Derivatives', domain: 'math', kind: 'method', status: 'path', parent: 'math-basics', stage: 'describe',
      summary: 'How fast one quantity changes as another does. Velocity is the derivative of position with respect to time.',
      docs: [wiki('Derivative', 'Derivative')], refs: [ref('openstax')],
    },
    {
      id: 'integrals', label: 'Integrals', domain: 'math', kind: 'method', status: 'context', parent: 'math-basics',
      summary: 'The reverse of a derivative: from a velocity over time back to the distance covered.',
    },
    {
      id: 'units', label: 'Dimensional analysis', domain: 'math', kind: 'method', status: 'context', parent: 'math-basics',
      summary: 'Checking that the units on both sides of an equation match. Catches most algebra mistakes.',
    },

    // Kinematics
    { id: 'describing', label: 'Describing motion', domain: 'kinematics', kind: 'category', status: 'path', summary: 'The quantities.' },
    {
      id: 'velocity', label: 'Velocity', domain: 'kinematics', kind: 'concept', status: 'path', parent: 'describing', stage: 'describe',
      summary: 'How fast position changes, and in which direction. Speed is its size.',
      docs: [wiki('Velocity', 'Velocity')], refs: [ref('openstax')],
    },
    {
      id: 'acceleration', label: 'Acceleration', domain: 'kinematics', kind: 'concept', status: 'path', parent: 'describing', stage: 'describe',
      summary: 'How fast velocity changes. Braking, speeding up and turning are all acceleration.',
      docs: [wiki('Acceleration', 'Acceleration')], refs: [ref('openstax')],
    },
    {
      id: 'reference-frames', label: 'Reference frames', domain: 'kinematics', kind: 'concept', status: 'context', parent: 'describing',
      summary: 'Motion is always measured relative to something. A ball dropped in a moving train falls straight down for the passenger.',
    },
    { id: 'solving', label: 'Solving motion', domain: 'kinematics', kind: 'category', status: 'path', summary: 'Getting positions and times.' },
    {
      id: 'constant-acceleration', label: 'Constant-acceleration equations', domain: 'kinematics', kind: 'method', status: 'path', parent: 'solving', stage: 'projectile',
      summary: 'Four equations linking position, velocity, acceleration and time when the acceleration does not change.',
      docs: [wiki('Equations of motion', 'Equations_of_motion')], refs: [ref('openstax')],
    },
    {
      id: 'numerical', label: 'Step-by-step simulation', domain: 'kinematics', kind: 'method', status: 'alternative', parent: 'solving', alternativeTo: 'constant-acceleration',
      summary: 'Advancing the motion in small time steps on a computer. Works for any force, but hides the pattern; we solve by hand first.',
      docs: [wiki('Numerical methods for ODEs', 'Numerical_methods_for_ordinary_differential_equations')],
    },
    {
      id: 'free-fall', label: 'Free fall', domain: 'kinematics', kind: 'concept', status: 'path', parent: 'solving', stage: 'projectile',
      summary: 'Near the ground everything falls with the same acceleration, g ≈ 9.8 m/s², if air resistance is small.',
      docs: [wiki('Free fall', 'Free_fall')], refs: [ref('openstax')],
    },
    {
      id: 'projectile-motion', label: 'Projectile motion', domain: 'kinematics', kind: 'concept', status: 'path', parent: 'solving', stage: 'projectile',
      summary: 'A thrown object moves steadily sideways while falling freely. Treating the two directions separately is the whole trick.',
      docs: [wiki('Projectile motion', 'Projectile_motion')], refs: [ref('openstax')],
    },
    {
      id: 'circular-motion', label: 'Circular motion', domain: 'kinematics', kind: 'concept', status: 'context', parent: 'solving',
      summary: 'Moving in a circle at constant speed is still acceleration, pointing to the centre.',
    },

    // Dynamics
    { id: 'newton', label: 'Newton’s laws', domain: 'dynamics', kind: 'category', status: 'path', summary: 'Why motion changes.' },
    {
      id: 'newtons-first', label: 'First law', domain: 'dynamics', kind: 'law', status: 'path', parent: 'newton', stage: 'forces',
      summary: 'Without a net force, an object keeps its velocity. Motion does not need a cause; changes in motion do.',
      docs: [wiki('Newton’s laws of motion', 'Newton%27s_laws_of_motion')], refs: [ref('openstax')],
    },
    {
      id: 'newtons-second', label: 'Second law', domain: 'dynamics', kind: 'law', status: 'path', parent: 'newton', stage: 'forces',
      summary: 'Net force equals mass times acceleration, F = ma. The bridge from forces to the kinematics you already know.',
      docs: [wiki('Newton’s laws of motion', 'Newton%27s_laws_of_motion')], refs: [ref('openstax')],
    },
    {
      id: 'newtons-third', label: 'Third law', domain: 'dynamics', kind: 'law', status: 'context', parent: 'newton',
      summary: 'Forces come in pairs: if A pushes B, B pushes A equally hard the other way.',
    },
    {
      id: 'lagrangian', label: 'Lagrangian mechanics', domain: 'dynamics', kind: 'method', status: 'alternative', parent: 'newton', alternativeTo: 'newtons-second',
      summary: 'The same physics derived from energies instead of forces. More powerful for complicated systems; forces are easier to picture first.',
      docs: [wiki('Lagrangian mechanics', 'Lagrangian_mechanics')],
    },
    { id: 'forces', label: 'Kinds of force', domain: 'dynamics', kind: 'category', status: 'path', summary: 'What pushes and pulls.' },
    {
      id: 'free-body-diagram', label: 'Free-body diagram', domain: 'dynamics', kind: 'method', status: 'path', parent: 'forces', stage: 'forces',
      summary: 'A sketch of one object with every force on it as an arrow. Almost every dynamics problem starts here.',
      docs: [wiki('Free body diagram', 'Free_body_diagram')], refs: [ref('openstax')],
    },
    {
      id: 'friction', label: 'Friction', domain: 'dynamics', kind: 'concept', status: 'path', parent: 'forces', stage: 'forces',
      summary: 'The force that resists sliding, roughly proportional to how hard the surfaces are pressed together.',
      docs: [wiki('Friction', 'Friction')], refs: [ref('openstax')],
    },
    { id: 'springs', label: 'Spring force', domain: 'dynamics', kind: 'law', status: 'context', parent: 'forces', summary: 'A spring pulls back in proportion to how far it is stretched (Hooke’s law).' },
    { id: 'drag', label: 'Air resistance', domain: 'dynamics', kind: 'concept', status: 'context', parent: 'forces', summary: 'Grows with speed; the reason a feather and a hammer fall differently on Earth.' },

    // Conservation laws
    { id: 'energy-group', label: 'Energy', domain: 'conservation', kind: 'category', status: 'path', summary: 'The quantity that is never lost.' },
    {
      id: 'work', label: 'Work', domain: 'conservation', kind: 'concept', status: 'path', parent: 'energy-group', stage: 'energy',
      summary: 'Force times distance moved along it. The way forces put energy into or take it out of an object.',
      docs: [wiki('Work', 'Work_(physics)')], refs: [ref('openstax')],
    },
    {
      id: 'kinetic-energy', label: 'Kinetic energy', domain: 'conservation', kind: 'concept', status: 'path', parent: 'energy-group', stage: 'energy',
      summary: 'Energy of motion, ½mv². Doubling the speed quadruples it.',
      docs: [wiki('Kinetic energy', 'Kinetic_energy')], refs: [ref('openstax')],
    },
    {
      id: 'potential-energy', label: 'Potential energy', domain: 'conservation', kind: 'concept', status: 'path', parent: 'energy-group', stage: 'energy',
      summary: 'Energy stored by position, like mgh for height above the ground.',
      docs: [wiki('Potential energy', 'Potential_energy')], refs: [ref('openstax')],
    },
    {
      id: 'energy-conservation', label: 'Conservation of energy', domain: 'conservation', kind: 'law', status: 'path', parent: 'energy-group', stage: 'energy',
      summary: 'Without friction, kinetic plus potential energy stays the same. With it, the missing energy has become heat.',
      docs: [wiki('Conservation of energy', 'Conservation_of_energy')], refs: [ref('openstax')],
    },
    { id: 'momentum-group', label: 'Momentum', domain: 'conservation', kind: 'category', status: 'context', summary: 'The other conserved quantity.' },
    { id: 'momentum', label: 'Momentum', domain: 'conservation', kind: 'concept', status: 'context', parent: 'momentum-group', summary: 'Mass times velocity. Conserved when no outside force acts, which makes collisions solvable.' },
    { id: 'collisions', label: 'Collisions', domain: 'conservation', kind: 'concept', status: 'context', parent: 'momentum-group', summary: 'Momentum is always conserved in a collision; kinetic energy only in elastic ones.' },
    { id: 'angular-momentum', label: 'Angular momentum', domain: 'conservation', kind: 'concept', status: 'context', parent: 'momentum-group', summary: 'Why a spinning skater speeds up when pulling in their arms.' },

    // Beyond mechanics
    { id: 'limits', label: 'Where it breaks down', domain: 'beyond', kind: 'category', status: 'context', summary: 'Newton is an approximation.' },
    { id: 'relativity', label: 'Special relativity', domain: 'beyond', kind: 'concept', status: 'context', parent: 'limits', summary: 'Near the speed of light, time and length depend on who measures them.' },
    { id: 'quantum', label: 'Quantum mechanics', domain: 'beyond', kind: 'concept', status: 'context', parent: 'limits', summary: 'At atomic sizes, position and velocity cannot both be known exactly.' },
    { id: 'chaos', label: 'Chaos', domain: 'beyond', kind: 'concept', status: 'context', parent: 'limits', summary: 'Some systems follow Newton exactly yet cannot be predicted far ahead, like a double pendulum.' },
  ],

  edges: [
    { from: 'velocity', to: 'derivatives', kind: 'is-defined-by' },
    { from: 'newtons-second', to: 'acceleration', kind: 'determines' },
    { from: 'free-fall', to: 'newtons-second', kind: 'follows-from', label: 'follows from' },
    { from: 'work', to: 'kinetic-energy', kind: 'changes' },
    { from: 'friction', to: 'energy-conservation', kind: 'turns-energy-into-heat', label: 'turns energy into heat in' },
    { from: 'integrals', to: 'constant-acceleration', kind: 'derives' },
    { from: 'momentum', to: 'newtons-third', kind: 'follows-from' },
  ],

  stages: [
    {
      id: 'describe', period: 1, order: 1,
      title: 'Describe a motion',
      feeling: 'I can describe how something moves with numbers, not words.',
      task: 'Film something moving, measure its position over time and turn that into velocity and acceleration.',
      delivers: ['kinematics'],
      waypoints: ['vectors', 'derivatives', 'velocity', 'acceleration'],
      do: [
        'Film a ball rolling across a table next to a ruler, with your phone.',
        { text: 'Step through the video and write down the position every 0.1 s.', tip: 'Most phones can play video frame by frame; at 30 fps, 0.1 s is every third frame.', tipKind: 'reveal' },
        { text: 'Work out the velocity between each pair of measurements.', nodes: ['velocity', 'derivatives'] },
      ],
      observe: [
        { text: 'Is the velocity the same everywhere? Where does it change?', note: true, nodes: ['acceleration'] },
        { text: 'What would the numbers look like for a ball rolling downhill?', note: true },
      ],
      read: ['vectors', 'derivatives', 'velocity', 'acceleration', 'reference-frames'],
      checkpoint: 'From your own table of positions you can give the ball’s velocity and say whether it accelerated.',
      refs: [ref('openstax')],
    },
    {
      id: 'projectile', period: 1, order: 2,
      title: 'Predict a throw',
      feeling: 'I predicted where something would land before it did.',
      task: 'Predict where a ball rolled off a table lands, then check.',
      delivers: ['projectiles'],
      waypoints: ['constant-acceleration', 'free-fall', 'projectile-motion'],
      do: [
        'Measure the table height and the speed of a ball rolling across it (as in stage 1).',
        { text: 'Predict how far from the table the ball lands.', tip: 'Fall time from the height: h = ½gt². Distance = speed × that time.', tipKind: 'reveal', nodes: ['projectile-motion'] },
        'Mark the spot, roll the ball, and see.',
      ],
      observe: [
        { text: 'How far off was your prediction? What could explain the difference?', note: true },
        { text: 'Did the sideways speed change during the fall?', nodes: ['projectile-motion'] },
      ],
      read: ['constant-acceleration', 'free-fall', 'projectile-motion', 'numerical', 'drag'],
      checkpoint: 'Your prediction lands within a few centimetres, and you can explain why sideways and downwards are separate.',
      refs: [ref('openstax')],
    },
    {
      id: 'forces', period: 2, order: 3,
      title: 'Find the forces',
      feeling: 'I can explain an acceleration from the forces that cause it.',
      task: 'Pull a block along a table with a spring scale and explain its motion with Newton’s second law.',
      delivers: ['dynamics'],
      waypoints: ['newtons-first', 'newtons-second', 'free-body-diagram', 'friction'],
      do: [
        'Pull a block steadily with a spring scale and note the force needed to keep it moving at constant speed.',
        { text: 'Draw the free-body diagram for the block.', nodes: ['free-body-diagram'] },
        { text: 'Pull harder and work out the acceleration you expect.', tip: 'Net force = pull − friction; a = net force / mass.', tipKind: 'reveal', nodes: ['newtons-second'] },
      ],
      observe: [
        { text: 'At constant speed, what is the net force? Why?', note: true, nodes: ['newtons-first'] },
        { text: 'Does friction change when you pull harder?', note: true, nodes: ['friction'] },
      ],
      read: ['newtons-first', 'newtons-second', 'free-body-diagram', 'friction', 'newtons-third', 'lagrangian'],
      checkpoint: 'You can draw the free-body diagram and predict the block’s acceleration from the pull, its mass and friction.',
      refs: [ref('openstax')],
    },
    {
      id: 'energy', period: 2, order: 4,
      title: 'Take the shortcut',
      feeling: 'I can get a speed from a height without following the motion.',
      task: 'Predict a ball’s speed at the bottom of a ramp using energy, and compare with a force-based answer.',
      delivers: ['energy'],
      waypoints: ['work', 'kinetic-energy', 'potential-energy', 'energy-conservation'],
      do: [
        { text: 'Predict the speed at the bottom of a ramp from its height alone.', tip: 'mgh = ½mv², so v = √(2gh). The mass cancels.', tipKind: 'reveal', nodes: ['energy-conservation'] },
        'Measure the actual speed with the filming method from stage 1.',
      ],
      observe: [
        { text: 'Why did you not need the ramp’s angle?', note: true, nodes: ['potential-energy'] },
        { text: 'Where did the missing energy go, if the measured speed is lower?', note: true, nodes: ['friction', 'work'] },
      ],
      read: ['work', 'kinetic-energy', 'potential-energy', 'energy-conservation', 'momentum'],
      checkpoint: 'You can predict the speed two ways — with forces and with energy — and explain why they differ.',
      refs: [ref('openstax')],
    },
  ],
});

The map of this project. It describes the package and route through its contributor work; the docs site you are reading is built from the same one-file format.

This projectHow to make your own onboarding map, mapped with onboarding-map.
import { defineMap, docLink } from 'onboarding-map';

/*
 * A map of onboarding-map itself: how someone goes from "I have a subject to
 * onboard people into" to "a map I can send". It is the example the docs show,
 * and it is written by hand in one file, like any other map.
 */

const docs = docLink('docs', 'https://chidirnweke.github.io/onboarding-map/');
const github = docLink('github', 'https://github.com/ChidiRnweke/onboarding-map');

export default defineMap({
  id: 'onboarding-map',
  version: '0.1.0',
  title: 'Make your own onboarding map',
  motto: 'Bring your subject. Get a map.',
  labels: {
    intro: 'One file describes your subject. The package draws it as a map you can send.',
    period: 'Part',
  },

  kinds: [
    { id: 'concept', label: 'Idea', plural: 'Ideas', description: 'Something to understand.' },
    { id: 'tool', label: 'Step', plural: 'Steps', description: 'Something you do.' },
  ],
  periods: [
    {
      period: 1,
      title: 'See and describe',
      summary: 'Look at an example, then say what you are onboarding someone into.',
    },
    { period: 2, title: 'Shape and share', summary: 'Draw one path, check the map, and publish it.' },
  ],

  documents: [
    {
      id: 'docs',
      title: 'The documentation',
      date: '2026-09-27',
      url: 'https://chidirnweke.github.io/onboarding-map/',
    },
    {
      id: 'github',
      title: 'The repository',
      date: '2026-09-27',
      url: 'https://github.com/ChidiRnweke/onboarding-map',
    },
  ],

  goal: {
    title: 'A map you can send',
    statement: 'Your subject written as one file, drawn as a map a newcomer can follow.',
    doneWhen: 'Someone follows your map from the first stage to the last and ends up with something real.',
    story:
      'A map is two things at once. It is everything a newcomer will run into, so nothing feels hidden. And it is one path through that, so they are never deciding what to learn next.',
    modules: [
      {
        id: 'example',
        title: 'A walked example',
        purpose: 'You cannot judge a map until you have followed one.',
        dependsOn: [],
        builtFrom: ['starter-example', 'physics-example', 'example-data'],
        produces: ['An example you have read and clicked through'],
      },
      {
        id: 'brief',
        title: 'A brief',
        purpose: 'The map is only as good as what you decide it is about.',
        dependsOn: ['example'],
        relations: [{ to: 'example', verb: 'is clearer after' }],
        builtFrom: ['audience', 'outcome', 'sources'],
        produces: ['Who is onboarding, what they should be able to do, and where the material is'],
      },
      {
        id: 'mapfile',
        title: 'A first draft',
        purpose: 'The subject becomes a file, by hand or with an agent.',
        dependsOn: ['brief'],
        relations: [{ to: 'brief', verb: 'is written from' }],
        builtFrom: ['map-file', 'agent-skills', 'draft-prompt'],
        produces: ['A map.ts with regions and items'],
      },
      {
        id: 'route',
        title: 'One path',
        purpose: 'Choosing a single route is what keeps the map from overwhelming the reader.',
        dependsOn: ['mapfile'],
        relations: [{ to: 'mapfile', verb: 'is drawn on' }],
        builtFrom: ['route', 'stages', 'checkpoint', 'goal'],
        produces: ['A journey with a beginning, stages, and a way to know it is done'],
      },
      {
        id: 'checked',
        title: 'A checked map',
        purpose: 'Links break and content ages; the tool finds both.',
        dependsOn: ['route'],
        relations: [{ to: 'route', verb: 'checks' }],
        builtFrom: ['validate', 'audit', 'changelog'],
        produces: ['A map whose references resolve and whose content is current'],
      },
      {
        id: 'published',
        title: 'A published map',
        purpose: 'A map nobody can open is not a map.',
        dependsOn: ['checked'],
        relations: [{ to: 'checked', verb: 'is built from' }],
        builtFrom: ['build', 'host'],
        produces: ['A static site you can send to the newcomer'],
      },
    ],
  },

  domains: [
    { id: 'start', label: 'Start', tagline: 'See one first', order: 0, color: '#22c55e' },
    { id: 'subject', label: 'Your subject', tagline: 'What you bring', order: 1, color: '#14b8a6' },
    { id: 'agent', label: 'Your agent', tagline: 'The heavy lifting', order: 2, color: '#a855f7' },
    { id: 'map', label: 'The map', tagline: 'The file and its path', order: 3, color: '#3b82f6' },
    { id: 'tool', label: 'Checking and sharing', tagline: 'The tool', order: 4, color: '#f59e0b' },
    { id: 'project', label: 'This project', tagline: 'Where it comes from', order: 5, color: '#64748b' },
  ],

  nodes: [
    {
      id: 'the-idea',
      label: 'Why a map',
      domain: 'start',
      kind: 'category',
      status: 'path',
      summary: 'The problem the map solves.',
    },
    {
      id: 'newcomer-problem',
      label: 'The reading pile',
      domain: 'start',
      kind: 'concept',
      status: 'context',
      parent: 'the-idea',
      summary:
        'Everything a newcomer needs is written down somewhere, but as separate documents for people who already know the system.',
      refs: [{ doc: 'docs' }],
    },
    {
      id: 'one-route',
      label: 'One path',
      domain: 'start',
      kind: 'concept',
      status: 'context',
      parent: 'the-idea',
      summary:
        'The map shows everything, but asks the newcomer to follow only one route. The rest is there to recognise, not to learn first.',
    },

    {
      id: 'examples',
      label: 'Examples',
      domain: 'start',
      kind: 'category',
      status: 'path',
      summary: 'Two maps to look at before you make your own.',
    },
    {
      id: 'starter-example',
      refs: [{ doc: 'docs' }],
      label: 'Starter example',
      domain: 'start',
      kind: 'concept',
      status: 'path',
      parent: 'examples',
      stage: 'see',
      summary: 'The smallest useful map: two regions, a handful of items, two stages.',
      docs: [docs('The starter template', 'user-guide/examples/')],
    },
    {
      id: 'physics-example',
      refs: [{ doc: 'docs' }],
      label: 'Physics example',
      domain: 'start',
      kind: 'concept',
      status: 'path',
      parent: 'examples',
      stage: 'see',
      summary: 'A complete map outside software: motion, forces and energy, with a full route.',
      docs: [docs('The physics template', 'user-guide/examples/')],
    },
    {
      id: 'example-data',
      refs: [{ doc: 'docs' }],
      label: 'The data behind it',
      domain: 'start',
      kind: 'concept',
      status: 'path',
      parent: 'examples',
      stage: 'see',
      summary: 'Every map is one file. The examples page shows the map and the file next to each other.',
      docs: [docs('Examples, map and data', 'user-guide/examples/')],
    },

    {
      id: 'your-subject',
      label: 'Your subject',
      domain: 'subject',
      kind: 'category',
      status: 'path',
      summary: 'What you decide the map is about.',
    },
    {
      id: 'audience',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'Who is arriving',
      domain: 'subject',
      kind: 'concept',
      status: 'path',
      parent: 'your-subject',
      stage: 'brief',
      summary: 'Who is being onboarded, and what they already know. It sets the level of every summary.',
    },
    {
      id: 'outcome',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'What they can do',
      domain: 'subject',
      kind: 'concept',
      status: 'path',
      parent: 'your-subject',
      stage: 'brief',
      summary: 'The thing that exists when the onboarding is done. The stages exist to build it.',
    },
    {
      id: 'sources',
      label: 'Your material',
      docs: [docs('Working with your agent', 'user-guide/working-with-your-agent/')],
      domain: 'subject',
      kind: 'concept',
      status: 'path',
      parent: 'your-subject',
      stage: 'brief',
      summary:
        'Notes, slides, a syllabus, a codebase. The map is based on these, and cites them, so it can be refreshed later.',
      refs: [{ doc: 'docs' }],
    },
    {
      id: 'existing-docs',
      label: 'Docs you already have',
      domain: 'subject',
      kind: 'concept',
      status: 'context',
      parent: 'your-subject',
      summary:
        'Existing documentation stays where it is. The map links to it at the right moment instead of copying it.',
    },

    {
      id: 'the-file',
      label: 'The file',
      domain: 'map',
      kind: 'category',
      status: 'path',
      summary: 'One file holds the whole map.',
    },
    {
      id: 'map-file',
      refs: [{ doc: 'docs' }],
      label: 'map.ts',
      domain: 'map',
      kind: 'tool',
      status: 'path',
      parent: 'the-file',
      stage: 'draft',
      summary:
        'Your subject as one TypeScript file. Types come from the package, so mistakes show up in your editor.',
      docs: [github('The templates', '/tree/main/templates')],
    },
    {
      id: 'hand-write',
      docs: [docs('Writing a map', 'user-guide/writing-a-map/')],
      label: 'Write it yourself',
      domain: 'map',
      kind: 'tool',
      status: 'alternative',
      alternativeTo: 'map-file',
      parent: 'the-file',
      summary:
        'You can write the file by hand. Nothing here needs an agent; the format is the whole interface.',
    },
    {
      id: 'json-map',
      docs: [docs('Writing a map', 'user-guide/writing-a-map/')],
      label: 'map.json',
      domain: 'map',
      kind: 'tool',
      status: 'alternative',
      alternativeTo: 'map-file',
      parent: 'the-file',
      summary: 'Use JSON instead of TypeScript when another tool needs to write the map.',
    },

    {
      id: 'the-journey',
      label: 'The journey',
      domain: 'map',
      kind: 'category',
      status: 'path',
      summary: 'The one path through everything.',
    },
    {
      id: 'route',
      label: 'The route',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      domain: 'map',
      kind: 'concept',
      status: 'path',
      parent: 'the-journey',
      stage: 'route',
      summary:
        'The items the newcomer actually uses, in order. Everything else is context or a choice you did not take.',
    },
    {
      id: 'stages',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'Stages',
      domain: 'map',
      kind: 'concept',
      status: 'path',
      parent: 'the-journey',
      stage: 'route',
      summary:
        'The route cut into stages. Each one is small, and follows the same shape: do something, watch what happens, then read about it.',
    },
    {
      id: 'checkpoint',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'Checkpoint',
      domain: 'map',
      kind: 'concept',
      status: 'path',
      parent: 'the-journey',
      stage: 'route',
      summary: 'How the newcomer knows a stage is finished: something they can see, not a feeling.',
    },
    {
      id: 'goal',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'The goal',
      domain: 'map',
      kind: 'concept',
      status: 'path',
      parent: 'the-journey',
      stage: 'route',
      summary: 'What the whole journey builds, shown as parts that fill in as the stages go.',
    },
    {
      id: 'context-items',
      label: 'Good to know it exists',
      domain: 'map',
      kind: 'concept',
      status: 'context',
      parent: 'the-journey',
      summary: 'Items off the route. Cheap to add, and they make the map honest about what is out there.',
    },

    {
      id: 'with-an-agent',
      label: 'With an agent',
      domain: 'agent',
      kind: 'category',
      status: 'path',
      summary: 'Let a coding agent do the first draft.',
    },
    {
      id: 'agent-skills',
      refs: [{ doc: 'docs' }],
      label: 'The skills',
      domain: 'agent',
      kind: 'concept',
      status: 'path',
      parent: 'with-an-agent',
      stage: 'draft',
      summary:
        'The package installs instructions for Claude Code, Codex and other tools: how to write, edit and refresh a map.',
      docs: [github('The skills', '/tree/main/skills')],
    },
    {
      id: 'draft-prompt',
      refs: [{ doc: 'docs' }],
      docs: [docs('Working with your agent', 'user-guide/working-with-your-agent/')],
      label: 'Ask for a draft',
      domain: 'agent',
      kind: 'tool',
      status: 'path',
      parent: 'with-an-agent',
      stage: 'draft',
      summary:
        'Point the agent at your material and ask it to turn it into a map. Read the result; you decide the route.',
    },

    {
      id: 'the-checks',
      label: 'The checks',
      domain: 'tool',
      kind: 'category',
      status: 'path',
      summary: 'The command line keeps the map true.',
    },
    {
      id: 'validate',
      refs: [{ doc: 'docs' }],
      label: 'validate',
      domain: 'tool',
      kind: 'tool',
      status: 'path',
      parent: 'the-checks',
      stage: 'check',
      summary: 'Checks that every reference in the file resolves, and names the ones that do not.',
      docs: [docs('Command line', 'reference/cli/')],
    },
    {
      id: 'audit',
      refs: [{ doc: 'docs' }],
      docs: [docs('Command line', 'reference/cli/')],
      label: 'audit',
      domain: 'tool',
      kind: 'tool',
      status: 'path',
      parent: 'the-checks',
      stage: 'check',
      summary:
        'Finds the content that has gone stale: dead links, missing sources, route items nobody is sent to.',
    },
    {
      id: 'changelog',
      refs: [{ doc: 'docs' }],
      docs: [docs('Command line', 'reference/cli/')],
      label: 'changelog',
      domain: 'tool',
      kind: 'tool',
      status: 'path',
      parent: 'the-checks',
      stage: 'check',
      summary: 'Shows what changed since the last commit, as a short summary for review.',
    },
    {
      id: 'dev-server',
      label: 'See it as you write',
      domain: 'tool',
      kind: 'tool',
      status: 'context',
      parent: 'the-checks',
      summary: 'The dev command serves the map and reloads it on every save.',
    },

    {
      id: 'publish',
      label: 'Publish',
      domain: 'tool',
      kind: 'category',
      status: 'path',
      summary: 'Turn the file into a site.',
    },
    {
      id: 'build',
      refs: [{ doc: 'docs' }],
      docs: [docs('Command line', 'reference/cli/')],
      label: 'build',
      domain: 'tool',
      kind: 'tool',
      status: 'path',
      parent: 'publish',
      stage: 'share',
      summary: 'Writes a static site: your map plus the app that draws it.',
    },
    {
      id: 'host',
      refs: [{ doc: 'docs' }],
      docs: [docs('Command line', 'reference/cli/')],
      label: 'Any static host',
      domain: 'tool',
      kind: 'concept',
      status: 'path',
      parent: 'publish',
      stage: 'share',
      summary:
        'The output is plain files. There is no server and no account; progress is kept in the reader’s browser.',
    },
    {
      id: 'static-shell',
      label: 'The app shell',
      domain: 'tool',
      kind: 'concept',
      status: 'context',
      parent: 'publish',
      summary: 'One prebuilt app renders any map. Your map file is all that changes.',
    },

    {
      id: 'codebase',
      label: 'The codebase',
      domain: 'project',
      kind: 'category',
      status: 'context',
      summary: 'What the package is made of.',
    },
    {
      id: 'core',
      label: 'The model',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary: 'Types, validation and derivation. It runs in Node and the browser, and has no UI.',
      docs: [github('src/core', '/tree/main/src/core')],
    },
    {
      id: 'cli',
      label: 'The command line',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary: 'init, dev, build, validate, changelog, audit, schema and skills install.',
    },
    {
      id: 'app-shell-code',
      label: 'The app',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary: 'The SvelteKit shell that draws the map and walks the stages.',
    },
    {
      id: 'templates-code',
      label: 'The templates',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary: 'starter and physics; the two examples this map points at.',
    },
    {
      id: 'docs-site',
      label: 'This documentation',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary:
        'Astro and Starlight, wearing the map’s own palette. The page you are reading is part of the repository.',
      docs: [github('docs', '/tree/main/docs')],
    },
  ],

  edges: [
    { from: 'agent-skills', to: 'sources', kind: 'drafts-from' },
    { from: 'validate', to: 'map-file', kind: 'checks' },
    { from: 'audit', to: 'map-file', kind: 'checks' },
    { from: 'build', to: 'map-file', kind: 'renders' },
    { from: 'route', to: 'outcome', kind: 'builds-toward' },
  ],

  stages: [
    {
      id: 'see',
      period: 1,
      order: 1,
      title: 'See one',
      feeling: 'I know what a finished map looks like.',
      task: 'Open the two examples and follow a stage or two.',
      delivers: ['example'],
      waypoints: ['starter-example', 'physics-example', 'example-data'],
      do: [
        'Open the examples page and click around the physics map.',
        {
          text: 'Switch to the data view and find one item in the file.',
          tip: 'The map and the file are shown side by side.',
          tipKind: 'reveal',
        },
      ],
      observe: [{ text: 'Which items are on the route, and which are just context?', note: true }],
      read: ['starter-example', 'example-data', 'one-route'],
      checkpoint: 'You can point at one item and say why it is on the route.',
      refs: [{ doc: 'docs' }],
    },
    {
      id: 'brief',
      period: 1,
      refs: [{ doc: 'docs' }],
      order: 2,
      title: 'Say what it is about',
      feeling: 'I can describe who this is for and what they should be able to do.',
      task: 'Write down who is arriving, what they can do at the end, and where the material lives.',
      delivers: ['brief'],
      waypoints: ['audience', 'outcome', 'sources'],
      do: [
        'Write one sentence for each: who, and what they can do at the end.',
        { text: 'List the documents, slides or repositories the map is based on.', note: true },
      ],
      observe: [{ text: 'Does the outcome describe something you could check, not a feeling?', note: true }],
      read: ['outcome', 'sources', 'existing-docs'],
      checkpoint: 'You have a short brief you could hand to another person.',
    },
    {
      id: 'draft',
      period: 2,
      refs: [{ doc: 'docs' }],
      order: 3,
      title: 'Draft the map',
      feeling: 'The subject exists as a file I can run.',
      task: 'Start a project and get a first version of the file, by hand or with an agent.',
      delivers: ['mapfile'],
      waypoints: ['agent-skills', 'draft-prompt', 'map-file'],
      do: [
        { text: 'Start a project from a template.', tip: 'npx onboarding-map init my-map' },
        {
          text: 'Ask your coding agent to turn your brief and sources into a map.',
          tip: 'Turn these notes into an onboarding map.',
          tipKind: 'copy',
        },
      ],
      observe: [{ text: 'What did the agent put on the route that you would not?', note: true }],
      read: ['map-file', 'agent-skills', 'hand-write'],
      checkpoint: 'The map opens in the dev server without errors.',
    },
    {
      id: 'route',
      period: 2,
      order: 4,
      title: 'Choose one path',
      feeling: 'I know exactly what the newcomer does, in order.',
      task: 'Mark the items the newcomer will use, and cut them into stages.',
      delivers: ['route'],
      waypoints: ['route', 'stages', 'checkpoint', 'goal'],
      do: [
        'Move anything the newcomer will not touch off the route.',
        'Cut the route into stages, each with one idea.',
      ],
      observe: [{ text: 'Does every stage end with something you can see?', note: true }],
      read: ['stages', 'checkpoint', 'goal', 'context-items'],
      checkpoint: 'Every stage has a checkpoint a newcomer could demonstrate.',
      refs: [{ doc: 'docs' }],
    },
    {
      id: 'check',
      period: 2,
      refs: [{ doc: 'docs' }],
      order: 5,
      title: 'Check it',
      feeling: 'I trust the map: it resolves, and I know what is unfinished.',
      task: 'Run the checks and fix what they report.',
      delivers: ['checked'],
      waypoints: ['validate', 'audit', 'changelog'],
      do: [
        { text: 'Validate and fix every error.', tip: 'npx onboarding-map validate --json' },
        { text: 'Audit for stale or unfinished content.', tip: 'npx onboarding-map audit' },
      ],
      observe: [{ text: 'What did the audit find that you had forgotten?', note: true }],
      read: ['validate', 'audit', 'changelog', 'dev-server'],
      checkpoint: 'validate reports no errors and you have read the audit findings.',
    },
    {
      id: 'share',
      period: 2,
      refs: [{ doc: 'docs' }],
      order: 6,
      title: 'Share it',
      feeling: 'I can send this to the next person who joins.',
      task: 'Build the static site and put it somewhere people can open it.',
      delivers: ['published'],
      waypoints: ['build', 'host'],
      do: [
        { text: 'Build the site.', tip: 'npx onboarding-map build' },
        'Upload the output folder to any static host.',
      ],
      observe: [{ text: 'Open the map on a phone. Is the route still easy to follow?', note: true }],
      read: ['build', 'host', 'static-shell'],
      checkpoint: 'Someone else opens your map and starts the first stage.',
    },
  ],
});
import { defineMap, docLink } from 'onboarding-map';

/*
 * A map of onboarding-map itself: how someone goes from "I have a subject to
 * onboard people into" to "a map I can send". It is the example the docs show,
 * and it is written by hand in one file, like any other map.
 */

const docs = docLink('docs', 'https://chidirnweke.github.io/onboarding-map/');
const github = docLink('github', 'https://github.com/ChidiRnweke/onboarding-map');

export default defineMap({
  id: 'onboarding-map',
  version: '0.1.0',
  title: 'Make your own onboarding map',
  motto: 'Bring your subject. Get a map.',
  labels: {
    intro: 'One file describes your subject. The package draws it as a map you can send.',
    period: 'Part',
  },

  kinds: [
    { id: 'concept', label: 'Idea', plural: 'Ideas', description: 'Something to understand.' },
    { id: 'tool', label: 'Step', plural: 'Steps', description: 'Something you do.' },
  ],
  periods: [
    {
      period: 1,
      title: 'See and describe',
      summary: 'Look at an example, then say what you are onboarding someone into.',
    },
    { period: 2, title: 'Shape and share', summary: 'Draw one path, check the map, and publish it.' },
  ],

  documents: [
    {
      id: 'docs',
      title: 'The documentation',
      date: '2026-09-27',
      url: 'https://chidirnweke.github.io/onboarding-map/',
    },
    {
      id: 'github',
      title: 'The repository',
      date: '2026-09-27',
      url: 'https://github.com/ChidiRnweke/onboarding-map',
    },
  ],

  goal: {
    title: 'A map you can send',
    statement: 'Your subject written as one file, drawn as a map a newcomer can follow.',
    doneWhen: 'Someone follows your map from the first stage to the last and ends up with something real.',
    story:
      'A map is two things at once. It is everything a newcomer will run into, so nothing feels hidden. And it is one path through that, so they are never deciding what to learn next.',
    modules: [
      {
        id: 'example',
        title: 'A walked example',
        purpose: 'You cannot judge a map until you have followed one.',
        dependsOn: [],
        builtFrom: ['starter-example', 'physics-example', 'example-data'],
        produces: ['An example you have read and clicked through'],
      },
      {
        id: 'brief',
        title: 'A brief',
        purpose: 'The map is only as good as what you decide it is about.',
        dependsOn: ['example'],
        relations: [{ to: 'example', verb: 'is clearer after' }],
        builtFrom: ['audience', 'outcome', 'sources'],
        produces: ['Who is onboarding, what they should be able to do, and where the material is'],
      },
      {
        id: 'mapfile',
        title: 'A first draft',
        purpose: 'The subject becomes a file, by hand or with an agent.',
        dependsOn: ['brief'],
        relations: [{ to: 'brief', verb: 'is written from' }],
        builtFrom: ['map-file', 'agent-skills', 'draft-prompt'],
        produces: ['A map.ts with regions and items'],
      },
      {
        id: 'route',
        title: 'One path',
        purpose: 'Choosing a single route is what keeps the map from overwhelming the reader.',
        dependsOn: ['mapfile'],
        relations: [{ to: 'mapfile', verb: 'is drawn on' }],
        builtFrom: ['route', 'stages', 'checkpoint', 'goal'],
        produces: ['A journey with a beginning, stages, and a way to know it is done'],
      },
      {
        id: 'checked',
        title: 'A checked map',
        purpose: 'Links break and content ages; the tool finds both.',
        dependsOn: ['route'],
        relations: [{ to: 'route', verb: 'checks' }],
        builtFrom: ['validate', 'audit', 'changelog'],
        produces: ['A map whose references resolve and whose content is current'],
      },
      {
        id: 'published',
        title: 'A published map',
        purpose: 'A map nobody can open is not a map.',
        dependsOn: ['checked'],
        relations: [{ to: 'checked', verb: 'is built from' }],
        builtFrom: ['build', 'host'],
        produces: ['A static site you can send to the newcomer'],
      },
    ],
  },

  domains: [
    { id: 'start', label: 'Start', tagline: 'See one first', order: 0, color: '#22c55e' },
    { id: 'subject', label: 'Your subject', tagline: 'What you bring', order: 1, color: '#14b8a6' },
    { id: 'agent', label: 'Your agent', tagline: 'The heavy lifting', order: 2, color: '#a855f7' },
    { id: 'map', label: 'The map', tagline: 'The file and its path', order: 3, color: '#3b82f6' },
    { id: 'tool', label: 'Checking and sharing', tagline: 'The tool', order: 4, color: '#f59e0b' },
    { id: 'project', label: 'This project', tagline: 'Where it comes from', order: 5, color: '#64748b' },
  ],

  nodes: [
    {
      id: 'the-idea',
      label: 'Why a map',
      domain: 'start',
      kind: 'category',
      status: 'path',
      summary: 'The problem the map solves.',
    },
    {
      id: 'newcomer-problem',
      label: 'The reading pile',
      domain: 'start',
      kind: 'concept',
      status: 'context',
      parent: 'the-idea',
      summary:
        'Everything a newcomer needs is written down somewhere, but as separate documents for people who already know the system.',
      refs: [{ doc: 'docs' }],
    },
    {
      id: 'one-route',
      label: 'One path',
      domain: 'start',
      kind: 'concept',
      status: 'context',
      parent: 'the-idea',
      summary:
        'The map shows everything, but asks the newcomer to follow only one route. The rest is there to recognise, not to learn first.',
    },

    {
      id: 'examples',
      label: 'Examples',
      domain: 'start',
      kind: 'category',
      status: 'path',
      summary: 'Two maps to look at before you make your own.',
    },
    {
      id: 'starter-example',
      refs: [{ doc: 'docs' }],
      label: 'Starter example',
      domain: 'start',
      kind: 'concept',
      status: 'path',
      parent: 'examples',
      stage: 'see',
      summary: 'The smallest useful map: two regions, a handful of items, two stages.',
      docs: [docs('The starter template', 'user-guide/examples/')],
    },
    {
      id: 'physics-example',
      refs: [{ doc: 'docs' }],
      label: 'Physics example',
      domain: 'start',
      kind: 'concept',
      status: 'path',
      parent: 'examples',
      stage: 'see',
      summary: 'A complete map outside software: motion, forces and energy, with a full route.',
      docs: [docs('The physics template', 'user-guide/examples/')],
    },
    {
      id: 'example-data',
      refs: [{ doc: 'docs' }],
      label: 'The data behind it',
      domain: 'start',
      kind: 'concept',
      status: 'path',
      parent: 'examples',
      stage: 'see',
      summary: 'Every map is one file. The examples page shows the map and the file next to each other.',
      docs: [docs('Examples, map and data', 'user-guide/examples/')],
    },

    {
      id: 'your-subject',
      label: 'Your subject',
      domain: 'subject',
      kind: 'category',
      status: 'path',
      summary: 'What you decide the map is about.',
    },
    {
      id: 'audience',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'Who is arriving',
      domain: 'subject',
      kind: 'concept',
      status: 'path',
      parent: 'your-subject',
      stage: 'brief',
      summary: 'Who is being onboarded, and what they already know. It sets the level of every summary.',
    },
    {
      id: 'outcome',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'What they can do',
      domain: 'subject',
      kind: 'concept',
      status: 'path',
      parent: 'your-subject',
      stage: 'brief',
      summary: 'The thing that exists when the onboarding is done. The stages exist to build it.',
    },
    {
      id: 'sources',
      label: 'Your material',
      docs: [docs('Working with your agent', 'user-guide/working-with-your-agent/')],
      domain: 'subject',
      kind: 'concept',
      status: 'path',
      parent: 'your-subject',
      stage: 'brief',
      summary:
        'Notes, slides, a syllabus, a codebase. The map is based on these, and cites them, so it can be refreshed later.',
      refs: [{ doc: 'docs' }],
    },
    {
      id: 'existing-docs',
      label: 'Docs you already have',
      domain: 'subject',
      kind: 'concept',
      status: 'context',
      parent: 'your-subject',
      summary:
        'Existing documentation stays where it is. The map links to it at the right moment instead of copying it.',
    },

    {
      id: 'the-file',
      label: 'The file',
      domain: 'map',
      kind: 'category',
      status: 'path',
      summary: 'One file holds the whole map.',
    },
    {
      id: 'map-file',
      refs: [{ doc: 'docs' }],
      label: 'map.ts',
      domain: 'map',
      kind: 'tool',
      status: 'path',
      parent: 'the-file',
      stage: 'draft',
      summary:
        'Your subject as one TypeScript file. Types come from the package, so mistakes show up in your editor.',
      docs: [github('The templates', '/tree/main/templates')],
    },
    {
      id: 'hand-write',
      docs: [docs('Writing a map', 'user-guide/writing-a-map/')],
      label: 'Write it yourself',
      domain: 'map',
      kind: 'tool',
      status: 'alternative',
      alternativeTo: 'map-file',
      parent: 'the-file',
      summary:
        'You can write the file by hand. Nothing here needs an agent; the format is the whole interface.',
    },
    {
      id: 'json-map',
      docs: [docs('Writing a map', 'user-guide/writing-a-map/')],
      label: 'map.json',
      domain: 'map',
      kind: 'tool',
      status: 'alternative',
      alternativeTo: 'map-file',
      parent: 'the-file',
      summary: 'Use JSON instead of TypeScript when another tool needs to write the map.',
    },

    {
      id: 'the-journey',
      label: 'The journey',
      domain: 'map',
      kind: 'category',
      status: 'path',
      summary: 'The one path through everything.',
    },
    {
      id: 'route',
      label: 'The route',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      domain: 'map',
      kind: 'concept',
      status: 'path',
      parent: 'the-journey',
      stage: 'route',
      summary:
        'The items the newcomer actually uses, in order. Everything else is context or a choice you did not take.',
    },
    {
      id: 'stages',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'Stages',
      domain: 'map',
      kind: 'concept',
      status: 'path',
      parent: 'the-journey',
      stage: 'route',
      summary:
        'The route cut into stages. Each one is small, and follows the same shape: do something, watch what happens, then read about it.',
    },
    {
      id: 'checkpoint',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'Checkpoint',
      domain: 'map',
      kind: 'concept',
      status: 'path',
      parent: 'the-journey',
      stage: 'route',
      summary: 'How the newcomer knows a stage is finished: something they can see, not a feeling.',
    },
    {
      id: 'goal',
      refs: [{ doc: 'docs' }],
      docs: [docs('How it works', 'user-guide/how-it-works/')],
      label: 'The goal',
      domain: 'map',
      kind: 'concept',
      status: 'path',
      parent: 'the-journey',
      stage: 'route',
      summary: 'What the whole journey builds, shown as parts that fill in as the stages go.',
    },
    {
      id: 'context-items',
      label: 'Good to know it exists',
      domain: 'map',
      kind: 'concept',
      status: 'context',
      parent: 'the-journey',
      summary: 'Items off the route. Cheap to add, and they make the map honest about what is out there.',
    },

    {
      id: 'with-an-agent',
      label: 'With an agent',
      domain: 'agent',
      kind: 'category',
      status: 'path',
      summary: 'Let a coding agent do the first draft.',
    },
    {
      id: 'agent-skills',
      refs: [{ doc: 'docs' }],
      label: 'The skills',
      domain: 'agent',
      kind: 'concept',
      status: 'path',
      parent: 'with-an-agent',
      stage: 'draft',
      summary:
        'The package installs instructions for Claude Code, Codex and other tools: how to write, edit and refresh a map.',
      docs: [github('The skills', '/tree/main/skills')],
    },
    {
      id: 'draft-prompt',
      refs: [{ doc: 'docs' }],
      docs: [docs('Working with your agent', 'user-guide/working-with-your-agent/')],
      label: 'Ask for a draft',
      domain: 'agent',
      kind: 'tool',
      status: 'path',
      parent: 'with-an-agent',
      stage: 'draft',
      summary:
        'Point the agent at your material and ask it to turn it into a map. Read the result; you decide the route.',
    },

    {
      id: 'the-checks',
      label: 'The checks',
      domain: 'tool',
      kind: 'category',
      status: 'path',
      summary: 'The command line keeps the map true.',
    },
    {
      id: 'validate',
      refs: [{ doc: 'docs' }],
      label: 'validate',
      domain: 'tool',
      kind: 'tool',
      status: 'path',
      parent: 'the-checks',
      stage: 'check',
      summary: 'Checks that every reference in the file resolves, and names the ones that do not.',
      docs: [docs('Command line', 'reference/cli/')],
    },
    {
      id: 'audit',
      refs: [{ doc: 'docs' }],
      docs: [docs('Command line', 'reference/cli/')],
      label: 'audit',
      domain: 'tool',
      kind: 'tool',
      status: 'path',
      parent: 'the-checks',
      stage: 'check',
      summary:
        'Finds the content that has gone stale: dead links, missing sources, route items nobody is sent to.',
    },
    {
      id: 'changelog',
      refs: [{ doc: 'docs' }],
      docs: [docs('Command line', 'reference/cli/')],
      label: 'changelog',
      domain: 'tool',
      kind: 'tool',
      status: 'path',
      parent: 'the-checks',
      stage: 'check',
      summary: 'Shows what changed since the last commit, as a short summary for review.',
    },
    {
      id: 'dev-server',
      label: 'See it as you write',
      domain: 'tool',
      kind: 'tool',
      status: 'context',
      parent: 'the-checks',
      summary: 'The dev command serves the map and reloads it on every save.',
    },

    {
      id: 'publish',
      label: 'Publish',
      domain: 'tool',
      kind: 'category',
      status: 'path',
      summary: 'Turn the file into a site.',
    },
    {
      id: 'build',
      refs: [{ doc: 'docs' }],
      docs: [docs('Command line', 'reference/cli/')],
      label: 'build',
      domain: 'tool',
      kind: 'tool',
      status: 'path',
      parent: 'publish',
      stage: 'share',
      summary: 'Writes a static site: your map plus the app that draws it.',
    },
    {
      id: 'host',
      refs: [{ doc: 'docs' }],
      docs: [docs('Command line', 'reference/cli/')],
      label: 'Any static host',
      domain: 'tool',
      kind: 'concept',
      status: 'path',
      parent: 'publish',
      stage: 'share',
      summary:
        'The output is plain files. There is no server and no account; progress is kept in the reader’s browser.',
    },
    {
      id: 'static-shell',
      label: 'The app shell',
      domain: 'tool',
      kind: 'concept',
      status: 'context',
      parent: 'publish',
      summary: 'One prebuilt app renders any map. Your map file is all that changes.',
    },

    {
      id: 'codebase',
      label: 'The codebase',
      domain: 'project',
      kind: 'category',
      status: 'context',
      summary: 'What the package is made of.',
    },
    {
      id: 'core',
      label: 'The model',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary: 'Types, validation and derivation. It runs in Node and the browser, and has no UI.',
      docs: [github('src/core', '/tree/main/src/core')],
    },
    {
      id: 'cli',
      label: 'The command line',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary: 'init, dev, build, validate, changelog, audit, schema and skills install.',
    },
    {
      id: 'app-shell-code',
      label: 'The app',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary: 'The SvelteKit shell that draws the map and walks the stages.',
    },
    {
      id: 'templates-code',
      label: 'The templates',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary: 'starter and physics; the two examples this map points at.',
    },
    {
      id: 'docs-site',
      label: 'This documentation',
      domain: 'project',
      kind: 'concept',
      status: 'context',
      parent: 'codebase',
      summary:
        'Astro and Starlight, wearing the map’s own palette. The page you are reading is part of the repository.',
      docs: [github('docs', '/tree/main/docs')],
    },
  ],

  edges: [
    { from: 'agent-skills', to: 'sources', kind: 'drafts-from' },
    { from: 'validate', to: 'map-file', kind: 'checks' },
    { from: 'audit', to: 'map-file', kind: 'checks' },
    { from: 'build', to: 'map-file', kind: 'renders' },
    { from: 'route', to: 'outcome', kind: 'builds-toward' },
  ],

  stages: [
    {
      id: 'see',
      period: 1,
      order: 1,
      title: 'See one',
      feeling: 'I know what a finished map looks like.',
      task: 'Open the two examples and follow a stage or two.',
      delivers: ['example'],
      waypoints: ['starter-example', 'physics-example', 'example-data'],
      do: [
        'Open the examples page and click around the physics map.',
        {
          text: 'Switch to the data view and find one item in the file.',
          tip: 'The map and the file are shown side by side.',
          tipKind: 'reveal',
        },
      ],
      observe: [{ text: 'Which items are on the route, and which are just context?', note: true }],
      read: ['starter-example', 'example-data', 'one-route'],
      checkpoint: 'You can point at one item and say why it is on the route.',
      refs: [{ doc: 'docs' }],
    },
    {
      id: 'brief',
      period: 1,
      refs: [{ doc: 'docs' }],
      order: 2,
      title: 'Say what it is about',
      feeling: 'I can describe who this is for and what they should be able to do.',
      task: 'Write down who is arriving, what they can do at the end, and where the material lives.',
      delivers: ['brief'],
      waypoints: ['audience', 'outcome', 'sources'],
      do: [
        'Write one sentence for each: who, and what they can do at the end.',
        { text: 'List the documents, slides or repositories the map is based on.', note: true },
      ],
      observe: [{ text: 'Does the outcome describe something you could check, not a feeling?', note: true }],
      read: ['outcome', 'sources', 'existing-docs'],
      checkpoint: 'You have a short brief you could hand to another person.',
    },
    {
      id: 'draft',
      period: 2,
      refs: [{ doc: 'docs' }],
      order: 3,
      title: 'Draft the map',
      feeling: 'The subject exists as a file I can run.',
      task: 'Start a project and get a first version of the file, by hand or with an agent.',
      delivers: ['mapfile'],
      waypoints: ['agent-skills', 'draft-prompt', 'map-file'],
      do: [
        { text: 'Start a project from a template.', tip: 'npx onboarding-map init my-map' },
        {
          text: 'Ask your coding agent to turn your brief and sources into a map.',
          tip: 'Turn these notes into an onboarding map.',
          tipKind: 'copy',
        },
      ],
      observe: [{ text: 'What did the agent put on the route that you would not?', note: true }],
      read: ['map-file', 'agent-skills', 'hand-write'],
      checkpoint: 'The map opens in the dev server without errors.',
    },
    {
      id: 'route',
      period: 2,
      order: 4,
      title: 'Choose one path',
      feeling: 'I know exactly what the newcomer does, in order.',
      task: 'Mark the items the newcomer will use, and cut them into stages.',
      delivers: ['route'],
      waypoints: ['route', 'stages', 'checkpoint', 'goal'],
      do: [
        'Move anything the newcomer will not touch off the route.',
        'Cut the route into stages, each with one idea.',
      ],
      observe: [{ text: 'Does every stage end with something you can see?', note: true }],
      read: ['stages', 'checkpoint', 'goal', 'context-items'],
      checkpoint: 'Every stage has a checkpoint a newcomer could demonstrate.',
      refs: [{ doc: 'docs' }],
    },
    {
      id: 'check',
      period: 2,
      refs: [{ doc: 'docs' }],
      order: 5,
      title: 'Check it',
      feeling: 'I trust the map: it resolves, and I know what is unfinished.',
      task: 'Run the checks and fix what they report.',
      delivers: ['checked'],
      waypoints: ['validate', 'audit', 'changelog'],
      do: [
        { text: 'Validate and fix every error.', tip: 'npx onboarding-map validate --json' },
        { text: 'Audit for stale or unfinished content.', tip: 'npx onboarding-map audit' },
      ],
      observe: [{ text: 'What did the audit find that you had forgotten?', note: true }],
      read: ['validate', 'audit', 'changelog', 'dev-server'],
      checkpoint: 'validate reports no errors and you have read the audit findings.',
    },
    {
      id: 'share',
      period: 2,
      refs: [{ doc: 'docs' }],
      order: 6,
      title: 'Share it',
      feeling: 'I can send this to the next person who joins.',
      task: 'Build the static site and put it somewhere people can open it.',
      delivers: ['published'],
      waypoints: ['build', 'host'],
      do: [
        { text: 'Build the site.', tip: 'npx onboarding-map build' },
        'Upload the output folder to any static host.',
      ],
      observe: [{ text: 'Open the map on a phone. Is the route still easy to follow?', note: true }],
      read: ['build', 'host', 'static-shell'],
      checkpoint: 'Someone else opens your map and starts the first stage.',
    },
  ],
});