detached 4fc291e

feat(web): move the inbox button of the home page next to the logo, with a mailbox icon and a darker count badge

commit
4fc291e7aec41ccf4f7b95936bd72ed5a7b060da
author
brice · · 2026-10-19 15:37 UTC
parent
5f6d2c6
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
9 files · +545 -539
M internal/adapters/web/home.go +2 -3
@@ -121,11 +121,10 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
121121 p.Sorts = append(p.Sorts, sl)
122122 }
123123 p.Rows = s.homeRows(ctx, repos, now)
124+ l := s.base()
124125 if n, show := s.inboxCount(ctx); show {
125- p.Inbox = &InboxButton{Href: inboxPath, Count: n}
126+ l.Inbox = &InboxButton{Href: inboxPath, Count: n}
126127 }
127-
128- l := s.base()
129128 l.Title = s.instance
130129 l.Live, l.LiveURL = true, liveURL("home", "")
131130 l.Cmd = append([]CmdItem{{Kind: "go", Label: "home", Href: "/"}}, repoCmds(repos, "")...)
@@ -121,11 +121,10 @@ func (s *Server) home(ctx context.Context, r *http.Request) (templ.Component, in
121 p.Sorts = append(p.Sorts, sl)121 p.Sorts = append(p.Sorts, sl)
122 }122 }
123 p.Rows = s.homeRows(ctx, repos, now)123 p.Rows = s.homeRows(ctx, repos, now)
124+ l := s.base()
124 if n, show := s.inboxCount(ctx); show {125 if n, show := s.inboxCount(ctx); show {
125- p.Inbox = &InboxButton{Href: inboxPath, Count: n}126+ l.Inbox = &InboxButton{Href: inboxPath, Count: n}
126 }127 }
127-
128- l := s.base()
129 l.Title = s.instance128 l.Title = s.instance
130 l.Live, l.LiveURL = true, liveURL("home", "")129 l.Live, l.LiveURL = true, liveURL("home", "")
131 l.Cmd = append([]CmdItem{{Kind: "go", Label: "home", Href: "/"}}, repoCmds(repos, "")...)130 l.Cmd = append([]CmdItem{{Kind: "go", Label: "home", Href: "/"}}, repoCmds(repos, "")...)
M internal/adapters/web/pages.templ +0 -8
@@ -9,14 +9,6 @@ templ Home(l Layout, p HomePage) {
99 <div class="home" data-layout="cards">
1010 <section class="home-repos" aria-label="Repositories">
1111 <div class="home-bar">
12- if p.Inbox != nil {
13- <a class="btn inbox-btn" href={ templ.SafeURL(p.Inbox.Href) } title="inbox">
14- Inbox
15- if p.Inbox.Count >= 0 {
16- <span class="count">{ itoa(p.Inbox.Count) }</span>
17- }
18- </a>
19- }
2012 <div class="filter">
2113 <label for="home-filter" class="sr">filter</label>
2214 <input
@@ -9,14 +9,6 @@ templ Home(l Layout, p HomePage) {
9 <div class="home" data-layout="cards">9 <div class="home" data-layout="cards">
10 <section class="home-repos" aria-label="Repositories">10 <section class="home-repos" aria-label="Repositories">
11 <div class="home-bar">11 <div class="home-bar">
12- if p.Inbox != nil {
13- <a class="btn inbox-btn" href={ templ.SafeURL(p.Inbox.Href) } title="inbox">
14- Inbox
15- if p.Inbox.Count >= 0 {
16- <span class="count">{ itoa(p.Inbox.Count) }</span>
17- }
18- </a>
19- }
20 <div class="filter">12 <div class="filter">
21 <label for="home-filter" class="sr">filter</label>13 <label for="home-filter" class="sr">filter</label>
22 <input14 <input
M internal/adapters/web/pages_templ.go +363 -409 generated file: collapsed
M internal/adapters/web/shell.templ +13 -0
@@ -80,6 +80,19 @@ templ Header(l Layout) {
8080 <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)">
8181 <span class="brand-mark" aria-hidden="true"></span>
8282 </a>
83+ if l.Inbox != nil {
84+ <a
85+ class="btn inbox-btn"
86+ href={ templ.SafeURL(l.Inbox.Href) }
87+ title="inbox"
88+ >
89+ <span class="ico i-mailbox" aria-hidden="true"></span>
90+ Inbox
91+ if l.Inbox.Count >= 0 {
92+ <span class="count">{ itoa(l.Inbox.Count) }</span>
93+ }
94+ </a>
95+ }
8396 if len(l.Crumbs) > 0 {
8497 <nav class="crumbs" aria-label="Path">
8598 <ol>
@@ -80,6 +80,19 @@ templ Header(l Layout) {
80 <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)">80 <a class="brand" href="/" data-g="h" aria-label="home" title="home (g h)">
81 <span class="brand-mark" aria-hidden="true"></span>81 <span class="brand-mark" aria-hidden="true"></span>
82 </a>82 </a>
83+ if l.Inbox != nil {
84+ <a
85+ class="btn inbox-btn"
86+ href={ templ.SafeURL(l.Inbox.Href) }
87+ title="inbox"
88+ >
89+ <span class="ico i-mailbox" aria-hidden="true"></span>
90+ Inbox
91+ if l.Inbox.Count >= 0 {
92+ <span class="count">{ itoa(l.Inbox.Count) }</span>
93+ }
94+ </a>
95+ }
83 if len(l.Crumbs) > 0 {96 if len(l.Crumbs) > 0 {
84 <nav class="crumbs" aria-label="Path">97 <nav class="crumbs" aria-label="Path">
85 <ol>98 <ol>
M internal/adapters/web/shell_templ.go +157 -115 generated file: collapsed
M internal/adapters/web/static/css/20-shell.css +6 -0
@@ -29,6 +29,7 @@
2929 .i-cards { --i: url(icons/cards.svg); }
3030 .i-split { --i: url(icons/split.svg); }
3131 .i-compact { --i: url(icons/compact.svg); }
32+ .i-mailbox { --i: url(icons/mailbox.svg); }
3233
3334 .pill {
3435 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);
@@ -77,6 +78,11 @@
7778 }
7879 /* the hermit by his fire: a grey picture in its own rounded square */
7980 .brand-mark { width: 32px; height: 32px; flex: none; background: url(icons/brand.svg) center / contain no-repeat; }
81+ /* the inbox, right of the logo on the home page: its number in a darker badge */
82+ .inbox-btn {
83+ flex: none;
84+ & .count { margin-left: 0; color: var(--ink-hi); background: var(--bg-canvas); box-shadow: inset 0 0 0 1px var(--border); }
85+ }
8086 .crumbs {
8187 min-width: 0; flex: 1 1 auto; overflow: hidden;
8288 & ol { display: flex; align-items: center; min-width: 0; font-family: var(--mono); font-size: var(--fs); }
@@ -29,6 +29,7 @@
29 .i-cards { --i: url(icons/cards.svg); }29 .i-cards { --i: url(icons/cards.svg); }
30 .i-split { --i: url(icons/split.svg); }30 .i-split { --i: url(icons/split.svg); }
31 .i-compact { --i: url(icons/compact.svg); }31 .i-compact { --i: url(icons/compact.svg); }
32+ .i-mailbox { --i: url(icons/mailbox.svg); }
32 33
33 .pill {34 .pill {
34 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);35 display: inline-flex; align-items: center; height: 20px; padding: 0 var(--s2); margin-left: var(--s1);
@@ -77,6 +78,11 @@
77 }78 }
78 /* the hermit by his fire: a grey picture in its own rounded square */79 /* the hermit by his fire: a grey picture in its own rounded square */
79 .brand-mark { width: 32px; height: 32px; flex: none; background: url(icons/brand.svg) center / contain no-repeat; }80 .brand-mark { width: 32px; height: 32px; flex: none; background: url(icons/brand.svg) center / contain no-repeat; }
81+ /* the inbox, right of the logo on the home page: its number in a darker badge */
82+ .inbox-btn {
83+ flex: none;
84+ & .count { margin-left: 0; color: var(--ink-hi); background: var(--bg-canvas); box-shadow: inset 0 0 0 1px var(--border); }
85+ }
80 .crumbs {86 .crumbs {
81 min-width: 0; flex: 1 1 auto; overflow: hidden;87 min-width: 0; flex: 1 1 auto; overflow: hidden;
82 & ol { display: flex; align-items: center; min-width: 0; font-family: var(--mono); font-size: var(--fs); }88 & ol { display: flex; align-items: center; min-width: 0; font-family: var(--mono); font-size: var(--fs); }
M internal/adapters/web/static/css/60-mr.css +0 -1
@@ -222,7 +222,6 @@
222222 & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); }
223223 & .since { align-self: flex-start; max-width: 100%; }
224224 }
225- .inbox-btn { gap: var(--s2); }
226225
227226 @media (max-width: 700px) {
228227 .list.mrs .c-who { display: none; }
@@ -222,7 +222,6 @@
222 & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); }222 & .inbox-counts strong { color: var(--ink-hi); font-family: var(--mono); }
223 & .since { align-self: flex-start; max-width: 100%; }223 & .since { align-self: flex-start; max-width: 100%; }
224 }224 }
225- .inbox-btn { gap: var(--s2); }
226 225
227 @media (max-width: 700px) {226 @media (max-width: 700px) {
228 .list.mrs .c-who { display: none; }227 .list.mrs .c-who { display: none; }
A internal/adapters/web/static/icons/mailbox.svg +1 -0
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z"/><polyline points="15,9 18,9 18,11"/><path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2"/><line x1="6" x2="7" y1="10" y2="10"/></svg>
@@ -0,0 +1,1 @@
1+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#000" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 17a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9.5C2 7 4 5 6.5 5H18c2.2 0 4 1.8 4 4v8Z"/><polyline points="15,9 18,9 18,11"/><path d="M6.5 5C9 5 11 7 11 9.5V17a2 2 0 0 1-2 2"/><line x1="6" x2="7" y1="10" y2="10"/></svg>
M internal/adapters/web/view.go +3 -3
@@ -71,6 +71,9 @@ type Layout struct {
7171 MR bool
7272 // IssueForm loads the script of the public issue form (issue-form.js).
7373 IssueForm bool
74+ // Inbox is the button to the owner's inbox, next to the logo; set on the
75+ // home page only, for the signed-in owner.
76+ Inbox *InboxButton
7477 }
7578
7679 // Link is a plain label + href pair.
@@ -137,9 +140,6 @@ type SortLink struct {
137140 type HomePage struct {
138141 Sorts []SortLink
139142 Rows []HomeRow
140- // Inbox is the button to the owner's inbox; nil unless the owner is signed
141- // in.
142- Inbox *InboxButton
143143 }
144144
145145 // InboxButton is the link to the inbox with the number of open items; Count is
@@ -71,6 +71,9 @@ type Layout struct {
71 MR bool71 MR bool
72 // IssueForm loads the script of the public issue form (issue-form.js).72 // IssueForm loads the script of the public issue form (issue-form.js).
73 IssueForm bool73 IssueForm bool
74+ // Inbox is the button to the owner's inbox, next to the logo; set on the
75+ // home page only, for the signed-in owner.
76+ Inbox *InboxButton
74 }77 }
75 78
76 // Link is a plain label + href pair.79 // Link is a plain label + href pair.
@@ -137,9 +140,6 @@ type SortLink struct {
137 type HomePage struct {140 type HomePage struct {
138 Sorts []SortLink141 Sorts []SortLink
139 Rows []HomeRow142 Rows []HomeRow
140- // Inbox is the button to the owner's inbox; nil unless the owner is signed
141- // in.
142- Inbox *InboxButton
143 }143 }
144 144
145 // InboxButton is the link to the inbox with the number of open items; Count is145 // InboxButton is the link to the inbox with the number of open items; Count is

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 3

Tags 44

↑↓ moveenter openesc close