Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ jobs:

- name: IPC registration check
run: pnpm run test:ipc
- name: Type scale check
run: pnpm run test:type-scale

- name: Frontend tests
run: pnpm exec vitest run
Expand Down Expand Up @@ -98,7 +100,7 @@ jobs:
'',
'Checks run:',
'- Rust: `cargo check --workspace`, `cargo clippy --workspace --all-targets -- -D warnings`, `cargo test --workspace`',
'- Frontend: `pnpm run type-check`, `pnpm run lint`, `pnpm run test:ipc`, `pnpm audit --audit-level moderate`, `pnpm exec vitest run`',
'- Frontend: `pnpm run type-check`, `pnpm run lint`, `pnpm run test:ipc`, `pnpm run test:type-scale`, `pnpm audit --audit-level moderate`, `pnpm exec vitest run`',
'',
'Please inspect the failed workflow run and fix main.'
].join('\n');
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ jobs:

- name: IPC registration check
run: pnpm run test:ipc
- name: Type scale check
run: pnpm run test:type-scale

- name: Dependency audit
run: pnpm audit --audit-level moderate
Expand Down
52 changes: 19 additions & 33 deletions .tickets/_docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,18 @@ replaces the terminal.
with a 60s budget and **never** hard-kills — exhausting it injects anyway and
leaves the pane inspectable. `CLI_HEALTH_SPECS` now probes the interactive
codex path so the same drift can't ship silently again.
- Settings wiring pass (Appearance): all five settings now reach the UI. Font Size
was worse than unwired — 247 arbitrary `text-[Npx]` values across 63 files were
absolute, so at "small" the root dropped to 12px while a caption pinned at 11px
stayed put and rendered *larger* than the `text-sm` body above it. The scale is
now four rem steps (`text-2xs` added to `@theme`, since Tailwind stops at `xs`),
all 247 converted, and `scripts/check-type-scale.js` fails CI on the next
absolute size. Card Density and Animation Speed were fully inert: the chain died
at `.card-padding`/`.card-gap`/`.transition-appearance`, three helper classes
zero components used. Density now drives the task card's padding and the
column's inter-card gap directly; Animation Speed rides Tailwind's own
`--default-transition-duration`, so every `transition-*` utility honours it
without opting in. The three dead classes are gone.
- Interactive done-advisory persisted (migration 048, `tasks.agent_done_signaled_at`):
the signal used to be a Tauri event only, so it existed just while the agent
panel was mounted. Now the board shows a "Ready to advance" badge that
Expand All @@ -50,37 +62,11 @@ replaces the terminal.

## 🟡 Important (rough edges, not blockers)

2. **Settings wiring pass — three of five Appearance settings don't reach the UI.**
Measured 2026-08-21 in a real browser, not inferred. The store→DOM plumbing is
fine (`lib/appearance.ts`, called from `main.tsx` and `settings-store.ts`);
what's missing is anything *consuming* the result.

| Setting | State |
|---|---|
| Theme | works |
| Accent colour | works (sets `--accent`) |
| **Font size** | **partial, and actively wrong at "small"** |
| **Card density** | **inert** |
| **Animation speed** | **inert** |

- **Font size.** `--base-font-size` lands on `html`, so rem-based utilities do
scale — `text-sm` measures 10.5 / 12.25 / 14px across small/medium/large.
But **247 arbitrary `text-[Npx]` values across 63 files** (144× `text-[10px]`,
96× `text-[11px]`) are absolute and never move. At "small" that *inverts the
hierarchy*: `text-sm` drops to 10.5px while a caption pinned at `text-[11px]`
stays larger than the body text it's meant to sit under.
Fix: add rem-based steps to the `@theme` block in `index.css` (the scale
wants a `text-micro`/`text-tiny` below `text-xs`) and convert the 247 uses.
- **Card density / animation speed.** The chain is
store → `data-card-density` / `data-animation-speed` → `--card-padding` /
`--card-gap` / `--transition-duration` → `.card-padding` / `.card-gap` /
`.transition-appearance`. That last hop is where it dies: those three helper
classes are used by **zero** components. Either apply them on the card/panel
primitives or drop the settings.

While in there, re-check the other known-inert surfaces listed under
"Inert / deferred" below — custom keyboard shortcuts render but do nothing,
and the OpenRouter / Google / Ollama provider cards are "Coming soon".
2. **Other inert settings surfaces.** Flagged while wiring Appearance, not fixed:
custom keyboard shortcuts render but do nothing (`shortcuts-tab.tsx:54`), and
the OpenRouter / Google / Ollama provider cards are "Coming soon". Framer
Motion animations also ignore Animation Speed — they read no CSS variable, so
"none" doesn't fully mean none.

3. **`effort_level` not wired into the trigger path.** It's a real DB field + has a
`thinking-selector.tsx` and works in the interactive chat panel, but no
Expand Down Expand Up @@ -123,5 +109,5 @@ replaces the terminal.

## Suggested order

`#1 rm stale claude` (free) → `#2 settings wiring pass` → `#3 effort wiring` →
`#4 checklist/roadmap MCP` → `#5 MCP gaps`.
`#1 rm stale claude` (free) → `#3 effort wiring` → `#4 checklist/roadmap MCP` →
`#5 MCP gaps`. `#2` is cosmetic cleanup; do it opportunistically.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"type-check": "tsc --noEmit",
"lint": "eslint src/",
"test:ipc": "node scripts/check-ipc-registration.js",
"test:type-scale": "node scripts/check-type-scale.js",
"format": "prettier --write 'src/**/*.{ts,tsx,css}'",
"test": "vitest",
"test:run": "vitest run",
Expand Down
59 changes: 59 additions & 0 deletions scripts/check-type-scale.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env node

/**
* Guards Settings → Appearance → Font Size.
*
* That setting works by putting `--base-font-size` on <html>, so only
* rem-based type scales with it. An arbitrary pixel size is absolute: it
* ignores the setting entirely. Once enough of them accumulate the hierarchy
* doesn't just stop scaling, it inverts — at "small" the root drops to 12px so
* `text-sm` renders 10.5px, and a caption pinned at a fixed 11px comes out *larger*
* than the body text it sits under.
*
* 247 of these had built up before this check existed. Use the scale instead:
* text-2xs / text-xs / text-sm / text-base / text-lg …
*/

import fs from 'node:fs'
import path from 'node:path'

const root = process.cwd()
const sourceExtensions = new Set(['.ts', '.tsx', '.css'])
const ARBITRARY_FONT_SIZE = /text-\[\d+(?:\.\d+)?(px|pt)\]/g

function walk(dir, files = []) {
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
if (entry.name === 'node_modules' || entry.name === 'dist') continue
const fullPath = path.join(dir, entry.name)
if (entry.isDirectory()) {
walk(fullPath, files)
} else if (sourceExtensions.has(path.extname(entry.name))) {
files.push(fullPath)
}
}
return files
}

const offenders = []
let scanned = 0
for (const file of walk(path.join(root, 'src'))) {
scanned += 1
const lines = fs.readFileSync(file, 'utf8').split('\n')
lines.forEach((line, i) => {
for (const match of line.matchAll(ARBITRARY_FONT_SIZE)) {
offenders.push(`${path.relative(root, file)}:${i + 1} ${match[0]}`)
}
})
}

if (offenders.length > 0) {
console.error('Absolute font sizes found — these ignore the Font Size setting:')
for (const offender of offenders) {
console.error(` - ${offender}`)
}
console.error('\nUse a rem step from the scale instead: text-2xs, text-xs, text-sm, text-base, text-lg.')
console.error('If a genuinely fixed size is required, add a rem token to the @theme block in src/index.css.')
process.exit(1)
}

