Submission draft editor: keep variable link in preview after fill + click-field-highlights-occurrences-in-preview #106

Open
opened 2026-05-25 14:24:12 +00:00 by mAi · 1 comment
Collaborator

m's report (2026-05-25 16:22)

Linking the preview with the form worked, but only with non-filled fields. When filled, the link disappears - can we not keep it linked?!

The same way that clicking into a field should highlight the occurrences in the draft preview.

Two bidirectional improvements over #92 (already shipped)

Current behavior: when a variable is unfilled (template literal {{project.case_number}} shows in preview), clicking it jumps to the sidebar field. After the user fills the field, the preview renders the substituted value but the click-to-jump linkage disappears.

Fix: render the <span class="draft-var" data-var="…"> wrapper around BOTH the unfilled placeholder AND the filled substituted value. Same data-var attribute, same click handler. The user can click ANY rendered variable (filled or not) and jump to its field.

Edge: the .draft-var styling on a filled variable should be subtle — current state-of-the-art is a dotted underline on hover or a small badge on right. Coder picks; default to dotted-underline-on-hover so the prose stays clean when not interacting.

Concern B — click-into-field highlights occurrences in preview

Reverse direction: when the user focuses a sidebar input (say project.case_number), the preview should highlight every <span class="draft-var" data-var="project.case_number">…</span> occurrence with a brief lime flash (or sticky highlight while the field stays focused, returning to normal on blur).

Use the same data-var mapping. On focusin of a [data-field-key] input, find matching .draft-var spans and add .draft-var--active class; on focusout, remove.

Files most likely touched

  • internal/services/submission_render.go (or wherever the preview HTML is generated) — wrap filled values in the same <span class="draft-var"> as unfilled (currently only unfilled get wrapped, judging by m's report)
  • frontend/src/client/submission-draft.ts — bidirectional event handlers (preview→field click + field→preview focus highlight)
  • frontend/src/styles/global.css.draft-var hover/active styling, dotted underline

Hard rules

  • Export-to-.docx output STILL unchanged — <span class="draft-var"> is preview-HTML-only.
  • Both directions must survive autosave-driven preview re-renders (last shipped fix #92 handled focus preservation — extend that to also re-bind the new highlight handler).
  • go build ./... && go test ./internal/... && cd frontend && bun run build clean.
  • Branch: mai/<worker>/draft-editor-bidirectional-link.

Out of scope

  • Inline-editing the variable from inside the preview.
  • Multi-cursor or collaborative highlighting.
  • Persistence of selected field state across browser reloads.

Reporting

mai report completed with branch + SHAs + UX path: A) type a value into a sidebar field → confirm the filled substituted value in the preview stays clickable + still jumps back to its field; B) click into any sidebar field → confirm every matching variable in the preview gets a highlight, blur → highlight clears.

## m's report (2026-05-25 16:22) > Linking the preview with the form worked, but only with non-filled fields. When filled, the link disappears - can we not keep it linked?! > > The same way that clicking into a field should highlight the occurrences in the draft preview. ## Two bidirectional improvements over #92 (already shipped) ### Concern A — link persists after fill Current behavior: when a variable is unfilled (template literal `{{project.case_number}}` shows in preview), clicking it jumps to the sidebar field. After the user fills the field, the preview renders the substituted value but the click-to-jump linkage disappears. Fix: render the `<span class="draft-var" data-var="…">` wrapper around BOTH the unfilled placeholder AND the filled substituted value. Same `data-var` attribute, same click handler. The user can click ANY rendered variable (filled or not) and jump to its field. Edge: the `.draft-var` styling on a filled variable should be subtle — current state-of-the-art is a dotted underline on hover or a small badge on right. Coder picks; default to dotted-underline-on-hover so the prose stays clean when not interacting. ### Concern B — click-into-field highlights occurrences in preview Reverse direction: when the user focuses a sidebar input (say `project.case_number`), the preview should highlight every `<span class="draft-var" data-var="project.case_number">…</span>` occurrence with a brief lime flash (or sticky highlight while the field stays focused, returning to normal on blur). Use the same `data-var` mapping. On `focusin` of a `[data-field-key]` input, find matching `.draft-var` spans and add `.draft-var--active` class; on `focusout`, remove. ## Files most likely touched - `internal/services/submission_render.go` (or wherever the preview HTML is generated) — wrap filled values in the same `<span class="draft-var">` as unfilled (currently only unfilled get wrapped, judging by m's report) - `frontend/src/client/submission-draft.ts` — bidirectional event handlers (preview→field click + field→preview focus highlight) - `frontend/src/styles/global.css` — `.draft-var` hover/active styling, dotted underline ## Hard rules - Export-to-.docx output STILL unchanged — `<span class="draft-var">` is preview-HTML-only. - Both directions must survive autosave-driven preview re-renders (last shipped fix #92 handled focus preservation — extend that to also re-bind the new highlight handler). - `go build ./... && go test ./internal/... && cd frontend && bun run build` clean. - Branch: `mai/<worker>/draft-editor-bidirectional-link`. ## Out of scope - Inline-editing the variable from inside the preview. - Multi-cursor or collaborative highlighting. - Persistence of selected field state across browser reloads. ## Reporting `mai report completed` with branch + SHAs + UX path: A) type a value into a sidebar field → confirm the filled substituted value in the preview stays clickable + still jumps back to its field; B) click into any sidebar field → confirm every matching variable in the preview gets a highlight, blur → highlight clears.
mAi self-assigned this 2026-05-25 14:24:12 +00:00
Author
Collaborator

Branch mai/hermes/gitster-draft-editor — commit d4df81e.

What landed

Audited the Go renderer: substituteInTextNodes / substituteAcrossRuns already pass both filled and missing values through htmlPreviewWrapper, so the <span class="draft-var" data-var="…"> wrapping was present for every substituted placeholder before this slice (verified by existing TestRenderHTML_ExtractsParagraphsAndFormatting and TestRenderHTML_WrapsMissingMarker).

What looked broken in your test was a visibility problem, not a markup problem: the always-on rgba(198, 244, 28, 0.12) tint was imperceptible against the serif preview prose, so a filled value read as plain text and the click affordance felt gone.

Fix:

  1. Locked the invariant with a regression test — new TestRenderHTML_WrapsOverriddenValueSameAsResolved pins that an override (project.case_number = "UPC_CFI_42/2026") and a resolved value (firm.name = "HLC") both end up wrapped in matching draft-var spans. Future refactors can't drop the wrap on either path without the test screaming.
  2. Rewrote the CSS per the issue's "prose stays clean when not interacting" guidance — dropped the always-on background; on hover of a --has-input span, layered a dotted underline + brighter lime tint so the affordance reveals itself. Missing markers carry their own [KEIN WERT: …] gap text and don't need extra visual highlight.

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 — the user can scan "where does this variable land?" while typing.

New CSS .draft-var--active uses a brighter lime + box-shadow ring so all occurrences pop at once.

Autosave-survival wiring

Both directions had to be re-applied at the end of paintVariables AND paintPreview because:

  • paintVariables runs after autosave and re-creates every input 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. Re-applied based on the currently-focused sidebar input.

Files changed

UX verification path

A — link persists after fill:

  1. Open a draft, find any sidebar field that resolves to a value from the project (e.g. project.case_number).
  2. Type a custom value into the field — autosave runs, preview refreshes.
  3. Hover the filled value in the preview → dotted underline + brighter lime tint reveal the click affordance.
  4. Click the filled value → cursor jumps back to the sidebar input, lime flash on the row.

B — click into field highlights occurrences:

  1. Click into any sidebar field (e.g. firm.name).
  2. Every matching firm.name occurrence in the preview lights up with the brighter lime + ring (sticky while the field stays focused).
  3. Tab to a different field → the previous key's highlights clear, the new key's highlights apply.
  4. Click outside the sidebar (blur) → all highlights clear.
  5. While typing in the focused field, the highlight persists across the autosave → preview-repaint cycle.

