detached d685499

fix(systemviews): refine history overview layout

commit
d68549983bcfd892c1ba1ac86a28d1fb4a71b42b
author
brice · · 2026-04-26 06:50 UTC
parent
95983dd
signature
GPG VerifiedCD88F9E564E0102646FB6077A1A07C7C7C3E349D · arch-gpg Verified for the committer email
5 files · +166 -110
M assets/systemviews/systemviews.css +35 -0
@@ -240,6 +240,29 @@ body {
240240 }
241241 }
242242
243+.sv-history-overview {
244+ display: grid;
245+ gap: 1rem;
246+ align-items: start;
247+}
248+
249+.sv-history-overview > .sv-section {
250+ min-width: 0;
251+ height: 100%;
252+}
253+
254+@media (min-width: 720px) {
255+ .sv-history-overview {
256+ grid-template-columns: minmax(12rem, 3.8fr) minmax(0, 6.2fr);
257+ }
258+}
259+
260+@media (min-width: 900px) {
261+ .sv-history-overview {
262+ grid-template-columns: minmax(13rem, 3fr) minmax(0, 7fr);
263+ }
264+}
265+
243266 .sv-stat-grid {
244267 display: grid;
245268 grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
@@ -247,6 +270,12 @@ body {
247270 margin-top: 0.75rem;
248271 }
249272
273+.sv-history-overview .sv-stat-grid {
274+ grid-template-columns: 1fr;
275+ gap: 0.55rem;
276+ margin-top: 0.5rem;
277+}
278+
250279 .sv-stat-card {
251280 display: grid;
252281 gap: 0.15rem;
@@ -256,10 +285,16 @@ body {
256285 background: var(--sv-surface-variant, #1d2129);
257286 }
258287
288+.sv-history-overview .sv-stat-card {
289+ padding: 0.65rem 0.75rem;
290+}
291+
259292 .sv-stat-value {
260293 color: var(--sv-accent, #8ab4ff);
261294 font-size: 1.35rem;
262295 font-weight: 700;
296+ line-height: 1.05;
297+ font-variant-numeric: tabular-nums;
263298 }
264299
265300 .sv-stat-label {
@@ -240,6 +240,29 @@ body {
240 }240 }
241 }241 }
242 242
243+.sv-history-overview {
244+ display: grid;
245+ gap: 1rem;
246+ align-items: start;
247+}
248+
249+.sv-history-overview > .sv-section {
250+ min-width: 0;
251+ height: 100%;
252+}
253+
254+@media (min-width: 720px) {
255+ .sv-history-overview {
256+ grid-template-columns: minmax(12rem, 3.8fr) minmax(0, 6.2fr);
257+ }
258+}
259+
260+@media (min-width: 900px) {
261+ .sv-history-overview {
262+ grid-template-columns: minmax(13rem, 3fr) minmax(0, 7fr);
263+ }
264+}
265+
243 .sv-stat-grid {266 .sv-stat-grid {
244 display: grid;267 display: grid;
245 grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));268 grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
@@ -247,6 +270,12 @@ body {
247 margin-top: 0.75rem;270 margin-top: 0.75rem;
248 }271 }
249 272
273+.sv-history-overview .sv-stat-grid {
274+ grid-template-columns: 1fr;
275+ gap: 0.55rem;
276+ margin-top: 0.5rem;
277+}
278+
250 .sv-stat-card {279 .sv-stat-card {
251 display: grid;280 display: grid;
252 gap: 0.15rem;281 gap: 0.15rem;
@@ -256,10 +285,16 @@ body {
256 background: var(--sv-surface-variant, #1d2129);285 background: var(--sv-surface-variant, #1d2129);
257 }286 }
258 287
288+.sv-history-overview .sv-stat-card {
289+ padding: 0.65rem 0.75rem;
290+}
291+
259 .sv-stat-value {292 .sv-stat-value {
260 color: var(--sv-accent, #8ab4ff);293 color: var(--sv-accent, #8ab4ff);
261 font-size: 1.35rem;294 font-size: 1.35rem;
262 font-weight: 700;295 font-weight: 700;
296+ line-height: 1.05;
297+ font-variant-numeric: tabular-nums;
263 }298 }
264 299
265 .sv-stat-label {300 .sv-stat-label {
M internal/ui/systemviews/app_test.go +2 -2
@@ -309,8 +309,8 @@ func TestAppLoadInitialHistoryRouteRendersManagementActions(t *testing.T) {
309309 assert.Contains(t, app.renderedHTML, `data-sv-action="history.deleteDomain"`)
310310 assert.Contains(t, app.renderedHTML, `data-domain="example.com:8080"`)
311311 assert.Contains(t, app.renderedHTML, `>example.com</button>`)
312- assert.Contains(t, app.renderedHTML, "Keys:")
313- assert.Contains(t, app.renderedHTML, "Enter")
312+ assert.Contains(t, app.renderedHTML, `data-sv-history-search data-sv-autofocus`)
313+ assert.NotContains(t, app.renderedHTML, "Keys:")
314314 }
315315
316316 func TestAppHandleHistoryActionsRefreshesDOM(t *testing.T) {
@@ -309,8 +309,8 @@ func TestAppLoadInitialHistoryRouteRendersManagementActions(t *testing.T) {
309 assert.Contains(t, app.renderedHTML, `data-sv-action="history.deleteDomain"`)309 assert.Contains(t, app.renderedHTML, `data-sv-action="history.deleteDomain"`)
310 assert.Contains(t, app.renderedHTML, `data-domain="example.com:8080"`)310 assert.Contains(t, app.renderedHTML, `data-domain="example.com:8080"`)
311 assert.Contains(t, app.renderedHTML, `>example.com</button>`)311 assert.Contains(t, app.renderedHTML, `>example.com</button>`)
312- assert.Contains(t, app.renderedHTML, "Keys:")312+ assert.Contains(t, app.renderedHTML, `data-sv-history-search data-sv-autofocus`)
313- assert.Contains(t, app.renderedHTML, "Enter")313+ assert.NotContains(t, app.renderedHTML, "Keys:")
314 }314 }
315 315
316 func TestAppHandleHistoryActionsRefreshesDOM(t *testing.T) {316 func TestAppHandleHistoryActionsRefreshesDOM(t *testing.T) {
M internal/ui/systemviews/dom_js.go +12 -0
@@ -43,6 +43,7 @@ func (d *browserDOM) Mount(markup string) error {
4343 d.updateDocumentTitle()
4444 d.scheduleAlertDismissal()
4545 d.syncPerformanceCustomInputStates()
46+ d.focusAutofocusTarget()
4647 d.setupHistoryInfiniteScroll()
4748 return nil
4849 }
@@ -116,6 +117,17 @@ func (d *browserDOM) updateDocumentTitle() {
116117 d.document.Set("title", title)
117118 }
118119
120+func (d *browserDOM) focusAutofocusTarget() {
121+ if d == nil || !d.target.Truthy() {
122+ return
123+ }
124+ target := d.target.Call("querySelector", "[data-sv-autofocus]")
125+ if !target.Truthy() || !target.Get("focus").Truthy() {
126+ return
127+ }
128+ target.Call("focus")
129+}
130+
119131 func (d *browserDOM) scheduleAlertDismissal() {
120132 if d == nil || !d.target.Truthy() {
121133 return
@@ -43,6 +43,7 @@ func (d *browserDOM) Mount(markup string) error {
43 d.updateDocumentTitle()43 d.updateDocumentTitle()
44 d.scheduleAlertDismissal()44 d.scheduleAlertDismissal()
45 d.syncPerformanceCustomInputStates()45 d.syncPerformanceCustomInputStates()
46+ d.focusAutofocusTarget()
46 d.setupHistoryInfiniteScroll()47 d.setupHistoryInfiniteScroll()
47 return nil48 return nil
48 }49 }
@@ -116,6 +117,17 @@ func (d *browserDOM) updateDocumentTitle() {
116 d.document.Set("title", title)117 d.document.Set("title", title)
117 }118 }
118 119
120+func (d *browserDOM) focusAutofocusTarget() {
121+ if d == nil || !d.target.Truthy() {
122+ return
123+ }
124+ target := d.target.Call("querySelector", "[data-sv-autofocus]")
125+ if !target.Truthy() || !target.Get("focus").Truthy() {
126+ return
127+ }
128+ target.Call("focus")
129+}
130+
119 func (d *browserDOM) scheduleAlertDismissal() {131 func (d *browserDOM) scheduleAlertDismissal() {
120 if d == nil || !d.target.Truthy() {132 if d == nil || !d.target.Truthy() {
121 return133 return
M internal/ui/systemviews/history.templ +9 -8
@@ -12,12 +12,14 @@ templ HistoryView(data historyRenderData) {
1212 @Section("sv-history-controls", "Manage history") {
1313 @HistoryControls(data)
1414 }
15- @Section("sv-history-summary", "History") {
16- @HistorySummary(data)
17- }
18- @Section("sv-history-domains", "Top domains") {
19- @HistoryDomains(data)
20- }
15+ <div class="sv-history-overview">
16+ @Section("sv-history-summary", "History stats") {
17+ @HistorySummary(data)
18+ }
19+ @Section("sv-history-domains", "Top domains") {
20+ @HistoryDomains(data)
21+ }
22+ </div>
2123 @Section("sv-history-timeline", "Recent visits") {
2224 @HistoryTimeline(data)
2325 }
@@ -27,7 +29,7 @@ templ HistoryControls(data historyRenderData) {
2729 <form class="sv-toolbar sv-history-search" data-sv-action="history.search">
2830 <label class="sv-search-label">
2931 <span>Search</span>
30- <input data-sv-history-search name="query" type="search" value={ data.Query } placeholder="Search title or URL" autocomplete="off"/>
32+ <input data-sv-history-search data-sv-autofocus name="query" type="search" value={ data.Query } placeholder="Search title or URL" autocomplete="off" autofocus/>
3133 </label>
3234 <button class="sv-button" type="submit">@ButtonIconLabel(iconSearch, "Search")</button>
3335 <button class="sv-button sv-button-secondary" type="button" data-sv-action="history.clear">@ButtonIconLabel(iconX, "Clear")</button>
@@ -53,7 +55,6 @@ templ HistoryControls(data historyRenderData) {
5355 }
5456 </div>
5557 </div>
56- <p class="sv-meta sv-key-hints">Keys: <kbd>/</kbd> search, <kbd>j</kbd>/<kbd>k</kbd> move, <kbd>Enter</kbd> open, <kbd>d</kbd> delete focused entry.</p>
5758 }
5859
5960 templ HistorySummary(data historyRenderData) {
@@ -12,12 +12,14 @@ templ HistoryView(data historyRenderData) {
12 @Section("sv-history-controls", "Manage history") {12 @Section("sv-history-controls", "Manage history") {
13 @HistoryControls(data)13 @HistoryControls(data)
14 }14 }
15- @Section("sv-history-summary", "History") {15+ <div class="sv-history-overview">
16- @HistorySummary(data)16+ @Section("sv-history-summary", "History stats") {
17- }17+ @HistorySummary(data)
18- @Section("sv-history-domains", "Top domains") {18+ }
19- @HistoryDomains(data)19+ @Section("sv-history-domains", "Top domains") {
20- }20+ @HistoryDomains(data)
21+ }
22+ </div>
21 @Section("sv-history-timeline", "Recent visits") {23 @Section("sv-history-timeline", "Recent visits") {
22 @HistoryTimeline(data)24 @HistoryTimeline(data)
23 }25 }
@@ -27,7 +29,7 @@ templ HistoryControls(data historyRenderData) {
27 <form class="sv-toolbar sv-history-search" data-sv-action="history.search">29 <form class="sv-toolbar sv-history-search" data-sv-action="history.search">
28 <label class="sv-search-label">30 <label class="sv-search-label">
29 <span>Search</span>31 <span>Search</span>
30- <input data-sv-history-search name="query" type="search" value={ data.Query } placeholder="Search title or URL" autocomplete="off"/>32+ <input data-sv-history-search data-sv-autofocus name="query" type="search" value={ data.Query } placeholder="Search title or URL" autocomplete="off" autofocus/>
31 </label>33 </label>
32 <button class="sv-button" type="submit">@ButtonIconLabel(iconSearch, "Search")</button>34 <button class="sv-button" type="submit">@ButtonIconLabel(iconSearch, "Search")</button>
33 <button class="sv-button sv-button-secondary" type="button" data-sv-action="history.clear">@ButtonIconLabel(iconX, "Clear")</button>35 <button class="sv-button sv-button-secondary" type="button" data-sv-action="history.clear">@ButtonIconLabel(iconX, "Clear")</button>
@@ -53,7 +55,6 @@ templ HistoryControls(data historyRenderData) {
53 }55 }
54 </div>56 </div>
55 </div>57 </div>
56- <p class="sv-meta sv-key-hints">Keys: <kbd>/</kbd> search, <kbd>j</kbd>/<kbd>k</kbd> move, <kbd>Enter</kbd> open, <kbd>d</kbd> delete focused entry.</p>
57 }58 }
58 59
59 templ HistorySummary(data historyRenderData) {60 templ HistorySummary(data historyRenderData) {
M internal/ui/systemviews/history_templ.go +108 -100 generated file: collapsed

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 8

Tags 42

↑↓ moveenter openesc close