Skip to content

feat(ui): metadata-id geometry on ui.metadata (SD-3204)#3379

Merged
caio-pizzol merged 3 commits into
mainfrom
caio-pizzol/SD-3204-ui-metadata-geometry
May 19, 2026
Merged

feat(ui): metadata-id geometry on ui.metadata (SD-3204)#3379
caio-pizzol merged 3 commits into
mainfrom
caio-pizzol/SD-3204-ui-metadata-geometry

Conversation

@caio-pizzol
Copy link
Copy Markdown
Contributor

Adds ui.metadata.getRect({ id }) and ui.metadata.scrollIntoView({ id }) keyed on the metadata id — the value the consumer passed to editor.doc.metadata.attach. Hides the metadata-id → SDT-node-id → painter-geometry bridge that the SD-3208 demo's CitationHighlights had to compose by hand from useSuperDocContentControls + a tag→nodeId map + ui.contentControls.getRect.

What the handle does

  • getRect({ id }) reads the cached content-controls slice, finds the item whose properties.tag === id, and delegates to ui.contentControls.getRect({ id: <SDT node id> }). Return shape is ViewportRectResult, identical to the rest of the ui.*.getRect family.
  • scrollIntoView({ id, block?, behavior? }) calls editor.doc.metadata.resolve for the SelectionTarget, converts it to a TextTarget (the shape ui.viewport.scrollIntoView accepts), and forwards block / behavior unchanged.

Failure mapping

Reuses the existing ViewportRectResult union β€” consumers learn one error model across ui.viewport, ui.contentControls, and ui.metadata.

Input Result
id: '' { success: false, reason: 'invalid-target' }
id not in cc.items (no matching properties.tag) { success: false, reason: 'unresolved' }
SDT exists but unpainted propagates contentControls.getRect ('not-mounted' / 'not-ready')

scrollIntoView returns { success: false } for unknown ids and for nodeEdge endpoints (no clean TextTarget representation) rather than scrolling to an approximation.

What's out

  • No getRects β€” ViewportRectResult.success.rects[] already exposes the per-line array; a sibling method with the same return shape would just add API noise.
  • No namespace param β€” metadata.attach enforces globally unique ids within a document.
  • No ui.metadata({ namespace }) handle, no React hook, no attach helpers. Those wait for second-customer signal that composing from primitives is too awkward.

Cross-block scroll

Anchored metadata v1 attaches over same-block text ranges only, so the cross-block branch of the local SelectionTarget β†’ TextTarget converter is defensive. It returns two collapsed segments at start and end points; scrollRangeIntoView walks segments in document order and scrolls to the first, so the effect is "scroll to the start endpoint." If a future metadata path produces a real cross-block anchor we revisit this β€” likely by returning null and surfacing the failure to the caller. Comment in create-super-doc-ui.ts flags the revisit condition.

Public facade

MetadataHandle re-exported through superdoc/ui via the four facade files (ui.d.ts, public/ui.ts, verify-public-facade-emit.cjs expectedNames, ui.barrel.test.ts regression). Facade count goes 70 β†’ 71 (3 runtime + 68 types).

