Edit profile button does nothing from the repositories/organizations tabs (form renders only on the profile view) #442

Closed
opened 2026-09-13 13:25:13 +00:00 by crueber · 4 comments
Owner

What's happening

Clicking Edit profile from the repositories tab (/:owner/repositories) or the organizations tab (/:owner/organizations) does nothing: no navigation, no form. The button works only on the profile view (/:owner).

Root cause (static code read — no local repro)

The owner page was split into three tab views in #422/#430/#437 (web/src/pages/Repos.jsx, component OwnerPage), all sharing one sidebar shell. The Edit profile button toggles an in-component signal, not a route:

  • Repos.jsx:363 — const [getEditing, setEditing] = createSignal(false);
  • Repos.jsx:665 — <button ... onClick={() => setEditing(true)}>Edit profile</button>

But the edit form renders only inside the profile view's main column, gated on the view:

  • Repos.jsx:432 — <Show when={view() === "profile"}> wraps the whole main column, including Repos.jsx:470-479 <Show when={getEditing() && getProfile()?.can_edit}><ProfileForm …>.

So on the repositories/organizations tabs, setEditing(true) flips the signal, the button correctly disappears (Repos.jsx:664 gates on !getEditing()), but the ProfileForm it reveals is inside a <Show> that evaluates false for view() !== "profile" — the user sees the button vanish and nothing appear. There is no useNavigate/route-based edit URL at all (grep confirms none in the file), so nothing else can reveal the form.

Fix prescription

