buttonManager.on('action:start', actionId, timestamp ); buttonManager.on('action:success', actionId, result, duration ); buttonManager.on('action:error', actionId, error, retryCount ); A single listener (e.g., a GlobalNotificationSystem ) consumes these events and displays UI-appropriate feedback. This decoupling means that a headless CLI tool and a rich web app can share the same button manager logic but present feedback differently. Before (v1): A "Delete Account" button in a dashboard. The developer wrote 120 lines of JSX with three useState hooks ( isDeleting , showConfirmModal , deleteError ). The button was accidentally enabled while the modal was open. The API retry logic was copy-pasted from the "Export Data" button, but with a bug that allowed double-deletion.
interface ButtonAction<T = any> Result; guards: Guard[]; // e.g., formIsValid, userHasRole, notRateLimited feedback: loading: string ; sideEffects: SideEffect[]; // e.g., invalidateQuery, trackAnalytics, closeModal button manager v2
A button seems trivial. A <button> element with a click listener. Done. But in a modern enterprise SaaS application or a complex game UI, a button is a nexus of state, permission, feedback, and side effects. The "v1" Button Manager—a loose collection of event handlers, inline disabled toggles, and scattered loading spinners—inevitably collapses under its own weight. buttonManager