fix(sidenav): show the skeleton, not "No Chats Yet", while sessions load - #985
Merged
Merged
Conversation
`isLoading()` tested `sessionsResource.value() === undefined`, which stopped being true during the cold-start fetch. The loader short-circuits to `null` when `sessionsRequest` is false, so the resource *resolves* before any session is fetched. That path is now the ordinary one: `SessionService`'s constructor only enables loading if the BFF session is already authenticated, but the service is constructed during the APP_INITIALIZER pass — `AnnouncementModalService` -> `MessageMapService` -> `SessionService` — and Angular's `runInitializers` invokes every initializer synchronously before awaiting any of them, so `bootstrap()` is still in flight and `isAuthenticated()` is false. The auth effect enables loading afterwards, but `reload()` preserves the previous value, so `null` survives the whole real fetch. `isLoading()` read false, `groupedSessions()` was empty, and the sidebar rendered the empty state until the response landed. `isLoading()` now means "nothing to draw yet": no API response (`undefined` before the first load resolves, or the short-circuit `null`) and no locally cached rows. An empty `sessions` array is a real response and still falls through to the empty state. `error()` is checked first — reading `value()` on an errored resource throws, which the old ordering walked into. Deliberately not keyed on `status() === 'reloading'`: `refreshSessions()` reloads after send/rename/mark-unread, and that would flash the list to a skeleton every time. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The regression
On a cold load the sidebar rendered the "No Chats Yet" empty state for the whole duration of the sessions fetch, then popped the list in. It should have shown the loading skeleton.
Root cause
isLoading()insession-list.tstestedsessionsResource.value() === undefined. That stopped being true during the fetch, for two reasons that compound:The loader short-circuits to
null.sessionsResource's loader returnsnullwhensessionsRequestis false, so the resource resolves —undefined→null— before a single session is fetched.SessionServiceis constructed before the BFF bootstrap resolves. Its constructor only callsenableSessionsLoading()ifbffSession.isAuthenticated()is already true, and the comment there assumes the service is instantiated post-bootstrap. That no longer holds:provideAppInitializer(() => { inject(AnnouncementModalService); })pulls inMessageMapService, which injectsSessionService. Angular'srunInitializersinvokes every initializer synchronously in registration order, collecting promises and onlyPromise.all-ing them afterwards — so that initializer runs whileSessionService.bootstrap()is still in flight,isAuthenticated()is false, and the eager-fetch branch is skipped.The auth effect then enables loading and calls
reload()— but Angular's resource preserves the previous value across a reload (status projects toreloading, the stream is kept). Sovalue()stayednullfor the entire real fetch:isLoading()read false,groupedSessions()was empty, and the template fell to the empty-state branch.The fix
isLoading()now means "we have nothing to draw yet":undefinedbefore the first load resolves, or the short-circuitnull— and no rows from the local cache.sessionsarray is a real response and still falls through to the empty state.error()is checked first: readingvalue()on an errored resource throws, which the old ordering walked straight into.Deliberately not keyed on
status() === 'reloading'—refreshSessions()reloads after send/rename/mark-unread, and that would have flashed the whole list to a skeleton every time.Testing
Four new specs covering the short-circuit
null, the genuinely-empty response, the locally-cached rows case, and the error path. Verified they are not vacuous: reverting the implementation makes 2 of them fail.ng test— 2500 passed, 221 filestsc --noEmit -p tsconfig.app.json— cleanFollow-up, not in this PR
The underlying fragility is that
SessionService's constructor gates on auth state that may not exist yet. This fix makes the UI correct under any construction order, but the service would be more honest with aparamsgate on the resource (undefined→idle) instead of a loader that resolvesnull. That changes logout/idle semantics, so it doesn't belong in a regression fix.🤖 Generated with Claude Code