Edit profile is an in-place affordance, not a page — pick ONE of:

  1. Lift the editing state to the shared sidebar scope and open the form on every view (recommended, smallest change): hoist the <Show when={getEditing() && getProfile()?.can_edit}><ProfileForm …/></Show> block (Repos.jsx:470-479) out of the view() === "profile" gate so it renders in the main column on all three views (e.g. immediately above the per-view <Show> blocks, or inside each view's column header position). The setEditing signal at line 363 already lives at the shared OwnerPage scope, so no state plumbing is needed. Keep the bio hide-while-editing gate (#420) profile-view-only.
  2. Alternatively, make the button a route link to /{owner} that auto-opens editing (e.g. /?edit=1 consumed at mount) — but the in-place approach matches how #421/#437 composed the sidebar.

Acceptance criteria:

  • Clicking Edit profile on /:owner/repositories shows the profile edit form in the main column (and on /:owner/organizations).
  • Clicking Edit profile on /:owner behaves exactly as before (no regression; #420 bio hide-while-editing intact).
  • Save/Cancel (onDone → setEditing(false), invalidate profile:{owner} on save) works from all three views.
  • Button still disappears while editing on all views (line 664 gate) and reappears on cancel/save.
## What's happening Clicking **Edit profile** from the repositories tab (`/:owner/repositories`) or the organizations tab (`/:owner/organizations`) does nothing: no navigation, no form. The button works only on the profile view (`/:owner`). ## Root cause (static code read — no local repro) The owner page was split into three tab views in #422/#430/#437 (`web/src/pages/Repos.jsx`, component `OwnerPage`), all sharing one sidebar shell. The Edit profile button toggles an **in-component signal, not a route**: - `Repos.jsx:363` — `const [getEditing, setEditing] = createSignal(false);` - `Repos.jsx:665` — `<button ... onClick={() => setEditing(true)}>Edit profile</button>` But the edit form renders only inside the profile view's main column, gated on the view: - `Repos.jsx:432` — `<Show when={view() === "profile"}>` wraps the whole main column, including `Repos.jsx:470-479` `<Show when={getEditing() && getProfile()?.can_edit}><ProfileForm …>`. So on the repositories/organizations tabs, `setEditing(true)` flips the signal, the button correctly disappears (`Repos.jsx:664` gates on `!getEditing()`), but the `ProfileForm` it reveals is inside a `<Show>` that evaluates false for `view() !== "profile"` — the user sees the button vanish and nothing appear. There is no `useNavigate`/route-based edit URL at all (grep confirms none in the file), so nothing else can reveal the form. ## Fix prescription Edit profile is an in-place affordance, not a page — pick ONE of: 1. **Lift the editing state to the shared sidebar scope and open the form on every view** (recommended, smallest change): hoist the `<Show when={getEditing() && getProfile()?.can_edit}><ProfileForm …/></Show>` block (Repos.jsx:470-479) out of the `view() === "profile"` gate so it renders in the main column on all three views (e.g. immediately above the per-view `<Show>` blocks, or inside each view's column header position). The `setEditing` signal at line 363 already lives at the shared `OwnerPage` scope, so no state plumbing is needed. Keep the bio hide-while-editing gate (#420) profile-view-only. 2. Alternatively, make the button a route link to `/{owner}` that auto-opens editing (e.g. `/?edit=1` consumed at mount) — but the in-place approach matches how #421/#437 composed the sidebar. Acceptance criteria: - [ ] Clicking Edit profile on `/:owner/repositories` shows the profile edit form in the main column (and on `/:owner/organizations`). - [ ] Clicking Edit profile on `/:owner` behaves exactly as before (no regression; #420 bio hide-while-editing intact). - [ ] Save/Cancel (`onDone` → `setEditing(false)`, invalidate `profile:{owner}` on save) works from all three views. - [ ] Button still disappears while editing on all views (line 664 gate) and reappears on cancel/save.
crueber added this to the v1 milestone 2026-09-13 13:25:48 +00:00
Author
Owner

Fixed by #448 (prescribed option 1): ProfileForm Show hoisted out of the view()==='profile' gate into the main column above the per-view Shows — Edit profile now opens on all three owner views. Gate/onDone byte-identical, #420 intact, no backend change. node --test 942/944 (2 pre-existing live-server smoke failures, identical on pristine main); vite + esbuild builds green.

Fixed by #448 (prescribed option 1): ProfileForm Show hoisted out of the view()==='profile' gate into the main column above the per-view Shows — Edit profile now opens on all three owner views. Gate/onDone byte-identical, #420 intact, no backend change. node --test 942/944 (2 pre-existing live-server smoke failures, identical on pristine main); vite + esbuild builds green.
Author
Owner

Review of PR #448 (origin/fix/issue-442, 182946b) — verified in scratch worktree /tmp/pr448 (node_modules symlinked from main), worktree removed afterward; main worktree left untouched (only pre-existing untracked .opencode/).

Law check (AGENTS.md 1/7/8/12): no new deps (web/package.json, pnpm files, go.mod untouched; runtime set still solid-js + @solidjs/router + marked + dompurify), backend untouched (5 files, all web/src + web/test), no long-work/task surface (L7 n/a), no registry/seam changes (L8 n/a), no doc contract describes the form position so no doc update owed (L12 n/a — header comment updated in-code).

(1) Single hoisted form: exactly one <ProfileForm USE in Repos.jsx (line 444; line 166 is the component definition). No per-view copies — profile/repos/orgs branches contain no ProfileForm. PASS.
(2) Position: the <Show when={getEditing() && getProfile()?.can_edit}> block sits directly inside <div class="profile-main...> (line 435), above all three per-view Shows (profile/repos/orgs) and before <div class="profile-sidebar-col...>. Main column on all views. PASS.
(3) Gate + onDone byte-identical: diff +/- line-set shows each JSX/handler line removed once and added once verbatim (only comments + indentation changed). onDone still setEditing(false) + invalidate(profile:${owner()}) on save; save still via repos.owners.updateProfile. PASS.
(4) #420 bio gate still profile-only: <Show when={profile().bio_markdown && !getEditing()}> stays inside the view()==="profile" branch; no !getEditing() leaks into the repos/orgs branches. PASS.
(5) Button hide/show: sidebar gate <Show when={getProfile()?.can_edit && !getEditing()}> untouched; exactly one onClick={() => setEditing(true)} opener in the shared sidebar scope, so it hides while editing and reappears on cancel/save on all views. PASS.
(6) #419 autogrow intact: ProfileForm definition body untouched by the diff (no autogrow/textarea-line changes); import initAutogrow/growTextarea (line 48) and textarea wiring (lines 175/268) intact. PASS.
(7) Pin updates faithful: profile-header(-403).test.js + profile-sidebar-421.test.js re-scope from the removed "\n <Show when={getEditing()" anchor to the header div's own "" — safe because the only inner div (markdown-body) is self-closing, so first is the header close; absence-pins stay valid, h1 presence-pins unaffected. sidebar-421 now asserts the form is NOT in the profile branch + IS in main above the gates (correct inversion for the hoist). PASS.
(8) No backend change; no new deps (confirmed via diff on go./web/package/pnpm* — empty). PASS.

Tests (scratch worktree): targeted files (owner-profile-edit-all-views-442, profile-header-403, profile-header, profile-sidebar-421, profile-bio-editing) — 36 pass, 0 fail. Full suite node --test web/test/unit/*.test.js — 944 tests, 942 pass, 2 fail; both failures are smoke.test.js live-server fetches against :8080, which fail identically on main (a live instance there answers 401 / non-SPA shell) — environmental, not a PR regression. vite build succeeds in 3.00s (only the usual >500kB chunk-size warning).

No fixes needed — nothing pushed. No browser used (node tests + source reasoning only, per instructions).

MERGE RECOMMENDATION: ready to merge.

Review of PR #448 (origin/fix/issue-442, 182946b) — verified in scratch worktree /tmp/pr448 (node_modules symlinked from main), worktree removed afterward; main worktree left untouched (only pre-existing untracked .opencode/). Law check (AGENTS.md 1/7/8/12): no new deps (web/package.json, pnpm files, go.mod untouched; runtime set still solid-js + @solidjs/router + marked + dompurify), backend untouched (5 files, all web/src + web/test), no long-work/task surface (L7 n/a), no registry/seam changes (L8 n/a), no doc contract describes the form position so no doc update owed (L12 n/a — header comment updated in-code). (1) Single hoisted form: exactly one <ProfileForm USE in Repos.jsx (line 444; line 166 is the component definition). No per-view copies — profile/repos/orgs branches contain no ProfileForm. PASS. (2) Position: the <Show when={getEditing() && getProfile()?.can_edit}> block sits directly inside <div class="profile-main...> (line 435), above all three per-view Shows (profile/repos/orgs) and before <div class="profile-sidebar-col...>. Main column on all views. PASS. (3) Gate + onDone byte-identical: diff +/- line-set shows each JSX/handler line removed once and added once verbatim (only comments + indentation changed). onDone still setEditing(false) + invalidate(`profile:${owner()}`) on save; save still via repos.owners.updateProfile. PASS. (4) #420 bio gate still profile-only: <Show when={profile().bio_markdown && !getEditing()}> stays inside the view()==="profile" branch; no !getEditing() leaks into the repos/orgs branches. PASS. (5) Button hide/show: sidebar gate <Show when={getProfile()?.can_edit && !getEditing()}> untouched; exactly one onClick={() => setEditing(true)} opener in the shared sidebar scope, so it hides while editing and reappears on cancel/save on all views. PASS. (6) #419 autogrow intact: ProfileForm definition body untouched by the diff (no autogrow/textarea-line changes); import initAutogrow/growTextarea (line 48) and textarea wiring (lines 175/268) intact. PASS. (7) Pin updates faithful: profile-header(-403).test.js + profile-sidebar-421.test.js re-scope from the removed "</div>\\n <Show when={getEditing()" anchor to the header div's own "</div>" — safe because the only inner div (markdown-body) is self-closing, so first </div> is the header close; absence-pins stay valid, h1 presence-pins unaffected. sidebar-421 now asserts the form is NOT in the profile branch + IS in main above the gates (correct inversion for the hoist). PASS. (8) No backend change; no new deps (confirmed via diff on go.*/web/package*/pnpm* — empty). PASS. Tests (scratch worktree): targeted files (owner-profile-edit-all-views-442, profile-header-403, profile-header, profile-sidebar-421, profile-bio-editing) — 36 pass, 0 fail. Full suite node --test web/test/unit/*.test.js — 944 tests, 942 pass, 2 fail; both failures are smoke.test.js live-server fetches against :8080, which fail identically on main (a live instance there answers 401 / non-SPA shell) — environmental, not a PR regression. vite build succeeds in 3.00s (only the usual >500kB chunk-size warning). No fixes needed — nothing pushed. No browser used (node tests + source reasoning only, per instructions). MERGE RECOMMENDATION: ready to merge.
Author
Owner

Fixed by PR #448 (review clean — all 8 checks pass, single hoisted form, #420/#419 intact), merged. Closing.

Fixed by PR #448 (review clean — all 8 checks pass, single hoisted form, #420/#419 intact), merged. Closing.
Author
Owner

Follow-up filed: #455 — the fix opens the form on every tab but does not navigate to the profile view.

Follow-up filed: #455 — the fix opens the form on every tab but does not navigate to the profile view.
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#442
No description provided.