Closed as not planned. Re-measured against nuxt/ui@v4: every as any this issue counts is upstream's, and the prompt() it calls a UX defect is not reached on the documented path. Fixing either means diverging from a file we sync. Figures below.
Background
Surfaced during the June 2026 multi-angle project audit (engineering review). Repo-wide TS discipline is otherwise strong (0 @ts-ignore, 1 as unknown as); the Editor stack is the concentrated exception. Complements #56, which covers only the DateTimePicker/@internationalized/date casts.
Problem as filed
src/runtime/utils/editor.ts (502 lines): 19 as any + 22 : any, 0/16 exported functions documented. Every handler takes cmd: any; commands are invoked via (editor.can() as any)[fnName](); mapEditorItems returns any[] | any[][].
EditorToolbar.vue: 5 as any + 3 vague @ts-expect-error.
useEditorMenu.ts (685 lines): cleanupMenu vs destroy duplicate ~25 lines of teardown.
EditorSuggestionMenu.vue: 7 : any.
createLinkHandler / createImageHandler call native prompt() — "blocking, unstylable, untestable, and inconsistent with the rest of the kit's overlay system".
Re-measured, 2026-08-30
The typing debt is upstream's, to the line
Counted the same way in both trees (grep -o … | wc -l), against nuxt/ui@v4:
| file |
as any upstream |
as any here |
: any upstream |
: any here |
src/runtime/utils/editor.ts |
21 |
21 |
22 |
22 |
src/runtime/composables/useEditorMenu.ts |
3 |
3 |
5 |
5 |
src/runtime/components/EditorSuggestionMenu.vue |
0 |
0 |
7 |
7 |
editor.ts diffs against upstream as 85 lines added, 0 removed — it is their file verbatim with our code appended, and our 85 lines contribute no as any at all. (The issue's "502 lines / 19" was accurate in June; the file is now 587 lines because of that appended code, which is why the counts here read 21.)
So there is no local sloppiness to clean up. Rewriting the handler layer into a discriminated EditorCommand union means rewriting upstream's file in 45 places, in exactly the code path we re-sync from them — the same cost we declined to pay in #87 for the snapshot corpus, and for the same reason.
The prompt() is not on the documented path
It is a fallback, not the default experience:
// utils/editor.ts — createLinkHandler
const href = cmd?.href || prompt('Enter the URL:')
The first example on the Editor docs page ships its own EditorLinkPopover.vue, which calls editor.chain().setLink({ href }) directly and never enters createLinkHandler. Images behave the same way. The native dialog appears only if a consumer drives the built-in toolbar item without supplying href and without wiring a popover — which is not what the documentation shows or recommends.
Both prompt() calls are also upstream's, at src/runtime/utils/editor.ts:112 and :150 in nuxt/ui@v4, character for character.
What is actually ours
Three @ts-expect-error in EditorToolbar.vue, where upstream has none, carrying the comment need test at nuxt.ui? but this work. Someone silenced the type checker and was not sure why. That is a handful of lines and the only part of this issue that is not upstream's; it is worth doing on its own terms, not as a refactor of the whole layer.
Decision
Not diverging from upstream for inherited debt in a file we sync. If upstream types this layer, we pick it up on the next sync for free.
The trigger to reopen: upstream typing it and us failing to pick it up, or a consumer actually hitting the prompt() fallback in a supported configuration.
Closed as not planned. Re-measured against
nuxt/ui@v4: everyas anythis issue counts is upstream's, and theprompt()it calls a UX defect is not reached on the documented path. Fixing either means diverging from a file we sync. Figures below.Background
Surfaced during the June 2026 multi-angle project audit (engineering review). Repo-wide TS discipline is otherwise strong (0
@ts-ignore, 1as unknown as); the Editor stack is the concentrated exception. Complements #56, which covers only the DateTimePicker/@internationalized/datecasts.Problem as filed
src/runtime/utils/editor.ts(502 lines): 19as any+ 22: any, 0/16 exported functions documented. Every handler takescmd: any; commands are invoked via(editor.can() as any)[fnName]();mapEditorItemsreturnsany[] | any[][].EditorToolbar.vue: 5as any+ 3 vague@ts-expect-error.useEditorMenu.ts(685 lines):cleanupMenuvsdestroyduplicate ~25 lines of teardown.EditorSuggestionMenu.vue: 7: any.createLinkHandler/createImageHandlercall nativeprompt()— "blocking, unstylable, untestable, and inconsistent with the rest of the kit's overlay system".Re-measured, 2026-08-30
The typing debt is upstream's, to the line
Counted the same way in both trees (
grep -o … | wc -l), againstnuxt/ui@v4:as anyupstreamas anyhere: anyupstream: anyheresrc/runtime/utils/editor.tssrc/runtime/composables/useEditorMenu.tssrc/runtime/components/EditorSuggestionMenu.vueeditor.tsdiffs against upstream as 85 lines added, 0 removed — it is their file verbatim with our code appended, and our 85 lines contribute noas anyat all. (The issue's "502 lines / 19" was accurate in June; the file is now 587 lines because of that appended code, which is why the counts here read 21.)So there is no local sloppiness to clean up. Rewriting the handler layer into a discriminated
EditorCommandunion means rewriting upstream's file in 45 places, in exactly the code path we re-sync from them — the same cost we declined to pay in #87 for the snapshot corpus, and for the same reason.The
prompt()is not on the documented pathIt is a fallback, not the default experience:
The first example on the Editor docs page ships its own
EditorLinkPopover.vue, which callseditor.chain().setLink({ href })directly and never enterscreateLinkHandler. Images behave the same way. The native dialog appears only if a consumer drives the built-in toolbar item without supplyinghrefand without wiring a popover — which is not what the documentation shows or recommends.Both
prompt()calls are also upstream's, atsrc/runtime/utils/editor.ts:112and:150innuxt/ui@v4, character for character.What is actually ours
Three
@ts-expect-errorinEditorToolbar.vue, where upstream has none, carrying the commentneed test at nuxt.ui? but this work. Someone silenced the type checker and was not sure why. That is a handful of lines and the only part of this issue that is not upstream's; it is worth doing on its own terms, not as a refactor of the whole layer.Decision
Not diverging from upstream for inherited debt in a file we sync. If upstream types this layer, we pick it up on the next sync for free.
The trigger to reopen: upstream typing it and us failing to pick it up, or a consumer actually hitting the
prompt()fallback in a supported configuration.