Ablation (PHASE2-FINDINGS.md) attributed the memory win to the
serverExternalPackages line and the integrations barrel diet. These three
measured at exactly zero and were most of the file churn, so they go:
next-cache-handler: orphaned. cacheHandlers and cacheComponents are both
already gone from next.config.ts and nothing imported the package — it
was reachable only through a stale dependency entry.
cache-tags / cache-invalidation: there is no "use cache", cacheTag,
cacheLife or unstable_cache anywhere in app source, so revalidateTag was
firing on tags nothing registers, with no handler to receive them, inside
a catch that swallowed the resulting out-of-scope error. Removing the
calls also removed the SELECTs that existed only to supply a board name
or member list to them, so board.ts, group.ts, user.ts, serverSettings.ts,
section-router.ts and change-search-preferences.ts return to the
merge-base exactly. The live in-process integration data cache is
untouched.
Redis 6 -> 2 client consolidation: no measurable effect at idle (139.3 vs
139.2 MiB) or under load. Reverted, but the pubSub:last:* TTL is kept —
that one is an unbounded-growth fix, not a consolidation.
23 files, -883 lines. Diff vs merge-base: 100 files/3336 insertions ->
82 files/2484. pnpm test 144/144 files, 1795 tests, 0 failures;
typecheck and lint clean; app and Docker image build.
Revert layout.tsx, _layout-creator.tsx, manage/layout.tsx,
_theme.tsx, _creator.tsx, mantine.tsx, and login/page.tsx
to origin/dev — the Suspense provider splits caused
"EditMode is required", stream close, and React #419 errors.
Also remove cacheComponents/cacheHandlers from next.config.ts
(incompatible with auth/i18n/dynamic rendering) and fix
Boolean() truthy bug in Redis connection.
- Redis get() rejects legacy expire=0 entries and cleans them up
- transferOwnership invalidates previous owner in addition to new
- SVG regex uses non-greedy match and handles self-closing <svg />
- SVG fallback returns empty string instead of HTML wrapper
- Memory channel comment: FIFO eviction, not timestamp-based
- Fix integration data cache: check pending promise before TTL to
prevent returning undefined on concurrent cold-cache access
- Add concurrent-miss test (two parallel callers, one fetch)
- SVG sanitizer: extract <svg> from DOMPurify output to strip
residual HTML wrapper
- Narrow safeRevalidateTag catch: log unexpected errors, silence only
known "outside request scope" messages
- Run oxfmt across all packages (206 files formatted)
* fix(beszel): harden cache layer and prevent blank widgets
- Add debug logging across Beszel integration, request handler, and tRPC
router (auth, fetch, SSE, per-procedure entry/exit) gated behind
LOG_LEVEL=debug
- cached-request-handler: retry with backoff, isValid predicate to skip
caching invalid data, in-flight dedup (single-flight per channel) to
avoid thundering herd after cache invalidation
- redis channel: set-then-publish so storage is always consistent even
when pubsub publish fails
- Beszel handlers: opt into fallbackToStaleOnError and retry so brief
upstream hiccups don't throw away good cached data
- beszel-system-stats: add refetchInterval matching each time period's
cache TTL so historical charts refresh in the background
- tRPC router: switch Promise.all -> Promise.allSettled for getSystems
and getAlerts; getSystemStats returns an error object instead of
throwing. Each result carries an optional error field so one failed
integration no longer nukes the whole widget
- Widgets: add explicit isPending loader, empty state for grid and
stats, and a shared per-integration error indicator (tooltip with
failed integration names). Alerts and table now only show their
empty state after loading completes
- Translation: add empty.noSystems key to beszelSystemGrid and
beszelSystemStats
Addresses #6030
* style: auto-fix lint and format
---------
Co-authored-by: ajnart <ajnart@users.noreply.github.com>