simframe 0.11.0 → 0.12.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/src/view.js CHANGED
@@ -22,10 +22,10 @@ import * as matching from './matching.js';
22
22
  const REGION_ORDER = ['nav-bar', 'content', 'tab-bar', 'keyboard', 'status-bar'];
23
23
 
24
24
  /**
25
- * The status bar says the time and the battery level. It is on every screen,
26
- * it is never what anybody wants to tap, and it costs a row every time.
25
+ * Which regions the map will not offer now lives in `regions.js`, because it is
26
+ * not only a presentation rule — see `regions.offerable`. A target this hides
27
+ * must also be one nothing resolves onto behind the caller's back.
27
28
  */
28
- const HIDDEN_REGIONS = new Set(['status-bar']);
29
29
 
30
30
  /** A keyboard is 30-odd keys nobody refers to by name. One line says it. */
31
31
  const COLLAPSE_REGIONS = new Set(['keyboard']);
@@ -249,7 +249,7 @@ export function rowsFor(entry, { screen, filter, interactive, all = false, limit
249
249
  if (!isNum(t.x) || !isNum(t.y)) return false;
250
250
  // Off-screen elements are real in the tree and untappable in fact.
251
251
  if (regions.offViewport(t, screen)) return false;
252
- if (!all && HIDDEN_REGIONS.has(t.region)) return false;
252
+ if (!all && !regions.offerable(t.region)) return false;
253
253
  if (!all && isNoise(t)) return false;
254
254
  return true;
255
255
  });
@@ -329,6 +329,28 @@ export function recalledNote(identity, now = Date.now()) {
329
329
  return `elements recalled from ${ago} ago — pass refresh for what is there now`;
330
330
  }
331
331
 
332
+ /**
333
+ * How old the frame behind this reading is, and whether that is a problem.
334
+ *
335
+ * Two thresholds, because "slightly behind" and "possibly a different screen"
336
+ * are different messages. Under `FRAME_FRESH_MS` nothing is said: a map that
337
+ * announced "42ms old" on every call would train a reader to skip the line that
338
+ * matters. Over `FRAME_STALE_MS` it shouts, because at that age the app may have
339
+ * moved on entirely and the whole element list is then a description of the past.
340
+ */
341
+ export const FRAME_FRESH_MS = 1500;
342
+ export const FRAME_STALE_MS = 4000;
343
+
344
+ export function frameAgeNote(identity, now = Date.now()) {
345
+ const at = identity?.state?.capturedAt;
346
+ if (!Number.isFinite(at)) return null;
347
+ const age = Math.max(0, now - at);
348
+ if (age < FRAME_FRESH_MS) return null;
349
+ if (age < FRAME_STALE_MS) return `frame ${(age / 1000).toFixed(1)}s old`;
350
+ return `WARNING this frame is ${(age / 1000).toFixed(1)}s old — the screen may have moved on `
351
+ + 'since, so treat the elements below as a description of the past and pass refresh';
352
+ }
353
+
332
354
  /**
333
355
  * What a control *contains*, from the sensor that actually knows.
334
356
  *
@@ -702,6 +724,20 @@ export function render({ device, identity, rows, truncated, collapsed, screen, n
702
724
  identity?.settled === false ? 'STILL MOVING' : null,
703
725
  // Still and finished are not the same thing.
704
726
  identity?.loading === true ? 'STILL LOADING' : null,
727
+ // How old the *frame* this map was read from is.
728
+ //
729
+ // `sim_look` and `sim_state` have printed this since they existed, and this
730
+ // map never has — so the one tool an agent is told to start with was the one
731
+ // that could not say how old its evidence was. Reported from the field: a
732
+ // complete 20-element map of a screen the app was not on, and *"a wrong
733
+ // answer is worse than an error here, because nothing downstream knows to
734
+ // doubt it"*. `ensureDaemon` will hand back a frame up to 30s old, so this
735
+ // was reachable without anything being broken.
736
+ //
737
+ // `recalledNote` below is a different claim — that the *element map* came
738
+ // from memory — and having one was what made the absence of the other easy
739
+ // to miss.
740
+ frameAgeNote(identity),
705
741
  recalledNote(identity),
706
742
  ].filter(Boolean).join(' · ');
707
743