Move New repository button from profile header into the Repositories section #413

Closed
opened 2026-09-12 20:42:30 +00:00 by crueber · 3 comments
Owner

Move the "New repository" button out of the profile header into the Repositories section

What's requested

On the owner profile page (/:owner, web/src/pages/Repos.jsx), the New repository button currently renders inside the profile header's action row — grouped with "Edit profile" under the bio (user profile, lines ~326-337) and in the org title row (orgs, lines ~404-408). Move it into the Repositories section, so the header carries only identity/profile actions and the primary create CTA sits with the list it populates.

Evidence (current tree, 0b0e9e6)

  • web/src/pages/Repos.jsx:326-337 — user profile action row: <Show when={canWrite()}><A class="btn primary px-3 py-1" href="/new?owner=…">New repository</A></Show> sits beside "Edit profile" under the bio. The header closes with a bottom divider (pb-6 border-b), then the Repositories section follows far below.
  • web/src/pages/Repos.jsx:394-409 — org variant: same CTA in the org title row (justify-between with the <h2>), again above the org doc fields and unrelated to the list placement.
  • web/src/pages/Repos.jsx:436-458 — the Repositories section is a bare <h3>Repositories</h3> with a count line and the two-column <RepoRow> grid; it has no toolbar row and no CTA.

Reference implementation (design language)

Follow the repo's toolbar pattern: a section heading row with the primary CTA right-anchored and the count/metadata beside the heading — the same flex items-center justify-between title-row shape the org header already uses (Repos.jsx:394), with flex-wrap gap-2 for mobile. The heading stays text-base font-semibold; the CTA keeps btn primary px-3 py-1 and its /new?owner=… href with encodeURIComponent.

