* feat(annotate): minimal-by-default HTML sessions with stale-preference decay
Raw-HTML annotate sessions now open minimal by default: Pinpoint input, tools
hidden, left sidebar closed, and the right annotations drawer closed (the
drawer joins the persisted HTML chrome state, previously it always opened on
desktop). Explicit user choices still persist between HTML sessions, but the
records now carry a timestamp and expire after 7 days without a refresh, so
users who have not changed anything or annotated HTML in a while come back to
the product defaults. Explicit changes and annotation activity both re-stamp
the records, so active users keep their setup. Legacy untimestamped cookies
are treated as expired (a one-time reset to the new defaults). The markdown
surface keeps its own preference with no TTL, unchanged.
Mutation-verified: disabling the TTL fails 4 tests.
* test(annotate): stamp seeded chrome cookies and compare semantic fields
The App-level chrome suite seeded legacy untimestamped cookies, which the
stale-preference decay now treats as expired by design, and one assertion
compared cookie bytes that re-stamping legitimately changes. Seeds carry a
fresh savedAt and the write-integrity assertion compares the chrome fields.
* feat(annotate): rebuild HTML pinpoint mode with element anchors, pin badges, and a minimal-first render
Raw-HTML annotate sessions now open as just the page: first-ever run hides
all annotation chrome (toolstrip, tongue tabs, action cluster, sidebar) and
the user's last chrome state is restored on every later session via the
plannotator-html-chrome cookie. Pinpoint becomes the default input method on
the HTML surface, persisted separately from the markdown preference.
Pinpoint mechanics are rebuilt in the sandbox bridge, modeled on
app-notes-extension and agentation: a fixed-position outline box + label
replaces class writes on author elements, hover is identity-gated and
re-hit-tested on scroll/resize via a rAF-coalesced reconcile pass, clicks
pin the element (crosshair cursor, pinned outline while composing) and go
straight to the comment popover. Each pin serializes a verified-unique CSS
selector anchor (id > identity attrs > meaningful classes > nth-of-type
path) with a text-snapshot fingerprint; restoration is anchor-first with
fail-closed validation and falls back to document-wide text search. Element
pins that cannot take an inline mark (SVG etc.) get numbered pin badges that
track their element and re-acquire it after re-renders.
All DOM inspection stays inside the bridge; only validated, size-capped DTOs
cross postMessage. Annotation model is extended additively (htmlAnchor), so
exported feedback, drafts, and share links keep working.
* fix(annotate): harden HTML element anchors per adversarial review
Four fixes from the pre-merge adversarial review of the pinpoint round:
- Stable identity is now only the element's own #id or data-* rung,
re-derived from the resolved element and compared whole instead of
parsed out of the selector string. Behavioral attributes (role, href,
aria-label, name, alt) no longer exempt an anchor from the text check,
so a regenerated page can no longer bind an annotation to the wrong
element. A weak anchor with a missing or empty snapshot is rejected,
not exempted.
- Restoration order: the document-wide text search now runs before the
pin-badge fallback, so text that moved elsewhere in a regenerated page
is followed rather than badging the stale container. Resolved SVG
anchors still pin directly.
- Selection text is capped at 10k chars on both sides of the bridge
(truncated, not rejected); one pinpoint click on a huge pre/table no
longer ships an unbounded page-controlled string into drafts,
feedback, or share URLs.
- buildAnchorSelector returns null instead of a terminal fallback path
already proven non-unique; no anchor beats a known-ambiguous one.
Also moves the HTML chrome restore out of the one-shot mount effect into
a surface-transition effect, so a linked .html doc opened from a
markdown session gets the minimal-first chrome and persists its state,
and a markdown surface's sidebar use never leaks into the HTML cookie.
* fix(annotate): skip the chrome save in the restore commit itself
The reviewer's residual finding: htmlChromeRestoredRef flips synchronously
inside the restore effect, but the restored state lands a commit later, so
the save effect's run in the restore commit wrote pre-restore values over
the remembered state (self-corrected next flush, but a page ending between
the two writes would freeze the inverted value). The restore now arms a
skip-one flag the writer consumes, so the stale write never happens. A
follow-up save fires from the changed deps when the restore changed
anything; when it changed nothing the cookie already holds those values.
Regression test instruments every chrome cookie write on a returning-user
mount and asserts none ever differs from the remembered state (mutation
tested: removing the skip fails it).