Standardize the standalone create-form pages (New repository / Import / New organization) on the canonical form-page pattern #479

Closed
opened 2026-09-13 19:10:15 +00:00 by crueber · 3 comments
Owner

Standardize the standalone create-form pages (New repository / Import / New organization) on the canonical form-page pattern

What's requested

Standardize the three standalone create-form pages — New repository (/new), Import repository (/import), and New organization (/orgs/new) — on the canonical form-page pattern the repo already established for composer forms (issue #49, IssueNew.jsx / ReleaseNew.jsx), then adopt that pattern as the standing rule for all future form pages.

Canonical form-page pattern (defined from the current tree)

Derived from ReleaseNew.jsx (header comment cites the #49 convention) and IssueNew.jsx:

  1. Layout: page content in a single centered column — mx-auto max-w-2xl (ReleaseNew line 139) — one h2 page heading, one short muted intro line.
  2. Form shell: a single <form class="card grid gap-3 p-4"> with aria-label.
  3. Fields: each field is label.grid.gap-1 → span.text-sm.font-medium label → .input control (.font-mono for identifiers/URLs) → optional help line.
  4. Help text: bounded and purposeful — one line per field max, wired via id + aria-describedby (ReleaseNew release-tag-help / release-title-help / release-notes-help pattern); prose paragraphs that restate the submit outcome or repeat a sibling page's copy are not field help.
  5. Multi-column rows: grid grid-cols-1 gap-3 sm:grid-cols-2 (the Fork.jsx idiom, lines 191/228) so the pair stacks on phones.
  6. Errors: inline validation + inline submit-error card (red rounded border block); amber pre-flight warnings for auth state; never the error tray on expected outcomes.
  7. Buttons: one primary btn primary px-3 py-1 with busy label swap; optional secondary btn action beside it (OrgNew's cancel).

Divergences found (evidence)

  • Grid not mobile-responsive — New.jsx:175 and Import.jsx:244 use grid grid-cols-2 gap-3 for the Owner/Name pair: fixed two columns with no sm: breakpoint, so owner-select and name input squeeze side-by-side at phone widths. Fork.jsx:191,228 already uses the correct grid-cols-1 sm:grid-cols-2.
  • Helper-text noise —
    • New.jsx:195 / Import.jsx:264: "you and your orgs only" under every owner select — restates the bounded owner list the select itself already shows; drop it.
    • New.jsx:229-232 and Import.jsx:324-327: the identical "Mirrors are pull-only: pushes are rejected…" paragraph is pasted into both pages — duplicate copy for the same feature; keep it once, and only where it carries decision-relevant detail.
    • Import.jsx:329-332: standing LFS/ssh limitation prose rendered on every visit regardless of relevance — fold into the token field's help line or a <details>.
    • None of these help lines carry id/aria-describedby (compare ReleaseNew's fieldset pattern).
  • Inconsistent button rows — OrgNew.jsx:126-137 has a cancel action; New.jsx:279-287 and Import.jsx:359-363 do not. Pick one: either every standalone create form gets a cancel affordance (OrgNew's btn linking to the natural back surface), or none do.

Acceptance criteria

  • New.jsx and Import.jsx Owner/Name rows use grid-cols-1 sm:grid-cols-2 (or the canonical row class); page pans nowhere at 390px width.
  • "you and your orgs only" helper removed from both owner selects.
  • Mirror pull-only copy exists once (not duplicated across New.jsx and Import.jsx); Import's LFS/ssh paragraph is either tied to a field via aria-describedby or collapsed under a <details>.
  • Help text on all three pages is field-scoped with id + aria-describedby following the ReleaseNew pattern.
  • Button-row treatment (cancel present/absent) is identical across New repository, Import repository, and New organization — implementer's call which way, note the choice.
  • No behavior change: submit targets, validation, inline error handling, and navigation stay as-is (visual/structure only).
  • web/test/unit/create-menu-466.test.js and related unit tests still pass in CI.

Standing rule for future form pages

Adopt the pattern above as the convention for every new form page (the #49 composer convention extended to standalone create/edit forms):

  • centered max-w-2xl column; one heading, one intro line;
  • single card grid gap-3 p-4 form; label.grid.gap-1 field composition;
  • multi-column rows always collapse (grid-cols-1 sm:grid-cols-2) — never a bare grid-cols-2;
  • help text is per-field, aria-describedby-wired, and only when it changes what the user does;
  • inline errors/warnings, single primary button with busy state, consistent cancel treatment.

Suggested placement: record this in the file-header convention of the next form page and reference ReleaseNew.jsx as the reference implementation, so reviewers can diff new forms against a canonical sibling.

Reference implementation: web/src/pages/ReleaseNew.jsx (fieldset + help-text composition), web/src/pages/Fork.jsx (responsive grid idiom).

# Standardize the standalone create-form pages (New repository / Import / New organization) on the canonical form-page pattern ## What's requested Standardize the three standalone create-form pages — **New repository** (`/new`), **Import repository** (`/import`), and **New organization** (`/orgs/new`) — on the canonical form-page pattern the repo already established for composer forms (issue #49, `IssueNew.jsx` / `ReleaseNew.jsx`), then adopt that pattern as the standing rule for all future form pages. ## Canonical form-page pattern (defined from the current tree) Derived from `ReleaseNew.jsx` (header comment cites the #49 convention) and `IssueNew.jsx`: 1. **Layout**: page content in a single centered column — `mx-auto max-w-2xl` (ReleaseNew line 139) — one `h2` page heading, one short muted intro line. 2. **Form shell**: a single `<form class="card grid gap-3 p-4">` with `aria-label`. 3. **Fields**: each field is `label.grid.gap-1` → `span.text-sm.font-medium` label → `.input` control (`.font-mono` for identifiers/URLs) → optional help line. 4. **Help text**: bounded and purposeful — one line per field max, wired via `id` + `aria-describedby` (ReleaseNew `release-tag-help` / `release-title-help` / `release-notes-help` pattern); prose paragraphs that restate the submit outcome or repeat a sibling page's copy are not field help. 5. **Multi-column rows**: `grid grid-cols-1 gap-3 sm:grid-cols-2` (the `Fork.jsx` idiom, lines 191/228) so the pair stacks on phones. 6. **Errors**: inline validation + inline submit-error card (red rounded border block); amber pre-flight warnings for auth state; never the error tray on expected outcomes. 7. **Buttons**: one primary `btn primary px-3 py-1` with busy label swap; optional secondary `btn` action beside it (OrgNew's cancel). ## Divergences found (evidence) - **Grid not mobile-responsive** — `New.jsx:175` and `Import.jsx:244` use `grid grid-cols-2 gap-3` for the Owner/Name pair: fixed two columns with no `sm:` breakpoint, so owner-select and name input squeeze side-by-side at phone widths. `Fork.jsx:191,228` already uses the correct `grid-cols-1 sm:grid-cols-2`. - **Helper-text noise** — - `New.jsx:195` / `Import.jsx:264`: "you and your orgs only" under every owner select — restates the bounded owner list the select itself already shows; drop it. - `New.jsx:229-232` and `Import.jsx:324-327`: the identical "Mirrors are pull-only: pushes are rejected…" paragraph is pasted into both pages — duplicate copy for the same feature; keep it once, and only where it carries decision-relevant detail. - `Import.jsx:329-332`: standing LFS/ssh limitation prose rendered on every visit regardless of relevance — fold into the token field's help line or a `<details>`. - None of these help lines carry `id`/`aria-describedby` (compare ReleaseNew's fieldset pattern). - **Inconsistent button rows** — `OrgNew.jsx:126-137` has a cancel action; `New.jsx:279-287` and `Import.jsx:359-363` do not. Pick one: either every standalone create form gets a cancel affordance (OrgNew's `btn` linking to the natural back surface), or none do. ## Acceptance criteria - [ ] `New.jsx` and `Import.jsx` Owner/Name rows use `grid-cols-1 sm:grid-cols-2` (or the canonical row class); page pans nowhere at 390px width. - [ ] "you and your orgs only" helper removed from both owner selects. - [ ] Mirror pull-only copy exists once (not duplicated across New.jsx and Import.jsx); Import's LFS/ssh paragraph is either tied to a field via `aria-describedby` or collapsed under a `<details>`. - [ ] Help text on all three pages is field-scoped with `id` + `aria-describedby` following the ReleaseNew pattern. - [ ] Button-row treatment (cancel present/absent) is identical across New repository, Import repository, and New organization — implementer's call which way, note the choice. - [ ] No behavior change: submit targets, validation, inline error handling, and navigation stay as-is (visual/structure only). - [ ] `web/test/unit/create-menu-466.test.js` and related unit tests still pass in CI. ## Standing rule for future form pages Adopt the pattern above as the convention for **every new form page** (the #49 composer convention extended to standalone create/edit forms): - centered `max-w-2xl` column; one heading, one intro line; - single `card grid gap-3 p-4` form; `label.grid.gap-1` field composition; - multi-column rows always collapse (`grid-cols-1 sm:grid-cols-2`) — never a bare `grid-cols-2`; - help text is per-field, `aria-describedby`-wired, and only when it changes what the user does; - inline errors/warnings, single primary button with busy state, consistent cancel treatment. Suggested placement: record this in the file-header convention of the next form page and reference `ReleaseNew.jsx` as the reference implementation, so reviewers can diff new forms against a canonical sibling. Reference implementation: `web/src/pages/ReleaseNew.jsx` (fieldset + help-text composition), `web/src/pages/Fork.jsx` (responsive grid idiom).
crueber added this to the v1 milestone 2026-09-13 19:10:38 +00:00
Author
Owner

Fix is up: #480 (branch fix/issue-479, no conflicts). All 7 acceptance criteria covered — visual/structure only, no behavior change. Cancel direction: every-form-gets-cancel (New/Import gain OrgNew's exact secondary btn to /explore). Tests: new create-forms-479 9/9, related 92/92, full suite 1027/1029 (2 pre-existing live-server smoke failures, identical on main); vite + esbuild green. Browser proof open (shared-daemon loopback guard). Do NOT merge yet.

Fix is up: https://git.packden.us/crueber/walhub/pulls/480 (branch fix/issue-479, no conflicts). All 7 acceptance criteria covered — visual/structure only, no behavior change. Cancel direction: every-form-gets-cancel (New/Import gain OrgNew's exact secondary btn to /explore). Tests: new create-forms-479 9/9, related 92/92, full suite 1027/1029 (2 pre-existing live-server smoke failures, identical on main); vite + esbuild green. Browser proof open (shared-daemon loopback guard). Do NOT merge yet.
Author
Owner

REVIEW PR #480 (fix/issue-479) — verified in scratch worktree /tmp/pr480 at 6ab11e1, node tests + reasoning, no browser per instructions.

AGENTS.md laws: (1) deps untouched — package.json/lock absent from diff, runtime set unchanged; (7) N/A, no async work; (8) no new routes/providers, existing A/router only; (12) decision appended to docs/go/12_web_ui.md in the same change, headers cite ReleaseNew.jsx. Main worktree untouched (read-only; still clean).

Acceptance criteria, all 7 PASS:

  1. Grids collapse — New.jsx:180, Import.jsx:256 both 'grid grid-cols-1 gap-3 sm:grid-cols-2'; zero bare 'grid-cols-2' in all three pages (only comments + sm: variant); mx-auto wrappers present New.jsx:163, Import.jsx:215, OrgNew.jsx:74.
  2. Helpers dropped — grep 'you and your orgs only' clean under web/src/.
  3. Mirror copy once — kept on New.jsx:239 ('pushes are rejected' total count across both pages = 1), wired via new-mirror-help to the schedule select (New.jsx:234); Import radio (Import.jsx:228) already reads '(recurring pull, pushes rejected)'. Correct call.
  4. LFS/ssh collapsed — Import.jsx:341-347
    , copy moved verbatim (pointer blobs + server-side ssh), no always-rendered prose left.
  5. aria wiring — pairs present: new-name-help, new-mirror-help, import-source-help, import-name-help, import-token-help, orgnew-name-help; copy matches enforced rules (sdk/src/create.js charset, lib/orgs.js rule text, token never-stored promise); label for= matches ReleaseNew convention. ADVISORY (non-blocking): import-source-help (Import.jsx:246) renders only inside the suggestion Show while aria-describedby (Import.jsx:243) is unconditional — dangling ref on empty form. Benign (AT ignores missing ids) but diverges from ReleaseNew's always-rendered help; consider always-render or conditional describedby in a follow-up.
  6. Cancel identical — byte-identical 'cancel' on all three (New.jsx:296-298, Import.jsx:378-380, OrgNew.jsx:143-145); every-form-gets-cancel direction noted in test + headers.
  7. No behavior change — line-level diff of all three pages shows only markup/attributes/copy moves; no onSubmit/submit/validate/navigate/fetch line touched; test pins repos.repos.create, repos.mirrors.create, repos.imports.start, repos.orgs.create, both navigates, validateRepoName/validateOrgName.
  • Standing rule recorded (headers + 12_web_ui.md); no backend change; docs accurate. NOTE: branch's 3 older README commits are already on origin/main (7cfecee restructures further) — branch-unique diff vs origin/main is exactly the 5 scoped files, no action needed.

Tests (scratch, node_modules symlinked from main): new create-forms-479.test.js 9/9 green; full 'node --test web/test/unit/*.test.js' 1029 total / 1027 pass / 2 fail — the 2 are the identical pre-existing live-server smoke subtests failing on pristine main (main: 1020/1018/2, same names; +9 net new, zero PR-caused). vite build green (2.35s), esbuild SDK bundle green (32.9kb). Browser proof open (shared-daemon loopback guard; headless drive not run).

MERGE RECOMMENDATION: ready to merge (nit in (5) advisory only). I am NOT merging per instructions.

REVIEW PR #480 (fix/issue-479) — verified in scratch worktree /tmp/pr480 at 6ab11e1, node tests + reasoning, no browser per instructions. AGENTS.md laws: (1) deps untouched — package.json/lock absent from diff, runtime set unchanged; (7) N/A, no async work; (8) no new routes/providers, existing A/router only; (12) decision appended to docs/go/12_web_ui.md in the same change, headers cite ReleaseNew.jsx. Main worktree untouched (read-only; still clean). Acceptance criteria, all 7 PASS: 1. Grids collapse — New.jsx:180, Import.jsx:256 both 'grid grid-cols-1 gap-3 sm:grid-cols-2'; zero bare 'grid-cols-2' in all three pages (only comments + sm: variant); mx-auto wrappers present New.jsx:163, Import.jsx:215, OrgNew.jsx:74. 2. Helpers dropped — grep 'you and your orgs only' clean under web/src/. 3. Mirror copy once — kept on New.jsx:239 ('pushes are rejected' total count across both pages = 1), wired via new-mirror-help to the schedule select (New.jsx:234); Import radio (Import.jsx:228) already reads '(recurring pull, pushes rejected)'. Correct call. 4. LFS/ssh collapsed — Import.jsx:341-347 <details>, copy moved verbatim (pointer blobs + server-side ssh), no always-rendered prose left. 5. aria wiring — pairs present: new-name-help, new-mirror-help, import-source-help, import-name-help, import-token-help, orgnew-name-help; copy matches enforced rules (sdk/src/create.js charset, lib/orgs.js rule text, token never-stored promise); label for= matches ReleaseNew convention. ADVISORY (non-blocking): import-source-help (Import.jsx:246) renders only inside the suggestion Show while aria-describedby (Import.jsx:243) is unconditional — dangling ref on empty form. Benign (AT ignores missing ids) but diverges from ReleaseNew's always-rendered help; consider always-render or conditional describedby in a follow-up. 6. Cancel identical — byte-identical '<A class="btn px-3 py-1" href="/explore">cancel</A>' on all three (New.jsx:296-298, Import.jsx:378-380, OrgNew.jsx:143-145); every-form-gets-cancel direction noted in test + headers. 7. No behavior change — line-level diff of all three pages shows only markup/attributes/copy moves; no onSubmit/submit/validate/navigate/fetch line touched; test pins repos.repos.create, repos.mirrors.create, repos.imports.start, repos.orgs.create, both navigates, validateRepoName/validateOrgName. + Standing rule recorded (headers + 12_web_ui.md); no backend change; docs accurate. NOTE: branch's 3 older README commits are already on origin/main (7cfecee restructures further) — branch-unique diff vs origin/main is exactly the 5 scoped files, no action needed. Tests (scratch, node_modules symlinked from main): new create-forms-479.test.js 9/9 green; full 'node --test web/test/unit/*.test.js' 1029 total / 1027 pass / 2 fail — the 2 are the identical pre-existing live-server smoke subtests failing on pristine main (main: 1020/1018/2, same names; +9 net new, zero PR-caused). vite build green (2.35s), esbuild SDK bundle green (32.9kb). Browser proof open (shared-daemon loopback guard; headless drive not run). MERGE RECOMMENDATION: ready to merge (nit in (5) advisory only). I am NOT merging per instructions.
Author
Owner

Fixed by PR #480 (review clean — all 7 criteria pass, markup/copy-only diff proven, no behavior change), merged. Closing.

Fixed by PR #480 (review clean — all 7 criteria pass, markup/copy-only diff proven, no behavior change), 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#479
No description provided.