@classic-homes/page-agent-core 0.2.0 → 0.2.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/README.md +10 -5
- package/dist/index.d.ts +33 -13
- package/dist/index.js +34 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -47,10 +47,14 @@ ignoreSelector })` and `executePageCall(call)`, the single entry point for
|
|
|
47
47
|
the model's tool calls. `PAGE_TOOL_NAMES` and `PAGE_TOOLS` (plain-JSON
|
|
48
48
|
input shapes) let the agent server mirror the contract exactly.
|
|
49
49
|
- **Visibility**: `measureVisibility(el)` says what of a target the user can
|
|
50
|
-
see: clipped to the viewport and its scroll boxes, cut where a
|
|
51
|
-
header / footer / dock
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
see: clipped to the viewport and its scroll boxes, cut where a strip
|
|
51
|
+
covers one side of it (a sticky header / footer / dock, or a menu over one
|
|
52
|
+
end: a cover spanning the target's visible part along one axis and
|
|
53
|
+
reaching past an edge on the other, viewport-wide or not; cuts combine),
|
|
54
|
+
drawn over smaller covers (hit-tested with `document.elementsFromPoint`
|
|
55
|
+
at a 3 x 3 grid plus a probe inside each edge, the layer skipped), and
|
|
56
|
+
`covered` under a cover that hides most of it, behind a modal dialog or in
|
|
57
|
+
`inert`. Bindings draw only `visible` and hide (not
|
|
54
58
|
remove) effects while it is null. The maths is the pure
|
|
55
59
|
`computeVisibility({ rect, viewport, clips, behindModal, coverAt })`.
|
|
56
60
|
- **Collapsed tab rows**: `collapsedTabMenu(tab)` and the
|
|
@@ -158,7 +162,8 @@ target) and a live region (`.chpa-sr`). Override `--chpa-accent` (default
|
|
|
158
162
|
`#6d28d9`) and `--chpa-z` (default `2147482000`, below the Access
|
|
159
163
|
Inspector). The layer stays on top so targets inside dialogs can be pointed
|
|
160
164
|
at; covers are handled by drawing only the visible part (an outline cut by
|
|
161
|
-
a header carries `data-cut="top"
|
|
165
|
+
a header carries `data-cut="top"`, one cut by a menu over its right end
|
|
166
|
+
`data-cut="right"`, and is left open on that side). Dark mode follows `.dark` / `[data-theme='dark']`; reduced
|
|
162
167
|
motion drops the glide and pulse.
|
|
163
168
|
|
|
164
169
|
## Testing
|
package/dist/index.d.ts
CHANGED
|
@@ -265,7 +265,7 @@ declare function placeLabel(rect: RectLike, size: Size, viewport: Size, opts?: {
|
|
|
265
265
|
declare function pointerTarget(rect: RectLike, viewport: Size, margin?: number): Point;
|
|
266
266
|
/** Overlap of two boxes; null when they do not overlap. */
|
|
267
267
|
declare function intersectBoxes(a: RectLike, b: RectLike): Box | null;
|
|
268
|
-
/** Which sides of a target are cut off (by clipping, the viewport or a covering
|
|
268
|
+
/** Which sides of a target are cut off (by clipping, the viewport or a covering strip). */
|
|
269
269
|
interface CutSides {
|
|
270
270
|
top: boolean;
|
|
271
271
|
right: boolean;
|
|
@@ -289,14 +289,18 @@ interface TargetVisibility {
|
|
|
289
289
|
visible: Box | null;
|
|
290
290
|
/**
|
|
291
291
|
* The area the target is shown in: the viewport, narrowed by clipping
|
|
292
|
-
* ancestors and by
|
|
293
|
-
*
|
|
292
|
+
* ancestors and by the strips (sticky header, footer, side dock, a menu
|
|
293
|
+
* over one end) that cut it. Captions are kept inside it. Null when there
|
|
294
|
+
* is none.
|
|
294
295
|
*/
|
|
295
296
|
bounds: Box | null;
|
|
296
297
|
cut: CutSides;
|
|
297
298
|
/** With `covered`: a modal dialog / inert page, or another element on top. */
|
|
298
299
|
coveredBy?: 'modal' | 'element';
|
|
299
|
-
/**
|
|
300
|
+
/**
|
|
301
|
+
* Strips that cut the target (or a header-like cover over all of it), as
|
|
302
|
+
* measured: sticky headers and footers, docks, menus over one side.
|
|
303
|
+
*/
|
|
300
304
|
bands: Box[];
|
|
301
305
|
}
|
|
302
306
|
interface VisibilityInput {
|
|
@@ -312,7 +316,11 @@ interface VisibilityInput {
|
|
|
312
316
|
* viewport point, or null when the target itself (or nothing) is.
|
|
313
317
|
*/
|
|
314
318
|
coverAt?: (x: number, y: number) => RectLike | null;
|
|
315
|
-
/**
|
|
319
|
+
/**
|
|
320
|
+
* Sample grid per side (default 3, so 9 points), plus one probe just
|
|
321
|
+
* inside the middle of each edge of the visible part (see
|
|
322
|
+
* {@link computeVisibility}).
|
|
323
|
+
*/
|
|
316
324
|
grid?: number;
|
|
317
325
|
}
|
|
318
326
|
/**
|
|
@@ -321,13 +329,25 @@ interface VisibilityInput {
|
|
|
321
329
|
*
|
|
322
330
|
* Occlusion rules, chosen to be robust with coarse sampling:
|
|
323
331
|
* - something that covers the whole visible part (a dialog backdrop) hides it;
|
|
324
|
-
* -
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
*
|
|
332
|
+
* - a side strip, a cover that spans the visible part's full height and
|
|
333
|
+
* reaches past its left or right edge (a side dock, a menu over the end of
|
|
334
|
+
* a heading), or its full width and past its top or bottom edge (a sticky
|
|
335
|
+
* header or footer), cuts the target off exactly at the cover's edge, so
|
|
336
|
+
* the effect stops where the cover starts. The cover does not have to
|
|
337
|
+
* span the viewport, and cuts on several sides combine;
|
|
338
|
+
* - anything else on top (a floating button over a corner, a popover over
|
|
339
|
+
* the middle) hides the target only when it covers most of the grid
|
|
340
|
+
* points left uncut, and is otherwise drawn over: clipping around
|
|
341
|
+
* arbitrary shapes is not worth the wrong guesses.
|
|
342
|
+
*
|
|
343
|
+
* Sampling: a `grid` x `grid` set of points over the visible part (weighs
|
|
344
|
+
* covers for the "most of it" rule), plus a probe just inside the middle of
|
|
345
|
+
* each edge (finds side strips the grid falls short of, such as a menu over
|
|
346
|
+
* the last tenth of a wide heading: any strip of an edge's full length
|
|
347
|
+
* contains that edge's midpoint). After each cut the new edges are probed
|
|
348
|
+
* again, so a strip that only spans the part below a header is found too.
|
|
349
|
+
* Edge probes only find strips; they never count towards "most of it".
|
|
350
|
+
* 9 + 4 hit tests for an unobstructed target, 4 more per cut.
|
|
331
351
|
*/
|
|
332
352
|
declare function computeVisibility(input: VisibilityInput): TargetVisibility;
|
|
333
353
|
|
|
@@ -581,7 +601,7 @@ declare function isBehindModal(el: Element): boolean;
|
|
|
581
601
|
* docks) hit-tested. See {@link computeVisibility} for the rules. Style
|
|
582
602
|
* checks (`hidden`, `display: none`, …) are not repeated here; callers
|
|
583
603
|
* check {@link isRendered} first. Cheap enough per animation frame for a
|
|
584
|
-
* handful of effects (
|
|
604
|
+
* handful of effects (13 hit tests each, 4 more per cut side; reads only).
|
|
585
605
|
*/
|
|
586
606
|
declare function measureVisibility(el: Element): TargetVisibility;
|
|
587
607
|
|
package/dist/index.js
CHANGED
|
@@ -211,6 +211,8 @@ function pointerTarget(rect, viewport, margin = 12) {
|
|
|
211
211
|
}
|
|
212
212
|
var EDGE = 1;
|
|
213
213
|
var MIN_VISIBLE = 4;
|
|
214
|
+
var PROBE_INSET = 2;
|
|
215
|
+
var MAX_TRIM_ROUNDS = 6;
|
|
214
216
|
var viewportBox = (viewport) => ({
|
|
215
217
|
top: 0,
|
|
216
218
|
left: 0,
|
|
@@ -270,11 +272,24 @@ function computeVisibility(input) {
|
|
|
270
272
|
}
|
|
271
273
|
}
|
|
272
274
|
const covers = [];
|
|
273
|
-
|
|
274
|
-
if (
|
|
275
|
-
}
|
|
275
|
+
const addCover = (cover) => {
|
|
276
|
+
if (cover && !covers.some((c) => sameBox(c, cover))) covers.push(cover);
|
|
277
|
+
};
|
|
278
|
+
for (const s of samples) addCover(s.cover);
|
|
279
|
+
const probed = /* @__PURE__ */ new Set();
|
|
280
|
+
const probeEdges = (box) => {
|
|
281
|
+
for (const [x, y] of edgeProbes(box)) {
|
|
282
|
+
const key = `${Math.round(x * 2)},${Math.round(y * 2)}`;
|
|
283
|
+
if (probed.has(key)) continue;
|
|
284
|
+
probed.add(key);
|
|
285
|
+
const hit = input.coverAt(x, y);
|
|
286
|
+
const cover = hit ? toBox(hit) : null;
|
|
287
|
+
addCover(cover && containsPoint(cover, x, y) ? cover : null);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
probeEdges(visible);
|
|
276
291
|
let trimmed = true;
|
|
277
|
-
|
|
292
|
+
for (let round = 0; trimmed && visible && round < MAX_TRIM_ROUNDS; round++) {
|
|
278
293
|
trimmed = false;
|
|
279
294
|
for (const cover of covers) {
|
|
280
295
|
if (bands.includes(cover) || !visible || !intersectBoxes(cover, visible)) continue;
|
|
@@ -293,6 +308,9 @@ function computeVisibility(input) {
|
|
|
293
308
|
bands.push(cover);
|
|
294
309
|
trimmed = true;
|
|
295
310
|
}
|
|
311
|
+
if (trimmed && visible && visible.width >= MIN_VISIBLE && visible.height >= MIN_VISIBLE) {
|
|
312
|
+
probeEdges(visible);
|
|
313
|
+
}
|
|
296
314
|
}
|
|
297
315
|
if (!visible || visible.width < MIN_VISIBLE || visible.height < MIN_VISIBLE) {
|
|
298
316
|
return result("covered", null, { coveredBy: "element", bands });
|
|
@@ -307,6 +325,18 @@ function computeVisibility(input) {
|
|
|
307
325
|
const whole = !cut.top && !cut.right && !cut.bottom && !cut.left;
|
|
308
326
|
return { state: whole ? "visible" : "partial", rect, visible, bounds, cut, bands };
|
|
309
327
|
}
|
|
328
|
+
function edgeProbes(box) {
|
|
329
|
+
const dx = Math.min(PROBE_INSET, box.width / 4);
|
|
330
|
+
const dy = Math.min(PROBE_INSET, box.height / 4);
|
|
331
|
+
const cx = box.left + box.width / 2;
|
|
332
|
+
const cy = box.top + box.height / 2;
|
|
333
|
+
return [
|
|
334
|
+
[cx, box.top + dy],
|
|
335
|
+
[box.left + box.width - dx, cy],
|
|
336
|
+
[cx, box.top + box.height - dy],
|
|
337
|
+
[box.left + dx, cy]
|
|
338
|
+
];
|
|
339
|
+
}
|
|
310
340
|
function sameBox(a, b) {
|
|
311
341
|
return a.top === b.top && a.left === b.left && a.width === b.width && a.height === b.height;
|
|
312
342
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@classic-homes/page-agent-core",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Framework-agnostic page layer for an in-app AI agent: named targets, safe target resolution, a page snapshot, highlight / spotlight / pointer effects and one schema-validated executor for model tool calls",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "dist/index.js",
|