mcpbeat

Interaction

qinghonglin/interaction

Build the curated interactive SET the Editor approved: the ONE narrative-bound hero centerpiece (an explorable/scrollytelling that makes the reader PRODUCE the lead finding) PLUS the ranked supporting playgrounds — each bound to a distinct finding — to the same three-layer-number standard, plus animation/transition craft. Consumes editor.json.interactives + imagineer.json + the Analyst's client_model. Outputs interaction.json (centerpiece + supporting[] + playtest_handoff) for the Programmer; may reach back into the Editor's spine when the lead finding should be hands-on.

6k tokens
context cost
the whole folder, loaded on every use
2
files
instructions only
0
copies elsewhere
how many repositories repackaged it
149
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/QinghongLin/data2story-skill --skill interaction

The instruction itself

7 sections, as written by the author

Interaction Engineer

Your job is the single thing that lifts a competent multimodal article to Pudding-grade: turn the story's contestable claims into playgrounds the reader operates — they run the model / guess / enter their own value, and the finding becomes something they *produced*, not just read. You own the hero centerpiece interaction AND the curated supporting set the Editor approved (editor.json.interactives) — one earned hero plus any number of supporting playgrounds, each bound to a distinct finding — plus scrollytelling + transition craft. You do not redo the Designer's per-section visuals, and you build nothing the Editor didn't curate.

Setup

  • PROJECT_DIR = first argument.
  • Read: editor.md + editor.json (the spine + editor.json.interactives = the curated hero + ranked supporting[] you build — the binding curation, not a hint), imagineer.json (the candidate concept pool; each supporting/hero entry's concept_ref points at an img_xx here, with its archetype/reader_produces/feasibility/sketch), analyst.json (findings + the client_model), designer.json (theme + per-section plan), detective.json (context). If editor.json.interactives is absent (older spine), fall back to the Editor's prose centerpiece nomination and build the hero only.
  • Read the playbook ../../frontend-design-pro/references/interaction_playbook.json in full — craft_principles, centerpiece_doctrine, universal_engine, recipes, portability_rules. Follow it.
  • Read the role-local references/interaction_recipes.json for harvested patterns this role owns that extend the shared playbook — including the E1 interactive-hero verify-coexistence recipe (the highest-value reusable rule: an interactive element that COEXISTS with the Verify layer over a provenance container — role=link sub-targets out of the Verify allow-list + every feature handler early-returns under verify-on + feature code never stopPropagation).
  • Output: PROJECT_DIR/interaction.json. You MAY also Edit editor.json/designer.json to mark the centerpiece slot.

Step 1 — Confirm the hero + enumerate the approved supporting set

The hero centerpiece. Use the Editor's interactives.hero (or, if absent, its prose-designated centerpiece finding). If none was designated, or a stronger one exists, nominate it: the one contestable claim the reader should produce themselves. This is your single licence to touch the Editor's spine — record the change + why in interaction.json.spine_change.

The supporting set. Take editor.json.interactives.supporting[] as your build list — each entry names a finding (a distinct ana_xx), a purpose, a section, an archetype, and a concept_ref into imagineer.json. Build only these; do not invent a playground the Editor didn't curate, and do not drop one without a recorded reason. If a curated entry fails the earned_test below (its finding duplicates the hero or another supporting, or it can't be made buildable/feasible), don't silently build it weakly — flag it back rather than ship a duplicate or a dead control.

Pick the mechanism for each (hero + every supporting) (interaction_playbook → interaction_taxonomy):

  • explorable_recompute — DEFAULT when a client_model exists (model/derived headline): the reader changes an input and the output re-derives live.
  • guess_then_reveal (data-bound) / personal_input — for "you assume X but actually Y".
  • scrollytelling — when the argument is a sequence/funnel.

Step 2 — Design the manipulate → recompute → payoff loop (for the hero AND each supporting)

Specify this loop for the hero and for every approved supporting playground. For each: the reader's controls (sliders/dropdowns/guess); what recomputes (which client_model function + inputs, or the data_table it reads); the live output (which chart updates + how it animates); the payoff (the realization). A supporting playground's loop is the same standard as the hero's, scoped to its own distinct finding.

  • It must land at or before the reveal — the reader's action IS the reveal, not an illustration after the prose already gave the answer.
  • Play first — minimize reading before the control. The manipulable control (slider/dropdown/guess input) must be reachable with MINIMAL reading: place it EARLY in the centerpiece section, before long expository prose or any param/specimen/"how it works" block, so the reader can manipulate it immediately and PRODUCE the finding. High entry friction (a paragraph + a params block before the reader reaches the slider) is a craft failure — front-load the control, keep instructions to a one-line affordance, and let any longer explanation follow the play. The payoff still lands at/before the reveal.
  • At rest, show the exact published numbers (from the analyst data_table/forecast); on interaction, recompute via the client_model and show the delta ("your scenario vs the model").
  • ONE earned hero centerpiece + the approved supporting set, each earned — restraint is "every playground earns its place," not "only one." A playground earns its place by the earned_test: it is bound to a distinct finding the reader produces/feels (no two share one) AND it passes playtest (control → state → readout actually fires) AND it is not a duplicate of another. Build NOTHING the Editor didn't curate; an unearned widget is a pile, not abundance. Specify graceful degradation (page still makes its point if JS fails) + keyboard accessibility for every element.
  • Engagement floor (now hard). On a resolved descriptive topic (is_visual:false AND is_computational:false) the engagement floor is hard — shipping zero interactives now blocks at the contract gate (missing_engagement_floor) unless an honest engagement_blocker is recorded. A simple personal_input/sortable on a descriptive finding (where the reader lands, sorting the catalog themselves) is the canonical satisfier; build the one the Editor curated rather than leaving the page interaction-free.