Hard rules verified

  • .docx export unchanged (Render passes nil wrap; covered by existing TestRender_DocxOutputUnchangedByPreviewWrap).
  • Both directions survive autosave-driven preview re-renders.
  • go build ./... + go test ./internal/... + bun run build all clean.
Branch [`mai/hermes/gitster-draft-editor`](https://mgit.msbls.de/m/paliad/src/branch/mai/hermes/gitster-draft-editor) — commit [`d4df81e`](https://mgit.msbls.de/m/paliad/commit/d4df81e). ## What landed ### Concern A — link persists after fill Audited the Go renderer: `substituteInTextNodes` / `substituteAcrossRuns` already pass **both** filled and missing values through `htmlPreviewWrapper`, so the `<span class="draft-var" data-var="…">` wrapping was present for every substituted placeholder before this slice (verified by existing `TestRenderHTML_ExtractsParagraphsAndFormatting` and `TestRenderHTML_WrapsMissingMarker`). What looked broken in your test was a **visibility problem**, not a markup problem: the always-on `rgba(198, 244, 28, 0.12)` tint was imperceptible against the serif preview prose, so a filled value read as plain text and the click affordance felt gone. Fix: 1. **Locked the invariant with a regression test** — new `TestRenderHTML_WrapsOverriddenValueSameAsResolved` pins that an override (`project.case_number = "UPC_CFI_42/2026"`) and a resolved value (`firm.name = "HLC"`) both end up wrapped in matching `draft-var` spans. Future refactors can't drop the wrap on either path without the test screaming. 2. **Rewrote the CSS** per the issue's "prose stays clean when not interacting" guidance — dropped the always-on background; on hover of a `--has-input` span, layered a dotted underline + brighter lime tint so the affordance reveals itself. Missing markers carry their own `[KEIN WERT: …]` gap text and don't need extra visual highlight. ### 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 — the user can scan "where does this variable land?" while typing. New CSS `.draft-var--active` uses a brighter lime + box-shadow ring so all occurrences pop at once. ### Autosave-survival wiring Both directions had to be re-applied at the end of `paintVariables` AND `paintPreview` because: - `paintVariables` runs after autosave and re-creates every input 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. Re-applied based on the currently-focused sidebar input. ## Files changed - [`internal/services/submission_merge_test.go`](https://mgit.msbls.de/m/paliad/src/branch/mai/hermes/gitster-draft-editor/internal/services/submission_merge_test.go) — new regression test - [`frontend/src/client/submission-draft.ts`](https://mgit.msbls.de/m/paliad/src/branch/mai/hermes/gitster-draft-editor/frontend/src/client/submission-draft.ts) — focus handlers + re-apply on repaint - [`frontend/src/styles/global.css`](https://mgit.msbls.de/m/paliad/src/branch/mai/hermes/gitster-draft-editor/frontend/src/styles/global.css) — `.draft-var` rewrite, `.draft-var--active` ## UX verification path **A — link persists after fill:** 1. Open a draft, find any sidebar field that resolves to a value from the project (e.g. `project.case_number`). 2. Type a custom value into the field — autosave runs, preview refreshes. 3. Hover the filled value in the preview → dotted underline + brighter lime tint reveal the click affordance. 4. Click the filled value → cursor jumps back to the sidebar input, lime flash on the row. **B — click into field highlights occurrences:** 1. Click into any sidebar field (e.g. `firm.name`). 2. Every matching `firm.name` occurrence in the preview lights up with the brighter lime + ring (sticky while the field stays focused). 3. Tab to a different field → the previous key's highlights clear, the new key's highlights apply. 4. Click outside the sidebar (blur) → all highlights clear. 5. While typing in the focused field, the highlight persists across the autosave → preview-repaint cycle. ## Hard rules verified - `.docx` export unchanged (`Render` passes `nil` wrap; covered by existing `TestRender_DocxOutputUnchangedByPreviewWrap`). - Both directions survive autosave-driven preview re-renders. - `go build ./...` + `go test ./internal/...` + `bun run build` all clean.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: m/paliad#106
No description provided.