Commit 7c015c39b3

7c015c39b36312e245114fb3a257a3537a088092

parent: 2dd9add6d0

Verified · cmc

cmc <hello@cleberg.net> · 2026-09-28 23:08 UTC

web, store: final-review wording, header and range-diff fixes

- privacy.html: tokens now also mint on the settings page.
- account.html's stock-OpenSSH example quoted token create, which the
  block above no longer offers; use web sessions list, which resolves
  (templatecmds_test).
- registered.html's next-steps links now match the settings page's
  actual section headings, API tokens and Email addresses.
- store.go: a foreign_key_check failure formatted the whole
  sql.NullInt64 struct instead of the row id.
- mrrangediff.go: a bad ?from=/?to= now renders the command's refusal
  on the page; only an unknown merge request 404s, since that is the
  only case checked before the command runs.
- CHANGELOG.org: fold the four #261 bullets into one, drop the watch
  three-state clause from the #271 bullet (already in the #261 one),
  even out blank-line spacing between bullets, and add bullets for
  the fork-owner MR link, the new-issue draft fixes, and the
  remaining empty-state cleanup.

Ref #270, Ref #271

Layout: unified · split

CHANGELOG.org +28 −18
@@ -14,18 +14,29 @@ anything beyond "replace the binary and restart" is needed.
1414- A wiki link to an existing non-page file (an .svg, .txt, .pdf) now
1515 resolves to the raw route instead of 404ing against the page route
1616 (#283).
17
1817- The new-issue form takes labels, milestone and assignee in one step
19 for writers; the watch button names watching, muted and default; a
20 Discussion heading sits before comment threads; the build page's
21 live note says the page updates itself; and the rail and the phone
22 More menu render from one list (#271).
23
18 for writers; a Discussion heading sits before comment threads; the
19 build page's live note says the page updates itself; and the rail
20 and the phone More menu render from one list (#271).
21- The new-issue form keeps milestone and assignee through preview and
22 a refused create, the way it already kept title, body and labels;
23 a refused create re-renders the form with the draft and the
24 refusal instead of an error page (#271).
25- The merge request range-diff page renders a bad =from=/=to== query
26 parameter inline instead of 404ing; only an unknown merge request
27 404s (#271).
2428- Empty states on the web state the fact instead of a CLI command, and
2529 drop "yet" on a finished item; the merge request list offers a New
2630 merge request link, a fork link, or a sign-in prompt depending on
2731 what the visitor can do; and the search page's scope caption is
2832 always visible, not only before a first search (#270).
33- Issues, milestones, org milestones and releases drop the CLI command
34 from their empty states too, matching the rest of the register: a
35 link to the web form that does the thing when one exists, otherwise
36 just the fact (#270).
37- The merge request list and compare page offer New merge request to a
38 reader who owns a writable fork of the repository, not only to a
39 writer (#270).
2940
3041Credentials and sessions: revocation, delegation, expiry and an idle
3142timeout (#256, #257, #276, #277).
@@ -148,18 +159,17 @@ missing, =gitbayd admin backup --verify <archive>= names it, and
148159- A =-- foreign_keys: off= migration's =foreign_key_check= now runs
149160 inside the migration's own transaction, before commit, so a
150161 violation rolls the migration back instead of leaving the bad
151 schema and =user_version= already persisted (#261).
152- The web pin and watch buttons dispatch through =repo pin=/=unpin=
153 and =repo watch=/=mute=/=unwatch= instead of writing the store
154 directly, so a refusal reaches the viewer as a message instead of
155 being dropped. The watch button now cycles three states — default,
156 watching, muted — instead of two (#261).
157- The response that consumes a login link's =?token== sends
158 =Cache-Control: no-store=, so no intermediary keeps a copy of the
159 single-use URL (#261).
160- Wiki documentation fixes: API.org clarifies token commands work on the
161 API, Parity.org documents batched review and web watch/pin dispatch,
162 Threat-Model.org documents the login-link URL exception (#261).
162 schema and =user_version= already persisted; the web pin and watch
163 buttons dispatch through =repo pin=/=unpin= and =repo
164 watch=/=mute=/=unwatch= instead of writing the store directly, so a
165 refusal reaches the viewer as a message instead of being dropped,
166 and the watch button now cycles three states — default, watching,
167 muted — instead of two; the response that consumes a login link's
168 =?token== sends =Cache-Control: no-store=, so no intermediary keeps
169 a copy of the single-use URL; and wiki documentation fixes:
170 API.org clarifies token commands work on the API, Parity.org
171 documents batched review and web watch/pin dispatch, Threat-Model.org
172 documents the login-link URL exception (#261).
163173- The account settings page quotes the CLI and SSH command forms that
164174 actually resolve; a test runs every command a web page quotes against
165175 the CLI and control registries so a renamed command fails CI instead
internal/httpd/accounts_test.go +2 −2
@@ -21,7 +21,7 @@ func TestRegisteredPageNumberedStepsAndTokenMention(t *testing.T) {
2121 if !strings.Contains(out, "<ol>") {
2222 t.Error("next steps are not a numbered list")
2323 }
24 if !strings.Contains(out, "Settings → Tokens") {
25 t.Error("no mention of Settings → Tokens for the iOS app")
24 if !strings.Contains(out, "Settings → API tokens") {
25 t.Error("no mention of Settings → API tokens for the iOS app")
2626 }
2727}
internal/httpd/mrrangediff.go +4 −4
@@ -37,11 +37,11 @@ func (s *Server) mrRangeDiff(w http.ResponseWriter, r *http.Request) {
3737 if to := r.URL.Query().Get("to"); to != "" {
3838 argv = append(argv, "--to", to)
3939 }
40 // Only an unknown MR 404s (checked above). A bad --from/--to also
41 // resolves to nothing in git, which range-diff reports as
42 // ExitNotFound too, so that result renders on the page instead of
43 // turning a bad query parameter into a 404 (#271).
4044 out, msg, code := s.runControlCode(viewer, argv)
41 if code == protocol.ExitNotFound {
42 s.notFound(w, r)
43 return
44 }
4545 errMsg := ""
4646 if code != protocol.ExitOK {
4747 errMsg = msg
internal/httpd/mrrangediff_test.go +12 −5
@@ -228,8 +228,8 @@ func TestMRRangeDiffPagePrivateRepo(t *testing.T) {
228228}
229229
230230// --from/--to reach the control command as real argv: an unknown
231// revision is refused with not-found, and two real revisions render the
232// range-diff between exactly those two.
231// revision renders the command's refusal on the page, and two real
232// revisions render the range-diff between exactly those two.
233233func TestMRRangeDiffPageFromToQuery(t *testing.T) {
234234 st, cfg, alice, _, repo, n, v1, v2, _ := rangeDiffFixture(t)
235235 s := New(cfg, st, nil)
@@ -244,11 +244,18 @@ func TestMRRangeDiffPageFromToQuery(t *testing.T) {
244244 return httptest.NewRecorder(), req
245245 }
246246
247 t.Run("unknown revision is 404", func(t *testing.T) {
247 // A bad --from/--to is a query parameter, not an unknown merge
248 // request: it renders the command's refusal inline rather than
249 // 404ing the page, which is reserved for an MR that does not exist
250 // (#271).
251 t.Run("unknown revision renders the refusal inline", func(t *testing.T) {
248252 rr, req := newReq("?from=0000000000000000000000000000000000000000")
249253 s.mrRangeDiff(rr, req)
250 if rr.Code != 404 {
251 t.Fatalf("status %d, want 404, body %s", rr.Code, rr.Body.String())
254 if rr.Code != 200 {
255 t.Fatalf("status %d, want 200 (the refusal renders on the page), body %s", rr.Code, rr.Body.String())
256 }
257 if !strings.Contains(rr.Body.String(), "is not a revision of") {
258 t.Errorf("page does not show the refusal:\n%s", rr.Body.String())
252259 }
253260 })
254261
internal/store/store.go +1 −1
@@ -284,7 +284,7 @@ func (s *Store) migrateStep(sqlText string, newVersion int, fkOff bool) (retErr
284284 return err
285285 }
286286 rows.Close()
287 return fmt.Errorf("foreign_key_check failed after migration: %s row %v", table, rowid)
287 return fmt.Errorf("foreign_key_check failed after migration: %s row %d", table, rowid.Int64)
288288 }
289289 if err := rows.Err(); err != nil {
290290 rows.Close()
internal/web/templates/account.html +1 −1
@@ -232,7 +232,7 @@ narrowest scope and shortest lifetime the job needs.</p>
232232gitbay admin ... # instance administration</pre>
233233<p class="meta">All of it works from stock OpenSSH too, with the CLI's
234234grouping words dropped: <code>ssh git@{{.Host}} whoami</code>,
235<code>ssh git@{{.Host}} token create --name laptop</code>.</p>
235<code>ssh git@{{.Host}} web sessions list</code>.</p>
236236</section>
237237</div>
238238</div>
internal/web/templates/privacy.html +1 −1
@@ -30,7 +30,7 @@ is active is kept in app preferences; nothing else is retained on the
3030device. The app embeds no analytics, no crash reporting, and no
3131third-party SDK, so there is nothing to opt out of. Removing the account
3232deletes the token from the Keychain.</p>
33<p>Tokens are minted over SSH and can be revoked at any time with
33<p>Tokens are minted over SSH or on the settings page and can be revoked at any time with
3434<code>gitbay auth token revoke</code>, which ends the app's access
3535immediately.</p>
3636
internal/web/templates/registered.html +2 −2
@@ -8,10 +8,10 @@
88<ol>
99<li>Copy the verification code from the mail you were just sent.</li>
1010<li><a href="/login">Sign in</a> with an emailed link.</li>
11<li>Paste the code in <a href="/settings#emails">Settings → Email</a>.</li>
11<li>Paste the code in <a href="/settings#emails">Settings → Email addresses</a>.</li>
1212</ol>
1313<p>Then + creates your first repository. Using the iOS app? Create a
14token in <a href="/settings#tokens">Settings → Tokens</a>.</p>
14token in <a href="/settings#tokens">Settings → API tokens</a>.</p>
1515<h2>From the terminal</h2>
1616<pre class="quickstart" tabindex="0">ssh git@{{.Host}} whoami
1717ssh git@{{.Host}} repo create {{.Username}}/hello