@broberg/lens-daemon 0.1.28 → 0.1.29

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/flow.ts +22 -5
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@broberg/lens-daemon",
3
- "version": "0.1.28",
3
+ "version": "0.1.29",
4
4
  "description": "The Lens module of the cardmem daemon (flows, critic, coverage, capture sessions, the /lens/* routes and the cardmem-lens MCP) as a package. The daemon mounts createLensRouter(deps) + createLensMcpRouter(deps). Bun runtime.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/flow.ts CHANGED
@@ -1383,6 +1383,20 @@ function subst(str: string, vars: Record<string, unknown>): string {
1383
1383
  return str.replace(/\{\{(\w+)\}\}/g, (_, k) => (k in vars ? String(vars[k]) : `{{${k}}}`));
1384
1384
  }
1385
1385
 
1386
+ /** F009.134 — a loop row reaches a selector step's selector, frame and `at` too, not
1387
+ * only its value. cardmem's flow 62ca6136 sent `[data-go="{{s}}"]` to Playwright
1388
+ * literally on all 9 iterations. Other step kinds are returned unchanged. */
1389
+ function bindSelectorVars(step: FlowStep, vars: Record<string, unknown>): FlowStep {
1390
+ if (step.type !== 'fillSelector' && step.type !== 'clickSelector') return step;
1391
+ const frame = step.frame == null ? step.frame : Array.isArray(step.frame) ? step.frame.map((f) => subst(f, vars)) : subst(step.frame, vars);
1392
+ return {
1393
+ ...step,
1394
+ selector: subst(step.selector, vars),
1395
+ ...(frame != null ? { frame } : {}),
1396
+ ...(step.type === 'clickSelector' && step.at ? { at: subst(step.at, vars) } : {}),
1397
+ } as FlowStep;
1398
+ }
1399
+
1386
1400
  const sel = (testid: string) => `[data-testid="${testid}"]`;
1387
1401
  const stepTimeout = (s: { timeout?: number }) => s.timeout ?? 8000;
1388
1402
 
@@ -2340,11 +2354,13 @@ async function execStep(page: Page, step: FlowStep, base: string, vars: Record<s
2340
2354
  }
2341
2355
  return shot;
2342
2356
  }
2343
- case 'fillSelector':
2344
- await resolveLocator(page, step.selector, step.frame)
2357
+ case 'fillSelector': {
2358
+ const b = bindSelectorVars(step, vars) as typeof step;
2359
+ await resolveLocator(page, b.selector, b.frame)
2345
2360
  .first()
2346
2361
  .fill(subst(step.value, vars), { timeout: stepTimeout(step) });
2347
2362
  return null;
2363
+ }
2348
2364
  case 'waitForBuild': {
2349
2365
  // F255.7 — polled from HERE, not through the page: a service worker will happily
2350
2366
  // serve a cached build and a page-side fetch would report the new marker on a page
@@ -2363,10 +2379,11 @@ async function execStep(page: Page, step: FlowStep, base: string, vars: Record<s
2363
2379
  };
2364
2380
  }
2365
2381
  case 'clickSelector': {
2366
- const loc = resolveLocator(page, step.selector, step.frame).first();
2382
+ const b = bindSelectorVars(step, vars) as typeof step;
2383
+ const loc = resolveLocator(page, b.selector, b.frame).first();
2367
2384
  await loc.click({
2368
2385
  timeout: stepTimeout(step),
2369
- ...(step.at ? { position: await positionFor(loc, step.at) } : {}),
2386
+ ...(b.at ? { position: await positionFor(loc, b.at) } : {}),
2370
2387
  });
2371
2388
  return null;
2372
2389
  }
@@ -2839,7 +2856,7 @@ export async function runFlow(flow: Flow, env: AuthEnv, opts: RunFlowOptions = {
2839
2856
  // ── leaf step ──
2840
2857
  const i = idx;
2841
2858
  idx += 1;
2842
- const label = step.label ?? describe(step);
2859
+ const label = step.label ?? describe(bindSelectorVars(step, vars));
2843
2860
  // F009.122 — which browser this step runs in. `sp` is the page every line
2844
2861
  // below acts on; the loop's own `page` is the default browser.
2845
2862
  const browserName = (step as { browser?: string }).browser ?? 'main';