Skip to content

feat(ui): add global query refresh indicator to board header#5811

Draft
ajnart wants to merge 15 commits into
devfrom
feat/global-refresh-indicator
Draft

feat(ui): add global query refresh indicator to board header#5811
ajnart wants to merge 15 commits into
devfrom
feat/global-refresh-indicator

Conversation

@ajnart
Copy link
Copy Markdown
Member

@ajnart ajnart commented May 28, 2026

Summary

  • Adds a global query refresh indicator to the board header (left of the edit button) that monitors all widget-related TanStack Query cache entries (widget.*, docker.*, app.*, integration.*)
  • Indicator shows an orange pulsing dot while any widget query is fetching, green flash on completion, and red if any query is in error state
  • Hover card displays per-query status rows with colored dots, labels, and relative timestamps (fromNow)
  • Plays a subtle 880Hz beep when all fetching transitions to idle (singleton AudioContext)
  • Removes the per-widget loading spinner from the calendar widget, which now relies on the global indicator instead of a local <Loader>

Continues the work from #5768 with a broader scope.

Test plan

  • Verify the indicator appears in the board header when widget queries are active
  • Confirm orange dot pulses during fetch, flashes green on completion
  • Hover over the indicator to see per-query status, staleness, and error counts
  • Verify beep plays on fetch-to-idle transition (requires prior user interaction for AudioContext)
  • Check that calendar widget renders immediately without a local spinner
  • Test with Docker, bookmarks, and app widgets to confirm they appear in the indicator

@ajnart ajnart requested a review from a team as a code owner May 28, 2026 13:32
@ajnart ajnart marked this pull request as draft May 28, 2026 13:35
@ajnart ajnart force-pushed the feat/global-refresh-indicator branch from a83bc66 to c209fd9 Compare May 28, 2026 13:54
Replace per-widget loading spinners with a shared header indicator that
shows live TanStack Query cache status for all widget-related queries.
Includes orange/green dot, completion beep, and hover card with
per-query staleness and error details.
@ajnart ajnart force-pushed the feat/global-refresh-indicator branch from 6aeaa49 to a266874 Compare May 28, 2026 14:06
ajnart and others added 6 commits May 28, 2026 14:07
Use Mantine Indicator with processing animation instead of custom CSS.
Switch from HoverCard to Menu with grouped sections. Remove beep,
position dot next to board logo, hide in edit mode.
Non-blocking data fetching for all 38 widget components. Widgets now
render immediately and show data when available, with loading state
delegated to the global refresh indicator in the board header.
…-mode guard

- Move hooks above early returns in system-disks, immich-carousel, media-transcoding
- Add enabled flag to immich-carousel query when albumId is absent
- Replace disconnected local state in ping-indicator with query cache setData
- Stabilize docker timestamp with useMemo to prevent useTimeAgo flicker
- Wrap releases results in useMemo with proper type narrowing
- Hide refresh indicator during edit mode
…scope indicator

- Add isFetched guard in media-requests/list to avoid throwing before data loads
- Scope refresh indicator to only track widget and integration queries
@ajnart
Copy link
Copy Markdown
Member Author

ajnart commented May 29, 2026

i believe this change will be nice, I'd much rather see the calendar and then 500-800ms later see the entries popping up rather than the loading circle, that's probably the case for a bunch of other integrations that could have fallback states in place of the loading spinner. The issue is I'm not sure where to indicate the freshness of the queries

ajnart added 8 commits May 29, 2026 17:25
Add tRPC router (queryCache.getItem/setItem/removeItem) for persisting
TanStack Query cache entries to Redis, scoped per user and board.
Includes isPersistableWidgetQueryKey filter that excludes widget.app.ping,
and comprehensive tests covering access control, TTL expiry, and size limits.
Replace individual Redis keys (queryCache:sha256(...)) with a Hash per
user+board (qc:{userId}:{boardId}). This enables HGETALL to read all
cached entries in a single call for server-side hydration. Remove unused
SHA256 hashKey function and createHash import.
Call setActiveQueryCacheBoardId when board context initializes so the
client-side persister knows which board to scope cache reads/writes to.
Add WidgetEmptyState component for widgets that need a loading fallback.
Disable refetch-on-mount/focus/reconnect and retry for integration widgets
so cached data is preserved. Ensure hooks are never called conditionally.
Remove WidgetEmptyState guard from downloads (widget renders fine with
empty data). Fix columnsSort filter logic from .some() to .every() so
non-sortable columns are correctly excluded. Add server-side prefetch
via tRPC caller so download data is available immediately on page load.
Add QueryCacheHydration async server component inside <Suspense> that
reads all persisted queries from Redis via HGETALL and streams a
HydrationBoundary to the client. Builds DehydratedState directly with
original dataUpdatedAt timestamps so fresh prefetch data is never
overwritten by stale cache. Make client persister write-only (getItem
returns null) since reads are handled server-side.
Filter out widget.app.ping from the indicator. Show colored status dots
(green/orange/red) per query alongside integration icons with name
tooltips. Use lookup maps and resolver functions instead of ternaries.
Display relative timestamps for last data update.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant