Repo description field: shown right of org/repo in the header, editable via a new 'General' tab in repo settings #235

Closed
opened 2026-09-09 15:16:45 +00:00 by crueber · 3 comments
Owner

What's requested

Every repo should support a short description, rendered to the right of the owner / repo title in the repo header, and editable through a new General settings tab in repo settings.

Currently there is no description anywhere: the repo summary (internal/api/summary.go:12 summaryBody) carries owner/name/head/branches/tags/health/clone URLs but no description field, and the repo header (web/src/pages/Repo.jsx:511-527) renders title + branch/tag counts with no description slot.

Desired behavior

  1. Storage: a per-repo short description persisted in the object store (consistent with "the object store is the only database" — e.g. alongside the existing settings/meta documents; see notes below).
  2. API: readable by anyone who can read the repo (rides the existing summary or settings GET), writable by repo admins via the existing WAL-published settings flow.
  3. Header: description shows to the right of the owner / repo title in repo-header (Repo.jsx:511), muted styling, omitted entirely when unset (no empty placeholder).
  4. Settings UI: a new General tab as the first entry in the settings sidebar (web/src/lib/settingsNav.js:11 SETTINGS_GROUP — add { id: "general", label: "General" } first and make it DEFAULT_SETTINGS_TAB), containing the description editor.

Architecture notes (for whoever implements)

  • Settings is the natural home. Repo settings already exist as a validated TOML document published through the WAL: internal/api/settings.go (GET = AuthRead, PUT = AuthAdmin, body ≤ 16 KiB, revision/author/updated_at wire fields). A description key in that TOML gets persistence, revisioning, authorship, and admin-only writes for free. The SPA already round-trips TOML text (web/src/pages/Settings.jsx — e.toml read/edit/diff around lines 285-316), so the General tab can present a single description input and serialize it into the doc rather than exposing raw TOML.
  • Surfacing it: the cleanest read path is adding description to summaryBody (summary.go:12) so the header, the repos listing, and future consumers get it in the request they already make — the header reads the shared summary signal via RepoCtx (Repo.jsx:502-505), so no new fetch is needed. Requires the summary implementation (RepoView.Summary) to read the settings/meta document; watch the ETag/SWR story — a description change with the same head sha will 304 unless the ETag covers the new field (same trap as the ~degraded suffix, summary.go:83-88).
  • Header placement: Repo.jsx repo-title block (:515-527) — description goes right of the <h1>, wrapping below on narrow widths (the header is already flex-wrap, :511).
  • Auth: PUT settings is already AuthAdmin-gated (:57), matching "changeable in settings". No new authz needed if description rides the settings doc.

Acceptance criteria

  • Repo settings TOML supports a description value; PUT persists it through the WAL with the usual revision/author metadata.
  • GET summary (…/api/summary and all exposed twins) includes description (empty string when unset); the ETag/cache story doesn't serve stale summaries after a description-only change.
  • Repo header shows the description to the right of the owner / repo title, hidden when unset.
  • New General tab in repo settings (first in SETTINGS_GROUP, default tab on bare /settings), containing a description input that writes through the SDK and reflects the change without a full reload.
  • Only admins can change the description (rides settings AuthAdmin); non-admins see the tab in read form or without the editor, consistent with how other admin-only settings tabs behave.
  • Description also appears on the owner repo list rows (web/src/pages/Repos.jsx <RepoRow>) or is explicitly descoped — pick one and note it.
  • Tests: TOML round-trip for the description key, summary includes the field with correct cache behavior, and a settings-nav test covering the new tab id (settingsNav.js is deliberately headless-testable).
## What's requested Every repo should support a short description, rendered to the right of the `owner / repo` title in the repo header, and editable through a new **General** settings tab in repo settings. Currently there is no description anywhere: the repo summary (`internal/api/summary.go:12` `summaryBody`) carries owner/name/head/branches/tags/health/clone URLs but no description field, and the repo header (`web/src/pages/Repo.jsx:511-527`) renders title + branch/tag counts with no description slot. ## Desired behavior 1. **Storage:** a per-repo short description persisted in the object store (consistent with "the object store is the only database" — e.g. alongside the existing settings/meta documents; see notes below). 2. **API:** readable by anyone who can read the repo (rides the existing summary or settings GET), writable by repo admins via the existing WAL-published settings flow. 3. **Header:** description shows to the right of the `owner / repo` title in `repo-header` (Repo.jsx:511), muted styling, omitted entirely when unset (no empty placeholder). 4. **Settings UI:** a new **General** tab as the first entry in the settings sidebar (`web/src/lib/settingsNav.js:11` `SETTINGS_GROUP` — add `{ id: "general", label: "General" }` first and make it `DEFAULT_SETTINGS_TAB`), containing the description editor. ## Architecture notes (for whoever implements) - **Settings is the natural home.** Repo settings already exist as a validated TOML document published through the WAL: `internal/api/settings.go` (GET = AuthRead, PUT = AuthAdmin, body ≤ 16 KiB, `revision`/`author`/`updated_at` wire fields). A `description` key in that TOML gets persistence, revisioning, authorship, and admin-only writes for free. The SPA already round-trips TOML text (`web/src/pages/Settings.jsx` — `e.toml` read/edit/diff around lines 285-316), so the General tab can present a single description input and serialize it into the doc rather than exposing raw TOML. - **Surfacing it:** the cleanest read path is adding `description` to `summaryBody` (summary.go:12) so the header, the repos listing, and future consumers get it in the request they already make — the header reads the shared summary signal via `RepoCtx` (Repo.jsx:502-505), so no new fetch is needed. Requires the summary implementation (`RepoView.Summary`) to read the settings/meta document; watch the ETag/SWR story — a description change with the same head sha will 304 unless the ETag covers the new field (same trap as the `~degraded` suffix, summary.go:83-88). - **Header placement:** Repo.jsx repo-title block (:515-527) — description goes right of the `<h1>`, wrapping below on narrow widths (the header is already `flex-wrap`, :511). - **Auth:** PUT settings is already AuthAdmin-gated (:57), matching "changeable in settings". No new authz needed if description rides the settings doc. ## Acceptance criteria - [ ] Repo settings TOML supports a description value; PUT persists it through the WAL with the usual revision/author metadata. - [ ] GET summary (`…/api/summary` and all exposed twins) includes `description` (empty string when unset); the ETag/cache story doesn't serve stale summaries after a description-only change. - [ ] Repo header shows the description to the right of the `owner / repo` title, hidden when unset. - [ ] New **General** tab in repo settings (first in `SETTINGS_GROUP`, default tab on bare `/settings`), containing a description input that writes through the SDK and reflects the change without a full reload. - [ ] Only admins can change the description (rides settings AuthAdmin); non-admins see the tab in read form or without the editor, consistent with how other admin-only settings tabs behave. - [ ] Description also appears on the owner repo list rows (`web/src/pages/Repos.jsx` `<RepoRow>`) or is explicitly descoped — pick one and note it. - [ ] Tests: TOML round-trip for the description key, summary includes the field with correct cache behavior, and a settings-nav test covering the new tab id (settingsNav.js is deliberately headless-testable).
Author
Owner

Fixed by PR #242 (#242) — per-repo description via the settings TOML + summary field + General tab. Notes: 512-char single-line limit (decision, documented in 11_config_cli.md); owner-list rows explicitly descoped (names-only listing, noted in code + 07_api.md). Browser verification is open: the hub chrome-cdp daemon blocks all local addresses so the UI flow (header show/omit, General save, both themes) still needs a real-browser pass.

Fixed by PR #242 (https://git.packden.us/crueber/walhub/pulls/242) — per-repo description via the settings TOML + summary field + General tab. Notes: 512-char single-line limit (decision, documented in 11_config_cli.md); owner-list rows explicitly descoped (names-only listing, noted in code + 07_api.md). Browser verification is open: the hub chrome-cdp daemon blocks all local addresses so the UI flow (header show/omit, General save, both themes) still needs a real-browser pass.
Author
Owner

Review of PR #242 (fix/issue-235, commit 52074e3) — verified in scratch worktree /tmp/pr242.

PASS

  • settings-TOML home correct: description is a top-level key on RepoSettings (internal/config/settings.go:30), validated single-line + ≤512 runes (ValidateRepoDescription, :81-89; rejects CR/LF/NUL), enforced at publish via settingsPut→ParseRepoSettings (internal/api/settings.go:65 → 400). Merge ignores it (:108-134) and settings/effective renders only the 4 config sections (:115-120), so host config is never merged and the key never appears in effective. ValidateAgainst covered with description set (load_test.go).
  • summary carry +0 trips: repoDescription (bind_wal.go:513) reads engine.Manifest → open handle → ManifestSnapshot (server/bind_wal.go:343, in-memory lock only, no store GET). Same guarantee unbornState already relies on. Verified code path; no new store op. (Nit, non-blocking: Summary now does 3 handle opens — snapshot, unbornState, repoDescription — all in-memory; could fold to one Manifest call later.)
  • ETag busts both ways: set appends ~d+fnv1a32hex (summary.go), clear drops the suffix; TestSummaryDescription304 proves stale-etag→200 on change AND on clear, and current-etag→304. Bare-sha clients pre-change also mismatch→200. Fail-open to '' on missing/corrupt/nil-engine covered.
  • header omitted-when-unset: (Repo.jsx) — '' is falsy, no placeholder, Solid-escaped. Flex-wrap wraps below on narrow widths.
  • General tab first + default: SETTINGS_GROUP[0]=general, DEFAULT_SETTINGS_TAB=general (settingsNav.js); bare /settings and #general land there (Settings.jsx:863-864); resolveSettingsTab otherwise untouched. Save serializes via withDescription into existing settings.get/put (no new endpoint/SDK method), invalidates repo:{full} matching Repo.jsx:492 key, so header refreshes without reload. Non-admin 403 surfaces in the note (catch→setNote), same read-mostly behavior as other admin tabs.
  • owner-list descoped + documented: RepoRow comment (Repos.jsx) + 07_api decision entry (N fetches avoided). Names-only listing preserved.
  • 512/single-line decision sane + documented in 11_config_cli §4.2 + Decisions entry; client mirror (MAX_DESCRIPTION_LENGTH=512, rune/code-point counting consistent).
  • coverage: api 95.4%, config 95.6% (≥95 gate holds). No new non-stdlib imports (stdlib only: hash/fnv, strconv, unicode/utf8; BurntSushi/toml pre-approved). gofmt/vet clean. Docs entries accurate (07 §9.1 + decision, 11 §4.2 + decision, 12 §2.9 + entry).

