fix(desktop): draw the person page's waits as skeletons, never a false Someone or empty line
Merged
DA
7d ago+177+177+012345678901234567890123456789-19-19-01234567890123456789
PR 3 of 9 in the loading-states rollout (Loading states audit, plan Fix Inbox loading state, "Loading states rollout"): the Person page (desktop/person-page.tsx).
Summary
- Corner spinners → skeleton sections. The bare 16px "Loading inbox" and "Loading automations" rings, which stacked in the column with no heading, are now placeholder sections: a heading-shaped block over placeholder rows. The Inbox uses
InboxListSkeletonas it is (left unmodified, since PR 2 owns its row-count prop). Automations get a localRowsSkeletonbuilt from@luke/ui'sListRow/Skeleton/SkeletonRegion: a tile, a name, and trigger text, with no time.AutomationRowis unchanged. - No "Someone · Not in this workspace" while the members load. A page opened by userId alone (⌘K People, an avatar) for a member with no task in the list used to resolve to SOMEONE/UNNAMED until the page's own
MemberDirectoryanswered. Until the members and former-members reads have answered, it now shows a placeholder head (a 48px circle, a name line, a caption line) and a placeholder toolbar title. Only settled reads can now say Not found. - No "Nothing of theirs here yet." before the task list is read. The empty line is now gated on
tasks.listRead. While the list is unread and nothing of theirs is listed yet, a placeholder tasks section stands in its place. A list read that failed says "Their tasks could not be read." with Retry (onRetryList), the same way Home does, so it doesn't pass for an empty page either. - The file's header comment is updated to describe the new loading behaviour.
Evidence
- Platform-independent checks:
pnpm check:affectedpassed, apart from two known sandbox quirks, each rerun alone and green:discarded-effectwith a larger heap, andapp-menu.test.tsafter fetching Electron's binary. person-page.test.tshas 4 new tests and 1 updated. All 5 fail with theperson-page.tsxchange stashed:- while the Inbox and the automations are read, placeholder rows stand for each, never a spinner
- a page opened by a member's id alone stands a placeholder head while the members are read, never "Someone · Not in this workspace", and names them once the read answers
- while the task list is read, placeholder rows stand for their tasks and the page is never called empty
- a task list that could not be read says so and offers Retry, never calling the page empty
- updated: "automations that could not be read leave the Inbox still being read…" now expects the Inbox placeholder instead of a spinner
- Screenshots in the comment below.
Invariants
-
./scripts/check.shgreen. (CI) - JSON Schema goldens unchanged (
LUKE_UPDATE_FIXTURESnot run, or diff explained line by line). (CI) - Every behavior change has, in this PR, the test that fails without it; no change-detector test added. (manual; root
AGENTS.md"Testing") - Gateway envelope goldens unchanged. (CI)
- No new
astype assertion outside the allowlisted files. (CI via anti-slop) - No
Effect.runPromise/runSync/runForkoutside the runtime edges and the shimstools/anti-slop/effect-edges.jsonrecords. (CI viaanti-slop/no-run-promise-outside-edges) - No new
setTimeout/setInterval/new Promise/AbortController/fs.watchoutside the filestools/anti-slop/effect-edges.jsonrecords. (CI viaanti-slop/no-raw-async-primitives). The test's held reads usenew Promise, as the suite's existinginboxHeldalready does. - No Effect, Stream, or Layer written as a statement and dropped; a description a caller walks past runs nothing. (CI via
pnpm discarded-effect) - Test count equals the pre-PR count or the delta is listed: +4 in
person-page.test.ts(23 → 27). - Each hand-rolled file the PR replaces is deleted, or the part that remains is a permanent adaptor named in root
AGENTS.md's "The permanent adaptors" with the reason it stays. - Every
CLAUDE.md/AGENTS.mdsentence naming a changed part is edited; root pair stays byte-identical. (CI for pair existence; manual for wording) -
PRIVACY.mdunchanged, or the change is a product decision called out in the PR body. - Package
package.jsondeps match what the sources import by bare specifier;effectviacatalog:. (CI) - Barrel/door rule: no Node-reaching Effect module (
@effect/platform-node,effect/sql) behind a barrel the renderer or a web function opens. (CI: existing renderernode:grep plus a new grep for those specifiers in renderer bundles)
Notes
- Blockers or follow-up verification:
useWorkspaceMembers/useFormerMembersswallow a refused read and stayundefined. If either read is refused, a person nothing else names keeps the placeholder head instead of the old false "Someone · Not in this workspace". Giving the member directory a failed state, so the head could offer Retry, would touchmember-directory.tsxanduse-workspace-members.ts, so it is left as a follow-up.
Made with Luke
Summary by cubic
Replaces the person page's loading waits with skeleton placeholders and stops the page from falsely naming someone as not in the workspace or declaring it empty while reads are pending.
- While the Inbox, automations, members, or task list are being read, skeleton placeholder sections stand in their place instead of bare spinners.
- A page opened by a member's id alone now shows a placeholder head until the member read answers, instead of "Someone · Not in this workspace".
- The empty line "Nothing of theirs here yet" now appears only after every read has answered and the page names someone; a failed task list read shows an error with Retry.