console.log(`Type scale check passed (${scanned} file(s), no absolute font sizes).`)
4 changes: 2 additions & 2 deletions src/components/checklist/checklist-item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function ChecklistItemRow({ item, categoryId, onFixThis }: Props) {
{/* Auto-detect badge */}
{item.detectType && item.detectType !== 'none' && (
<span
className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-[10px] font-medium ${
className={`inline-flex items-center gap-1 rounded px-1.5 py-0.5 text-xs font-medium ${
item.autoDetected
? 'bg-success/20 text-success'
: 'bg-bg-secondary text-text-secondary'
Expand All @@ -91,7 +91,7 @@ export function ChecklistItemRow({ item, categoryId, onFixThis }: Props) {
{/* Linked task badge */}
{item.linkedTaskId && (
<span
className="inline-flex items-center gap-1 rounded bg-accent/20 px-1.5 py-0.5 text-[10px] font-medium text-accent"
className="inline-flex items-center gap-1 rounded bg-accent/20 px-1.5 py-0.5 text-xs font-medium text-accent"
title="Linked to task"
>
Linked Task
Expand Down
6 changes: 3 additions & 3 deletions src/components/command-palette/command-palette.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ export function CommandPalette({ onClose, onShowShortcuts }: Props) {
autoFocus
className="flex-1 bg-transparent text-sm text-text-primary placeholder:text-text-secondary focus:outline-none"
/>
<kbd className="rounded bg-bg px-1.5 py-0.5 font-mono text-[10px] text-text-secondary" aria-label="Escape to close">
<kbd className="rounded bg-bg px-1.5 py-0.5 font-mono text-xs text-text-secondary" aria-label="Escape to close">
Esc
</kbd>
</div>
Expand Down Expand Up @@ -345,11 +345,11 @@ export function CommandPalette({ onClose, onShowShortcuts }: Props) {
<div className="flex items-center gap-0.5" aria-hidden="true">
{cmd.shortcut.map((key, j) => (
<span key={j}>
<kbd className="rounded bg-bg px-1.5 py-0.5 font-mono text-[10px] text-text-secondary">
<kbd className="rounded bg-bg px-1.5 py-0.5 font-mono text-xs text-text-secondary">
{key}
</kbd>
{j < (cmd.shortcut?.length ?? 0) - 1 && (
<span className="mx-0.5 text-[10px] text-text-secondary">+</span>
<span className="mx-0.5 text-xs text-text-secondary">+</span>
)}
</span>
))}
Expand Down
2 changes: 1 addition & 1 deletion src/components/kanban/column-automation-sentence.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export function AutomationSentence({ onEntry, setOnEntry, exitCriteria, setExitC
<span>.</span>
</div>

<p className="text-[11px] text-text-secondary/60">
<p className="text-xs text-text-secondary/60">
The prompt, runtime, retries and more live under Advanced. Most columns never need them.
</p>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/components/kanban/column-config-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ function CapacityFields({
aria-pressed={selected}
>
<span className="block text-sm font-medium leading-4">{option.label}</span>
<span className="mt-1 block text-[11px] leading-3 text-text-secondary/70">{option.hint}</span>
<span className="mt-1 block text-xs leading-3 text-text-secondary/70">{option.hint}</span>
</button>
)
})}
Expand Down
10 changes: 5 additions & 5 deletions src/components/kanban/column-header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ export const ColumnHeader = memo(function ColumnHeader({
>
{getIcon(icon)}
</span>
<span className="rounded bg-surface-hover px-1.5 py-0.5 text-[10px] font-medium text-text-secondary tabular-nums">
<span className="rounded bg-surface-hover px-1.5 py-0.5 text-xs font-medium text-text-secondary tabular-nums">
{taskCount}
</span>
{isRenaming ? (
Expand Down Expand Up @@ -240,7 +240,7 @@ export const ColumnHeader = memo(function ColumnHeader({
<button
type="button"
onClick={() => { setMetricsExpanded(false) }}
className="flex items-center gap-1 rounded text-[10px] text-text-secondary/60 tabular-nums whitespace-nowrap hover:text-text-secondary"
className="flex items-center gap-1 rounded text-xs text-text-secondary/60 tabular-nums whitespace-nowrap hover:text-text-secondary"
>
<span>⏱{formatDuration(metrics.avgDurationSeconds)}</span>
<span className="text-text-secondary/30">·</span>
Expand All @@ -267,7 +267,7 @@ export const ColumnHeader = memo(function ColumnHeader({

{scriptTrigger && (
<span
className="truncate rounded bg-purple-500/10 px-1.5 py-0.5 text-[10px] font-medium text-purple-400"
className="truncate rounded bg-purple-500/10 px-1.5 py-0.5 text-xs font-medium text-purple-400"
title={`Script: ${scriptTrigger.scriptName} (${scriptTrigger.event === 'both' ? 'entry + exit' : `on ${scriptTrigger.event}`})`}
>
{scriptTrigger.scriptName}
Expand All @@ -276,7 +276,7 @@ export const ColumnHeader = memo(function ColumnHeader({

{/* Batch queue progress badge */}
{batchQueue && (
<span className="rounded bg-accent/15 px-1.5 py-0.5 text-[10px] font-medium text-accent">
<span className="rounded bg-accent/15 px-1.5 py-0.5 text-xs font-medium text-accent">
Queued: {batchQueue.completed}/{batchQueue.total}
</span>
)}
Expand All @@ -292,7 +292,7 @@ export const ColumnHeader = memo(function ColumnHeader({
wrap
>
<span
className={`rounded px-1.5 py-0.5 text-[10px] font-medium tabular-nums ${
className={`rounded px-1.5 py-0.5 text-xs font-medium tabular-nums ${
agentConcurrency.running >= agentConcurrency.max
? 'bg-warning/10 text-warning'
: agentConcurrency.running > 0
Expand Down
2 changes: 1 addition & 1 deletion src/components/kanban/column-trigger-action-editors.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ export function ActionEditor({
<span className="text-amber-400" title="Irreversible action">⚠</span>
)}
</div>
<div className="mt-0.5 text-[10px] opacity-60 leading-tight">{t.description}</div>
<div className="mt-0.5 text-xs opacity-60 leading-tight">{t.description}</div>
</button>
)
})}
Expand Down
2 changes: 1 addition & 1 deletion src/components/kanban/column.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ export const Column = memo(function Column({
<SortableContext items={taskIds} strategy={verticalListSortingStrategy}>
<div
ref={setDroppableRef}
className={`flex min-h-0 flex-1 flex-col gap-2 overflow-y-auto px-2 pt-1 pb-2 transition-colors ${
className={`flex min-h-0 flex-1 flex-col gap-[var(--card-gap)] overflow-y-auto px-2 pt-1 pb-2 transition-colors ${
isOver ? 'bg-accent/5' : ''
}`}
>
Expand Down
6 changes: 3 additions & 3 deletions src/components/kanban/drag-overlay.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function DragOverlayContent({ item }: DragOverlayContentProps) {

{/* Metadata row */}
{hasMetadata && (
<div className="flex items-center gap-x-3 text-[11px] text-text-secondary">
<div className="flex items-center gap-x-3 text-xs text-text-secondary">
{cardSettings.showPrBadge && task.prNumber && (
<span className="inline-flex items-center gap-1">
<svg className="h-3 w-3" viewBox="0 0 16 16" fill="currentColor">
Expand All @@ -84,14 +84,14 @@ export function DragOverlayContent({ item }: DragOverlayContentProps) {
{taskLabels.slice(0, 3).map((label) => (
<span
key={label.id}
className="inline-flex items-center gap-1 rounded-full bg-surface-hover px-2 py-0.5 text-[10px] text-text-secondary"
className="inline-flex items-center gap-1 rounded-full bg-surface-hover px-2 py-0.5 text-xs text-text-secondary"
>
<span className="h-1.5 w-1.5 rounded-full" style={{ backgroundColor: label.color }} />
{label.name}
</span>
))}
{taskLabels.length > 3 && (
<span className="text-[10px] text-text-secondary/70">+{taskLabels.length - 3}</span>
<span className="text-xs text-text-secondary/70">+{taskLabels.length - 3}</span>
)}
</div>
)}
Expand Down
12 changes: 6 additions & 6 deletions src/components/kanban/spawn-cli-action-editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ export function SpawnCliActionEditor({
}`}
>
<span className="block text-xs font-medium">{opt.label}</span>
<span className="block text-[10px] opacity-60">{opt.description}</span>
<span className="block text-xs opacity-60">{opt.description}</span>
</button>
)
})}
Expand Down Expand Up @@ -147,13 +147,13 @@ export function SpawnCliActionEditor({
}`}
>
<span className="block text-xs font-medium">{mode.label}</span>
<span className="block text-[10px] opacity-60">{mode.caption}</span>
<span className="block text-xs opacity-60">{mode.caption}</span>
</button>
)
})}
</div>
{isManagedMode && (
<p className="mt-1 text-[10px] text-amber-400">
<p className="mt-1 text-xs text-amber-400">
Managed mode streams structured events; Terminal is more reliable for long-running agents.
</p>
)}
Expand Down Expand Up @@ -182,8 +182,8 @@ export function SpawnCliActionEditor({
<div className="mt-1.5 grid grid-cols-2 gap-x-4 gap-y-0.5 rounded-lg border border-border-default bg-bg/60 p-2">
{TEMPLATE_VARIABLES.map((v) => (
<div key={v.name} className="flex items-baseline gap-1.5">
<code className="shrink-0 text-[10px] font-mono text-accent">{v.name}</code>
<span className="truncate text-[10px] text-text-secondary">{v.desc}</span>
<code className="shrink-0 text-xs font-mono text-accent">{v.name}</code>
<span className="truncate text-xs text-text-secondary">{v.desc}</span>
</div>
))}
</div>
Expand All @@ -199,7 +199,7 @@ export function SpawnCliActionEditor({
className="h-4 w-4 rounded border-border-default accent-accent"
/>
Use agent queue
<span className="ml-0.5 rounded bg-bg-elevated px-1.5 py-0.5 text-[10px] text-text-secondary">
<span className="ml-0.5 rounded bg-bg-elevated px-1.5 py-0.5 text-xs text-text-secondary">
max 3 concurrent
</span>
</label>
Expand Down
Loading
Loading