Skip to content

toast is re-exported verbatim from primereact/toaster, so the entire notification surface is unsubstitutable #165

Description

@woksin

What happens

toast — a PrimeReact runtime value, not a type — is re-exported verbatim as Cratis public API:

// dist/esm/Notifications/toast.js
export { toast } from 'primereact/toaster';

and re-exported again from the Notifications barrel. So import { toast } from '@cratis/components/Notifications' hands the consumer PrimeReact's imperative toast API under a Cratis name.

This is the only PrimeReact runtime value in Components' public surface — every other public leak is a type.

Evidence

Measured against the published @cratis/components@3.4.0 artifact with primereact@11.1.0.

Of 27 distinct PrimeReact symbols reachable from Components' public entry points, 26 are types (18 of them only via pt/ptOptions). toast is the single runtime value, which makes it categorically different: a type leak costs a consumer a commercial type import, whereas this one means the behavior itself is PrimeReact's, including its options shape, its dismissal semantics and its store.

Two consequences already visible in the issue tracker:

  • A custom toast render silently removes the toast's dismiss control, and Toaster exposes no pt #156 — a custom toast.render silently removes the toast's dismiss control, and Toaster exposes no pt. That is PrimeReact's rendering contract surfacing through a Cratis-named API, and the workaround (mint the id up front, render your own close button inside the custom body) is a consumer working around PrimeReact through a seam that does not admit to being PrimeReact.
  • toastCommandResult — genuinely Cratis-owned behavior, mapping an Arc ICommandResult to a severity — is built on top of that re-export, so the one piece that is Cratis's differentiator sits on a foundation the consumer cannot substitute.

What it costs a consumer

Small but real, and structurally awkward out of proportion to its size.

  • The whole notification surface is unsubstitutable. An application that has its own toast system (ours does not, but many do) cannot supply it while keeping toastCommandResult, because the Cratis-authored part depends on the PrimeReact-authored part rather than on a contract.
  • It is a licence-surface item with no opt-out. Unlike pt, a consumer cannot decline to use it and still get command-result notifications.
  • It blocks the Prove a renderer-neutral Arc React query/table state seam #109 direction for notifications. toastCommandResult is exactly the kind of Arc-binding behavior Prove a renderer-neutral Arc React query/table state seam #109 argues should live headlessly — it maps a command result to a severity and message, which is pure logic — but it cannot move while its dispatch is a PrimeReact re-export.

Honest limit: this has caused no defect in our application beyond #156, which is already filed. Filing this for the shape, not for a failure.

Suggested fix — the seam

Put a thin Cratis-owned dispatch in front of it:

  1. Define a Cratis toast APIsuccess / info / warn / error / promise / dismiss with a Cratis-owned options type — and have the PrimeReact implementation satisfy it, rather than re-exporting PrimeReact's function directly.
  2. Let toastCommandResult depend on that contract, so the Arc-binding logic (result → severity → message) becomes independent of who renders the toast. This is the piece worth making headless; the renderer is not.
  3. Optionally, allow a consumer-supplied dispatch so an application with an existing notification system can receive command-result notifications through it.

(1) alone is nearly free — a wrapper function and an options type — and is what makes (2) possible.

What is explicitly not being asked for

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions