spinuptui 0.24.3 → 0.24.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,25 @@ versions; such changes are called out here.
11
11
 
12
12
  ## [Unreleased]
13
13
 
14
+ ## [0.24.4] - 2026-08-06
15
+
16
+ ### Fixed
17
+ - **The `Unknown component type: toaster` startup crash is now actually
18
+ fixed for real installs**, not just local project installs. `0.24.3`'s
19
+ `package.json` `overrides` only take effect when this repo's own
20
+ `package.json` is the root of the install — they're silently ignored when
21
+ spinuptui is installed as a dependency of something else, which is exactly
22
+ what a global `bun add -g` / `npm install -g` does, so the crash persisted
23
+ for every real user. Root cause traces back to the toast library
24
+ (`@opentui-ui/toast`) itself: it's stuck on a peer-dependency range from
25
+ opentui's `0.1.x` days, and no version pin on our side can bind how a
26
+ downstream install resolves *its* transitive dependencies. Every call site
27
+ in this app only ever fires a single-line success message, so it's
28
+ replaced with a small first-party toast implementation (`src/ui/toast.ts`
29
+ + `src/ui/ToastStack.tsx`) built directly on the `@opentui/core`/
30
+ `@opentui/react` we already depend on — no external package, no peer-
31
+ dependency surface for a future version bump to break again.
32
+
14
33
  ## [0.24.3] - 2026-08-06
15
34
 
16
35
  ### Fixed
@@ -1280,7 +1299,8 @@ Initial tagged release.
1280
1299
  ### Notes
1281
1300
  - Read-only release: works with a SpinupWP **Read Only** API token.
1282
1301
 
1283
- [Unreleased]: https://github.com/mwender/spinupwp-tui/compare/v0.24.3...HEAD
1302
+ [Unreleased]: https://github.com/mwender/spinupwp-tui/compare/v0.24.4...HEAD
1303
+ [0.24.4]: https://github.com/mwender/spinupwp-tui/compare/v0.24.3...v0.24.4
1284
1304
  [0.24.3]: https://github.com/mwender/spinupwp-tui/compare/v0.24.2...v0.24.3
1285
1305
  [0.24.2]: https://github.com/mwender/spinupwp-tui/compare/v0.24.1...v0.24.2
1286
1306
  [0.24.1]: https://github.com/mwender/spinupwp-tui/compare/v0.24.0...v0.24.1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spinuptui",
3
- "version": "0.24.3",
3
+ "version": "0.24.4",
4
4
  "description": "SpinupTUI — a terminal dashboard for browsing, monitoring, and managing your SpinupWP servers and sites.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,9 +41,8 @@
41
41
  "author": "Michael Wender",
42
42
  "license": "MIT",
43
43
  "dependencies": {
44
- "@opentui-ui/toast": "^0.0.5",
45
- "@opentui/core": "0.4.2",
46
- "@opentui/react": "0.4.2",
44
+ "@opentui/core": "^0.4.1",
45
+ "@opentui/react": "^0.4.1",
47
46
  "react": "^19.0.0",
48
47
  "socket.io-client": "^4.8.3"
49
48
  },
@@ -51,9 +50,5 @@
51
50
  "@types/bun": "^1.3.14",
52
51
  "@types/react": "^19.0.0",
53
52
  "typescript": "^5.6.0"
54
- },
55
- "overrides": {
56
- "@opentui/core": "0.4.2",
57
- "@opentui/react": "0.4.2"
58
53
  }
59
54
  }
package/src/ui/App.tsx CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { useEffect, useState } from "react"
4
4
  import { useKeyboard, useRenderer, useTerminalDimensions } from "@opentui/react"
5
- import { Toaster } from "@opentui-ui/toast/react"
5
+ import { ToastStack } from "./ToastStack.tsx"
6
6
  import { theme } from "../lib/theme.ts"
7
7
  import { useStore } from "./store.tsx"
8
8
  import { Splash } from "./Splash.tsx"
@@ -262,10 +262,7 @@ export function App() {
262
262
  {store.connectZoneTarget && <ProviderConnect />}
263
263
  {store.dnsRecordsTarget && <DnsRecords />}
264
264
  {store.kumaSite && <KumaSite />}
265
- {/* Async-completion toasts (PHP upgrade, server reboot/restart). Mounted last
266
- so it draws over every view + overlay; top-right, nudged clear of the
267
- 2-row Header. It never takes keyboard focus. */}
268
- <Toaster position="top-right" offset={{ top: 2, right: 2 }} />
265
+ <ToastStack />
269
266
  </box>
270
267
  )
271
268
  }
@@ -0,0 +1,33 @@
1
+ // Renders the toast queue from ./toast.ts. See that file for why this is
2
+ // first-party instead of a dependency. Mounted last in App.tsx so it draws
3
+ // over every view + overlay; top-right, nudged clear of the 2-row Header.
4
+ // It never takes keyboard focus.
5
+
6
+ import { useSyncExternalStore } from "react"
7
+ import { theme } from "../lib/theme.ts"
8
+ import { getToasts, subscribeToasts } from "./toast.ts"
9
+
10
+ const TOAST_WIDTH = 60
11
+
12
+ export function ToastStack() {
13
+ const toasts = useSyncExternalStore(subscribeToasts, getToasts, getToasts)
14
+
15
+ if (toasts.length === 0) return null
16
+
17
+ return (
18
+ <box style={{ position: "absolute", top: 2, right: 2, flexDirection: "column", gap: 1, zIndex: 300 }}>
19
+ {toasts.map((t) => (
20
+ <box
21
+ key={t.id}
22
+ border
23
+ borderColor={theme.good}
24
+ backgroundColor={theme.bgPanel}
25
+ style={{ flexDirection: "row", gap: 1, maxWidth: TOAST_WIDTH, paddingLeft: 1, paddingRight: 1 }}
26
+ >
27
+ <text content="✓" fg={theme.good} />
28
+ <text content={t.message} fg={theme.text} />
29
+ </box>
30
+ ))}
31
+ </box>
32
+ )
33
+ }
package/src/ui/store.tsx CHANGED
@@ -6,7 +6,7 @@
6
6
  // trigger a refresh.
7
7
 
8
8
  import { createContext, useContext, useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"
9
- import { toast } from "@opentui-ui/toast"
9
+ import { toast } from "./toast.ts"
10
10
  import { SpinupWPClient, ApiError, type ServerService, type SpinupWPClientLike } from "../api/client.ts"
11
11
  import { isDevMode } from "../dev/devMode.ts"
12
12
  import { createMockClient } from "../dev/mockClient.ts"
@@ -0,0 +1,49 @@
1
+ // Minimal first-party toast notifications.
2
+ //
3
+ // Replaces the @opentui-ui/toast package, which is stuck on a peer-dependency
4
+ // range from opentui's 0.1.x days and breaks against the @opentui/core 0.4.x
5
+ // line we're on (see CHANGELOG v0.24.2/v0.24.3). Every call site in this app
6
+ // only ever fires a single-line success message, so a small first-party
7
+ // implementation is simpler — and immune to upstream drift — than depending
8
+ // on a package built for a much larger feature set we don't use.
9
+ //
10
+ // A plain module-level subscriber list (rather than React state) so callers
11
+ // outside the component tree — store.tsx's async completion handlers — can
12
+ // fire a toast without needing a ref or context. ToastStack.tsx reads this
13
+ // via useSyncExternalStore.
14
+
15
+ export interface ToastItem {
16
+ id: number
17
+ message: string
18
+ }
19
+
20
+ const TOAST_DURATION_MS = 4000
21
+
22
+ let nextId = 1
23
+ let toasts: ToastItem[] = []
24
+ const listeners = new Set<() => void>()
25
+
26
+ function notify() {
27
+ for (const listener of listeners) listener()
28
+ }
29
+
30
+ export function subscribeToasts(listener: () => void): () => void {
31
+ listeners.add(listener)
32
+ return () => listeners.delete(listener)
33
+ }
34
+
35
+ export function getToasts(): ToastItem[] {
36
+ return toasts
37
+ }
38
+
39
+ export const toast = {
40
+ success(message: string) {
41
+ const id = nextId++
42
+ toasts = [...toasts, { id, message }]
43
+ notify()
44
+ setTimeout(() => {
45
+ toasts = toasts.filter((t) => t.id !== id)
46
+ notify()
47
+ }, TOAST_DURATION_MS)
48
+ },
49
+ }