The branch was 12 commits behind dev and GitHub reported it CONFLICTING, which is
why the real CI workflow had not run since 2026-08-10 - a conflicting PR has no
mergeable ref, so pull_request-triggered workflows are skipped. Only the
lightweight checks (semantic title, CodeQL, CodeRabbit) were still reporting,
which made the PR look green while nothing was actually being tested.
Three conflicts, all of them this branch's barrel-diet work meeting dev's new
What's Up Docker integration. In every case the branch's structure is kept and
WUD is absorbed into it rather than the reverse:
integrations/base/creator.ts keep the lazy loader map; add wud as
() => import(...) rather than dev's static import,
which would have undone the lazy creator
integrations/src/index.ts keep the slim barrel; re-export WudStats and
WudContainerUpdate as `export type`, which is
erased at build and so adds no runtime module
widgets/src/index.tsx stays deleted - this was the barrel that put
every widget in the initial client graph
Registering WUD in the slimmed structures then needed three more entries that the
merge could not infer: base/kind-map.ts (type-only kind -> class map),
widgets/src/import.ts (type-only WidgetImports map, both the import and the
member). Each was found by typecheck rather than guessed at.
Verified: 37/37 packages typecheck, 1866/1881 unit tests pass. The 3 failures are
home-assistant and pi-hole, both testcontainers-based; both pass 11/11 in
isolation and fail only under 150-file parallel load with the container's payload
half-initialised.
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.
- Replace `as any` casts in integration-test-connection.spec.ts with
Pick<Integration, "testConnectionAsync"> + mockResolvedValue
- build-compare.sh: split local declarations (SC2318), clean .next
between builds with --force, add EXIT trap for safe stash/cleanup
The barrel no longer exports PiHoleIntegrationV6 after the dynamic
import refactor. The mock only needs testConnectionAsync, so a type
cast is sufficient.
- BoardLayoutFallback: use HomarrLogoWithTitle (no board context required)
- integration-data-cache: guard cache.set with placeholder identity to
prevent stale write-back after invalidation + test
- next-cache-handler: memoize the promise (not resolved handler) to
prevent concurrent Redis connections
- redis-handler: tee ReadableStream before consuming in set(), hand fresh
branch to each get() caller
- redis-handler: base64-encode body instead of number[] (3-4x smaller)
Merge origin/dev to pick up service isolation (concurrently dev script,
turbopack aliases, instrumentation simplification), PostHog analytics,
color scheme, and other dev improvements while preserving all Phase 1
performance work (Suspense boundaries, cache components, Redis handler,
integration barrel diet, cache invalidation).
- 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 critical bug: tag expiration comparison was inverted (< should be >)
in Redis cache handler, which would prevent revalidateTag from clearing
stale entries
- Pass board name to invalidateBoardCache in saveItemOptions
- Add clarifying comment about two-tier invalidation helpers
- Remove all concrete integration class re-exports from barrel
- Replace instanceof check with kind check in health-monitoring
- Document cache invalidation as preparatory (cacheTag follow-up)
- Pass board.name to all invalidateBoardCache calls (permission mutations)
- Align integration create invalidation with update/delete
- Add initial-fetch-failure test for integration-data-cache
- Expand SVG XSS test corpus (javascript:, xlink:href, @import, onbegin)
- Forbid <style> tags in SVG sanitizer to prevent CSS injection
- Fix duplicate export in packages/common/src/index.ts