Skip to content

feat(examples): vanilla TypeScript Custom UI example (SD-2874)#3087

Draft
caio-pizzol wants to merge 2 commits intomainfrom
caio/sd-2874-vanilla-custom-ui
Draft

feat(examples): vanilla TypeScript Custom UI example (SD-2874)#3087
caio-pizzol wants to merge 2 commits intomainfrom
caio/sd-2874-vanilla-custom-ui

Conversation

@caio-pizzol
Copy link
Copy Markdown
Contributor

First non-React proof of life on top of createSuperDocUI. Mirrors demos/custom-ui feature-for-feature in plain DOM + TypeScript so the framework-agnostic story stops being theoretical.

What it covers: custom toolbar with built-in commands and one ui.commands.register custom command, comments sidebar with ui.selection.capture composer, tracked-changes review panel, mode toggle, import / export, dirty indicator, clean teardown on Vite HMR and tab close.

  • Linear: SD-2874. Source for the SD-2917SD-2921 follow-up tickets capturing the DX gaps surfaced during the port (cast on createSuperDocUI, lifecycle scope helper, observe alias, command discovery helpers, stale JSDoc).
  • Adds a custom-ui smoke job to ci-examples.yml and exposes the example under a new Custom UI section in examples/README.md.

Vue / Svelte / Angular ports follow under SD-2874. Each is meant to surface its own ergonomic gaps before any framework-agnostic docs go public.

First non-React proof of life on top of createSuperDocUI directly.
Mirrors demos/custom-ui feature-for-feature: custom toolbar with
built-in commands and one ui.commands.register custom command,
comments sidebar with ui.selection.capture composer, tracked-changes
review panel, mode toggle, import / export, dirty indicator, clean
teardown on Vite HMR and tab close.

Validates that the controller is genuinely framework-agnostic, not
React-only. Source for the SD-2917 through SD-2921 follow-up tickets
that capture the DX gaps surfaced during the port.

Wires a smoke job into ci-examples and exposes the example under a
new Custom UI section in examples/README.md.
@caio-pizzol caio-pizzol requested a review from a team as a code owner May 2, 2026 11:34
@linear
Copy link
Copy Markdown

linear Bot commented May 2, 2026

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: 4dad1cd230

ℹ️ 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 on lines +93 to +96
const teardown = () => {
disposer.flush();
ui.destroy();
};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Destroy SuperDoc instance during teardown

The teardown path only calls disposer.flush() and ui.destroy(), but it never destroys the SuperDoc instance itself. On Vite HMR (the import.meta.hot.dispose path), this leaves the previous editor mounted and subscribed while a new SuperDoc is created on re-evaluation, which can accumulate duplicate editor instances/listeners and cause inconsistent behavior after a few hot reloads.

Useful? React with 👍 / 👎.

Comment on lines +133 to +134
const empty = state.selection.empty || state.selection.selectionTarget == null;
return { active: false, disabled: empty };
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep Insert clause enabled for caret selections

This state check disables the custom insert-clause command whenever the selection is collapsed (state.selection.empty), but insertion operations should still be valid at a caret position as long as a selection target exists. As written, the button stays disabled in the common “cursor only” case and only works when text is actively selected, which is a functional regression from typical insert behavior.

Useful? React with 👍 / 👎.

@codecov-commenter
Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

- Add superdoc.destroy() to teardown so HMR does not leak the editor
- Drop selection.empty term from Insert clause gate (insert works at
  collapsed caret; matches React reference)
- Add predev script so pnpm dev resolves superdoc/ui without a
  separate build step
- Replace linear.app URLs in README with bare ticket IDs per
  public-repo content rule
- Remove em-dashes across the example per project writing-style rule
@caio-pizzol caio-pizzol marked this pull request as draft May 2, 2026 14:25
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