transitions-refine 0.2.0 → 0.3.1

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.2.0",
3
+ "version": "0.3.1",
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": {
package/server/inject.mjs CHANGED
@@ -86,15 +86,13 @@ function buildEpilogue(css) {
86
86
 
87
87
  function InjectedRoot(){
88
88
  const registry = useMemo(() => new TransitionRegistry(), []);
89
- const preview = useMemo(() => new PreviewController(), []);
90
89
  const [activeId, setActiveId] = useState(null);
91
90
  useEffect(() => {
92
91
  const scanner = new DomScanner(document.body, registry);
93
- preview.setScanner(scanner);
94
92
  scanner.start();
95
- return () => { scanner.stop(); preview.setScanner(null); };
96
- }, [registry, preview]);
97
- const ctx = useMemo(() => ({ registry, preview, activeId, setActiveId }), [registry, preview, activeId]);
93
+ return () => { scanner.stop(); };
94
+ }, [registry]);
95
+ const ctx = useMemo(() => ({ registry, activeId, setActiveId }), [registry, activeId]);
98
96
  return h(TimelineCtx.Provider, { value: ctx }, h(TimelinePanel));
99
97
  }
100
98
 
package/server/relay.mjs CHANGED
@@ -16,7 +16,9 @@
16
16
  // and POSTs the result back. This is the default, install-free path.
17
17
  // b) A headless CLI: start the relay with REFINE_AGENT_CMD set and the
18
18
  // relay spawns it once per job (stdin = prompt, stdout = JSON).
19
- // e.g. REFINE_AGENT_CMD='cursor-agent -p' npm run relay
19
+ // e.g. REFINE_AGENT_CMD='cursor-agent -p --trust --force' npm run relay
20
+ // (for cursor-agent the relay auto-appends any missing -p/--trust/--force
21
+ // so headless jobs don't fail the workspace-trust prompt.)
20
22
  //
21
23
  // Run: node server/relay.mjs (or: npm run relay)
22
24
 
@@ -31,7 +33,23 @@ import { buildInjectModule } from "./inject.mjs";
31
33
 
32
34
  const PORT = Number(process.env.REFINE_RELAY_PORT) || 7331;
33
35
  const AUTO = process.env.REFINE_AUTO !== "0";
34
- const AGENT_CMD = process.env.REFINE_AGENT_CMD || null;
36
+
37
+ // A bare `cursor-agent` goes interactive: it prints "⚠ Workspace Trust Required"
38
+ // and exits 1, so every headless refine/scan/apply job fails. Force the headless
39
+ // trio whenever the command is cursor-agent: -p (print/headless, reads the prompt
40
+ // from stdin), --trust (trust the workspace without prompting; only valid with
41
+ // --print), and --force (auto-allow tool calls so apply/scan don't hang on
42
+ // approval). Append only the missing flags; leave non-cursor-agent commands alone.
43
+ function augmentAgentCmd(cmd) {
44
+ if (!cmd || !/(^|\s|\/)cursor-agent(\s|$)/.test(cmd)) return cmd;
45
+ const has = (...flags) => flags.some((f) => new RegExp(`(^|\\s)${f.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(\\s|$)`).test(cmd));
46
+ const extra = [];
47
+ if (!has("-p", "--print")) extra.push("-p");
48
+ if (!has("--trust")) extra.push("--trust");
49
+ if (!has("-f", "--force", "--yolo")) extra.push("--force");
50
+ return extra.length ? `${cmd} ${extra.join(" ")}` : cmd;
51
+ }
52
+ const AGENT_CMD = augmentAgentCmd(process.env.REFINE_AGENT_CMD || null);
35
53
  const AGENT_TIMEOUT_MS = Number(process.env.REFINE_AGENT_TIMEOUT_MS) || 120000;
36
54
  const LONGPOLL_MS = Number(process.env.REFINE_LONGPOLL_MS) || 25000;
37
55
  // Grace window after a `/refine live` agent's last poll during which LLM mode is
