Skip to content

Toast stack: allow multiple toasts to be displayed simultaneously #775

@HaleTom

Description

@HaleTom

Description

The TUI toast system currently displays only one toast at a time. Each call to toast.show() replaces the current toast immediately. When two different errors occur close together (e.g., a permission failure followed by a question-reply failure), the first toast disappears before the user can read it.

This is the structural complement to #771 (deduplication/suppression for repeated messages). #771 addresses the same message appearing too many times; this issue addresses different messages hiding each other.

Current behavior

toast.tsx:54 stores a single currentToast in a SolidJS store. toast.show() replaces it immediately:

const [store, setStore] = createStore({
  currentToast: null as ToastOptions | null,
})

show(options: ToastOptions) {
  const { duration = 5000, ...currentToast } = options
  setStore("currentToast", currentToast)  // overwrites previous
  if (timeoutHandle) clearTimeout(timeoutHandle)
  timeoutHandle = setTimeout(() => {
    setStore("currentToast", null)
  }, duration).unref()
},

The Toast component renders a single <Show when={toast.currentToast}>.

Why this matters

Error messages that are immediately replaced by the next error are invisible — functionally equivalent to not showing them at all. When multiple user-initiated actions fail in quick succession (e.g., permission reply + fork failure), the user only sees the second error. This was discovered while auditing silent failures from #758.

Proposed behavior

Allow multiple toasts to be displayed simultaneously, with:

  1. Stack layout: Show up to N toasts (e.g., 3–5) stacked vertically, newest on top
  2. Individual auto-dismiss: Each toast still auto-dismisses after its own duration
  3. Position: Top-right as currently, but with vertical stacking and small gaps
  4. Overflow: When max is reached, either drop the oldest or suppress new (configurable)

This pairs with #771's suppression (max warns + minimum interval per message). Together, they ensure:

Related

Operating System

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions