Files
Michael Ramos 782d9740c1 feat(annotate): carry agent-facing element context on HTML and live-app pinpoints (#1517)
A pinpointed element exported as a one-word placeholder ('[element:
Navigation]') or its flattened textContent. The bridge now captures a
bounded description at click time (tag, id, classes, ancestor path, role,
accessible name, allowlisted attributes, rendered text, an adaptive HTML
skeleton, box, landmark, heading, component hint, live route) as
elementContext; the parent re-validates and re-caps it; the export prints
a fenced skeleton plus selector/path/role/name lines under the comment;
the annotation panel gains a per-row Copy for element-bearing cards; the
feedback archive records element identity. Additive: annotations without
the field export byte-identically, share links drop it, no protocol bump.
2026-09-12 16:20:39 -07:00

51 lines
2.2 KiB
TypeScript

/**
* Share-URL contract for multi-target HTML annotations: element anchors are
* deliberately dropped from share payloads (they are meaningless in another
* viewer's DOM), and htmlAdditionalTargets follow the exact same rule — the
* compact tuple format never carries them. The COMMENT itself (text, quoted
* primary text, author, images) still shares.
*/
import { describe, expect, test } from "bun:test";
import { AnnotationType, type Annotation } from "../types";
import { fromShareable, toShareable } from "./sharing";
const MULTI: Annotation = {
id: "ann-1",
blockId: "",
startOffset: 0,
endOffset: 0,
type: AnnotationType.COMMENT,
text: "Unify these",
originalText: "Primary chip",
createdA: 1,
author: "reviewer",
htmlAnchor: { selector: "p.primary", tagName: "p", text: "Primary chip" },
// Element context describes a DOM the link's recipient does not have: it
// follows the anchor rule and never enters a share payload.
elementContext: { tag: "p", path: "body > p.primary", outline: "<p>Primary chip</p>" },
htmlAdditionalTargets: [
{ label: "Button", text: "Create", anchor: { selector: "span.btn", tagName: "span", text: "Create" } },
],
};
describe("sharing — multi-target annotations", () => {
test("toShareable serializes the comment without anchors or additional targets", () => {
const shareable = toShareable([MULTI]);
expect(shareable).toEqual([["C", "Primary chip", "Unify these", "reviewer", undefined]]);
expect(JSON.stringify(shareable)).not.toContain("htmlAdditionalTargets");
expect(JSON.stringify(shareable)).not.toContain("selector");
expect(JSON.stringify(shareable)).not.toContain("elementContext");
expect(JSON.stringify(shareable)).not.toContain("outline");
});
test("round trip keeps the comment but has no target array", () => {
const restored = fromShareable(toShareable([MULTI]));
expect(restored.length).toBe(1);
expect(restored[0]!.text).toBe("Unify these");
expect(restored[0]!.originalText).toBe("Primary chip");
expect(restored[0]!.htmlAnchor).toBeUndefined();
expect(restored[0]!.htmlAdditionalTargets).toBeUndefined();
expect(restored[0]!.elementContext).toBeUndefined();
});
});