TESTS (scratch worktree, after symlinking main web/node_modules which worktrees lack)

  • go test -race ./internal/api/... → ok; ./internal/config/... → ok
  • go test -cover api 95.4% / config 95.6%
  • node --test web/test/unit/*.test.js → 455 pass, 0 fail (smoke.test.js excluded: hangs identically on main — pre-existing sandbox artifact, no server up, file untouched by PR; description.test.js + settings-nav.test.js 18/18 pass)
  • gofmt -l clean; go vet clean
  • server/maintain packages untouched by diff — not re-run.
  • Browser: OPEN per PR author (hub chrome-cdp blocks loopback); likewise not attempted here per instructions — browser check remains open before merge.

No fixes pushed — nothing blocking found. RECOMMENDATION: ready to merge once a real-browser pass (/, repo header with/without description, /setup, settings#general both themes, console clean) is recorded.

Review of PR #242 (fix/issue-235, commit 52074e3) — verified in scratch worktree /tmp/pr242. PASS - settings-TOML home correct: description is a top-level key on RepoSettings (internal/config/settings.go:30), validated single-line + ≤512 runes (ValidateRepoDescription, :81-89; rejects CR/LF/NUL), enforced at publish via settingsPut→ParseRepoSettings (internal/api/settings.go:65 → 400). Merge ignores it (:108-134) and settings/effective renders only the 4 config sections (:115-120), so host config is never merged and the key never appears in effective. ValidateAgainst covered with description set (load_test.go). - summary carry +0 trips: repoDescription (bind_wal.go:513) reads engine.Manifest → open handle → ManifestSnapshot (server/bind_wal.go:343, in-memory lock only, no store GET). Same guarantee unbornState already relies on. Verified code path; no new store op. (Nit, non-blocking: Summary now does 3 handle opens — snapshot, unbornState, repoDescription — all in-memory; could fold to one Manifest call later.) - ETag busts both ways: set appends ~d+fnv1a32hex (summary.go), clear drops the suffix; TestSummaryDescription304 proves stale-etag→200 on change AND on clear, and current-etag→304. Bare-sha clients pre-change also mismatch→200. Fail-open to '' on missing/corrupt/nil-engine covered. - header omitted-when-unset: <Show when={s().description}> (Repo.jsx) — '' is falsy, no placeholder, Solid-escaped. Flex-wrap wraps below on narrow widths. - General tab first + default: SETTINGS_GROUP[0]=general, DEFAULT_SETTINGS_TAB=general (settingsNav.js); bare /settings and #general land there (Settings.jsx:863-864); resolveSettingsTab otherwise untouched. Save serializes via withDescription into existing settings.get/put (no new endpoint/SDK method), invalidates repo:{full} matching Repo.jsx:492 key, so header refreshes without reload. Non-admin 403 surfaces in the note (catch→setNote), same read-mostly behavior as other admin tabs. - owner-list descoped + documented: RepoRow comment (Repos.jsx) + 07_api decision entry (N fetches avoided). Names-only listing preserved. - 512/single-line decision sane + documented in 11_config_cli §4.2 + Decisions entry; client mirror (MAX_DESCRIPTION_LENGTH=512, rune/code-point counting consistent). - coverage: api 95.4%, config 95.6% (≥95 gate holds). No new non-stdlib imports (stdlib only: hash/fnv, strconv, unicode/utf8; BurntSushi/toml pre-approved). gofmt/vet clean. Docs entries accurate (07 §9.1 + decision, 11 §4.2 + decision, 12 §2.9 + entry). TESTS (scratch worktree, after symlinking main web/node_modules which worktrees lack) - go test -race ./internal/api/... → ok; ./internal/config/... → ok - go test -cover api 95.4% / config 95.6% - node --test web/test/unit/*.test.js → 455 pass, 0 fail (smoke.test.js excluded: hangs identically on main — pre-existing sandbox artifact, no server up, file untouched by PR; description.test.js + settings-nav.test.js 18/18 pass) - gofmt -l clean; go vet clean - server/maintain packages untouched by diff — not re-run. - Browser: OPEN per PR author (hub chrome-cdp blocks loopback); likewise not attempted here per instructions — browser check remains open before merge. No fixes pushed — nothing blocking found. RECOMMENDATION: ready to merge once a real-browser pass (/, repo header with/without description, /setup, settings#general both themes, console clean) is recorded.
Author
Owner

Fixed by PR #242 (review clean; +0 trips verified, ETag both directions, 403 surfaces; 95.4/95.6% + 455 node tests), merged. Closing.

Fixed by PR #242 (review clean; +0 trips verified, ETag both directions, 403 surfaces; 95.4/95.6% + 455 node tests), merged. Closing.
crueber added this to the v1 milestone 2026-09-10 22:27:11 +00:00
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#235
No description provided.