Extension of #92 (m/paliad/issues/106). Two related polish fixes for the
submission draft editor's preview ↔ sidebar wiring.
Concern A — link persists after fill (regression coverage + UX visibility)
Audited the Go renderer: substituteInTextNodes / substituteAcrossRuns
already pass both filled and missing values through htmlPreviewWrapper,
so the <span class="draft-var" data-var="…"> wrapping is present for
every substituted placeholder regardless of source (resolved bag,
lawyer override, missing marker). What looked broken to m was a
visibility problem: the always-on rgba(198, 244, 28, 0.12) tint is
imperceptible against the serif preview prose, so a filled value
reads as plain text and the user concludes "the link is gone".
Added TestRenderHTML_WrapsOverriddenValueSameAsResolved that pins the
invariant explicitly — an override (project.case_number = "UPC_CFI_
42/2026") and a resolved value (firm.name = "HLC") both end up in
matching draft-var spans. Locks future refactors out of dropping the
wrap on either path.
CSS rewrite per m's "prose stays clean when not interacting" guidance
(issue body): drop the always-on background; on hover of a
--has-input span, layer a dotted-underline + brighter lime tint so
the click affordance reveals itself. Missing markers carry their own
[KEIN WERT: …] / [NO VALUE: …] gap-text and don't need extra visual.
Concern B — sidebar-field-focus → preview-occurrence highlight (new)
Reverse direction of the click-to-jump from #92. focusin on any
.submission-draft-var-input applies .draft-var--active to every
matching span in the preview; focusout (or focus shift via Tab)
clears them. Sticky-while-focused, not a one-shot flash — the lawyer
can scan "where does this variable land in my prose?" while the
field stays focused.
New CSS class .draft-var--active uses a brighter lime + box-shadow
ring so all occurrences pop at once. Handlers are wired in
paintVariables and re-applied at the end of both paintVariables AND
paintPreview because:
- paintVariables runs after autosave and re-creates inputs via
innerHTML, so the focusin listener attached to the old input is
gone; restoreVarFocus puts focus back programmatically without
firing focusin again. We re-apply explicitly to bridge.
- paintPreview blows away the preview HTML on every autosave, so
any prior --active class is gone too. Re-apply based on the
currently-focused sidebar input.
Files
internal/services/submission_merge_test.go — new regression test
frontend/src/client/submission-draft.ts — focus handlers + re-apply
frontend/src/styles/global.css — draft-var rewrite, --active
Hard rules
- .docx export path unchanged (Render passes nil wrap, covered by
existing TestRender_DocxOutputUnchangedByPreviewWrap).
- Both directions survive autosave-driven preview re-renders (see
paintPreview re-apply + paintVariables re-apply).
- go build ./... && go test ./internal/... && bun run build all clean.