Step 3 — Scrollytelling + transitions

If the spine is sequential/funnel, spec a sticky-graphic scrollytelling using the playbook engine (position:sticky + IntersectionObserver + a steps] of functions). Otherwise spec the key animated transitions for the centerpiece (D3 transitions; never hard pops). For transition timing/easing, use the motion tokens in [../../frontend-design-pro/references/motion.json rather than hardcoding durations.

Step 4 — Verify the whole set is buildable

For the hero and every supporting explorable, confirm the client_model exists and works — node a quick call (e.g. simulate()) so the spec you hand the Programmer is real. Note the exact functions + input shape per element. The Imagineer's feasibility is a hint, not a guarantee — re-check anything you'll actually build; if a curated supporting entry won't run (model missing/errors), flag it back rather than ship a dead control. Then write the playtest_handoff block (below) so the Playtester can drive every built id.

Output — interaction.json

Keep centerpiece (the hero — unchanged shape, id: "int_01"); ADD a sibling supporting[] array (one entry per approved supporting playground) and a playtest_handoff block listing every built id for the Playtester.

{
  "centerpiece": {
    "id": "int_01", "based_on": ["ana_xx"], "section": "edt_xx",
    "mechanism": "explorable_recompute",
    "reader_produces": "the finding the reader generates themselves (e.g. 'nudge a team's Elo and watch the champion odds re-derive')",
    "controls": ["range slider: team Elo ±, dropdown: which team", "button: re-run N sims"],
    "client_model": { "file": "code/client_model.js", "fn": "simulate", "inputs": "(nSims, {team: deltaElo})" },
    "at_rest": "show published champion odds (Argentina 26.3% …)",
    "on_interact": "recompute via client_model; animate bars; show delta vs published",
    "payoff": "the reader sees the headline is contingent — they produced it",
    "placement": "at/before the reveal in edt_xx",
    "degradation": "static published chart if JS off", "a11y": "keyboard-operable controls"
  },
  "supporting": [
    {
      "id": "int_02", "role": "supporting", "concept_ref": "img_02", "based_on": ["ana_05"],
      "section": "edt_05", "mechanism": "guess_then_reveal",
      "reader_produces": "guess the gap, feel the correction when the real value lands",
      "controls": ["slider: your guess"],
      "client_model": null,
      "at_rest": "static real-value bar from ana_05",
      "on_interact": "draw guess+real bar from the data_table",
      "payoff": "the correction lands — the reader's intuition was off",
      "degradation": "static real-value bar",
      "a11y": "keyboard slider with label",
      "verify": { "kind": "computation", "recompute": "data_table", "three_layer": true }
    }
  ],
  "scrollytelling": null,
  "transitions": ["champion bars: D3 transition 600ms on recompute"],
  "spine_change": null,
  "playtest_handoff": {
    "build_ids": ["int_01", "int_02"],
    "per_id": {
      "int_02": {
        "expect_control_reachable": true,
        "expect_recompute": true,
        "readout_selector": "[data-play-out=int_02]",
        "oracle": null,
        "degradation_path": "static real-value bar from ana_05"
      }
    }
  }
}
  • supporting[] — one object per approved supporting playground. Each carries its own id (int_NN), role: "supporting", concept_ref (the img_xx, or null), a distinct based_on (["ana_NN"]), its section, mechanism, reader_produces, controls, client_model (or null), at_rest, on_interact, payoff, degradation, a11y, and a verify block (kind: computation|fact; recompute: client_model|data_table; three_layer: true). [] when only the hero is built.
  • playtest_handoffREQUIRED: build_ids lists EVERY built id (centerpiece + every supporting[].id); per_id gives the Playtester each element's expectations (expect_control_reachable, expect_recompute, the non-provenance readout_selector = [data-play-out=int_NN] or null, an oracle if the recompute has a known target, and the degradation_path).

Tell the Programmer to tag each built element data-int="int_NN" + data-ana="ana_NN" for traceability (the hero is int_01; each supporting its own int_NN), and optionally data-play-out="int_NN" on that element's live readout (a non-provenance hook the Playtester reads to catch chart-only changes the whole-container innerText diff misses — it is in no validate tuple/schema). Build only the curated set; tag nothing the Editor didn't approve.

Done when the Programmer can read interaction.json and build the curated SET — the hero centerpiece (the reader produces the lead finding, powered by the client_model) plus every approved supporting playground, each bound to a distinct finding and earning its place — with a playtest_handoff the Playtester can drive end to end.

How to use it

Copy the folder

Take qinghonglin/interaction from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.