transitions-refine 0.3.9 → 0.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transitions-refine",
3
- "version": "0.3.9",
3
+ "version": "0.3.10",
4
4
  "description": "Live, agent-driven Refine panel for CSS/Motion transitions — injects a timeline + Refine UI and runs transitions.dev suggestions via your coding agent.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,6 +14,7 @@
14
14
  ],
15
15
  "scripts": {
16
16
  "prepack": "node scripts/sync-skill.mjs",
17
+ "build:website-demo": "node scripts/build-website-demo.mjs",
17
18
  "live": "node bin/cli.mjs live",
18
19
  "relay": "node server/relay.mjs"
19
20
  },
package/server/relay.mjs CHANGED
@@ -28,7 +28,7 @@ import { spawn } from "node:child_process";
28
28
  import { existsSync } from "node:fs";
29
29
  import { homedir } from "node:os";
30
30
  import { delimiter, join } from "node:path";
31
- import { refineTimings } from "./motion-tokens.mjs";
31
+ import { refineTimings, DURATION_TOKENS, SMOOTH_OUT } from "./motion-tokens.mjs";
32
32
  import { buildInjectModule } from "./inject.mjs";
33
33
 
34
34
  const PORT = Number(process.env.REFINE_RELAY_PORT) || 7331;
@@ -55,6 +55,13 @@ const AGENT_CMD = augmentAgentCmd(process.env.REFINE_AGENT_CMD || null);
55
55
  // (Opus / GPT-5.5) — forcing a fast model here keeps the initial scan snappy.
56
56
  // Override with REFINE_SCAN_MODEL="" to fall back to the agent's default.
57
57
  const SCAN_MODEL = process.env.REFINE_SCAN_MODEL ?? "composer-2.5-fast";
58
+ // Pin a fast model for refine (suggestion) jobs too. The motion-token vocabulary
59
+ // is inlined into the prompt (see MOTION_TOKENS_BLOCK) so a fast model has every
60
+ // fact it needs for the common token-tweak path — keeping suggestions snappy
61
+ // without falling back to the user's (possibly heavy/slow) default model.
62
+ // Override with REFINE_MODEL="" to use the agent's default model if you ever find
63
+ // the fast model regresses a tricky `replace`/recipe judgement. cursor-agent only.
64
+ const REFINE_MODEL = process.env.REFINE_MODEL ?? "composer-2.5-fast";
58
65
  const AGENT_TIMEOUT_MS = Number(process.env.REFINE_AGENT_TIMEOUT_MS) || 120000;
59
66
 
60
67
  // Inject `--model <m>` into a `cursor-agent …` command (after the binary).
