gitshark

Clone repository

git clone https://gitshark.ha1nz.de/git/workaround/Gitshark.git
git clone git@gitshark.ha1nz.de:workaround/Gitshark.git

← Commits

๐Ÿ› (layout): Keep the header gutter the same on every page

88727cf9201c459b395082a688c9551a5b259fc0 ยท Phillip Souza Furtner ยท 2026-09-08T12:06:46Z

Changes

5 files changed, +43 -20

MODIFY README.md +1 -1
diff --git a/README.md b/README.md
index 55a1f08..f5b5f56 100644
--- a/README.md
+++ b/README.md
@@ -58,7 +58,7 @@
58 58 - Line-level review comments on a merge request's diff: any authenticated user who can read the repository can comment on a specific diff line (added, deleted, or context) from the merge-request detail page; comments render inline beneath the line they anchor to. A comment can be deleted by its author, the repository owner, or a collaborator. Comments are anchored to a file plus the diff line's old/new line numbers and must land on a line that's part of the current diff. Hovering a commentable line reveals a comment icon on the right; clicking it opens the form inline โ€” a progressive-enhancement disclosure that works without JavaScript
59 59 - OIDC login (authorization code flow) via `GET /login`; on first login the user account is created without a username and the browser is redirected to `/onboarding`, where the user picks a URL-safe handle (`^[a-z0-9][a-z0-9-]{0,38}$`, unique across users and organisations). The chosen handle โ€” not the OIDC `preferred_username` claim (which is an SPN form in kanidm and not URL-safe) โ€” is used in all repo, SSH, ActivityPub, and webfinger URLs. The `name` claim becomes an editable display name; both can be changed later at `/settings/profile`. A request filter blocks all app pages until a handle is chosen. The code-flow callback is pinned to `/login` (`redirect-path` + `restore-path-after-redirect`, so strict-`redirect_uri` IdPs like kanidm register one URI) and after login the user lands back on the page they came from โ€” the header's Log in button carries `?redirect=<current page>` (local paths only, open-redirect guarded). Expired ID tokens are refreshed silently with the refresh token (`refresh-expired=true`, 60 s proactive skew, session cookie usable 12 h past expiry) instead of logging the user out. Logout is local-session only via `POST /logout` (the kanidm provider advertises no `end_session_endpoint`, so RP-Initiated Logout is disabled)
60 60 - Profile pictures: users can upload a PNG/JPEG/GIF/WebP avatar (โ‰ค 2 MB, content-type and magic bytes both validated) at `/settings/profile`, stored on the filesystem keyed by user UUID and served publicly at `GET /users/{username}/avatar`; shown wherever a local user is rendered (header nav, repo lists, repo sidebar, issue/MR/comment authors) via a reusable Qute avatar tag, removable, and falling back to an initials badge when absent. Git commit authors and remote federation actors are not local users and keep their existing pseudo-avatars
61 -- Per-user content width setting (Appearance section on `/settings/profile`): **Full** (default โ€” repository pages span the entire screen, other pages cap at 1400px), **Comfortable (fixed 1400px)**, or **Compact (fixed 1120px)** on every page; the width also aligns the header nav content (the bar background stays edge-to-edge), and windows narrower than the cap just fill the screen. Guide: [profile settings](docs/users/profile.md)
61 +- Per-user content width setting (Appearance section on `/settings/profile`): **Full** (default โ€” repository pages span the entire screen, other pages cap at 1400px), **Comfortable (fixed 1400px)**, or **Compact (fixed 1120px)** on every page; the header bar keeps a fixed gutter regardless of the preset, and windows narrower than the cap just fill the screen. Guide: [profile settings](docs/users/profile.md)
62 62 - **Collaborators** โ€” the repository owner can grant other local users read+write access
63 63 (one flat role) on a per-repository settings page (`โ€ฆ/settings/collaborators`, reached from a
64 64 "Manage collaborators" link on the owner-only repository Settings page). Collaborators can read and push โ€” UI, HTTP, and SSH alike โ€” even
MODIFY docs/users/profile.md +6 -6
diff --git a/docs/users/profile.md b/docs/users/profile.md
index 1c6e768..c618419 100644
--- a/docs/users/profile.md
+++ b/docs/users/profile.md
@@ -32,12 +32,12 @@
32 32 same fixed pixel column, so the width doesn't shift around when you resize the
33 33 window or navigate. Full is uniform per page type instead โ€” repository pages
34 34 get the entire screen (diffs and file trees benefit most), everything else the
35 -centered 1400px column. Whatever width the current page uses, the header bar's
36 -content (logo, navigation, account menu) aligns with the same column while the
37 -bar's background still spans the full screen; on windows narrower than the cap
38 -the content simply fills the screen. The setting applies as soon as you save
39 -and sticks across sessions (it's stored on your account, not in the browser).
40 -Visitors who aren't logged in always get the Full layout.
35 +centered 1400px column. The header bar is not affected: its content (logo,
36 +navigation, account menu) keeps the same gutter on every page, so the bar never
37 +shifts as you navigate. On windows narrower than the cap the content simply
38 +fills the screen. The setting applies as soon as you save and sticks across
39 +sessions (it's stored on your account, not in the browser). Visitors who aren't
40 +logged in always get the Full layout.
41 41
42 42 ---
43 43
MODIFY src/main/java/de/workaround/account/CurrentUser.java +2 -2
diff --git a/src/main/java/de/workaround/account/CurrentUser.java b/src/main/java/de/workaround/account/CurrentUser.java
index 31474c7..97f457e 100644
--- a/src/main/java/de/workaround/account/CurrentUser.java
+++ b/src/main/java/de/workaround/account/CurrentUser.java
@@ -47,8 +47,8 @@
47 47 }
48 48
49 49 /**
50 - * CSS class for the user's content-width preset, applied on {@code <body>} by the layout so
51 - * header nav and main column share one width. Empty for anonymous visitors and the FULL default.
50 + * CSS class for the user's content-width preset, applied on {@code <body>} by the layout and
51 + * driving the main column's width. Empty for anonymous visitors and the FULL default.
52 52 */
53 53 public String contentWidthClass()
54 54 {
MODIFY src/main/resources/META-INF/resources/shark.css +9 -9
diff --git a/src/main/resources/META-INF/resources/shark.css b/src/main/resources/META-INF/resources/shark.css
index b89f88c..5c49b11 100644
--- a/src/main/resources/META-INF/resources/shark.css
+++ b/src/main/resources/META-INF/resources/shark.css
@@ -94,12 +94,12 @@
94 94 }
95 95
96 96 body {
97 - /* Per-user content width preset (settings > profile > appearance): one shared width for the
98 - header nav content and the page's main column. --preset-w is what the user picked (Full =
99 - the whole screen; the other presets are fixed px caps so the column stays put while the
100 - window is resized โ€” narrower windows simply fill the viewport). Non-repo pages additionally
101 - cap at a comfortable 1400px; repo pages (the :has override below) get the raw preset width,
102 - so Full there means the entire screen for diffs and file trees. */
97 + /* Per-user content width preset (settings > profile > appearance) for the page's main column;
98 + the header bar keeps its own fixed gutter. --preset-w is what the user picked (Full = the
99 + whole screen; the other presets are fixed px caps so the column stays put while the window
100 + is resized โ€” narrower windows simply fill the viewport). Non-repo pages additionally cap at
101 + a comfortable 1400px; repo pages (the :has override below) get the raw preset width, so
102 + Full there means the entire screen for diffs and file trees. */
103 103 --preset-w: 100%;
104 104 --content-w: min(var(--preset-w), 1400px);
105 105 margin: 0;
@@ -180,9 +180,9 @@
180 180 display: flex;
181 181 align-items: center;
182 182 gap: var(--s4);
183 - /* bar background spans the viewport, its content aligns with the preset content column;
184 - max() keeps the normal gutter when the window is narrower than the preset cap */
185 - padding: var(--s3) max(var(--s5), calc((100% - var(--content-w)) / 2 + var(--s5)));
183 + /* a fixed gutter on purpose: tracking --content-w made the bar jump between page types,
184 + because repo pages opt out of the 1400px cap */
185 + padding: var(--s3) var(--s5);
186 186 background: var(--surface);
187 187 border-bottom: 1px solid var(--border);
188 188 }
MODIFY src/test/java/de/workaround/web/ContentWidthTest.java +25 -2
diff --git a/src/test/java/de/workaround/web/ContentWidthTest.java b/src/test/java/de/workaround/web/ContentWidthTest.java
index 7e5e432..168d8ae 100644
--- a/src/test/java/de/workaround/web/ContentWidthTest.java
+++ b/src/test/java/de/workaround/web/ContentWidthTest.java
@@ -11,6 +11,8 @@
11 11 import static io.restassured.RestAssured.given;
12 12 import static org.hamcrest.CoreMatchers.containsString;
13 13 import static org.hamcrest.CoreMatchers.not;
14 +import static org.junit.jupiter.api.Assertions.assertFalse;
15 +import static org.junit.jupiter.api.Assertions.assertTrue;
14 16
15 17 @QuarkusTest
16 18 class ContentWidthTest
@@ -48,7 +50,7 @@
48 50 .when().get("/explore")
49 51 .then()
50 52 .statusCode(200)
51 - // the preset class sits on <body> so header nav and content scale together
53 + // the preset class sits on <body> and drives the main column's width
52 54 .body(containsString("<body class=\"width-compact\">"));
53 55
54 56 given()
@@ -90,7 +92,7 @@
90 92 .when().get("/shark.css")
91 93 .then()
92 94 .statusCode(200)
93 - // presets live on <body> and drive one shared width variable for header and main;
95 + // presets live on <body> and drive one shared width variable for the main column;
94 96 // fixed px caps so the column doesn't shift while resizing the window
95 97 .body(containsString("body.width-comfortable"))
96 98 .body(containsString("body.width-compact"))
@@ -101,6 +103,27 @@
101 103 .body(containsString("body:has(.repo-layout)"));
102 104 }
103 105
106 + @Test
107 + void headerBarKeepsTheSameGutterOnEveryPage() throws Exception
108 + {
109 + String css = given()
110 + .when().get("/shark.css")
111 + .then()
112 + .statusCode(200)
113 + .extract().body().asString();
114 +
115 + int start = css.indexOf("header.site {");
116 + assertTrue(start >= 0, "header.site rule missing from the stylesheet");
117 + String rule = css.substring(start, css.indexOf('}', start));
118 + int padding = rule.indexOf("padding:");
119 + assertTrue(padding >= 0, "header.site declares no padding: " + rule);
120 + String gutter = rule.substring(padding, rule.indexOf(';', padding));
121 +
122 + assertTrue(gutter.contains("var(--s5)"), "the header gutter must be the fixed spacing token: " + gutter);
123 + assertFalse(gutter.contains("--content-w"),
124 + "the header gutter must not follow the per-page content width, or the bar shifts between pages: " + gutter);
125 + }
126 +
104 127 @Transactional
105 128 User persistUser(String name)
106 129 {

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog