feat(demo): smart-tags palette for custom SDT fields (SD-3320)#3574
Merged
Conversation
A searchable "Smart tags" palette in the contract-templates sidebar. Clicking a
tag inserts it as an inline content control at the caret, and the inserted field
paints with the SAME token look (--tag-* / .smart-tag) as the palette chip - so
the sidebar tag and the in-editor field read as one object. This is the core
custom-SDT story: turn off built-in chrome, style the painted wrapper, author
fields from your own UI.
Insert path (verified): ui.selection.capture() -> bridge the TextTarget to a
collapsed SelectionTarget -> editor.doc.create.contentControl({ at, content,
tag }) -> ui.contentControls.focus(). Adds a behavior test proving collapsed-
caret insertion works (no API gap) and a demo acceptance test for chip -> field.
- Smart tags get a deliberate amber identity (one --tag-* token set drives both the palette chip and the painted in-editor field, so they look identical). - Two-way loop: clicking a smart-field token in the document highlights its sidebar chip (content-control:click); cleared on blur (active-change). - README reframed around the custom content-control UI story (chrome:'none' + host-owned field look + smart-tags authoring), with the new flow documented. - Adds a demo test for the click-token -> highlight-chip sync.
There was a problem hiding this comment.
cubic analysis
No issues found across 5 files
Linked issue analysis
Linked issue: SD-3320: contract-templates demo: Smart tags palette for custom SDT fields
| Status | Acceptance criteria | Notes |
|---|---|---|
| ✅ | Sidebar Smart-tags palette (searchable), chips grouped under a section header | The demo renders a Smart-tags section with a search input, a group header, and chips for each field. |
| ✅ | Click a chip inserts an inline structuredContent SDT at the caret using capture() → SelectionTarget → editor.doc.create.contentControl(...) and focuses it | Insertion uses ui.selection.capture(), bridges the segment to a SelectionTarget, calls editor.doc.create.contentControl with content/tag, and then focuses the new control; demo and behavior tests verify insertion at a collapsed caret. |
| ✅ | Shared token styling between palette chips and the painted in-editor inline field so they look identical | A single --tag-* token set is defined and applied to both the painted inline SDT selector and the sidebar chips. |
| ✅ | Clicking a token in the document highlights its sidebar chip; cleared on blur (two-way document↔panel sync) | Event listeners wire content-control:click to set an activeTagKey and content-control:active-change to clear it; highlightActiveTag toggles .is-active on the matching chip; a demo test asserts the chip becomes active after clicking a token. |
Tip: cubic could auto-approve low-risk PRs like this, if it thinks it's safe to merge. Learn more
Re-trigger cubic
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
…field chip - Remove the floating field chip (sd-field-chip): redundant now fields are styled inline, and it clashed with the amber palette. Drops field-chip.ts and the chip-anchor test (the chip was the only getRect/viewport.observe consumer). - Inline and block fields now share one amber token language: inline as a token pill, block clauses as a quiet left-rail card (a region, not a token). - Kill the jitter: under chrome:'none' SuperDoc resets the SDT border/fill on hover (:hover / .sdt-group-hover) and select (.ProseMirror-selectednode) so consumers own the look; without re-asserting, the box shifted ~2px and lost the amber. We re-assert both states for inline and block to hold the exact box and keep a controlled amber fill. The !important is ours, to win over the reset without coupling to SuperDoc's selector specificity -- a custom-UI styling rough edge (no first-class per-control hook yet) worth a follow-up. - Size the sidebar chips to match the in-editor pills. - Add regression tests asserting the inline pill and block clause boxes stay constant across hover/select (no jitter).
…SDTs
Reframe the contract-templates demo as a building-block library on a locked
template surface, driven entirely through the public superdoc/ui + editor.doc.*
API with chrome:'none'. This shows the legal-tech workflow: assemble a contract
from governed, reusable Word content controls whose variables stay consistent.
- Enable the formatting toolbar and center the editor. Fold Clauses into a
Template tab; the sidebar is now Template (build) + Values (fill).
- Template tab is a catalog: smart-field chips and clause cards (each with
category / jurisdiction / version and a "used N times" count, plus a
library-only Indemnification clause). Drag or click to insert; a field goes
inline at the caret, a clause snaps to a block boundary. Inserts resolve the
drop point with ui.viewport.positionAt.
- Every control is contentLocked, so it can't be edited by typing. Fields show
their name token (e.g. DISCLOSING_PARTY) as a placeholder. Values are filled
only through the Values form, which broadcasts to every occurrence - including
ones nested in a locked clause (the write briefly unlocks clauses, since a
clause's content lock otherwise silently vetoes nested writes).
- Clauses are assembled from structured parts (prose + {field} slots): inserting
one wraps each slot as a nested, locked inline smart field, so an inserted
Permitted Use carries real Receiving party / Purpose fields like the seeded one.
- Remove the clause version review/replace lifecycle (out of scope here; it's a
separate clause-lifecycle demo). Drop the floating field chip earlier in the arc.
- Rewrite the README and file header to the library model; add tests for locking,
nested-clause broadcast, clause insert, and inserted-clause field nesting.
Make the clause library a single-use inclusion checklist instead of a duplicate stamp tool. A clause is either "In contract" or available to "Add clause": a clause already placed can't be inserted again - clicking its card reveals the existing section, while an available card adds it (click or drag) and then flips to "In contract". Drops the "used N times" surface. This teaches the right model: fields are reusable variables, clauses are governed sections included once. - Add a library-only "Return of Materials" clause carrying a nested Receiving party slot, so insert-with-nested-fields stays demonstrable now that the seeded Permitted Use is "In contract" and no longer insertable. - Recolor fields and clauses to the SuperDoc brand blue (--sd-color-blue-500/600, per brand.md) instead of amber. They render as tinted/outlined pills, so they stay distinct from the solid-blue primary buttons. - Update tests (single-use status badges, add-once-no-duplicate, nested-field on add), the README, and code comments to the single-use + blue model.
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.
Makes the contract-templates demo the clearest proof of the custom-SDT-UI direction: build your own field UI on top of SuperDoc, in the editor and in surrounding panels, on standard Word-compatible content controls.
With
chrome: 'none'SuperDoc keeps painting the content but drops its built-in field chrome, so the demo styles the painted SDT wrapper itself. One--tag-*token set drives both the in-editor inline field and the sidebar "Smart tags" palette chips, so a chip and the field it inserts look identical. Clicking a chip captures the caret (ui.selection.capture()), inserts an inline SDT there (editor.doc.create.contentControl({ at, content, tag })), and focuses it. Clicking a token in the document highlights its chip (content-control:click) — the two-way loop.I verified the load-bearing primitive first: a behavior test proves collapsed-caret insertion via
create.contentControlactually works (theatis a text range to wrap; a collapsed range +contentcreates the field) — no API gap. Note the type bridge:ui.selection.capture()returns aTextTarget(segments), butcreate.contentControl.atwants aSelectionTarget(start/end points), so build the point fromcapture().target.segments[0].Verified: behavior test for collapsed-caret insertion; demo suite 7/7; demo
tscclean.