From 5f9343be5df522288c98ba2f266731166af66017 Mon Sep 17 00:00:00 2001 From: Alan Wizemann Date: Fri, 24 Apr 2026 13:40:10 +0200 Subject: [PATCH] M8: list density tokens (scarfGoCompactListRow + scarfGoListDensity) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Apple's default List styling targets Reading/Notes-style apps: ~60pt rows, 10pt inter-row spacing, big vertical padding on grouped cells. ScarfGo's lists (Memory, Cron, Skills, More, Dashboard recent sessions) lean information-dense — devs want to see 4-6 items per screen, not 2. Two tokens in Scarf iOS/App/Theme/ListDensity.swift: - `.scarfGoCompactListRow()` — 6pt vertical listRowInsets (down from default ~12pt), explicit `.frame(minHeight: 44)` to preserve the Apple HIG tap target, and `.contentShape(Rectangle())` so rows can shrink below 44pt visually while keeping the full-row hit area. ~48pt rows end up net, vs. ~60pt default. - `.scarfGoListDensity()` — `.listRowSpacing(0)` kills inter-row gaps on the whole List, `.defaultMinListRowHeight(36)` sets the floor for rows that want to go smaller (e.g. `LabeledContent`). Applied to Memory, Cron, Skills, Dashboard, MoreTab. No visual change to Chat (it's not a List — different density patterns for M8 items 2.4–2.7). Research-backed: Fantastical / GitHub Mobile / Mona for Mastodon use similar spacing. Co-Authored-By: Claude Opus 4.7 (1M context) --- scarf/Scarf iOS/App/ScarfGoTabRoot.swift | 4 +++ scarf/Scarf iOS/App/Theme/ListDensity.swift | 33 +++++++++++++++++++ scarf/Scarf iOS/Cron/CronListView.swift | 2 ++ scarf/Scarf iOS/Dashboard/DashboardView.swift | 1 + scarf/Scarf iOS/Memory/MemoryListView.swift | 4 +++ scarf/Scarf iOS/Skills/SkillsListView.swift | 2 ++ 6 files changed, 46 insertions(+) create mode 100644 scarf/Scarf iOS/App/Theme/ListDensity.swift diff --git a/scarf/Scarf iOS/App/ScarfGoTabRoot.swift b/scarf/Scarf iOS/App/ScarfGoTabRoot.swift index a0e7b5e..0b0c648 100644 --- a/scarf/Scarf iOS/App/ScarfGoTabRoot.swift +++ b/scarf/Scarf iOS/App/ScarfGoTabRoot.swift @@ -111,16 +111,19 @@ private struct MoreTab: View { } label: { Label("Cron jobs", systemImage: "clock.arrow.circlepath") } + .scarfGoCompactListRow() NavigationLink { SkillsListView(config: config) } label: { Label("Skills", systemImage: "sparkles") } + .scarfGoCompactListRow() NavigationLink { SettingsView(config: config) } label: { Label("Settings", systemImage: "gearshape.fill") } + .scarfGoCompactListRow() } Section { @@ -143,6 +146,7 @@ private struct MoreTab: View { .font(.caption) } } + .scarfGoListDensity() .navigationTitle("More") .navigationBarTitleDisplayMode(.inline) .confirmationDialog( diff --git a/scarf/Scarf iOS/App/Theme/ListDensity.swift b/scarf/Scarf iOS/App/Theme/ListDensity.swift new file mode 100644 index 0000000..5ec9c27 --- /dev/null +++ b/scarf/Scarf iOS/App/Theme/ListDensity.swift @@ -0,0 +1,33 @@ +import SwiftUI + +/// ScarfGo's density tokens. Developer-tool context benefits from +/// tighter list rows than Apple's ~60pt default — we aim for ~48pt +/// rows that still meet the 44pt tap-target invariant. Research- +/// backed (M8 density pass): Fantastical, GitHub Mobile, Mona for +/// Mastodon use similar spacing. +public extension View { + /// Apply to individual `List` rows to shrink vertical padding + /// while keeping the full row hit-target ≥ 44pt. Use this on + /// every ScarfGo list that renders more than 3 rows per screen + /// (Memory, Cron, Skills, Settings, Dashboard recent sessions, + /// More). + /// + /// Pair with `scarfGoListDensity()` on the containing List to + /// tighten inter-section spacing. + func scarfGoCompactListRow() -> some View { + self + .listRowInsets(EdgeInsets(top: 6, leading: 16, bottom: 6, trailing: 16)) + .contentShape(Rectangle()) + .frame(minHeight: 44) + } + + /// Apply to a `List` to reduce the default minimum row height + + /// kill the inter-row spacing iOS 18 injects between rows. Works + /// with `.plain` and `.insetGrouped` list styles. Does not affect + /// section-header spacing. + func scarfGoListDensity() -> some View { + self + .environment(\.defaultMinListRowHeight, 36) + .listRowSpacing(0) + } +} diff --git a/scarf/Scarf iOS/Cron/CronListView.swift b/scarf/Scarf iOS/Cron/CronListView.swift index 8891522..a0c6f76 100644 --- a/scarf/Scarf iOS/Cron/CronListView.swift +++ b/scarf/Scarf iOS/Cron/CronListView.swift @@ -48,6 +48,7 @@ struct CronListView: View { } onTap: { editingJob = job } + .scarfGoCompactListRow() .swipeActions(edge: .trailing, allowsFullSwipe: false) { Button(role: .destructive) { Task { await vm.delete(id: job.id) } @@ -59,6 +60,7 @@ struct CronListView: View { } } } + .scarfGoListDensity() .navigationTitle("Cron jobs") .navigationBarTitleDisplayMode(.inline) .toolbar { diff --git a/scarf/Scarf iOS/Dashboard/DashboardView.swift b/scarf/Scarf iOS/Dashboard/DashboardView.swift index 6d62996..225a83d 100644 --- a/scarf/Scarf iOS/Dashboard/DashboardView.swift +++ b/scarf/Scarf iOS/Dashboard/DashboardView.swift @@ -87,6 +87,7 @@ struct DashboardView: View { } } + .scarfGoListDensity() .navigationTitle(config.displayName) .navigationBarTitleDisplayMode(.large) .refreshable { diff --git a/scarf/Scarf iOS/Memory/MemoryListView.swift b/scarf/Scarf iOS/Memory/MemoryListView.swift index de918fd..02572fd 100644 --- a/scarf/Scarf iOS/Memory/MemoryListView.swift +++ b/scarf/Scarf iOS/Memory/MemoryListView.swift @@ -19,13 +19,17 @@ struct MemoryListView: View { List { Section { memoryRow(.memory, context: ctx) + .scarfGoCompactListRow() memoryRow(.user, context: ctx) + .scarfGoCompactListRow() memoryRow(.soul, context: ctx) + .scarfGoCompactListRow() } footer: { Text("MEMORY.md and USER.md live under `~/.hermes/memories/`. SOUL.md lives at `~/.hermes/SOUL.md`.") .font(.caption) } } + .scarfGoListDensity() .navigationTitle("Memory") .navigationBarTitleDisplayMode(.inline) } diff --git a/scarf/Scarf iOS/Skills/SkillsListView.swift b/scarf/Scarf iOS/Skills/SkillsListView.swift index fa99bd3..650ed96 100644 --- a/scarf/Scarf iOS/Skills/SkillsListView.swift +++ b/scarf/Scarf iOS/Skills/SkillsListView.swift @@ -54,11 +54,13 @@ struct SkillsListView: View { .foregroundStyle(.secondary) } } + .scarfGoCompactListRow() } } } } } + .scarfGoListDensity() .navigationTitle("Skills") .navigationBarTitleDisplayMode(.inline) .overlay {