@@ -144,13 +151,57 @@ function nextPendingLlm() {
144
151
 
145
152
  // ── answering a job (one run per job) ────────────────────────────────────────
146
153
 
154
+ // Inlined transitions.dev motion-token vocabulary (mirrors motion-tokens.mjs and
155
+ // the transitions-dev skill's "## Motion tokens"). Embedding it in the refine
156
+ // prompt means the common token-tweak path needs ZERO file reads — only recipe
157
+ // selection (replace/both) still opens the skill + a reference file.
158
+ const MOTION_TOKENS_BLOCK = [
159
+ "Motion tokens (transitions.dev) — match on USAGE intent, not the nearest number:",
160
+ "Durations:",
161
+ ...DURATION_TOKENS.map((t) => ` - ${t.ms}ms ${t.name}: ${t.usage}`),
162
+ `Default easing "Smooth ease out": ${SMOOTH_OUT}.`,
163
+ "Token easings (already on-grid — leave unchanged): ease-out, ease-in-out, linear, cubic-bezier(0.34, 1.36, 0.64, 1) (bouncy overshoot, badge pop), cubic-bezier(0.34, 3.85, 0.64, 1) (strong overshoot, avatar return).",
164
+ 'Generic curves to nudge toward "Smooth ease out": "ease", "ease-in", or any hand-rolled cubic-bezier()/linear() that is not a token above.',
165
+ ].join("\n");
166
+
167
+ // Inlined recipe catalog + decision hints (mirrors the transitions-dev skill's
168
+ // "## Quick reference" + "## Decision rules"). Embedding it lets the `replace`
169
+ // path pick a recipe WITHOUT reading SKILL.md — at most it opens the ONE chosen
170
+ // reference file for exact structure/timings.
171
+ const RECIPES_BLOCK = [
172
+ "transitions.dev recipes — match the inferred USAGE to ONE recipe (reference file in parens):",
173
+ "- Card resize: a container changes width/height on a layout change (01-card-resize.md)",
174
+ "- Number pop-in: a number/digit updates (02-number-pop-in.md)",
175
+ "- Notification badge: a small dot/badge appears on a trigger (03-notification-badge.md)",
176
+ "- Text states swap: text content changes in place (04-text-states-swap.md)",
177
+ "- Menu dropdown: an anchored surface grows from its trigger (05-menu-dropdown.md)",
178
+ "- Modal open/close: a centered dialog scales up, softer scale-down on close (06-modal.md)",
179
+ "- Panel reveal: a surface slides into a region with a cross-blur (07-panel-reveal.md)",
180
+ "- Page side-by-side: slide between list<->detail or step1<->step2 (08-page-side-by-side.md)",
181
+ "- Icon swap: two icons cross-fade in the same slot (09-icon-swap.md)",
182
+ "- Success check: a checkmark celebration, fade+rotate+bob+stroke-draw (10-success-check.md)",
183
+ "- Avatar group hover: hover lifts an item in a horizontal stack (11-avatar-group-hover.md)",
184
+ "- Error state shake: invalid-input shake (12-error-state-shake.md)",
185
+ "- Input clear with dissolve: clearing a text field (13-input-clear-dissolve.md)",
186
+ "- Skeleton loader and reveal: placeholder pulses then swaps to real content (14-skeleton-reveal.md)",
187
+ "- Shimmer text: in-progress/'thinking' text shimmer (15-shimmer-text.md)",
188
+ "- Tabs sliding: a moving highlight across segmented options (16-tabs-sliding.md)",
189
+ "- Tooltip open/close: delayed fade+scale in, instant out (17-tooltip.md)",
190
+ "- Texts reveal: staggered blurred rise of stacked text lines (18-texts-reveal.md)",
191
+ "- Card hover tilt: 3D tilt toward the pointer (19-card-tilt.md)",
192
+ "- Plus to menu morph: a circular trigger becomes the surface it opens (20-plus-menu-morph.md)",
193
+ "- Accordion expand: a collapsible body grows/shrinks in height (21-accordion.md)",
194
+ "Tie-break: prefer the lower-overhead recipe (card resize over panel reveal, dropdown over modal). If no recipe genuinely fits, return an empty suggestions array.",
195
+ ].join("\n");
196
+
147
197
  function buildPrompt(job) {
148
198
  const r = job.request || {};
149
199
  const rawType = r.refineType || "small";
150
200
  const refineType = rawType === "replace" ? "replace" : rawType === "both" ? "both" : "small";
201
+ const needsRecipe = refineType === "replace" || refineType === "both";
151
202
  const lines = [
152
203
  "You are refining ONE CSS transition against the transitions.dev library and motion tokens.",
153
- "Read the transitions-dev skill's SKILL.md (look in .agents/skills/transitions-dev/ or ~/.agents/skills/transitions-dev/) and apply its `transitions refine` behaviour, `## Motion tokens`, and `## Decision rules`.",
204
+ MOTION_TOKENS_BLOCK,
154
205
  "",
155
206
  "Transition context (JSON):",
156
207
  JSON.stringify({ label: r.label, selector: r.selector, refineType, timings: r.timings }, null, 2),
@@ -158,21 +209,31 @@ function buildPrompt(job) {
158
209
  "Infer each declaration's USAGE (modal close, dropdown open, tooltip, badge, resize, color/theme change…) from the label/selector. Match on usage intent, not the nearest number.",
159
210
  "",
160
211
  ];
212
+ if (needsRecipe) {
213
+ lines.push(
214
+ "To pick a whole-transition replacement, match the inferred USAGE against the recipe list below (the skill's decision rules are summarised here — do NOT read SKILL.md or any reference file). Derive the `patch`'s duration/easing from the MOTION TOKENS above for the recipe's phase (open vs close), and put the recipe's reference filename in `reference` so the user can paste the full recipe themselves. The patch only drives the live preview; exact keyframes/structure come from that pasted file — so you never need to open it.",
215
+ "",
216
+ RECIPES_BLOCK,
217
+ "",
218
+ );
219
+ }
161
220
  if (refineType === "replace") {
162
221
  lines.push(
163
222
  "refineType is \"replace\": suggest a WHOLE-TRANSITION replacement ONLY — do NOT propose motion-token tweaks (no kind \"duration\"/\"delay\"/\"easing\").",
164
- "Run the skill's `## Decision rules` on the inferred usage, pick the SINGLE best-fit transitions.dev recipe, and read its reference file (e.g. 06-modal.md) for the real timings/easing. Emit ONE suggestion with kind \"replace\": set its `patch` to the recipe's recommended duration/easing for the property that already transitions (or \"all\") so Apply works live, add a `reference` field with the reference filename, and name the recipe in `title`/`reason`. Never invent timings — quote the reference file. If no recipe genuinely fits the usage, return an empty suggestions array.",
223
+ "Pick the SINGLE best-fit recipe from the list above. Emit ONE suggestion with kind \"replace\": set its `patch` to the motion-token duration/easing for the recipe's phase on the property that already transitions (or \"all\") so Apply works live, add a `reference` field with the reference filename, and name the recipe in `title`/`reason`. If no recipe genuinely fits the usage, return an empty suggestions array.",
224
+ "Answer in ONE response — do NOT read or search files.",
165
225
  );
166
226
  } else if (refineType === "both") {
167
227
  lines.push(
168
228
  "refineType is \"both\": produce TWO independent groups in the SAME suggestions array — the UI shows them in separate tabs, so include each group whenever it applies.",
169
229
  "(1) Motion-token tweaks (kind \"duration\"/\"delay\"/\"easing\"): for each declaration, propose the token value only where it DIFFERS from the current one.",
170
- "(2) Whole-transition replacement (kind \"replace\"): ALWAYS evaluate one — run the skill's `## Decision rules` on the inferred usage, pick the SINGLE best-fit transitions.dev recipe, and read its reference file (e.g. 06-modal.md) for the real timings/easing. Emit at most ONE \"replace\" suggestion: set its `patch` to the recipe's recommended duration/easing for the property that already transitions (or \"all\"), add a `reference` field with the reference filename, and name the recipe in `title`/`reason`. Never invent timings — quote the reference file. If no recipe genuinely fits the usage, simply omit the replace suggestion.",
230
+ "(2) Whole-transition replacement (kind \"replace\"): ALWAYS evaluate one — pick the SINGLE best-fit recipe from the list above. Emit at most ONE \"replace\" suggestion: set its `patch` to the motion-token duration/easing for the recipe's phase on the property that already transitions (or \"all\"), add a `reference` field with the reference filename, and name the recipe in `title`/`reason`. If no recipe genuinely fits, omit the replace suggestion.",
231
+ "Answer in ONE response — do NOT read or search files.",
171
232
  );
172
233
  } else {
173
234
  lines.push(
174
- "refineType is \"small\": FIRST suggest motion-token tweaks — for each declaration, propose the token value only where it DIFFERS from the current one (kind \"duration\"/\"delay\"/\"easing\").",
175
- "THEN, when it is possible and sensible, ALSO add at most ONE kind \"replace\" suggestion (alongside, not instead of, the token tweaks): run the skill's `## Decision rules`, pick the SINGLE best-fit recipe, read its reference file for the real timings, set its `patch` to the recipe's recommended duration/easing for the existing property (or \"all\"), add a `reference` field with the reference filename, and name the recipe in `title`/`reason`. Only add it when the transition is clearly a hand-rolled version of a catalogued recipe or is missing structure the usage calls for; otherwise omit it and let the token tweaks stand alone.",
235
+ "refineType is \"small\": suggest motion-token tweaks ONLY — for each declaration, propose the token value only where it DIFFERS from the current one (kind \"duration\"/\"delay\"/\"easing\"). Do NOT propose a whole-transition replacement (no kind \"replace\") — the Replace tab requests that separately.",
236
+ "Answer in ONE response using ONLY the data above. Do NOT read or search files, run tools, spawn subagents, or explore the codebase the motion tokens contain everything you need. This is a quick judgement, not a coding task.",
176
237
  );
177
238
  }
178
239
  lines.push(
@@ -372,7 +433,9 @@ async function answerJob(job) {
372
433
  );
373
434
  }
374
435
  job.statusLog.push({ message: "Asking your agent…", at: now() });
375
- result = await runAgentCmd(AGENT_CMD, buildPrompt(job));
436
+ const t0 = now();
437
+ result = await runAgentCmd(withModel(AGENT_CMD, REFINE_MODEL), buildPrompt(job));
438
+ console.log(` ⏱ refine agent ${job.id.slice(0, 8)} (${job.request?.refineType || "small"}) ${now() - t0}ms`);
376
439
  } else {
377
440
  job.statusLog.push({ message: "Matching values to the motion tokens…", at: now() });
378
441
  result = refineDeterministic(job);