Edit profile from the repositories/organizations tabs opens the form but does not navigate to the profile view (follows #442) #455

Closed
opened 2026-09-13 14:14:00 +00:00 by crueber · 3 comments
Owner

Edit profile from a non-profile tab should navigate to the profile view (follow-up to #442)

What's requested

#442's fix hoisted ProfileForm out of the profile-view gate so the Edit profile button now opens the form on every owner tab (setEditing(true) from the sidebar on /repositories and /organizations). But the click only flips the shared editing signal — it does not change the route. On the repositories or organizations tab, the form renders inline at the top of the repo list column while the tab bar still shows that tab as active, which reads as broken navigation rather than "editing my profile".

Expected: clicking Edit profile routes to /{owner} (the profile view) with the Profile tab active and the form open.

Evidence (current tree, static read — no local repro)

  • web/src/pages/Repos.jsx (~line 676): the sidebar button's handler is just onClick={() => setEditing(true)} — no navigation.
  • web/src/pages/Repos.jsx (~line 443): the shared-scope <Show when={getEditing() && getProfile()?.can_edit}> renders ProfileForm in the main column on all three owner views (the #442 fix).
  • web/src/pages/Repos.jsx OwnerTabs (~line 110): tab activation is purely pathname-derived — /{owner}/organizations → orgs, /{owner}/repositories → repos, else profile. So after the click, the tab highlight stays on the tab you were on, contradicting "editing profile".

Architecture notes

  • editing already lives at the shared OwnerPage scope (setEditing / getEditing), so the form survives navigation to /{owner} with no new state — navigation alone is sufficient.
  • OwnerTabs already treats any non-repositories/organizations path as the active Profile tab, so navigating to /{owner} lights the Profile tab for free.
  • The component already imports Solid Router primitives (useLocation; <A> is used in OwnerTabs) — useNavigate is the natural seam. No API/wire changes; server untouched.

Acceptance criteria

  • Clicking Edit profile on /{owner}/repositories navigates to /{owner} with the Profile tab active and the form open (editing state still true).
  • Clicking Edit profile on /{owner}/organizations behaves identically.
  • Clicking Edit profile on the profile view is unchanged (no navigation, form opens as today).
  • Existing gates byte-identical: button renders only when profile.can_edit && !editing; form only when editing && profile.can_edit; #420 bio hide-while-editing stays profile-view-only.
  • Unit test covering the navigate-on-click behavior from a non-profile tab (precedent: web/test/unit/owner-links-445.test.js).
# Edit profile from a non-profile tab should navigate to the profile view (follow-up to #442) ## What's requested #442's fix hoisted `ProfileForm` out of the profile-view gate so the Edit profile button now opens the form on every owner tab (`setEditing(true)` from the sidebar on /repositories and /organizations). But the click only flips the shared `editing` signal — it does not change the route. On the repositories or organizations tab, the form renders inline at the top of the repo list column while the tab bar still shows that tab as active, which reads as broken navigation rather than "editing my profile". Expected: clicking **Edit profile** routes to `/{owner}` (the profile view) with the **Profile** tab active and the form open. ## Evidence (current tree, static read — no local repro) - `web/src/pages/Repos.jsx` (~line 676): the sidebar button's handler is just `onClick={() => setEditing(true)}` — no navigation. - `web/src/pages/Repos.jsx` (~line 443): the shared-scope `<Show when={getEditing() && getProfile()?.can_edit}>` renders `ProfileForm` in the main column on all three owner views (the #442 fix). - `web/src/pages/Repos.jsx` `OwnerTabs` (~line 110): tab activation is purely pathname-derived — `/{owner}/organizations` → orgs, `/{owner}/repositories` → repos, else profile. So after the click, the tab highlight stays on the tab you were on, contradicting "editing profile". ## Architecture notes - `editing` already lives at the shared OwnerPage scope (`setEditing` / `getEditing`), so the form survives navigation to `/{owner}` with no new state — navigation alone is sufficient. - `OwnerTabs` already treats any non-repositories/organizations path as the active Profile tab, so navigating to `/{owner}` lights the Profile tab for free. - The component already imports Solid Router primitives (`useLocation`; `<A>` is used in `OwnerTabs`) — `useNavigate` is the natural seam. No API/wire changes; server untouched. ## Acceptance criteria - [ ] Clicking Edit profile on /{owner}/repositories navigates to /{owner} with the Profile tab active and the form open (editing state still true). - [ ] Clicking Edit profile on /{owner}/organizations behaves identically. - [ ] Clicking Edit profile on the profile view is unchanged (no navigation, form opens as today). - [ ] Existing gates byte-identical: button renders only when `profile.can_edit && !editing`; form only when `editing && profile.can_edit`; #420 bio hide-while-editing stays profile-view-only. - [ ] Unit test covering the navigate-on-click behavior from a non-profile tab (precedent: `web/test/unit/owner-links-445.test.js`).
crueber added this to the v1 milestone 2026-09-13 14:14:00 +00:00
Author
Owner

Fix ready for review: #472 (branch fix/issue-455). openEditor navigates to /{owner} from non-profile tabs with the module-scope edit state keeping the form open; profile-view click unchanged; gates byte-identical. Note: the issue's 'navigation alone is sufficient' assumption was wrong — sibling Route components remount OwnerPage, so the signal had to move to module scope (details in the PR). Tests: 977 pass / 0 fail, vite+esbuild green, smoke 3/3 vs scratch server.

Fix ready for review: #472 (branch fix/issue-455). openEditor navigates to /{owner} from non-profile tabs with the module-scope edit state keeping the form open; profile-view click unchanged; gates byte-identical. Note: the issue's 'navigation alone is sufficient' assumption was wrong — sibling Route components remount OwnerPage, so the signal had to move to module scope (details in the PR). Tests: 977 pass / 0 fail, vite+esbuild green, smoke 3/3 vs scratch server.
Author
Owner

REVIEW PR #472 (origin/fix/issue-455, 23e65ec) — Edit-profile-navigates fix for #455.

Verdict: READY TO MERGE (no fixes pushed; nothing blocking found).

Scope: 1 commit, 3 files, web-only — web/src/pages/Repos.jsx + owner-profile-edit-navigates-455.test.js (new) + owner-profile-edit-all-views-442.test.js (pins updated). No backend change (non-web diff empty), no new deps (useNavigate joins the existing @solidjs/router import; package.json untouched), no docker/compose changes.

(1) Remount claim VERIFIED TRUE. web/src/index.jsx:79,85,86 — /:owner, /:owner/repositories, /:owner/organizations are three sibling s with three distinct wrapper components (Repos/OwnerRepositories/OwnerOrganizations), each rendering . Tab navigation unmounts/remounts OwnerPage, so an OwnerPage-local editing signal would reset before the profile view renders. Module-scope signal is justified; alternatives (navigate-state, parent layout, query param) would all be more invasive. Precedent cited (Notifications unreadCount / lib/store theme) matches.

(2) Module-scope signal SOUND. Repos.jsx:318 — single createSignal(null) holding one owner slug or null: per-owner keyed (getEditing, Repos.jsx:382, strict === owner()), so no cross-owner leak; memory O(1), no growth. Cleared on save AND cancel — both funnel through onDone -> setEditing(false) (Repos.jsx:475-478; Cancel calls props.onDone(null), Repos.jsx:302). Deliberately NOT cleared on unmount — that is the load-bearing property. One known edge (observation only): open editor on owner A, navigate to owner B directly, return to A — the form is still open (stale-but-explicit state, arguably draft preservation). No fix proposed; clearing on unmount would defeat the remount survival.

(3) Profile-view click UNCHANGED. openEditor (Repos.jsx:389-392) always setEditing(true), navigates only if view() !== 'profile'. Test pins exactly one navigate-to-path call and no navigation to either list tab.

(4) Sub-tab clicks NAVIGATE + FORM OPEN + PROFILE ACTIVE. navigate(/${owner()}) lands on /{owner}; OwnerTabs (Repos.jsx:118-123) derives 'profile' for any non-repositories/organizations pathname, so the Profile tab lights for free. Form stays open via the module signal across the remount; ProfileForm remounts freshly seeded from props.doc — no stale seed.

(5) Gates BYTE-IDENTICAL. Changed Show-when lines in diff: none (grep empty). Only JSX logic change is onClick={() => setEditing(true)} -> onClick={openEditor} (Repos.jsx:704). Button gate, form gate, #420 bio hide-while-editing (profile-view-only, Repos.jsx:512) all untouched; getEditing/setEditing names preserved so every other call site works unchanged.

(6) #442 pin updates FAITHFUL. Updated test expects the module-scope signal above OwnerPage + per-owner accessors + single onClick={openEditor} — all strings match the shipped code exactly.

(7) AGENTS.md laws: (1) dep budget intact; (7) N/A — no long work; (8) change stays inside web/ page layer, no core-package imports; (12) code comments updated with the #455 rationale in the same commit.

VERIFY (scratch worktree /tmp/pr472, node_modules symlinked from main, removed afterward): targeted #455+#442 suites 13/13 pass; full node --test web/test/unit/*.test.js 975/977 — the 2 failures are both smoke.test.js fetch-based checks against http://127.0.0.1:8080, where an unrelated foreign listener answers /healthz 200 but / 401 (left untouched per no-live-instance rule; smoke tests read no repo files so the result is worktree-independent and pre-existing). vite build succeeds (2.17s; only the pre-existing >500kB chunk-size warning). No browser drive used — static JSX-pin tests + router-structure reasoning cover this change; noted explicitly per instructions.

REVIEW PR #472 (origin/fix/issue-455, 23e65ec) — Edit-profile-navigates fix for #455. Verdict: READY TO MERGE (no fixes pushed; nothing blocking found). Scope: 1 commit, 3 files, web-only — web/src/pages/Repos.jsx + owner-profile-edit-navigates-455.test.js (new) + owner-profile-edit-all-views-442.test.js (pins updated). No backend change (non-web diff empty), no new deps (useNavigate joins the existing @solidjs/router import; package.json untouched), no docker/compose changes. (1) Remount claim VERIFIED TRUE. web/src/index.jsx:79,85,86 — /:owner, /:owner/repositories, /:owner/organizations are three sibling <Route>s with three distinct wrapper components (Repos/OwnerRepositories/OwnerOrganizations), each rendering <OwnerPage view=.../>. Tab navigation unmounts/remounts OwnerPage, so an OwnerPage-local editing signal would reset before the profile view renders. Module-scope signal is justified; alternatives (navigate-state, parent layout, query param) would all be more invasive. Precedent cited (Notifications unreadCount / lib/store theme) matches. (2) Module-scope signal SOUND. Repos.jsx:318 — single createSignal(null) holding one owner slug or null: per-owner keyed (getEditing, Repos.jsx:382, strict === owner()), so no cross-owner leak; memory O(1), no growth. Cleared on save AND cancel — both funnel through onDone -> setEditing(false) (Repos.jsx:475-478; Cancel calls props.onDone(null), Repos.jsx:302). Deliberately NOT cleared on unmount — that is the load-bearing property. One known edge (observation only): open editor on owner A, navigate to owner B directly, return to A — the form is still open (stale-but-explicit state, arguably draft preservation). No fix proposed; clearing on unmount would defeat the remount survival. (3) Profile-view click UNCHANGED. openEditor (Repos.jsx:389-392) always setEditing(true), navigates only if view() !== 'profile'. Test pins exactly one navigate-to-path call and no navigation to either list tab. (4) Sub-tab clicks NAVIGATE + FORM OPEN + PROFILE ACTIVE. navigate(`/${owner()}`) lands on /{owner}; OwnerTabs (Repos.jsx:118-123) derives 'profile' for any non-repositories/organizations pathname, so the Profile tab lights for free. Form stays open via the module signal across the remount; ProfileForm remounts freshly seeded from props.doc — no stale seed. (5) Gates BYTE-IDENTICAL. Changed Show-when lines in diff: none (grep empty). Only JSX logic change is onClick={() => setEditing(true)} -> onClick={openEditor} (Repos.jsx:704). Button gate, form gate, #420 bio hide-while-editing (profile-view-only, Repos.jsx:512) all untouched; getEditing/setEditing names preserved so every other call site works unchanged. (6) #442 pin updates FAITHFUL. Updated test expects the module-scope signal above OwnerPage + per-owner accessors + single onClick={openEditor} — all strings match the shipped code exactly. (7) AGENTS.md laws: (1) dep budget intact; (7) N/A — no long work; (8) change stays inside web/ page layer, no core-package imports; (12) code comments updated with the #455 rationale in the same commit. VERIFY (scratch worktree /tmp/pr472, node_modules symlinked from main, removed afterward): targeted #455+#442 suites 13/13 pass; full node --test web/test/unit/*.test.js 975/977 — the 2 failures are both smoke.test.js fetch-based checks against http://127.0.0.1:8080, where an unrelated foreign listener answers /healthz 200 but / 401 (left untouched per no-live-instance rule; smoke tests read no repo files so the result is worktree-independent and pre-existing). vite build succeeds (2.17s; only the pre-existing >500kB chunk-size warning). No browser drive used — static JSX-pin tests + router-structure reasoning cover this change; noted explicitly per instructions.
Author
Owner

Fixed by PR #472 (review clean — remount claim verified, module signal sound, all gates byte-identical), merged. Closing.

Fixed by PR #472 (review clean — remount claim verified, module signal sound, all gates byte-identical), merged. Closing.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
crueber/walhub#455
No description provided.