Render arbitrary HTML in HtmlViewer without altering it#1023
Merged
Conversation
HtmlViewer injected host-app state into rendered documents: ~26 bare theme tokens (--muted, --background, ...) written into :root and re-applied inline on the author's documentElement on every host theme flip, a `light` class toggled on the author's root, an asymmetric color-scheme:light injection, and unconditional ins/del diff styles. Documents defining the same token names rendered with wrong colors in both Plannotator and @plannotator/ui consumers. Arbitrary documents now render exactly as in a standalone tab: - Host tokens are pushed only under the viewer-owned --pn-* prefix; annotation CSS and the bridge read only var(--pn-*, fallback). The bridge refuses non---pn- writes and never touches the root class list unless the document opts in. - Host theme-following is opt-in per document via <meta name="plannotator-theme" content="host">, which restores the bare-token push, the light class, and a symmetric color-scheme sync. The visual-explainer skill now emits the tag in generated artifacts. - Diff CSS is injected only while the version-diff view is active and scoped to ins/del.plannotator-diff, which htmlDiff now emits on its generated wrappers; author <ins>/<del> markup is never restyled. The srcdoc injection logic moves to a pure module (srcdoc.ts) with tests pinning the neutrality contract, including a DOM test that runs the actual bridge script and asserts a theme flip lands nothing on the author's root except --pn-* properties. Claude-Session: https://claude.ai/code/session_01MDqD8jdgTbdjiXcVVigzV2
backnotprop
added a commit
that referenced
this pull request
Jul 8, 2026
HtmlViewer no longer injects bare theme tokens, root classes, or color-scheme into rendered documents (#1023); host theming is opt-in via <meta name="plannotator-theme" content="host">. Supersedes Workspaces' H-ask-1 patch. Claude-Session: https://claude.ai/code/session_01MDqD8jdgTbdjiXcVVigzV2
Owner
Author
|
This is now released in version 0.23.0 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
HtmlViewerinjected host-app state into rendered documents and clobbered author CSS. A document defining common token names (--muted,--background,--border, …) rendered with wrong colors — e.g. body text set via the author's--mutedturned near-invisible in a dark host because the host's--muted(a background color) overwrote the author's (a text gray). Reproduced in Plannotator and in Workspaces (which consumes@plannotator/ui); root-caused to three injection sites::rootblock inside the document.documentElement(beats any author stylesheet), plus alightclass toggled on their root.color-scheme:lightinjection, andins/deldiff styles injected unconditionally, restyling legitimate author markup.The fix
Arbitrary documents now render exactly as in a standalone browser tab — the viewer writes nothing into the document's namespace:
--pn-*prefix (packages/ui/components/html-viewer/srcdoc.ts, new pure module).ANNOTATION_HIGHLIGHT_CSSand the bridge read onlyvar(--pn-…, fallback).--pn-writes and never touches the author root's class list unless the document opts in. Nocolor-schemeinjection — light/dark resolves from the document + OS.<meta name="plannotator-theme" content="host">, which restores the bare-token push, thelightclass, and a now-symmetriccolor-schemesync. Plannotator's own artifact generator (visual-explainer skill) emits the tag, so generated artifacts keep following the host theme.ins.plannotator-diff/del.plannotator-diff, whichhtmlDiffnow emits on its generated wrappers. Author<ins>/<del>markup is never restyled, even in diff view.Acceptance tests (pinned)
packages/ui/components/html-viewer/srcdoc.test.ts:color-schemeinjection in either host theme; opt-in restores bare tokens + symmetric color-scheme.ins/delselectors ever.--pn-*variables.documentElementexcept--pn-*properties (and that the opt-in restores the old behavior).packages/shared/html-diff.test.ts: updated for the class-tagged wrappers + new case pinning that author-written<ins>/<del>pass through untagged.Notes
:rootdefaults +prefers-color-scheme) — intended: arbitrary documents get zero unprefixed injection by default.@plannotator/uiversion bump (supersedes their 2-line patch / H-ask-1).Test plan
bun test— 1980 pass, 0 fail (incl.DOM_TESTS=1for the bridge DOM test)bun run typecheck— clean, including the strict-consumer tsconfigbun run --cwd apps/review build && bun run build:hook— bundles buildhttps://claude.ai/code/session_01MDqD8jdgTbdjiXcVVigzV2