@@ -109,7 +127,8 @@ function nextPendingLlm() {
109
127
 
110
128
  function buildPrompt(job) {
111
129
  const r = job.request || {};
112
- const refineType = (r.refineType || "small") === "replace" ? "replace" : "small";
130
+ const rawType = r.refineType || "small";
131
+ const refineType = rawType === "replace" ? "replace" : rawType === "both" ? "both" : "small";
113
132
  const lines = [
114
133
  "You are refining ONE CSS transition against the transitions.dev library and motion tokens.",
115
134
  "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`.",
@@ -125,6 +144,12 @@ function buildPrompt(job) {
125
144
  "refineType is \"replace\": suggest a WHOLE-TRANSITION replacement ONLY — do NOT propose motion-token tweaks (no kind \"duration\"/\"delay\"/\"easing\").",
126
145
  "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.",
127
146
  );
147
+ } else if (refineType === "both") {
148
+ lines.push(
149
+ "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.",
150
+ "(1) Motion-token tweaks (kind \"duration\"/\"delay\"/\"easing\"): for each declaration, propose the token value only where it DIFFERS from the current one.",
151
+ "(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.",
152
+ );
128
153
  } else {
129
154
  lines.push(
130
155
  "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\").",
@@ -241,10 +266,18 @@ function buildScanPrompt(job) {
241
266
  "Steps:",
242
267
  "1. Identify each animated UI component (dropdown, modal, tooltip, accordion, drawer, toast…). Read its source (CSS/CSS Modules, styled-components/emotion, Tailwind, inline styles, Motion/Framer variants).",
243
268
  "2. For each component, split into PHASES — typically `open` and `close` (a hover-only component may have a single phase). Open and close often live on different selectors (e.g. `.is-open` vs `.is-closing`) with different timings; report BOTH even though only one is in the DOM right now.",
244
- "3. For each phase, list its MEMBER elements (panel, backdrop, the staggered items…). Give each member a stable `id`, a human `label`, a CSS `selector` that resolves in the live DOM, an optional `toState` hint (the class/attribute that drives that phase, e.g. `.is-open`), and its real `propertyTimings` (durationMs, delayMs, easing per animated property). Quote the real timings from source — never invent.",
269
+ "3. PHASE STATE how the phase is driven (REQUIRED for playback to work). For each phase provide:",
270
+ " - `stateTarget`: a CSS selector for the ONE element whose class/attribute is toggled to drive the whole phase (e.g. the dropdown root, the `.modal`, the element with `[data-open]`). It MUST resolve in the live DOM RIGHT NOW, in any state — so it must NOT itself contain the toggled state (write `.t-morph`, never `.t-morph[data-open=\"true\"]`).",
271
+ " - `fromState` and `toState`: the class/attribute on `stateTarget` at the START and END of this phase, as a token: a class `\".is-open\"`, an attribute `\"[data-open=\\\"true\\\"]\"`, or `null`/`\"\"` for the base/no-class state. OPEN usually goes base→open (`fromState:null`, `toState:\".is-open\"`); CLOSE goes open→base (`fromState:\".is-open\"`, `toState:null`). Get the DIRECTION right — open must animate into the open look, close must animate back out.",
272
+ "4. For each phase, list its MEMBER elements (panel, backdrop, the staggered items…). Give each member a stable `id`, a human `label`, a CSS `selector`, and its real `propertyTimings`. The member `selector` MUST resolve in the live DOM RIGHT NOW regardless of phase — use the BASE element selector and do NOT bake the phase's toggled class/attribute into it (write `.t-morph .t-morph-plus`, never `.t-morph[data-open=\"true\"] .t-morph-plus`). The toggled state belongs only in the phase's `stateTarget`/`toState`.",
273
+ "5. TIMINGS MUST BE EXACT AND PER-PROPERTY. This is the most common mistake — do not make it:",
274
+ " - List one `propertyTimings` entry per animated property. Read EACH property's own duration/delay/easing from the shorthand `transition:` list (or the property-specific longhand). Do NOT copy one property's duration onto the others, and do NOT use the phase's longest/representative duration for every lane.",
275
+ " - Resolve CSS custom properties (e.g. `var(--morph-fade-dur)`) to concrete numbers by following the `:root`/scope where they're defined; convert `s`→ms (`0.25s`→250). Never emit a `var(...)` or a guess.",
276
+ " - It is normal and expected for properties within one phase to differ (e.g. opacity/filter 200ms but transform 350ms). If every property in a phase ends up identical, re-read the source — you probably collapsed them by mistake.",
277
+ " - Open and close usually have DIFFERENT durations/easings; report each from its own rule.",
245
278
  "",
246
279
  "Output ONLY a JSON object — no prose, no markdown fences — shaped exactly like:",
247
- '{"summary":"Grouped 3 components.","groups":[{"id":"dropdown","label":"Dropdown","component":"src/Dropdown.tsx","phases":[{"id":"dropdown:open","phase":"open","label":"Open","members":[{"id":"panel","label":"Panel","selector":".dropdown-panel","toState":".is-open","propertyTimings":[{"property":"opacity","durationMs":200,"delayMs":0,"easing":"ease-out"},{"property":"transform","durationMs":200,"delayMs":0,"easing":"cubic-bezier(0.22, 1, 0.36, 1)"}]}]},{"id":"dropdown:close","phase":"close","label":"Close","members":[{"id":"panel","label":"Panel","selector":".dropdown-panel","toState":".is-closing","propertyTimings":[{"property":"opacity","durationMs":150,"delayMs":0,"easing":"ease-in"}]}]}]}]}',
280
+ '{"summary":"Grouped 3 components.","groups":[{"id":"dropdown","label":"Dropdown","component":"src/Dropdown.tsx","phases":[{"id":"dropdown:open","phase":"open","label":"Open","stateTarget":".dropdown","fromState":null,"toState":".is-open","members":[{"id":"panel","label":"Panel","selector":".dropdown .dropdown-panel","propertyTimings":[{"property":"opacity","durationMs":200,"delayMs":0,"easing":"ease-out"},{"property":"transform","durationMs":200,"delayMs":0,"easing":"cubic-bezier(0.22, 1, 0.36, 1)"}]}]},{"id":"dropdown:close","phase":"close","label":"Close","stateTarget":".dropdown","fromState":".is-open","toState":null,"members":[{"id":"panel","label":"Panel","selector":".dropdown .dropdown-panel","propertyTimings":[{"property":"opacity","durationMs":150,"delayMs":0,"easing":"ease-in"},{"property":"transform","durationMs":150,"delayMs":0,"easing":"ease-in"}]}]}]}]}',
248
281
  "If you cannot confidently group anything, return an empty groups array with a short summary; the panel keeps its flat DOM scan.",
249
282
  ].join("\n");
250
283
  }
@@ -318,7 +351,7 @@ async function answerJob(job) {
318
351
  if (!AGENT_CMD) {
319
352
  throw new Error(
320
353
  "LLM mode needs an agent CLI. Restart the relay with REFINE_AGENT_CMD set " +
321
- "(e.g. REFINE_AGENT_CMD='cursor-agent -p' npm run relay), or switch to the Deterministic tab."
354
+ "(e.g. REFINE_AGENT_CMD='cursor-agent -p --trust --force' npm run relay), or switch to the Deterministic tab."
322
355
  );
323
356
  }
324
357
  job.statusLog.push({ message: "Asking your agent…", at: now() });