transitions-refine 0.3.12 → 0.3.14

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.
@@ -54,6 +54,19 @@ The relay reports the agent as "available" for ~120s after your last poll, so
54
54
  short pauses keep you live. A successful job always resets idle, so an active
55
55
  session never backs off.
56
56
 
57
+ 0. **Announce yourself once, before the first poll.** The relay keeps a *sticky*
58
+ Stop latch: after a panel **Stop** (or the idle auto-stop) it answers every
59
+ `GET /jobs/next` with `{"stop": true}` until a new agent explicitly resumes —
60
+ so a stopped session can't silently come back. Clear the latch a single time
61
+ at startup, then begin polling:
62
+
63
+ ```bash
64
+ curl -s -X POST http://localhost:7331/poller/start
65
+ ```
66
+
67
+ Do **not** call this again mid-loop (it would defeat a user's Stop). Only on a
68
+ fresh `/refine live`.
69
+
57
70
  1. **Claim the next job (long-poll).** This call blocks up to ~25s, then returns.
58
71
 
59
72
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "transitions-refine",
3
- "version": "0.3.12",
3
+ "version": "0.3.14",
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": {
@@ -446,6 +446,10 @@ async function pollOnce() {
446
446
 
447
447
  async function loop() {
448
448
  log(`live refine loop → ${RELAY} (workspace: ${WORKSPACE})`);
449
+ // Announce ourselves so the relay clears any prior Stop latch — otherwise a
450
+ // relay that's still "stopped" from a previous panel Stop would answer our
451
+ // first poll with {stop:true} and we'd exit immediately.
452
+ try { await post("/poller/start", {}); } catch {}
449
453
  // eslint-disable-next-line no-constant-condition
450
454
  while (true) {
451
455
  try {
package/server/relay.mjs CHANGED
@@ -99,7 +99,14 @@ const now = () => Date.now();
99
99
  // When did a `/refine live` agent last poll? Used to know if LLM mode can be
100
100
  // served by a live editor agent (vs. needing REFINE_AGENT_CMD).
101
101
  let lastPollAt = 0;
102
- const pollerActive = () => now() - lastPollAt < POLLER_TTL_MS;
102
+ // Stop latch. Set by POST /poller/stop (and idle auto-stop); cleared ONLY by an
103
+ // explicit POST /poller/start (a fresh `/refine live` / agent loop announcing
104
+ // itself). While latched the poller reports inactive and every GET /jobs/next
105
+ // answers {stop:true}, so a loop that ignores the stop — or keeps polling /
106
+ // re-polls — can never silently revive the session. This is what makes the
107
+ // panel's Stop button stick instead of flipping "Live" back on seconds later.
108
+ let pollerStopped = false;
109
+ const pollerActive = () => !pollerStopped && now() - lastPollAt < POLLER_TTL_MS;
103
110
  const llmAvailable = () => Boolean(AGENT_CMD) || pollerActive();
104
111
 
105
112
  // Stop signal for the in-chat `/refine live` loop. Set by POST /poller/stop
@@ -653,12 +660,19 @@ const server = createServer(async (req, res) => {
653
660
 
654
661
  // GET /jobs/next — long-poll claimed by a `/refine live` agent (LLM jobs).
655
662
  if (method === "GET" && path === "/jobs/next") {
663
+ // Stopped latch: a Stop was issued and not yet resumed. Keep telling every
664
+ // poller to exit and report inactive (don't touch lastPollAt) until a fresh
665
+ // POST /poller/start. A pending job still wins so we never drop real work.
666
+ if (pollerStopped && !nextPendingLlm()) {
667
+ return send(res, 200, { stop: true });
668
+ }
656
669
  lastPollAt = now();
657
670
  if (!lastJobAt) lastJobAt = now(); // seed idle window on the loop's first poll
658
- // Stop signal (manual Stop button or idle auto-stop) → tell the loop to exit.
659
- // A pending job always wins so we never drop real work on the stop edge.
671
+ // Idle auto-stop (or a leftover stopRequested) → latch stopped + tell the
672
+ // loop to exit. A pending job always wins so we never drop real work.
660
673
  if ((stopRequested || (POLLER_IDLE_STOP_MS && now() - lastJobAt >= POLLER_IDLE_STOP_MS)) && !nextPendingLlm()) {
661
674
  stopRequested = false;
675
+ pollerStopped = true; // latch until an explicit /poller/start
662
676
  lastJobAt = 0;
663
677
  lastPollAt = 0; // loop is exiting → report it inactive immediately on /health
664
678
  return send(res, 200, { stop: true });
@@ -668,6 +682,7 @@ const server = createServer(async (req, res) => {
668
682
  if (res.writableEnded) return;
669
683
  if (stopRequested && !nextPendingLlm()) {
670
684
  stopRequested = false;
685
+ pollerStopped = true; // latch until an explicit /poller/start
671
686
  lastJobAt = 0;
672
687
  lastPollAt = 0; // loop is exiting → report it inactive immediately on /health
673
688
  return send(res, 200, { stop: true });
@@ -688,8 +703,23 @@ const server = createServer(async (req, res) => {
688
703
  // POST /poller/stop — the panel's "Stop" button. Flags the in-chat loop to
689
704
  // exit on its next poll. No-op for a wired REFINE_AGENT_CMD (never polls).
690
705
  if (method === "POST" && path === "/poller/stop") {
706
+ const wasActive = now() - lastPollAt < POLLER_TTL_MS;
691
707
  stopRequested = true;
692
- return send(res, 200, { ok: true, stopping: pollerActive() });
708
+ pollerStopped = true; // latch: stays stopped until POST /poller/start
709
+ lastPollAt = 0; // report inactive immediately; a straggler poll won't revive it
710
+ return send(res, 200, { ok: true, stopping: wasActive });
711
+ }
712
+
713
+ // POST /poller/start — a fresh `/refine live` agent (or loop) announces itself
714
+ // and clears the Stop latch so its polls count as live again. Without this an
715
+ // explicit re-run couldn't resume, since a latched relay tells every poll to
716
+ // stop. Call it once at loop startup, before the first GET /jobs/next.
717
+ if (method === "POST" && path === "/poller/start") {
718
+ pollerStopped = false;
719
+ stopRequested = false;
720
+ lastJobAt = now();
721
+ lastPollAt = now();
722
+ return send(res, 200, { ok: true });
693
723
  }
694
724
 
695
725
  const m = path.match(/^\/jobs\/([^/]+)(?:\/(status|result|error))?$/);