title, id and dark all shipped in the toast runtime for #1913, but stx.d.ts was never updated to match. The result is that the feature built to unblock a migration is unreachable from any typechecked call site — stx typecheck rejects the exact snippet the issue was closed with.
Found on 0.2.176 while doing the migration #1913 was filed for.
The drift
addToast reads four options (src/signals.ts):
var duration = opts.duration !== undefined ? opts.duration : 3000; // 5808
var key = opts.id != null ? String(opts.id) : ''; // 5821
var isDark = _resolveDark(opts.dark); // 5843
if (opts.title) { ... } // 5872stx.d.ts declares one of the four:
interface StxToastOptions {
/** Auto-dismiss duration in ms. 0 = persistent. Default: 3000 */
duration?: number
}
interface StxToast {
success: (message: string, options?: StxToastOptions) => number
// ...
/** Dismiss a toast by id, or all toasts if no id is given */
dismiss: (id?: number) => void
}dismiss is the second half: the runtime branches on typeof id === 'string' to look up [data-stx-toast-key], which is the whole point of replace-by-id, but the declaration accepts number only.
Reproduction
<script client>
toast.success('Post published.', { title: 'Published', id: 'publish' })
toast.dismiss('publish')
toast.error('Nope.', { duration: 12000 })
</script>4:40 error TS2353 Object literal may only specify known properties,
and 'title' does not exist in type 'StxToastOptions'.
5:19 error TS2345 Argument of type 'string' is not assignable to
parameter of type 'number'.Both calls work correctly at runtime. duration — the one declared option — passes.
Suggested
interface StxToastOptions {
/** Auto-dismiss duration in ms. 0 = persistent. Default: 3000 */
duration?: number
/** Rendered above the message in bold. Omit for the original single-line shape. */
title?: string
/** Semantic key. A second toast with the same key replaces the first in place. */
id?: string
/** Overrides the resolved colour scheme for this toast. */
dark?: boolean
}
interface StxToast {
// ...
/** Dismiss by semantic key or by the handle `addToast` returned; all toasts if omitted. */
dismiss: (id?: number | string) => void
}Workaround, and why it only half-works
StxToastOptions is a bare global interface, so an app can merge into it from its own --lib file:
interface StxToastOptions { title?: string, id?: string, dark?: boolean }That clears the first error. dismiss cannot be fixed the same way — it is declared as a property with a function type rather than a method, so a merging declaration does not add an overload, and the original signature wins. Every dismiss('key') call site needs a cast:
(toast.dismiss as (id?: number | string) => void)('publish')Declaring dismiss as a method (dismiss(id?: number | string): void) would at least make it overload-mergeable by downstream apps, independent of this fix.
Why this keeps happening
This is the sixth stx.d.ts-vs-implementation drift I have hit in one app; the others were useEventListener arity, navigate's boolean shorthand, useRoute.fullPath, and useQuery's isStale / invalidate (the last two fixed in #1929). The pattern is consistent: a runtime option is added, the hand-maintained declaration is not, and nothing fails until someone runs stx typecheck.
Since stx typecheck is itself shipped, a test that typechecks a snippet using every documented option of each composable would catch the whole class. The #1913 closing comment is a ready-made case — it is currently a two-error snippet.