Skip to content

fix(webapp): landing on / opens the project you last used (BEA-75) - #115

Open
ssowonny wants to merge 1 commit into
mainfrom
bea-75-remember-the-project-visited-last-time
Open

fix(webapp): landing on / opens the project you last used (BEA-75)#115
ssowonny wants to merge 1 commit into
mainfrom
bea-75-remember-the-project-visited-last-time

Conversation

@ssowonny

@ssowonny ssowonny commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

TL;DR

  • Opening the hub with no project in the URL threw you at whatever project sorts first alphabetically, not where you actually were.
  • It now reopens the project this browser last had open — and rewrites the address bar to that, so the bookmark is right too.
  • A remembered project that's been deleted, or that you've lost access to, just falls back to the old behavior. No error, nothing on screen.
  • Behavior change worth knowing: the sidebar brand button on project B used to bounce you to project A. It now lands on B's root.
  • One string in localStorage, per browser. Not synced across devices, not per account — deliberately (see below).

What changed

HubApp resolves the current project from a precedence chain. This adds one clause to it:

flowchart TB
    U["landing with no project in the URL"]
    R1["1. URL names a visible project"]
    R2["2. an org was joined this page-load"]
    R3["3. remembered id, still in the list"]
    R4["4. projects[0]"]
    U --> R1 --> R2 --> R3 --> R4
    classDef added fill:#22c55e22,stroke:#22c55e,stroke-width:2px
    class R3 added
Loading

Rule 3 is the whole feature. Deep links still win, the invite flow still lands you in the org you just joined, and an empty or unrecognized memory falls through to rule 4 exactly as before.

The membership check is free: the remembered id is looked up in the project list the server just sent. Deleted project, revoked membership, or a different account signed in on the same browser — none of them match, so rule 4 takes over silently. That's one .find, not a code path.

src/util.ts lastProject() / rememberProject(id), key bdrive.lastProject. Both swallow — localStorage throws in Safari private mode and wherever storage is disabled, and a preference is never worth a broken page.
HubApp.tsx one clause in the current useMemo; one rememberProject(current.id) in the effect that already sets document.title. That effect fires on exactly the events that matter — sidebar, deep link, palette, post-create navigate — so the memory needs no subscription of its own.
src/util.test.ts new. Node has no localStorage, so the bare run is the storage-missing case; the other two stub a Map-backed store and a throwing one.
e2e/hub.spec.ts two specs at the end of the file (both create or seed state, which is where the file puts those).
internal/webapp/static/ rebuilt and committed, per CLAUDE.md.

What it looks like

Both shots: create a project named zz-last (so it can never be projects[0]), open it, then load bare / — the way a bookmark or a new tab arrives.

before after
before after
lands on wiki — alphabetically first, and not what you opened lands on zz-last — where you actually were

What you're accepting

  • The brand button changes meaning. onHome navigates to /, which used to mean "project A" from anywhere. On project B it now stays on B. That is what e2e/home.spec.ts:124 always claimed it did; it only passed because wiki happens to sort first. Strictly better, but visible.
  • /orgs/<id> and /billing resolve a current too, so visiting them re-remembers a project you didn't explicitly open. In practice it rewrites the id that was already there, or seeds projects[0] on a first-ever visit — which is today's behavior. Not worth a route guard.
  • Per browser, not per account. A stale id from another account is harmless (rule 3's list lookup rejects it), and keying by account would mean putting the email in localStorage for no gain. This is the spec author's inference, not a stated requirement.
  • Nothing is written in volume mode by construction: HubApp only mounts when config.mode === "hub", and VolumeApp shares none of this code. No guard, no test.

Verified

go build ./..., go vet ./... clean
go test ./... all packages ok (webapp 184s, daemon 66s, cmd/bdrive 43s)
npm test 53 pass, including the 3 new storage tests
npm run e2e 145 passed
the two new specs, run alone both pass
./check-dist.sh internal/webapp/static is fresh
UI driven for real in the seeded hub — the two screenshots above

Architecture changes

architecture/webapp-frontend.mdHubApp gains one member: it now holds a persisted preference (the last opened project, in localStorage). No new types, no new edges; every relationship it already had is unchanged.

✅ added · ❌ removed (strikethrough) · unmarked = unchanged

flowchart TB
    App["App"]
    HubApp["<div style='text-align:left'><b>HubApp</b><br/>project list, org walls<br/>admin panels, invites<br/><span style='background:#22c55e55;padding:0 4px;border-radius:3px'>✅ remembers last opened project (localStorage)</span></div>"]
    Browser["Browser"]
    router["router"]
    components["components"]
    hooks["hooks"]
    App --> HubApp
    HubApp --> Browser
    HubApp --> router
    HubApp --> components
    HubApp --> hooks
Loading

The one open loop

Rule 3 remembers the project but not the view inside it — land on / and you get the project's root, never the History tab you were reading. Explicitly out of scope for BEA-75; worth a follow-up if anyone actually misses it.

Closes BEA-75.

Build session

cd $(git worktree list | grep bea-75-remember-the-project-visited-last-time | awk '{print $1}') && claude --resume 4acdc67e-56b7-4e34-bfbb-a860f0915ee7

(only works on the machine this branch was built on)

🤖 Generated with Claude Code

Hitting the hub with no project in the URL always resolved to projects[0]
— alphabetically first, unrelated to what you were doing — and HubApp then
rewrote the address bar to it, so the wrong choice was the one that got
bookmarked.

The browser now remembers the project it last opened and prefers it. The
precedence chain gains one clause between the just-joined org and the
fallback; a remembered id is looked up in the project list, so one that was
deleted, or that this account can no longer see, simply doesn't match and
projects[0] takes over with nothing on screen to say so.

localStorage, per browser and origin-scoped, so two hubs never share an
answer. Both helpers swallow — storage throws in Safari private mode, and a
preference is never worth a broken page.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ssowonny ssowonny self-assigned this Aug 4, 2026
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