fix(desktop): draw Home in its own shape while the task list loads
Merged
DA
14h ago+196+196+012345678901234567890123456789-11-11-01234567890123456789
Summary
PR 2 of 9 in the loading-states rollout (Home). Context: Loading states audit and the plan Fix Inbox loading state, under "Loading states rollout".
- Home no longer goes blank on launch.
DesktopHomereturnednulluntil the task list was read. Now it drawsHomeSkeleton, laid out like Home: a title-sized block for the greeting beside the real New task (the welcome offers it too), then heading-sized blocks over placeholder rows for the Inbox (3 rows) and Recent (6 rows, one for each row Recent lists). The page lands where the blocks stood. The header comment now describes this instead of "draws nothing". - The welcome state. The skeleton contains no word that depends on the list: no "Welcome back", no section names. A brand-new account still sees a skeleton for a moment before "Start your first task", but the skeleton never claimed the account had tasks, so nothing misleading flashes.
HomeRecent's empty, unreadnullis covered because the whole page is the skeleton until the list is read. - Home's Inbox section drew a 16px
Spinnerat the section's left edge, outside the rows' inset. It now drawsInboxListSkeleton. That component gains two optional props:rows(defaults to today's five) andcontext, which adds a block before each row's time where Home's rows name what the item is about.
Evidence
- Platform-independent checks:
pnpm check:affectedpassed. In the sandbox, Electron's binary was fetched first forapp-menu.test.ts, anddiscarded-effectran with the larger heap. - New tests in
desktop/desktop-home.test.tsall fail onmain:- while the task list is unread, Home draws the skeleton: New task, placeholder rows under the Inbox and Recent, no greeting
- an unread list for an account with no tasks shows neither the welcome nor "Welcome back"
- once the list lands, the real page replaces the skeleton
- while what waits is read, Home's Inbox draws three placeholder rows under its name and no ring
Screenshots
| While the task list loads | List read, Inbox items still loading | Loaded |
|---|---|---|
![]() | ![]() | ![]() |
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) - 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
desktop-home.test.ts) - 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: None. The screenshots below come from the jsdom markup under the built stylesheet in the dark scheme, not from the Mac app.
Made with Luke
Summary by cubic
Home no longer shows a blank window on launch. While the task list is unread, DesktopHome draws a skeleton shaped like the page: a block for the greeting beside the real New task button, then heading blocks over placeholder rows for the Inbox (3 rows) and Recent (6 rows).
- The skeleton says no word that depends on the list (no "Welcome back", no section names), so a brand-new account's welcome replaces it without flashing anything misleading.
- Home's Inbox section replaces its lone spinner with
InboxListSkeleton, which gains optionalrowsandcontextprops to match Home's row layout. - Four new tests in
desktop-home.test.tscover the skeleton, the unread new-account state, the swap to the real page once the list lands, and the Inbox section's rows.


