transitions-refine 0.3.15 → 0.3.16

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/README.md CHANGED
@@ -98,6 +98,8 @@ Like Replace, Accept needs the agent — run `/refine live` (or `--llm` / `REFIN
98
98
  | `REFINE_RELAY_PORT` | `7331` | relay port |
99
99
  | `REFINE_AGENT_CMD` | — | one-shot LLM CLI the relay spawns per job |
100
100
  | `REFINE_AGENT_TIMEOUT_MS` | `120000` | per-run timeout |
101
+ | `REFINE_SCAN_MODEL` | `composer-2.5-fast` | cursor-agent model for the initial group scan (`""` = agent default) |
102
+ | `REFINE_SCAN_EFFORT` | `low` | Codex reasoning effort for the scan — `minimal\|low\|medium\|high` (`""` = configured effort) |
101
103
  | `REFINE_AUTO=0` | — | disable auto-answer and wait for an external poller |
102
104
  | `window.REFINE_RELAY_URL` | injected origin | browser override for the relay URL |
103
105
 
package/demo.html CHANGED
@@ -1437,32 +1437,49 @@
1437
1437
  .tl-scan-note .tl-dotm { width: 14px; height: 14px; }
1438
1438
  .tl-scan-error { color: var(--c-warn, #c2410c); }
1439
1439
  .tl-scan-error svg { flex: none; }
1440
- /* /refine live chip — warns the chat-loop mode bills credits while idle */
1440
+ /* /refine live chip — Figma icons 594:22938. Live = green chip + green Stop
1441
+ pill; Live Off = gray chip, no button. Tooltip warns the chat-loop mode
1442
+ bills credits while idle. */
1441
1443
  .tl-live-wrap { display: inline-flex; }
1442
- .tl-live-chip { display: inline-flex; align-items: center; gap: 6px;
1443
- height: 22px; padding: 0 4px 0 8px; border-radius: 999px;
1444
- font-size: 11px; font-weight: 600; line-height: 1; cursor: help;
1445
- color: #b45309; background: rgba(217,119,6,0.12);
1446
- border: 1px solid rgba(217,119,6,0.28);
1444
+ /* LIVE (active) node 594:22828 */
1445
+ .tl-live-chip { box-sizing: border-box; display: inline-flex; align-items: center;
1446
+ justify-content: space-between; width: 94px; height: 28px; padding: 0 2px 0 9px;
1447
+ border-radius: 36px; cursor: help;
1448
+ background: rgba(132,211,125,0.1);
1449
+ box-shadow: 0 1px 3px 0 rgba(0,220,37,0.06),
1450
+ inset 0 0 0 1px rgba(48,124,21,0.06), inset 0 -1px 0 0 rgba(0,85,9,0.08);
1447
1451
  /* mount: token dropdown-open feel — 250ms Fast, scale 0.97, smooth ease out */
1448
1452
  animation: tl-live-in 250ms cubic-bezier(0.22,1,0.36,1) both; }
1449
- html[data-theme="dark"] .tl-live-chip { color: #fbbf24;
1450
- background: rgba(251,191,36,0.13); border-color: rgba(251,191,36,0.3); }
1451
- .tl-live-chip.is-stopped { color: var(--c-count); background: rgba(127,127,127,0.1);
1452
- border-color: rgba(127,127,127,0.26); padding: 0 8px; }
1453
- .tl-live-chip.is-stopped svg { flex: none; }
1454
- .tl-live-dot { width: 7px; height: 7px; border-radius: 50%;
1455
- background: currentColor; flex: none;
1453
+ .tl-live-main { display: inline-flex; align-items: center; gap: 5px; flex: none; }
1454
+ .tl-live-label { font-size: 12px; font-weight: 400; line-height: 14px; color: #18a32f; white-space: nowrap; }
1455
+ html[data-theme="dark"] .tl-live-label { color: #4ade80; }
1456
+ .tl-live-dot { width: 8px; height: 8px; border-radius: 50%; flex: none;
1457
+ background: #34CC20; box-shadow: inset 0 0 0 0.5px rgba(0,182,30,0.55);
1456
1458
  animation: tl-live-pulse 1600ms linear infinite; }
1457
- .tl-live-stop { all: unset; cursor: pointer; font: inherit; font-size: 10px;
1458
- font-weight: 700; letter-spacing: 0.02em; text-transform: uppercase;
1459
- color: inherit; padding: 3px 7px; border-radius: 999px;
1460
- background: rgba(217,119,6,0.16);
1461
- transition: background 150ms cubic-bezier(0.22,1,0.36,1); }
1462
- html[data-theme="dark"] .tl-live-stop { background: rgba(251,191,36,0.18); }
1463
- .tl-live-stop:hover { background: rgba(217,119,6,0.32); }
1464
- html[data-theme="dark"] .tl-live-stop:hover { background: rgba(251,191,36,0.34); }
1465
- .tl-live-stop:focus-visible { outline: 2px solid currentColor; outline-offset: 1px; }
1459
+ /* Stop pill node 594:22874 (default / hover+active = Variant2/3) */
1460
+ .tl-live-stop { all: unset; box-sizing: border-box; cursor: pointer; flex: none;
1461
+ display: inline-flex; align-items: center; justify-content: center;
1462
+ height: 24px; padding: 0 8px; border-radius: 36px;
1463
+ font-size: 12px; font-weight: 500; line-height: 14px; color: #118c25;
1464
+ background: rgba(68,167,84,0.1);
1465
+ box-shadow: inset 0 0 0 1px rgba(18,130,28,0.04);
1466
+ transition: background 150ms cubic-bezier(0.22,1,0.36,1),
1467
+ color 150ms cubic-bezier(0.22,1,0.36,1); }
1468
+ .tl-live-stop:hover,
1469
+ .tl-live-stop:active { background: rgba(68,167,84,0.2); color: #057217; }
1470
+ html[data-theme="dark"] .tl-live-stop { color: #4ade80; }
1471
+ html[data-theme="dark"] .tl-live-stop:hover,
1472
+ html[data-theme="dark"] .tl-live-stop:active { color: #86efac; }
1473
+ .tl-live-stop:focus-visible { outline: 2px solid #118c25; outline-offset: 1px; }
1474
+ /* LIVE OFF — node 594:22912 (gray, no button) */
1475
+ .tl-live-chip.is-stopped { width: auto; justify-content: flex-start; padding: 0 9px;
1476
+ background: rgba(102,102,102,0.04);
1477
+ box-shadow: 0 1px 3px 0 rgba(0,0,0,0.02),
1478
+ inset 0 0 0 1px rgba(77,77,77,0.1), inset 0 -1px 0 0 rgba(0,0,0,0.04); }
1479
+ .tl-live-chip.is-stopped .tl-live-dot { background: #A1A1A1;
1480
+ box-shadow: inset 0 0 0 0.5px rgba(146,146,146,0.6); animation: none; }
1481
+ .tl-live-chip.is-stopped .tl-live-label { color: #525252; }
1482
+ html[data-theme="dark"] .tl-live-chip.is-stopped .tl-live-label { color: #a1a1a1; }
1466
1483
  @keyframes tl-live-in { from { opacity: 0; transform: scale(0.97); } to { opacity: 1; transform: scale(1); } }
1467
1484
  @keyframes tl-live-pulse { 0%,100% { opacity: 1; } 50% { opacity: 0.3; } }
1468
1485
  @media (prefers-reduced-motion: reduce) {
@@ -2906,8 +2923,9 @@
2906
2923
  chatLoop
2907
2924
  ? h("span",{className:"t-tt-wrap tl-live-wrap"},
2908
2925
  h("span",{className:"tl-live-chip",tabIndex:0},
2909
- h("span",{className:"tl-live-dot"}),
2910
- h("span",null,"Live"),
2926
+ h("span",{className:"tl-live-main"},
2927
+ h("span",{className:"tl-live-dot"}),
2928
+ h("span",{className:"tl-live-label"},"Live")),
2911
2929
  h("button",{className:"tl-live-stop",title:"Stop /refine live",
2912
2930
  onClick:(e)=>{e.stopPropagation();onStopLive&&onStopLive();}},"Stop")),
2913
2931
  h("span",{className:"t-tt tl-tt-below tl-tt-msg",role:"tooltip"},
@@ -2915,8 +2933,9 @@
2915
2933
  : liveStopped
2916
2934
  ? h("span",{className:"t-tt-wrap tl-live-wrap"},
2917
2935
  h("span",{className:"tl-live-chip is-stopped",tabIndex:0},
2918
- h(Ic,{name:"check",size:12}),
2919
- h("span",null,"Live stopped")),
2936
+ h("span",{className:"tl-live-main"},
2937
+ h("span",{className:"tl-live-dot"}),
2938
+ h("span",{className:"tl-live-label"},"Live Off"))),
2920
2939
  h("span",{className:"t-tt tl-tt-below tl-tt-msg",role:"tooltip"},
2921
2940
  "The `/refine live` loop was asked to stop and will exit on its next poll. Run `/refine live` again to resume."))
2922
2941
  : null,
@@ -4159,9 +4178,11 @@
4159
4178
  cssRules:harvestCssHints(e)}));
4160
4179
  try{
4161
4180
  const{id}=await relayCreateJob({kind:"scan",url:location.href,raw});
4162
- for(let i=0;i<500;i++){
4181
+ for(let i=0;i<520;i++){
4163
4182
  if(scanTokenRef.current!==token)return; // superseded / unmounted
4164
- await new Promise(r=>setTimeout(r,300));
4183
+ // Ramp: poll fast (120ms) for the first ~2.4s so a quick scan returns
4184
+ // almost immediately, then settle to 300ms to keep the request rate low.
4185
+ await new Promise(r=>setTimeout(r,i<20?120:300));
4165
4186
  if(scanTokenRef.current!==token)return;
4166
4187
  const job=await relayGetJob(id);
4167
4188
  if(job.status==="done"){
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transitions-refine",
3
- "version": "0.3.15",
3
+ "version": "0.3.16",
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/relay.mjs CHANGED
@@ -47,7 +47,15 @@ try {
47
47
  // --print), and --force (auto-allow tool calls so apply/scan don't hang on
48
48
  // approval). Append only the missing flags; leave non-cursor-agent commands alone.
49
49
  function augmentAgentCmd(cmd) {
50
- if (!cmd || !/(^|\s|\/)cursor-agent(\s|$)/.test(cmd)) return cmd;
50
+ if (!cmd) return cmd;
51
+ // `codex exec` reads the prompt from stdin ONLY when given a trailing `-`
52
+ // (the stdin marker). The relay always pipes the prompt on stdin, so without
53
+ // it Codex gets no prompt and exits 1. Append `-` when it's missing (a lone
54
+ // `-` token, not the dashes inside flags like `--sandbox`).
55
+ if (/(^|\s|\/)codex(\s|$)/.test(cmd) && /(^|\s)exec(\s|$)/.test(cmd)) {
56
+ return /(^|\s)-(\s|$)/.test(cmd) ? cmd : `${cmd} -`;
57
+ }
58
+ if (!/(^|\s|\/)cursor-agent(\s|$)/.test(cmd)) return cmd;
51
59
  const has = (...flags) => flags.some((f) => new RegExp(`(^|\\s)${f.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}(\\s|$)`).test(cmd));
52
60
  const extra = [];
53
61
  if (!has("-p", "--print")) extra.push("-p");
@@ -61,6 +69,13 @@ const AGENT_CMD = augmentAgentCmd(process.env.REFINE_AGENT_CMD || null);
61
69
  // (Opus / GPT-5.5) — forcing a fast model here keeps the initial scan snappy.
62
70
  // Override with REFINE_SCAN_MODEL="" to fall back to the agent's default.
63
71
  const SCAN_MODEL = process.env.REFINE_SCAN_MODEL ?? "composer-2.5-fast";
72
+ // Reasoning effort for scan jobs on Codex (cursor-agent uses SCAN_MODEL instead).
73
+ // Grouping is a structured, near-mechanical task — the cssRules payload already
74
+ // hands the agent the timings + state selectors — so it does NOT need deep
75
+ // reasoning. A default-effort Codex run can take tens of seconds; "low" cuts that
76
+ // dramatically with no grouping/naming loss. Override with REFINE_SCAN_EFFORT
77
+ // (minimal|low|medium|high) or "" to use the agent's configured effort.
78
+ const SCAN_EFFORT = process.env.REFINE_SCAN_EFFORT ?? "low";
64
79
  // Pin a fast model for refine (suggestion) jobs too. The motion-token vocabulary
65
80
  // is inlined into the prompt (see MOTION_TOKENS_BLOCK) so a fast model has every
66
81
  // fact it needs for the common token-tweak path — keeping suggestions snappy
@@ -83,6 +98,28 @@ function withModel(cmd, model) {
83
98
  if (/(^|\s)--model(\s|=)/.test(cmd)) return cmd; // respect an explicit choice
84
99
  return cmd.replace(/^(\s*\S+)/, `$1 --model ${model}`);
85
100
  }
101
+
102
+ // Speed up the SCAN job per agent — grouping is structured and doesn't need a
103
+ // heavy model / deep reasoning:
104
+ // • cursor-agent → pin SCAN_MODEL (a fast model) via --model.
105
+ // • codex exec → drop reasoning to SCAN_EFFORT ("low") and silence reasoning
106
+ // summaries (less work AND cleaner stdout for JSON parsing) via `-c` config
107
+ // overrides, inserted right after `exec` (before the trailing `-` stdin marker).
108
+ // No-ops if the user already pinned a model / reasoning effort, or disabled it
109
+ // (REFINE_SCAN_MODEL="" / REFINE_SCAN_EFFORT=""). Other CLIs are left untouched.
110
+ function withScanSpeed(cmd) {
111
+ if (!cmd) return cmd;
112
+ let out = withModel(cmd, SCAN_MODEL); // cursor-agent fast model
113
+ const isCodexExec =
114
+ /(^|\s|\/)codex(\s|$)/.test(out) && /(^|\s)exec(\s|$)/.test(out);
115
+ if (isCodexExec && SCAN_EFFORT && !/model_reasoning_effort/.test(out)) {
116
+ out = out.replace(
117
+ /(^|\s)exec(\s|$)/,
118
+ `$1exec -c model_reasoning_effort="${SCAN_EFFORT}" -c model_reasoning_summary="none"$2`
119
+ );
120
+ }
121
+ return out;
122
+ }
86
123
  const LONGPOLL_MS = Number(process.env.REFINE_LONGPOLL_MS) || 25000;
87
124
  // Grace window after a `/refine live` agent's last poll during which LLM mode is
88
125
  // still reported "available". Kept well above LONGPOLL_MS so the normal gaps
@@ -369,6 +406,18 @@ const AGENT_RETRIES = Number.isFinite(Number(process.env.REFINE_AGENT_RETRIES))
369
406
  ? Number(process.env.REFINE_AGENT_RETRIES)
370
407
  : 2;
371
408
 
409
+ // CLIs (Codex especially) print verbose startup WARNINGs to stderr even on
410
+ // success — e.g. "could not create PATH aliases" and "failed to open state db"
411
+ // (Codex literally logs "proceeding" right after). Those lines used to eat the
412
+ // 300-char error budget, truncating away the REAL failure. Strip the known
413
+ // noise so the genuine error surfaces; keep a generous cap.
414
+ function cleanAgentErr(err) {
415
+ const noise = /could not create PATH aliases|failed to open state db|state DB at|codex_state::runtime|^\s*WARNING:|(^|\s)WARN(\s|:)/i;
416
+ const lines = (err || "").split(/\r?\n/).filter((l) => l.trim() && !noise.test(l));
417
+ const msg = lines.join("\n").trim() || (err || "").trim() || "(no stderr)";
418
+ return msg.slice(0, 1500);
419
+ }
420
+
372
421
  function runAgentOnce(cmd, prompt, parse) {
373
422
  return new Promise((resolve, reject) => {
374
423
  const child = spawn("sh", ["-c", cmd], { stdio: ["pipe", "pipe", "pipe"] });
@@ -386,7 +435,7 @@ function runAgentOnce(cmd, prompt, parse) {
386
435
  });
387
436
  child.on("close", (code) => {
388
437
  clearTimeout(timer);
389
- if (code !== 0) return reject(new Error(`agent exited ${code}: ${err.slice(0, 300)}`));
438
+ if (code !== 0) return reject(new Error(`agent exited ${code}: ${cleanAgentErr(err)}`));
390
439
  try {
391
440
  resolve(parse(out));
392
441
  } catch (e) {
@@ -535,7 +584,7 @@ async function answerJob(job) {
535
584
  );
536
585
  }
537
586
  job.statusLog.push({ message: "Reading components from source…", at: now() });
538
- result = await runAgentCmd(withModel(AGENT_CMD, SCAN_MODEL), buildScanPrompt(job), parseScanOutput);
587
+ result = await runAgentCmd(withScanSpeed(AGENT_CMD), buildScanPrompt(job), parseScanOutput);
539
588
  job.result = { groups: result.groups, summary: result.summary };
540
589
  job.status = "done";
541
590
  job.updatedAt = now();