Per-repo General-settings toggles: disable Issues/Pulls/Releases tabs, forks, watching, starring #522
Labels
No labels
actions
bug
cli
duplicate
enhancement
fork
forum
git storage
help wanted
insights
invalid
issues
moderation
oidc
ownership transfer
packages
pr/merge protection rules
projects
pull requests
question
releases
sponsorships
tags
webhooks
wiki
wontfix
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
crueber/walhub#522
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Per-repo General-settings toggles: disable Issues/Pulls/Releases tabs, forks, watching, starring
What's requested
Six per-repo toggles on the repo General settings tab (like Forgejo's repository feature checkboxes), each backed end-to-end — settings model, summary projection, tab gating, and API-level guards:
POST /api/v1/repos/{owner}/{repo}/forksrefuses at the API level.Evidence / current shape (static read of
d5c706f)internal/api/settings.go, GET AuthRead / PUT AuthAdmin, <=16 KiB).RepoSettingsininternal/config/settings.go:29carriesDescription+ pointer sections;ParseRepoSettingsrejects unknown keys,Mergeoverrides host config, andDescriptionis the precedent for display-metadata fields that ride the same doc but never merge. A new[repo]-style section (e.g.featuresbooleans, default all-on) follows the Description pattern: validated, persisted, revisioned, merge-ignored, extraction tolerant on read paths (cf.DescriptionOf).summaryBody(internal/api/summary.go) already carries feature flags with the #319 badge discipline (always present, never null):OpenIssues/OpenPulls,HasChecks(#505),ForkParent/Forks(#424). The six toggles project onto this shape (e.g.features: {issues: true, pulls: true, releases: true, forks: true, watch: true, star: true}or six booleans) so a single shared summary fetch gates everything client-side.showChecksTab/checksHiddeninweb/src/pages/Repo.jsx(~661, ~851) already hides the Checks tab from the same shared summary — the new toggles are a second instance of the identical mechanism (filter theTABSlist at ~167 by summary flags, keep Code highlighted for paths of hidden tabs, deep links to disabled surfaces must land on a sane page, not a dead 404 route).PUT /{o}/{r}/api/star(internal/social/http.gostar, ~247)PUT /{o}/{r}/api/watch(internal/notify/http.gowatch, ~437;SetWatchin internal/notify)POST /api/v1/repos/{owner}/{repo}/forks(internal/pulls/http.goroute ~278,fork~757)Architecture notes
internal/social/http.go~246 comment).repo:{full}and the summary entry like the General tab's description save already does.Acceptance criteria
[repo] features-style TOML section (exact shape: planner's call) accepted byParseRepoSettings; unknown keys still rejected; defaults = all features enabled (zero migration for existing repos).settings:*invalidations); non-admin saves surface the 403 like visibility does.showChecksTabpattern; direct URLs to disabled surfaces do not 404 into a broken route; Code stays highlighted for their paths.POST …/forkson a forks-disabled repo fails at the API level with a clear status + reason; the Fork pill/CTA is hidden or disabled with a tooltip.PUT …/api/starandPUT …/api/watchon prevented repos fail at the API level with a clear status + reason; DELETE (un-star/un-watch) always succeeds; existing counts unaffected.Awaiting implementation via the normal pipeline. Scope note: one settings model, six booleans, one summary projection — do not split into six tickets.
Implemented end-to-end in PR #532 (#532), branch fix/issue-522 — one settings model, six booleans, no split. All 9 acceptance criteria covered (settings section, General-tab toggles, summary projection + ETag, tab gating, fork/star/watch guards, pill affordances, re-enable, tests). Verification: -race green, coverage gate holds, node 1211/1212 (sole fail pre-existing live smoke), vite+esbuild green, e2e green, live HTTP probe green. Not merging — review requested.
REVIEW PR #532 (fix/issue-522,
d592b73) — adversarial pass, verified in scratch worktree (removed afterward). No browser (tests + reasoning; no browser-facing layout risk beyond gated Shows).(1) SETTINGS MODEL — PASS. internal/config/settings.go:29-56: [features] six *bool (nil=unset→enabled), zero-migration default via Resolve()/AllFeatures(). ParseRepoSettings rejects unknown keys incl. inside [features] (Undecoded head check); wrong types fail toml decode→400. Merge ignores Features (no host counterpart; effective/describe untouched by construction). Tests: TestParseRepoSettingsFeatures{,/Rejects}, TestRepoSettingsFeaturesMergeIgnored, TestFeaturesOf, TestResolvedFeaturesETagBits.
(2) SUMMARY PROJECTION — PASS. internal/api/summary.go:60,174-177,263-272: features always present (non-omitempty, #319 discipline), folded from manifest-inline TOML at +0 probes (same snapshot repoDescription uses; law 4/6 clean, off budgeted paths). ~t ETag suffix UNCONDITIONAL with fixed-order bits — the #513 lesson applied. Proven by TestSummaryFeaturesRevalidate: pre-#522 ETag (~k0, no ~t) revalidates to 200 not 304; settings-only flip busts (→~t011111); current ETag 304s; re-enable restores original ETag. TestWalSummaryFeatures covers the walView fold incl. fail-open.
(3) TAB GATING — PASS. web/src/lib/tabs.js: FEATURE_TABS={issues,pulls,releases}, showFeatureTab fail-open; activeTab(pathname, summary) re-highlights disabled sections onto Code (raw mapping pinned by tests). Repo.jsx gates tab strip only — routes/lists keep rendering, deep links sane. Composers (IssueNew/PullNew/ReleaseNew/Fork) hide forms behind explainers with back-links; list pages gate New-buttons + Empty CTAs. Reads stay (display-only, Forgejo semantics — stated in 07_api Decisions).
(4) API GUARDS — PASS. Star (social/service.go:49), SetWatch on-branch only (notify/watch.go:64 — unwatch path untouched), StartFork (pulls/merge.go:691, after role gate → binds everyone incl. admins, no bypass). All wrap ErrForbidden→403 with naming reasons (statusFor errors.Is in all three packages; fork via writeErr→statusFor). Fail-open on unwired/declining hook (CollabCounts precedent). Tests: TestStarDisabledRefuses/BindsEveryone/UnstarAlwaysWorks, TestSetWatchDisabledRefuses/UnwatchAlwaysWorks, TestStartForkDisabledRefuses/BindsWriter. Note: already-starred re-PUT also 403s (uniform refusal, documented) — spec-compliant since PUT is the new-star affordance. Issues/PRs/releases creation APIs deliberately unguarded (display-only decision, documented).
(5) PILLS — PASS. Star/Watch keep #447 idiom (count left), disabled+muted with reason title/aria-label only when off AND viewer not engaged; retained star/watch keep enabled Unstar/Unwatch. Fork count link stays, CTA → muted span with reason. Flip handlers belt-and-braces return early.
(6) GENERAL TAB — PASS. Six toggles on existing save path (put(doc,"") — same CAS behavior as description save, no new hazard); withFeatures replaces only [features] block; failure reseeds from server truth with 403 Surfacing in note; invalidates repo:/settings:/settings-effective/settings-history. Dirty tracking + unsaved-changes hint.
(7) GATES — all green in scratch: config/api/social/notify/pulls/cmd -race PASS; coverage config 95.9 / api 95.3 / social 99.5 / notify 95.3 / pulls 96.1 (≥95 ✓); gofmt clean; go vet clean; go build ✓; node 1211/1212 (sole fail = smoke.test.js live-fetch vs 127.0.0.1:8080 where /setup→403 — environmental, PR-code-independent); vite+esbuild ✓ (via direct binaries; pnpm shim unusable in scratch, node_modules was a symlink); e2e PASS (56s). push_budget_test pins the three Features hooks wired + fail-open. Docs: 07_api §9.1/§11 + Decisions, 12_web_ui, features/02/03/06/07 updated in-change (law 12 ✓). No new deps (go.mod/package.json untouched). Concurrency: no new goroutines/locks; guard reads precede all shard/CAS locks — no lock-order issue.
No fixes needed — nothing pushed. MERGE RECOMMENDATION: ready to merge.
Fixed by PR #532 (review clean — all 9 criteria pass incl. ~t unconditional per the #513 lesson, guards fail-closed, e2e green), merged. Closing.