SIGN IN SIGN UP

fix(annotate): QA follow-ups for the folder, All files, and HTML surfaces (#1537)

* fix(annotate): honor and persist the HTML tools toggle in folder sessions

A folder annotate session suppressed the whole HTML chrome restore, so the
eye could never remember anything there: every folder session opened with
the floating tools hidden no matter how often the reviewer showed them, and
the toggle wrote nothing back (the save effect is gated on the restore
having run).

Only the sidebar/panel halves are genuinely not a folder session's to
remember — its file browser owns the left sidebar for the whole session.
`toolsHidden` describes the HTML surface itself and means the same thing
everywhere, so it now restores and persists there too, with the other two
halves kept as the last ordinary HTML session left them
(`mergeHtmlChromeState`).

* fix(ui): keep 44px touch targets on the cross-file panel controls

The This file / All files buttons, the document group headers, "Show in
files" and "View all …" carry no data-pn-touch-target, so inside the compact
touch shell they render at desktop density — the affordance they replaced
(the legacy "+N in M other files" button) had the marker. The attribute only
grows the box within the compact scope, so desktop density is unchanged.

* fix(plan): keep the plan's own comments in the All files view

The panel's cross-file groups are keyed by document path, and a plan-review
session's document is the plan itself, which has no path (`sourceFilePath`
is annotate-only). The open document therefore contributed no group at all:
switching to All files listed every linked document and silently dropped the
reviewer's comments on the plan in front of them, from the list and from the
header count.

The open document is now always contributed, under a synthetic key when it
has no path of its own, with an explicit "(this plan)" / "(this document)"
label. It is still the open document, so its group is `isCurrent` and the
panel routes select/edit/delete to the live host state rather than to the
cross-document store. The memo body moved into a pure
`buildAnnotationDocumentGroups` so the rule is testable on its own.

* fix(ui): compare cross-file annotation paths normalized, and act on a refused write

The panel addresses documents by the normalized path its groups carry
(forward slashes, collapsed separators) while the linked-doc cache is keyed
by the raw path the server sent. On Windows those differ, so the lookup in
`updateStoredAnnotations` missed every time and a cross-file Edit or Delete
was a silent no-op — the card vanished from the view but the comment still
shipped in the export. `useAnnotationJump` compared the same two spellings,
so a jump inside the open document navigated instead of selecting, and the
commit for a destination never matched the pending request.

Both sides now normalize before comparing. App also acts on the `false`
`updateStoredAnnotations` returns instead of discarding it: the open
document falls back to the live mutators, and anything else raises a toast
rather than appearing to work.

* fix(plan): do not announce the terminal tools in a session with no server

The gate skipped read-only shared plans, but the share portal's own root —
and any demo session that never reaches /api/plan — is not a "shared
session", so the announcement (and its video fetch to plannotator.ai) fired
on a public page with no author to address. `!isApiMode` now joins the
read-only term; the cookie is still deferred, not consumed, and isApiMode is
settled by the time isLoading clears, so a real session can never be
deferred by it.

* ci: run the DOM-gated tests that drifted off the allowlist, and guard it

A DOM-gated test file runs nowhere but the hand-maintained list in
test.yml, and three PRs in this release added seven files (34 tests) that
guard controls which had just shipped — none of them ran. Another 34 older
files had drifted off the same way.

All of them are added here, and a guard test asserts that every DOM-gated
test file in the repo is named in one of the workflow's DOM_TESTS steps, so
the list cannot silently drift again. The guard needs no DOM and runs in the
default sweep as well as its own step.

* fix(ui): scrub query and fragment from relative URLs in element context

The captured element context documents its href/src values as "scrubbed of
query and fragment", but only absolute http(s) URLs were: a relative
`./checkout?session=…#access_token=…` was copied through whole into drafts,
submission records and exported feedback. Relative URLs carry the same
per-visit state absolute ones do, and the implicit-flow convention puts
tokens in the fragment specifically.

The path still survives in every form; data: truncation and the javascript:
drop are unchanged.

* fix(ui): restore byte-identical export for multi-target comments with no context

The element-context work claimed to be additive, but the extra-target
locator suffix was keyed off `anchor.selector`, which every multi-target
annotation has always carried — so an annotation captured before element
context existed (or restored from an older draft) silently changed export
shape. The locator is a property of the captured context, and is now emitted
only when the target carries one.

* docs(marketing): HTML tools start hidden; document the eye and pen chords

annotate.md still described the eye as the way to hide controls that were on
screen by default, and the pen as one-way. Both surfaces now open with the
tools hidden, the eye (Cmd/Ctrl+Shift+X) shows them and remembers the
choice, and the pen (Cmd/Ctrl+Shift+A) toggles annotate mode in both
directions.
M
Michael Ramos committed
4fd3b42f28ced9575c0d13bb15c94f9dc67fb385
Parent: 02ce55f
Committed by GitHub <noreply@github.com> on 9/15/2026, 10:29:50 AM