Skip to content

feat(demo): smart-tags palette for custom SDT fields (SD-3320)#3574

Merged
caio-pizzol merged 5 commits into
mainfrom
caio/contract-templates-smart-tags
May 29, 2026
Merged

feat(demo): smart-tags palette for custom SDT fields (SD-3320)#3574
caio-pizzol merged 5 commits into
mainfrom
caio/contract-templates-smart-tags

Conversation

@caio-pizzol
Copy link
Copy Markdown
Contributor

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.contentControl actually works (the at is a text range to wrap; a collapsed range + content creates the field) — no API gap. Note the type bridge: ui.selection.capture() returns a TextTarget (segments), but create.contentControl.at wants a SelectionTarget (start/end points), so build the point from capture().target.segments[0].

  • Searchable Smart tags palette; click → insert at cursor (+ a status hint when there's no caret)
  • Shared token styling between palette chips and painted in-editor fields
  • Click-token → highlight-chip sync
  • README reframed around custom content-control UI
  • Builds on the now-merged SD-3310 / SD-3311 / SD-3312 / SD-3314 surface

Verified: behavior test for collapsed-caret insertion; demo suite 7/7; demo tsc clean.

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.
@caio-pizzol caio-pizzol requested a review from a team as a code owner May 29, 2026 18:36
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 29, 2026

SD-3320

SD-3322

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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-commenter
Copy link
Copy Markdown

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.
@caio-pizzol caio-pizzol merged commit 6c9ae00 into main May 29, 2026
25 checks passed
@caio-pizzol caio-pizzol deleted the caio/contract-templates-smart-tags branch May 29, 2026 23:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants