Repository navigation
feat(motion-kit): render the Studio and capture site pages as Claude Motion films - #82
Merged
Merged
Conversation
…sets.ts barDatasets, lineDatasets, barCap and legendShown move out of ChartCard.tsx unchanged, so code outside the card can build the same Chart.js datasets. motion-kit draws a chart card's plot from them.
…Motion films pnpm motion-kit <spec> renders a Studio workspace from the app's own components (Header, WorkspaceToolbar, ChatPanel with PlanCard and ApprovedPlanPill, the card components) as one static HTML fragment and its scoped CSS, with each chart drawn by Chart.js into SVG. pnpm motion-kit capture <page> drives Chrome through the running site and snapshots each state of any route as inert HTML and CSS. The manifest records the app's own keyframes and transitions, read in Chrome. A cue sheet beside the spec times the film: typing that grows the prompt as PromptForm does, streamed replies, the plan approved into its pill, cards arriving with their own keyframes, series drawing, and a camera that centres on named hooks. runtime/dfo-motion.js plays it as a pure function of time, restoring each element's own style every frame. Every run writes the Motion store documents and a preview.html that plays the film from disk. The generator refuses a cue, spec or module a film cannot play: a missing hook, state or keyframe, a field of the wrong type, a store document over 200 KiB, an outside URL, a media query it cannot decide. motion-kit/demos/kenya-debt-health.json is the worked example, and motion-kit/README.md documents the specs, cues and outputs.
The skill takes a demo from a data question to a published Motion film: the figures from the DFO MCP server (health aid as CRS 120 plus 130), a Studio or page-capture spec, the cue sheet, generating and previewing, and loading the modules into a film. .gitignore now keeps .claude/skills/ while ignoring the rest of .claude/.
The runtime measured a hook from layout offsets, which leave out the translate a capture uses for its scroll, so a camera key on a hook in a scrolled state aimed hundreds of px off. box() now measures the element as rendered inside the frame and divides out the frame's own scale, so every transform inside the frame counts, the transcript pin included. A capture's preview.html offers "film" when its camera keys name states: it mounts each state from its first key's time, as the film's scenes do, so a whole capture film plays and scrubs before it reaches Motion. motion-kit/pages/uganda-oda-search.json captures a data search on the site, "ODA to Uganda since 2015": the question typed, its match, the answer, the measure menu, net disbursements chosen, and the results table, with a cue sheet that frames each state.
A stylesheet over the module limit split into dfo-ui.css~1, ~2 and so on, but a Motion film's store names a module with letters, digits, _, . and - only. The player could not load the pieces, so a large page capture played without its stylesheet. The pieces are now dfo-ui-1.css, dfo-ui-2.css and so on, and writeFilm refuses a module name the store does not take or modules that hold more than 2 MiB together.
The skill now ships in the dfo-dev plugin of ONEcampaign/one-data-claude-marketplace, so it leaves .claude/skills/ here and .gitignore ignores all of .claude/ again.
… and fonts in the preview A tab or state cue wrote both its states at every frame, so a later cue undid an earlier one: Chat to Activity at 5 s and back at 10 s showed Activity at 0 s. The runtime now restores every attribute a cue writes (style, class or a state's own) at the start of each frame, and a tab or state cue writes only from its own time. A capture translated an ancestor of a fixed element by the scroll, which made that ancestor the fixed element's containing block: a header fixed at 20 px showed thousands of px down. The scroll now moves the children of an element that holds a fixed one, never the element itself, so fixed elements stay anchored to the frame. preview.html embeds Outfit and DM Mono from @fontsource and mounts the film once they load, since the stylesheet carries no @font-face and a machine without them wrapped text, and measured the camera, in fallback fonts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
motion-kit/turns the site into Claude Motion films that show the real interface.pnpm motion-kit <spec>renders a Studio workspace from the app's own components into one static HTML fragment and scoped CSS, with charts drawn by Chart.js into SVG.pnpm motion-kit capture <page>drives Chrome through the running site and snapshots each state of any other route. A cue sheet beside each spec times the film, andruntime/dfo-motion.jsplays it in Motion as a pure function of time, using the app's own keyframes, prompt growth and transcript pinning.Each run writes the Motion store documents and a
preview.htmlthat plays the film from disk with a scrubber; for a capture it plays every state in turn, as the film's scenes do. The generator refuses what a film cannot play: a missing hook, state or keyframe, a cue field of the wrong type, a store document over 200 KiB or a module name a film's store does not take, an outside URL, or a media query it cannot decide.motion-kit/README.mddocuments the specs, cue kinds and outputs;motion-kit/demos/kenya-debt-health.jsonis the worked example, and three page specs capture data search, a search refined from gross to net disbursements (uganda-oda-search.json, with its cue sheet), and the IDS source page.The
dfo-motion-demoskill, which walks a demo from a data question to a published film with the figures taken from the DFO MCP server, ships in thedfo-devplugin of the ONE data marketplace (https://github.com/ONEcampaign/one-data-claude-marketplace/pull/26).The one app change moves
ChartCard's dataset builders intocanvas/chart/chartDatasets.tsunchanged, so the kit draws the same datasets. Nothing undermotion-kit/is imported by the app, andplaywright-coreis a dev dependency.Checks:
node --test motion-kit/test/*.test.tspasses 18 tests, including a film scrubbed forwards and backwards with every frame compared, and a fixture page captured and played through its preview.pnpm typecheckandpnpm lintpass. The database tests inpnpm testneed a local Postgres and were not run.