Verified:

  • src/ui/metadata.test.ts β†’ 6/6 (3 getRect including the bridge-boundary test, 3 scrollIntoView)
  • src/ui/* full suite β†’ 312/312
  • src/ui.barrel.test.ts β†’ 7/7
  • verify-public-facade-emit.cjs β†’ ui: 71 exports
  • pnpm --filter superdoc run build:es β†’ clean

Adds `ui.metadata.getRect({ id })` and `ui.metadata.scrollIntoView({ id })`
keyed on the metadata id (= the value passed to
`editor.doc.metadata.attach`, or the SDT's w:tag underneath). The
handle hides the metadata-id β†’ SDT-node-id β†’ painter-geometry bridge
that the SD-3208 demo had to compose by hand from
`useSuperDocContentControls` + a tag→nodeId map +
`ui.contentControls.getRect`.

Failure mapping reuses the existing ViewportRectResult union:
empty id β†’ `invalid-target`; unknown id (no matching
`properties.tag` in cc.items) β†’ `unresolved`; SDT present but
unpainted β†’ whatever `contentControls.getRect` returns
(`not-mounted` / `not-ready`) propagates as-is.

`scrollIntoView` resolves the id via
`editor.doc.metadata.resolve`, converts the SelectionTarget into a
TextTarget (same-block is one segment, cross-block is two collapsed
endpoints β€” defensive, since metadata v1 anchors are same-block),
and forwards to `ui.viewport.scrollIntoView`. nodeEdge endpoints
fail with `{ success: false }` rather than approximating.

No `getRects` (`ViewportRectResult.success.rects[]` already
exposes the per-line array), no namespace param (`attach` enforces
globally unique ids), no React hook, no mutation helpers β€” those
wait for second-customer signal.
@caio-pizzol caio-pizzol requested a review from a team as a code owner May 19, 2026 09:29
@linear-code
Copy link
Copy Markdown

linear-code Bot commented May 19, 2026

SD-3204

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

πŸ’‘ Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c762e597bf

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with πŸ‘.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread packages/super-editor/src/ui/create-super-doc-ui.ts
@codecov-commenter
Copy link
Copy Markdown

Codecov Report

βœ… All modified and coverable lines are covered by tests.

πŸ“’ Thoughts on this report? Let us know!

…data.* (SD-3204)

Anchored-metadata uses an inline SDT's `w:tag` to mark anchors in
the body, but `w:tag` is not reserved for metadata β€” an imported
DOCX can carry Word-authored content controls whose tag happens to
match a metadata id. `editor.doc.metadata.resolve` previously
matched on tag alone, so foreign controls would resolve as if they
were metadata anchors and any consumer (including `ui.metadata.*`)
would be steered at an unrelated control.

Fix at the source: `metadataResolveWrapper` now requires both
halves of the anchor β€” the SDT in the body AND a payload entry in a
customXml part β€” to agree before returning a non-null result.
Mirrors what `metadata.get` already does for payload reads.

Defensive UI-layer gate: `ui.metadata.getRect` and
`ui.metadata.scrollIntoView` both call `editor.doc.metadata.get`
first and short-circuit on null. Keeps the UI handle symmetrical
for direct callers that bypass `resolve` and protects against the
same class of bug if a future source-side change widens `resolve`.

Tests:
- anchored-metadata-wrappers: foreign SDT with matching w:tag and
  no payload β†’ `metadata.resolve` returns null.
- ui.metadata.getRect / scrollIntoView: same scenario β†’ reports
  `unresolved` / `{ success: false }` without delegating to
  viewport.
`hasMetadataPayload` used `!== null` against an `unknown | null`
structural return type. Production `metadata.get` always returns
null on miss, so this was correct for the runtime path, but a stub
or adapter returning `undefined` would have slipped through.
Switched to `!= null` so both shapes gate the same way.
@caio-pizzol caio-pizzol merged commit 5321993 into main May 19, 2026
70 checks passed
@caio-pizzol caio-pizzol deleted the caio-pizzol/SD-3204-ui-metadata-geometry branch May 19, 2026 10:20
@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 22, 2026

πŸŽ‰ This PR is included in superdoc-cli v0.12.0

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 22, 2026

πŸŽ‰ This PR is included in superdoc-sdk v1.11.0

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 22, 2026

πŸŽ‰ This PR is included in @superdoc-dev/mcp v0.7.0

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 22, 2026

πŸŽ‰ This PR is included in superdoc v1.35.0

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 22, 2026

πŸŽ‰ This PR is included in @superdoc-dev/react v1.6.0

The release is available on GitHub release

@superdoc-bot
Copy link
Copy Markdown
Contributor

superdoc-bot Bot commented May 22, 2026

πŸŽ‰ This PR is included in vscode-ext v2.7.0

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.

2 participants