fix(web): refresh the project list when a Discover run ends (#808) - #809
Merged
Conversation
Reported from real first-run use: Discovery found the user's directories and the import succeeded, but nothing refreshed — a manual browser reload was needed before the imported projects appeared. The refresh was deferred all the way to "Get started" on purpose, because on the Home mount DiscoverView exists only while the instance is empty: importing is the act that ends that condition, so refreshing mid-run made the first successful row unmount the screen the user was watching the rest of the run on. What the deferral cost was a success screen asserting "They are in the sidebar now" over a sidebar that still read "No projects yet". Refresh once, on completion, and latch Home's empty-instance decision for the life of the mount so that refresh cannot yank its own success screen — including the failed rows, which are the ones with something to say. The latch is released by "Get started", which is what `useInstanceEmpty`'s hitherto-uncalled `recheck()` was written for; RootHome now passes it to DiscoverView as `onLeave`, since a screen rendered INSTEAD of Home cannot leave by navigating to Home. Co-Authored-By: Claude <noreply@anthropic.com>
Deploying paddock with
|
| Latest commit: |
b12d455
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://22d562d3.paddock-7u2.pages.dev |
| Branch Preview URL: | https://fix-808-discover-refresh.paddock-7u2.pages.dev |
Merged
This was referenced Aug 10, 2026
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.
Fixes #808.
What actually happened
Reproduced on a throwaway instance with synthetic transcripts, before changing anything. It is candidate 2 — the deferral working exactly as designed, with a UX that lies about it — but with a twist the issue could not see from reading the code: the fix candidate 2 proposes cannot stand alone.
Candidate 1 is refuted. Clicking Get started works correctly today:
await refresh()resolves, the sidebar populates, and Home swaps to the root workspace. No race, no stale closure, no lagging server. I drove it in a browser and watched it work.What the user actually saw is on the screen before that click:
…rendered next to a sidebar reading "No projects yet." The success screen asserts the import has landed while every other surface still says the instance is empty. Someone who reads that sentence, looks at the sidebar, and sees nothing does not go hunting for a button labelled "Get started" — they reload the browser. That is the whole bug.
Why the obvious candidate-2 fix makes it worse
The issue suggests refreshing when the run completes, on the grounds that "the success screen is already showing; nothing is mid-run to unmount". I tried exactly that first, as a throwaway commit, and it is wrong: on the Home mount
DiscoverViewexists only while the instance is empty, and the refresh is what ends that condition. The success screen and every per-row outcome vanished the instant the list came back, replaced by the root workspace — the failure rows, the ones that had something to say, hardest hit.So both of these must hold at once, and they pull in opposite directions:
The change
Refresh once, on completion (
submit) — not per row, which is the mid-run unmount the original comment rightly forbids, and not only on Get started, which is what left the sidebar stale.Latch Home's empty-instance decision for the life of the mount (
useInstanceEmpty). The instance's emptiness stays live data; Home's front door stops being live data.projectsstill updates underneath — that is the point, it is what populates the sidebar — but the answer Home routes on is pinned until something asks again.recheck()is now wired up, not deleted. The issue flagged it as an escape hatch nobody called, and its docstring ("Re-ask. The import run creates projects, so Home has to re-evaluate") describes precisely the release side of this latch. It was written for a latch that was never built.RootHomepasses it toDiscoverViewasonLeave, because a screen rendered instead of Home cannot leave by navigating to Home —navigate("/")is a no-op there, and the latch releasing is what actually hands over.One subtlety worth flagging for review:
recheckhad to clearrootHasChatsitself, not just bump the nonce. Bumping alone releases the latch in the same render that still holds the pre-recheck answer, which re-pins the very value being discarded and then never moves again. My first draft had that bug; the pre-existing "re-asks on recheck" test caught it.Tests
packages/web/src/routes/RootHome.discover.test.tsx— new, and the real guard. Mocks the API and nothing else:RootHome,useInstanceEmpty,ProjectsProviderandDiscoverVieware all the real thing, because the seam between them is the defect. It asserts both halves — the list is refetched when the run ends, and the per-row outcomes are still rendered — plus that nothing refreshes between rows.2 of its 3 tests fail on
main(expected "spy" to be called 2 times, but got 1 times); verified by stashing the source changes and keeping the tests.Also added: latch/release cases in
useInstanceEmpty.test.tsx, the completion refresh andonLeaveinDiscoverView.test.tsx, and aRootHome.test.tsxcase that fails if theonLeavewiring is ever dropped (which would strand a first-run user on the success screen for good).Verification
/discoverroute mount (which passes noonLeave) with a second import on a now-populated instance — refreshes on completion, navigates away on Get started.npm run typecheckclean; full web suite 85 files / 1782 tests passing;check:nulclean.🤖 Generated with Claude Code