Architecture notes

  • Layout only — all gating unchanged: New button still gated on canWrite() (mirrors require_write; #395 follow-up rule that the button never promises what POST /api/v1/repos refuses); Edit profile still gated on server can_edit; avatar actions stay self-only (#376). No data-fetch changes; me/profile/org caches untouched.
  • Both variants (user profile and org) need the move — the CTA should leave the org title row too and land in the same Repositories toolbar so the section is the single surface owning repo creation.
  • The header divider (pb-6 border-b, #403) stays; with the CTA gone the user-profile action row collapses to just "Edit profile" — keep the row (or drop the wrapper if the Show reduces to a single button; implementer's call, note it).
  • Mobile: toolbar wraps (flex-wrap), CTA full-width or wrapped per #273-#278 conventions.
  • The import into {owner()} link (line ~460) may belong with the same toolbar as a secondary action if the implementer prefers; not required.

Acceptance criteria

  • "New repository" no longer renders in the profile header action row (user variant) nor the org title row (org variant)
  • It renders once, in the Repositories section, right-anchored on the heading row, gated on canWrite() and linking to /new?owner=<owner> (pre-filled owner preserved)
  • "Edit profile" (user) and "Manage organization" (org) gating/behavior unchanged
  • Mobile 390px: toolbar wraps without horizontal overflow (page scrollWidth === clientWidth)
  • No changes to data fetching, cache keys, or gating logic — layout/markup only
# Move the "New repository" button out of the profile header into the Repositories section ## What's requested On the owner profile page (`/:owner`, `web/src/pages/Repos.jsx`), the **New repository** button currently renders inside the profile header's action row — grouped with "Edit profile" under the bio (user profile, lines ~326-337) and in the org title row (orgs, lines ~404-408). Move it into the **Repositories** section, so the header carries only identity/profile actions and the primary create CTA sits with the list it populates. ## Evidence (current tree, 0b0e9e6) - `web/src/pages/Repos.jsx:326-337` — user profile action row: `<Show when={canWrite()}><A class="btn primary px-3 py-1" href="/new?owner=…">New repository</A></Show>` sits beside "Edit profile" under the bio. The header closes with a bottom divider (`pb-6 border-b`), then the Repositories section follows far below. - `web/src/pages/Repos.jsx:394-409` — org variant: same CTA in the org title row (`justify-between` with the `<h2>`), again above the org doc fields and unrelated to the list placement. - `web/src/pages/Repos.jsx:436-458` — the Repositories section is a bare `<h3>Repositories</h3>` with a count line and the two-column `<RepoRow>` grid; it has no toolbar row and no CTA. ## Reference implementation (design language) Follow the repo's toolbar pattern: a section heading row with the primary CTA right-anchored and the count/metadata beside the heading — the same `flex items-center justify-between` title-row shape the org header already uses (Repos.jsx:394), with `flex-wrap gap-2` for mobile. The heading stays `text-base font-semibold`; the CTA keeps `btn primary px-3 py-1` and its `/new?owner=…` href with `encodeURIComponent`. ## Architecture notes - **Layout only — all gating unchanged**: New button still gated on `canWrite()` (mirrors `require_write`; #395 follow-up rule that the button never promises what POST /api/v1/repos refuses); Edit profile still gated on server `can_edit`; avatar actions stay self-only (#376). No data-fetch changes; `me`/profile/org caches untouched. - Both variants (user profile and org) need the move — the CTA should leave the org title row too and land in the same Repositories toolbar so the section is the single surface owning repo creation. - The header divider (`pb-6 border-b`, #403) stays; with the CTA gone the user-profile action row collapses to just "Edit profile" — keep the row (or drop the wrapper if the Show reduces to a single button; implementer's call, note it). - Mobile: toolbar wraps (`flex-wrap`), CTA full-width or wrapped per #273-#278 conventions. - The `import into {owner()}` link (line ~460) may belong with the same toolbar as a secondary action if the implementer prefers; not required. ## Acceptance criteria - [ ] "New repository" no longer renders in the profile header action row (user variant) nor the org title row (org variant) - [ ] It renders once, in the Repositories section, right-anchored on the heading row, gated on `canWrite()` and linking to `/new?owner=<owner>` (pre-filled owner preserved) - [ ] "Edit profile" (user) and "Manage organization" (org) gating/behavior unchanged - [ ] Mobile 390px: toolbar wraps without horizontal overflow (page `scrollWidth === clientWidth`) - [ ] No changes to data fetching, cache keys, or gating logic — layout/markup only
crueber added this to the v1 milestone 2026-09-12 20:43:30 +00:00
Author
Owner

Fixed by PR #414 (#414): New repository CTA moves from both header spots into a shared Repositories toolbar — layout only, all gates/fetch/cache unchanged, D-WEB-11 recorded.

Fixed by PR #414 (https://git.packden.us/crueber/walhub/pulls/414): New repository CTA moves from both header spots into a shared Repositories toolbar — layout only, all gates/fetch/cache unchanged, D-WEB-11 recorded.
Author
Owner

Review of PR #414 (fix/issue-413, commit 50e311a) — verified in scratch worktree /tmp/pr414 (removed afterward); main worktree left untouched (still clean on main, only pre-existing untracked .opencode/).

Acceptance criteria (all 5 check out):

  1. CTA gone from both header spots — CONFIRMED. The user action-row Show(canWrite) block (old Repos.jsx:326-337) and the org title-row Show(canWrite) block (old :404-408) are both deleted; grep finds no '/new?owner' inside either header block.
  2. Renders ONCE in Repositories toolbar — CONFIRMED. Repos.jsx:432-439: single 'repos-toolbar' div ('mb-2 mt-6 flex flex-wrap items-center justify-between gap-2'), h3 first ('text-base font-semibold', text unchanged) then CTA — right-anchored via justify-between. Exactly one 'New repository' string and one '/new?owner' href in Repos.jsx (other hits are different pages: Owners.jsx:147 nav link, New.jsx form — pre-existing, out of scope).
  3. Gate + href preserved — CONFIRMED. Toolbar CTA keeps '' and 'href={/new?owner=${encodeURIComponent(owner())}}' byte-identical; styling 'btn primary px-3 py-1' unchanged.
  4. Edit profile / Manage org untouched — CONFIRMED. Gates 'getProfile()?.can_edit && !getEditing()' and 'canManage()' byte-identical in diff; avatar self-only gates kept. User row keeps its 'mt-3 flex flex-wrap gap-2' wrapper with just Edit profile (implementer's call, as the issue allowed).
  5. Divider intact; import link untouched — CONFIRMED. profile-header keeps 'pb-6 border-b border-zinc-200 dark:border-zinc-700'; 'import into {owner()}' + '/import?owner=' href unchanged.
  6. 390px wrap (reasoning only, no browser per task scope) — SOUND. Toolbar carries flex-wrap + gap-2 per #273-#278; children are a short h3 and a small CTA with no fixed widths/nowrap, so the row stacks instead of overflowing. Note: no live-browser check run (explicitly out of scope for this review).
  7. #395/#403 pin rewrites faithful, not weakened — CONFIRMED. profile-header.test.js, profile-header-403.test.js, identity-nav.test.js now assert absence-from-header + presence-in-toolbar + the single-CTA count ('exactly one New-repository link', strengthened from two); gate/fetch/cache pins byte-identical. New repos-toolbar-413.test.js (7 tests) covers all five acceptance criteria including the no-fetch/cache/gating-change pin.
  8. D-WEB-11 warranted; laws clean — AGENTS.md L1 (no package.json/go.mod change, zero new deps), L8 (web-only, no seam/backend impact), L12 (decision appended in same change). No backend change anywhere in the diff.

Tests (scratch worktree, node_modules symlinked from main):

  • Targeted: node --test repos-toolbar-413 + profile-header + profile-header-403 + identity-nav → 53/53 pass.
  • Full: node --test web/test/unit/*.test.js → 823 pass (main baseline 816 + 7 new), same 2 pre-existing smoke.test.js failures on main AND branch (they require a live server at WALHUB_TEST_WEB_BASE_URL/:8080; something on :8080 here answers 401 — environmental, untouched per instructions).
  • vite build + esbuild SDK bundle in scratch worktree → both succeed (chunk-size warning only, pre-existing).

Nits (non-blocking, not fixed to avoid churn): Repos.jsx:391 org '

' open tag sits at 6-space indent vs 8-space '

' at :399 — cosmetic, matches the file's loose JSX indentation; no formatter covers web/.

No fixes pushed — nothing functional to fix. MERGE RECOMMENDATION: ready to merge.

Review of PR #414 (fix/issue-413, commit 50e311a) — verified in scratch worktree /tmp/pr414 (removed afterward); main worktree left untouched (still clean on main, only pre-existing untracked .opencode/). Acceptance criteria (all 5 check out): 1. CTA gone from both header spots — CONFIRMED. The user action-row Show(canWrite) block (old Repos.jsx:326-337) and the org title-row Show(canWrite) block (old :404-408) are both deleted; grep finds no '/new?owner' inside either header block. 2. Renders ONCE in Repositories toolbar — CONFIRMED. Repos.jsx:432-439: single 'repos-toolbar' div ('mb-2 mt-6 flex flex-wrap items-center justify-between gap-2'), h3 first ('text-base font-semibold', text unchanged) then CTA — right-anchored via justify-between. Exactly one 'New repository' string and one '/new?owner' href in Repos.jsx (other hits are different pages: Owners.jsx:147 nav link, New.jsx form — pre-existing, out of scope). 3. Gate + href preserved — CONFIRMED. Toolbar CTA keeps '<Show when={canWrite()}>' and 'href={/new?owner=${encodeURIComponent(owner())}}' byte-identical; styling 'btn primary px-3 py-1' unchanged. 4. Edit profile / Manage org untouched — CONFIRMED. Gates 'getProfile()?.can_edit \&\& !getEditing()' and 'canManage()' byte-identical in diff; avatar self-only gates kept. User row keeps its 'mt-3 flex flex-wrap gap-2' wrapper with just Edit profile (implementer's call, as the issue allowed). 5. Divider intact; import link untouched — CONFIRMED. profile-header keeps 'pb-6 border-b border-zinc-200 dark:border-zinc-700'; 'import into {owner()}' + '/import?owner=' href unchanged. 6. 390px wrap (reasoning only, no browser per task scope) — SOUND. Toolbar carries flex-wrap + gap-2 per #273-#278; children are a short h3 and a small CTA with no fixed widths/nowrap, so the row stacks instead of overflowing. Note: no live-browser check run (explicitly out of scope for this review). 7. #395/#403 pin rewrites faithful, not weakened — CONFIRMED. profile-header.test.js, profile-header-403.test.js, identity-nav.test.js now assert absence-from-header + presence-in-toolbar + the single-CTA count ('exactly one New-repository link', strengthened from two); gate/fetch/cache pins byte-identical. New repos-toolbar-413.test.js (7 tests) covers all five acceptance criteria including the no-fetch/cache/gating-change pin. 8. D-WEB-11 warranted; laws clean — AGENTS.md L1 (no package.json/go.mod change, zero new deps), L8 (web-only, no seam/backend impact), L12 (decision appended in same change). No backend change anywhere in the diff. Tests (scratch worktree, node_modules symlinked from main): - Targeted: node --test repos-toolbar-413 + profile-header + profile-header-403 + identity-nav → 53/53 pass. - Full: node --test web/test/unit/*.test.js → 823 pass (main baseline 816 + 7 new), same 2 pre-existing smoke.test.js failures on main AND branch (they require a live server at WALHUB_TEST_WEB_BASE_URL/:8080; something on :8080 here answers 401 — environmental, untouched per instructions). - vite build + esbuild SDK bundle in scratch worktree → both succeed (chunk-size warning only, pre-existing). Nits (non-blocking, not fixed to avoid churn): Repos.jsx:391 org '<h2>' open tag sits at 6-space indent vs 8-space '</h2>' at :399 — cosmetic, matches the file's loose JSX indentation; no formatter covers web/. No fixes pushed — nothing functional to fix. MERGE RECOMMENDATION: ready to merge.
Author
Owner

Fixed by PR #414 (review clean — all 8 checks pass, single instance, gates byte-identical), merged. Closing.

Fixed by PR #414 (review clean — all 8 checks pass, single instance, 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#413
No description provided.