feat(examples): vanilla TypeScript Custom UI example (SD-2874)#3087
feat(examples): vanilla TypeScript Custom UI example (SD-2874)#3087caio-pizzol wants to merge 2 commits intomainfrom
Conversation
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.
There was a problem hiding this comment.
💡 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".
| const teardown = () => { | ||
| disposer.flush(); | ||
| ui.destroy(); | ||
| }; |
There was a problem hiding this comment.
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 👍 / 👎.
| const empty = state.selection.empty || state.selection.selectionTarget == null; | ||
| return { active: false, disabled: empty }; |
There was a problem hiding this comment.
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 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
First non-React proof of life on top of
createSuperDocUI. Mirrorsdemos/custom-uifeature-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.registercustom command, comments sidebar withui.selection.capturecomposer, tracked-changes review panel, mode toggle, import / export, dirty indicator, clean teardown on Vite HMR and tab close.createSuperDocUI, lifecycle scope helper, observe alias, command discovery helpers, stale JSDoc).custom-uismoke job toci-examples.ymland exposes the example under a new Custom UI section inexamples/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.