diff --git a/web/src/features/settings/DisplaySection.tsx b/web/src/features/settings/DisplaySection.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..237006d069c20044b6365fc64b0752ef984ed205
--- /dev/null
+++ b/web/src/features/settings/DisplaySection.tsx
@@ -0,0 +1,128 @@
+import React from 'react'
+import { getThemePreference, setTheme } from '../../lib/theme'
+import type { ThemePreference } from '../../lib/theme'
+import { getDensityPreference, setDensity } from '../../lib/density'
+import type { DensityPreference } from '../../lib/density'
+import { getToolCallsPreference, setToolCalls } from '../../lib/toolCalls'
+import type { ToolCallsPreference } from '../../lib/toolCalls'
+
+export function DisplaySection(): React.JSX.Element {
+ const [theme, setThemeState] = React.useState
(getThemePreference)
+ const [density, setDensityState] = React.useState(getDensityPreference)
+ const [toolCalls, setToolCallsState] = React.useState(
+ getToolCallsPreference,
+ )
+
+ function handleThemeChange(value: ThemePreference) {
+ setThemeState(value)
+ setTheme(value === 'system' ? null : value)
+ }
+
+ function handleDensityChange(value: DensityPreference | null) {
+ const next = value ?? 'cozy'
+ setDensityState(next)
+ setDensity(value)
+ }
+
+ function handleToolCallsChange(value: ToolCallsPreference | null) {
+ const next = value ?? 'yes'
+ setToolCallsState(next)
+ setToolCalls(value)
+ }
+
+ return (
+
+ )
+}
diff --git a/web/src/main.tsx b/web/src/main.tsx
index 63883dce1115cf45c2a022e012a1a736f546b9e3..7807e48423aa23703544f2a405bf11695a5b5094 100644
--- a/web/src/main.tsx
+++ b/web/src/main.tsx
@@ -3,6 +3,9 @@ import { createRoot } from 'react-dom/client'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { RouterProvider, createRouter } from '@tanstack/react-router'
import { routeTree } from './routeTree.gen'
+import { bootstrapTheme } from './lib/theme'
+import { bootstrapDensity } from './lib/density'
+import { bootstrapToolCalls } from './lib/toolCalls'
const queryClient = new QueryClient()
@@ -16,6 +19,10 @@ }
const rootEl = document.getElementById('root')
if (!rootEl) throw new Error('Root element #root not found')
+
+bootstrapTheme()
+bootstrapDensity()
+bootstrapToolCalls()
createRoot(rootEl).render(
diff --git a/web/src/routes/__root.tsx b/web/src/routes/__root.tsx
index 6554cf0636dbf5b30cfbcc67d236b04a1ca9c626..de8ea436f093dc7ffc6ab345d2d701a9f8af6432 100644
--- a/web/src/routes/__root.tsx
+++ b/web/src/routes/__root.tsx
@@ -1,6 +1,5 @@
import React, { useState, useEffect, useRef, createContext, useContext } from 'react'
import { createRootRoute, Outlet, useNavigate } from '@tanstack/react-router'
-import { bootstrapTheme } from '../lib/theme'
import { createKeyboardController } from '../lib/keyboard'
import type { RouteName } from '../lib/keyboard'
import { TopBar } from '../shell/TopBar'
@@ -48,9 +47,6 @@ // Ref that routes swap in to wire their cursor into the global keyboard handler
const cursorRef = useRef(noopCursor)
useEffect(() => {
- // Bootstrap theme once on mount
- bootstrapTheme()
-
const controller = createKeyboardController({
go: (route: RouteName) => {
const paths: Record = {
diff --git a/web/src/routes/settings.tsx b/web/src/routes/settings.tsx
index 4836631fbc8bead8ff20380cc51f3b856a66da11..dbdc04b269081015ef43de7c88b585420907754b 100644
--- a/web/src/routes/settings.tsx
+++ b/web/src/routes/settings.tsx
@@ -4,13 +4,14 @@ import { Tag } from '../primitives'
import { SubBar } from '../shell/SubBar'
import { SectionRail } from '../features/settings/SectionRail'
import { SavedSearchesSection } from '../features/settings/SavedSearchesSection'
+import { DisplaySection } from '../features/settings/DisplaySection'
import '../styles/settings.css'
type SectionKey = 'saved-searches' | 'display'
-const SECTIONS: { key: SectionKey; label: string; disabled?: boolean; tag?: string }[] = [
+const SECTIONS: { key: SectionKey; label: string }[] = [
{ key: 'saved-searches', label: 'Saved searches' },
- { key: 'display', label: 'Display', disabled: true, tag: 'in #8' },
+ { key: 'display', label: 'Display' },
]
export const Route = createFileRoute('/settings')({
@@ -29,6 +30,7 @@
{active === 'saved-searches' && }
+ {active === 'display' && }
>
diff --git a/web/src/styles/home.css b/web/src/styles/home.css
index b9814f43faf322a9be905878389e8fb3a2414ba1..cb6588b2048194fd3385f3c208f455d1f3d2f248 100644
--- a/web/src/styles/home.css
+++ b/web/src/styles/home.css
@@ -23,7 +23,7 @@ }
.home-row {
display: grid;
- padding: 4px 14px;
+ padding: var(--row-pad) 14px;
border-bottom: 1px solid var(--rule-2);
align-items: center;
gap: 10px;
diff --git a/web/src/styles/projects.css b/web/src/styles/projects.css
index 5bd0fab4fa102e4476feca7f1e81509131c317af..308acfe3851b765ae96d689f99496b2701e7e2a5 100644
--- a/web/src/styles/projects.css
+++ b/web/src/styles/projects.css
@@ -23,7 +23,7 @@ }
.projects-row {
display: grid;
- padding: 4px 14px;
+ padding: var(--row-pad) 14px;
border-bottom: 1px solid var(--rule-2);
align-items: center;
gap: 10px;
diff --git a/web/src/styles/session.css b/web/src/styles/session.css
index 6a031ad1501a93788b0a7375d7c3f6ea03d1c14a..71a8ddd7feea3e4f48d45a2b7f4e5645a1a88552 100644
--- a/web/src/styles/session.css
+++ b/web/src/styles/session.css
@@ -46,6 +46,8 @@ border-left: 3px solid transparent;
cursor: pointer;
}
+[data-show-toolcalls="false"] .turn[data-tool="1"] { display: none; }
+
.turn.user { background: var(--turn-user); border-left-color: var(--ink-4); }
.turn.assistant { background: var(--turn-asst); border-left-color: var(--accent); }
.turn.tool { background: var(--turn-tool); }
diff --git a/web/src/styles/settings.css b/web/src/styles/settings.css
index b89c17fb0f186060f7b15f0b5d8677b94bda717a..31d146de5f09844a7af8ca0527ccd2e5a67f9895 100644
--- a/web/src/styles/settings.css
+++ b/web/src/styles/settings.css
@@ -173,7 +173,7 @@
.saved-searches-row {
display: grid;
grid-template-columns: 180px 1fr 90px 120px;
- padding: 5px 10px;
+ padding: var(--row-pad) 10px;
border-bottom: 1px solid var(--rule-2);
align-items: center;
gap: 10px;
@@ -191,7 +191,7 @@ }
.saved-searches-row-editing {
display: block;
- padding: 6px 10px;
+ padding: var(--row-pad) 10px;
}
.saved-searches-cell {
@@ -223,3 +223,46 @@
.muted {
color: var(--ink-3);
}
+
+/* ── Display section ──────────────────────────────────────────────────────────── */
+
+.display-section {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+}
+
+.display-section fieldset {
+ border: 1px solid var(--rule-2);
+ border-radius: 4px;
+ padding: 12px 14px;
+ margin: 0;
+}
+
+.display-section legend {
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--ink);
+ padding: 0 4px;
+}
+
+.display-section label {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ padding: 4px 0;
+ font-size: 12px;
+ color: var(--ink-2);
+ cursor: pointer;
+}
+
+.display-section input[type="radio"] {
+ accent-color: var(--accent);
+}
+
+.display-help {
+ font-size: 11px;
+ color: var(--ink-3);
+ margin-top: 4px;
+ padding-left: 18px;
+}
diff --git a/web/src/styles/tokens.css b/web/src/styles/tokens.css
index 3bbdb3e0ab022d57f106f919c3bd12a05b70f7db..e0a282fb9455c9d0e108a7a50282c293f592ee77 100644
--- a/web/src/styles/tokens.css
+++ b/web/src/styles/tokens.css
@@ -53,6 +53,9 @@ --mono: 'JetBrains Mono', ui-monospace, Menlo, Consolas, monospace;
--sans: 'Inter', system-ui, sans-serif;
}
+[data-density="cozy"] { --row-pad: 4px; }
+[data-density="compact"] { --row-pad: 2px; }
+
[data-theme="dark"] {
/* Same accent hue but tuned for dark surface contrast */
--accent: oklch(0.78 0.16 120);