Active Tags Article Series / Part III

From Theory to Practice

A readable Active Tags walkthrough: the tutorial that ships in the repository, the runtime shape behind it, and the shortest path from theory to a working, inspectable system.

Tutorial Builtins Events and intervals Request + patch

Why This Tutorial Matters

Part I described the problem. Part II mapped each failure to the system model. Part III is the practical walkthrough using the tutorial that already ships in this repository.

This tutorial code was machine-generated on purpose as a quality check for the platform. The goal was to prove that documentation and runtime contracts are clean enough for an LLM to produce working code without hidden glue hacks.

If an LLM can produce reliable walkthrough code from the docs, your life and your LLM sidekicks get dramatically easier: code is easier to reason about, behavior is more predictable, and integration glue drops.

Notes on Builtins and DSL

Active Tags ships with a broad built-in operation surface out of the box: form, http, dom, error, buffer, target, e, and confirm. This reduces one-off utility glue and keeps pipelines focused on business intent.

Active Tags also makes heavy use of m7.js, which provides a wider utility surface designed to make integration and runtime composition easier.

This demonstration also uses the Active Tags v1.0 DSL: a compact pipeline syntax with semicolon-delimited stage rows, positional and named args, and explicit builtin markers (@op) for deterministic operation resolution.

What We Are Building

A small counter component with:

  • deterministic startup
  • event-driven actions (increment, reset, load fragment)
  • interval-driven updates (tick)
  • request + DOM patch flow
  • observable runtime behavior

In other words: a compact proof of the Part II claims.

1) Markup: Declare the Component Roots

Start with two Active Tags in HTML:

<div
  data-activetag
  at-name="tutorial-loaded"
  at-at="import:tutorial-loaded-job.js"
  class="tutorial-loaded-flag">
  waiting for ActiveTags...
</div>

<div
  data-activetag
  at-name="tutorial-counter"
  at-at="import:tutorial-job.js"
  class="tutorial-host">
  <h2>Counter Job</h2>
  <p>Count: <strong class="tutorial-count">0</strong></p>

  <div class="tutorial-actions">
    <button type="button" data-inc>Increment</button>
    <button type="button" data-reset>Reset</button>
    <button type="button" data-load-fragment>Load Fragment</button>
  </div>

  <div class="tutorial-fragment">Fragment output appears here.</div>
</div>

data-activetag marks discoverable roots. at-name gives stable runtime identity. at-at points to config source. This is boundary discipline in practice: semantic markup plus explicit runtime config pointers.

2) Boot the Runtime

Use standalone install plus service lookup plus explicit start:

import { install, SERVICE_ID } from "/vendor/m7-js-lib-active-tags/dist/activeTags.standalone.v1.0.min.js";

const conf = {
  boot: {
    observeDom: true,
    intervals: true,
    events: true,
  },
  engine: {
    opResolution: {
      auto: true,
    },
  },
  job: {
    config: {
      evalEnabled: true,
      evalType: "text/at-eval",
      importEnabled: true,
      importPath: ["/vendor/m7-js-lib-active-tags/examples/"],
    },
  },
};

const lib = install({ conf });
const AT = lib.service.get(SERVICE_ID);
if (!AT) throw new Error(`[tutorial] missing ActiveTags service '${SERVICE_ID}'.`);

await AT.start();

Why this matters: deterministic startup boundary, explicit config import policy, and no framework takeover required.

3) Confirm Startup with a Tiny Job

The tutorial includes a startup marker job:

// you can also use @dom.patch and skip this function
function markLoaded({ job } = {}) {
  const root = job && job.e;
  if (!root) return true;

  root.innerHTML = "ActiveTags loaded.";
  return true;
}

export default {
  name: "tutorial-loaded",
  enabled: true,
  autorun: false,
  pipeline: {
    run: [markLoaded],
    error: ["@error.dump"],
  },
};

Then trigger it once after startup:

const loadedJob = AT.toJob("tutorial-loaded");
if (loadedJob) {
  const ticket = AT.engine.enqueue(loadedJob, "default", {
    inputs: { reason: "tutorial.startup" },
    meta: { source: "tutorial-example" },
  });
  if (ticket) await AT.engine.drain({ ticket });
}

Why this matters: it demonstrates explicit ticket lifecycle, shows the run/error pipeline contract, and proves the operability path early.

4) Define Behavior as Pipelines, Not Glue Functions

The counter job demonstrates staged behavior:

function renderCounterSteps() {
  return [
    renderCounterToBuffer,
    "@target.find:selector=.tutorial-count,reset=true",
    "@target.patch:textContent=${buffer:count}",
    "@target.reset",
  ];
}

export default {
  name: "tutorial-counter",
  enabled: true,
  autorun: true,

  pipeline: {
    run: renderCounterSteps(),
    error: ["@error.dump"],
  },

  pipelines: {
    increment: {
      run: [incrementCounter, ...renderCounterSteps()],
      error: ["@error.dump"],
    },
    reset: {
      run: [resetCounter, ...renderCounterSteps()],
      error: ["@error.dump"],
    },
  },
};

Why this matters: each stage stays narrow, shared step composition reduces duplication, and the run/error split stays explicit and predictable.

5) Wire Events Without Listener Sprawl

Event mappings stay declarative:

events: {
  inc_click: {
    event: "click",
    selector: "[data-inc]",
    pipeline: "increment",
  },
  reset_click: {
    event: "click",
    selector: "[data-reset]",
    pipeline: "reset",
  },
}

Why this matters: no scattered addEventListener maze, trigger source is separated from execution, and behavior ownership is visible in one place.

6) Add Interval Behavior Safely

Interval mapping uses the same config shape:

intervals: {
  tick: {
    repeat: 1500,
    pipeline: "tick",
    allowOverlap: false,
    onError: "continue",
  },
}

Why this matters: recurring behavior has explicit policy, overlap policy is declared instead of implicit, and timer behavior stays inside the same governance model.

7) Add Request + Patch Flow

The tutorial includes fragment loading via a request pipeline:

requests: {
  fragment: {
    transport: "http",
    endpoint: { url: "./fragment.html" },
    method: "GET",
    response: {
      parse: "text",
      return: "text",
      requireOk: true,
    },
  },
},

pipelines: {
  load_fragment: {
    run: [
      { op: "@http.send", args: { name: "fragment", url: "./fragment.html" } },
      "@target.find:selector=.tutorial-fragment,reset=true",
      "@target.patch:innerHTML=${buffer}",
      "@target.reset",
    ],
    error: ["@error.dump"],
  },
}

Why this matters: request, validation, and patch choreography live in one pipeline, not as an ad-hoc fetch and DOM glue chain per feature.

8) Operability: Keep the System Explainable

During rollout or debugging, enable engine hooks:

engine: {
  hooks: {
    onStage(trace) {
      console.log("stage", trace);
    },
    onTicketDone(trace) {
      console.log("done", trace.summary);
    },
  },
}

Why this matters: you can inspect run history and terminal summaries. Diagnosis replaces archaeology, and behavior has evidence instead of guesses.

How This Proves the Part II Claims

  • Boundary collapse: solved by semantic roots plus explicit config pointers.
  • Glue-driven architecture: solved by staged pipelines and reusable step composition.
  • State governance gaps: solved by explicit job identity and deterministic runtime execution.
  • Poor operability: solved by hooks, trace surfaces, and ticket lifecycle visibility.
  • Non-linear scale pressure: reduced by scoped, repeatable pipeline contracts.
  • Framework dependency debt: reduced by additive runtime adoption in plain JavaScript environments.

Practical Next Step

Run the tutorial example exactly as shipped:

  • HTML shell: examples/tutorial/tutorial.html
  • startup/runtime boot: examples/tutorial/tutorial.js
  • startup marker job: examples/tutorial/tutorial-loaded-job.js
  • counter + events + intervals + request job: examples/tutorial/tutorial-job.js

This is the shortest path from theory to a working, inspectable system.

Series Navigation