A feature release that lifts Scarf's Kanban surface from a read-only list (the v2.6 placeholder shipped while upstream Kanban was still mid-rework) to a full drag-and-drop board with the complete Hermes v0.12 mutation surface wired up — plus per-project boards bound to a Scarf-minted tenant slug, and a read-only board on iOS for at-a-glance status from your phone. No data migrations, no schema changes; pre-v0.12 hosts gracefully hide the surface.
+todo + ready) / Running / Blocked / Done — collapsing Hermes's seven status values into a layout that doesn't waste space on ready, which the dispatcher only ever holds for a few seconds. Triage hides itself when empty; archived hides behind a header toggle. Drop a card onto a column and Scarf maps the gesture to the right Hermes verbs through a pure transition planner: drop-on-Running fires kanban dispatch (the dispatcher then spawns a worker), drop-on-Blocked opens a sheet asking for a reason and calls kanban block, drop-on-Done opens a result sheet and calls kanban complete, blocked → running chains unblock + dispatch. Forbidden transitions (anything dropped on Done; anything dragged out of Triage) reject with a red drop-target stroke and a tooltip explaining why — Done is terminal, Triage is promoted by a specifier worker, neither has a CLI verb that maps cleanly. Optimistic local updates apply on drop and revert on CLI failure with a toast, so the UI feels instant.hermes kanban show <id> data: Comments (with an inline composer that calls kanban comment), Events (the task_events log with per-kind glyphs), Runs (one row per attempt with outcome badge + summary + error), and Log — the worker's captured stdout/stderr from hermes kanban log <id>, polled every 2 s while the task is running with a "● streaming" indicator and auto-scroll to the latest line, snapshot-only with a refresh button when the task is in a terminal state. The action bar at the bottom has all the per-status verbs — Start (which is claim rebranded as a user-visible action), Complete, Block, Unblock, Archive — every one with a help tooltip explaining what it does and what Hermes verb it invokes. The "Archive" tooltip explicitly notes Hermes has no hard-delete: archived tasks remain in ~/.hermes/kanban.db and are recoverable via the "Show archived" toggle until hermes kanban gc runs.running..brand (rust) chip, unassigned means a .warning (yellow) chip so the eye catches it instantly. Tapping opens a menu of every known profile (union of ~/.hermes/profiles/, current task assignees, and the active local profile from HermesProfileResolver) plus an "Unassigned" option. Selection routes through kanban assign and immediately follows with kanban dispatch so the task gets picked up promptly. Solves the "I assigned a profile but nothing happened" gap end-to-end without the user touching a terminal.ready / todo: "Won't run automatically — Hermes's dispatcher silently skips tasks with no assignee." The dispatcher's own --json output literally lists these under skipped_unassigned; we now surface that to the human. Red when the most-recently-completed run ended in a non-success outcome (stale_lock / crashed / gave_up / timed_out / spawn_failed / reclaimed / failed): banner displays the outcome label + the raw error field from the run record, so you don't have to dig into the Runs tab to discover it. The red banner is suppressed while a fresh attempt is running — once status flips back to running, the previous outcome is stale signal and the Log tab's live stream is the right thing to look at.running get a 2 px ScarfColor.info left edge + a subtle title shimmer so live work is obvious at a glance. Blocked cards get a 2 px ScarfColor.warning left edge + a ⚠ glyph next to the title. Done cards dim to 0.7 opacity in light mode, 0.55 in dark, with a green ✓ in the title row. Cards in ready / todo with no assignee get a yellow ⚠ glyph in the title row with a tooltip explaining the dispatcher won't pick them up — same signal as the inspector banner, just at the board level so triage is one keypress away.Board | List toggle at the top of the route. The v2.6 read-only list view is preserved in KanbanListView.swift and surfaced via a segmented picker, so users on narrow windows or anyone who prefers a flat sortable list can opt in. Choice persists across launches via @AppStorage.HermesProfileResolver.activeProfileName() so newly-created tasks actually run), workspace kind (segmented Scratch / Worktree / Project Dir; locked to Project Dir on per-project boards), priority slider, comma-separated skills with autocomplete from ~/.hermes/skills/, optional tenant (hidden on per-project boards — the slug is implicit), and a "Send to triage" toggle. Submit fires kanban create --json and immediately follows with kanban dispatch so an assigned task transitions ready → running within seconds rather than waiting for the gateway dispatcher's internal cycle.DashboardTab.kanban on every project, capability-gated on HermesCapabilities.hasKanban. Renders a project-scoped KanbanBoardView filtered to the project's tenant slug. Workspace defaults in the New Task sheet are pre-pinned to dir:<project.path>. Empty state explains the project doesn't have any tasks yet and offers a "New Task" CTA — the empty board IS the discovery surface.scarf:<slug> tenant minted on first kanban interaction and persisted to <project>/.scarf/manifest.json (new optional kanbanTenant field on ProjectTemplateManifest). Slug rules: lowercased, hyphenated, ≤ 48 chars, scarf: prefix to avoid collision with hand-typed tenants. Once minted, the tenant is immutable across rename — tasks already on the board carry the original slug, so renaming the project doesn't orphan them. Bare projects (no manifest) get a sentinel manifest written with id: scarf/<project-id> + version: 0.0.0 + just the kanbanTenant set; the ProjectAgentContextService reader recognizes the sentinel and refuses to surface it as a "Template" line in the AGENTS.md block, so the project doesn't suddenly start advertising a fake template to the agent.<!-- scarf-project --> markers in <project>/AGENTS.md whenever a tenant exists, instructing the agent to pass --tenant scarf:<slug> on hermes kanban create. ChatViewModel.startACPSession already calls refresh(for:) before opening every project chat, so the agent reads a fresh tenant on every session start with no extra wiring. Agents are imperfect at flag discipline; a forgotten --tenant lands the task in the global "Untagged" group rather than failing — acceptable v2.7.5 behavior.kanban_summary dashboard widget (KanbanSummaryWidgetView.swift). New widget kind for project dashboards: shows the top three running / blocked / todo tasks for the project's tenant by priority, plus a glance footer ("12 todo · 3 running · 5 blocked") sourced from kanban stats. Polls every 10 s while the dashboard is foregrounded. Widget vocabulary registered in tools/widget-schema.json and rendered on the catalog site via site/widgets.js; template authors can drop a { kind: kanban_summary, max_rows: 3 } block into dashboard.json.ProjectDetailView (Scarf iOS/Kanban/ScarfGoKanbanView.swift). Same five-column collapse rendered as a horizontally-paged segmented Picker of single-column lists — HIG-friendly on iPhone where a 5-column grid forces unreadable card widths. Pulls live status, assignee, workspace, skills, priority chips. Tap a card → modal NavigationStack detail sheet (ScarfGoKanbanDetailSheet.swift) with the same Comments / Events / Runs tabs the Mac inspector has. Read-only in v2.7.5 — mutations + drag-drop on iPad land in v2.8 once the Mac flow is fully shaken out. Card titles use semantic .headline (not ScarfFont) so Dynamic Type works; chrome (badges) stays on ScarfBadge for fixed visual weight per the project's iOS conventions.KanbanService actor (Packages/ScarfCore/Sources/ScarfCore/Services/KanbanService.swift) — pure-I/O Sendable actor wrapping every Hermes v0.12 verb (list / show / runs / stats / assignees / create / assign / claim / comment / complete / block / unblock / archive / dispatch / link / unlink / log). Dispatches each CLI invocation through Task.detached(priority: .utility) matching the existing concurrency conventions. Errors land in KanbanError and surface as inline banners (not modal alerts) since the board is high-frequency. The "no matching tasks" stdout sentinel is normalized to [] rather than thrown.KanbanService.plan(for: KanbanTransition) is a synchronous function that maps a (from, to) column pair to the right verb sequence — (.upNext, .running) → [.dispatch], (.blocked, .running) → [.unblock, .dispatch], etc. Disallowed transitions throw KanbanError.forbiddenTransition with a user-actionable reason. The planner is fully tested in KanbanModelsTests.swift. Critically: dispatch (not claim) is the verb used for Up-Next → Running. Hermes's claim is documented as "manual alternative to the dispatcher" and assumes the caller spawns the worker themselves — Scarf doesn't, so calling claim from drag-drop reserved tasks but never spawned work, and the dispatcher reclaimed them ~15 minutes later (stale_lock). dispatch is the right primitive for a GUI client.<project>/.scarf/manifest.json's kanbanTenant field. The full ProjectTemplateManifest type lives in the Mac target; this lightweight reader gives iOS a way to filter the per-project board by tenant without linking the full manifest model.created_at / started_at / completed_at / last_heartbeat_at etc. as Unix integer seconds (its SQLite columns are INTEGER), but earlier wire docs implied ISO-8601 strings. The decoder now accepts either an integer or a string and normalizes to ISO-8601 so downstream code only handles one type. Locked in by decodeUnixIntegerTimestamps in KanbanModelsTests.KanbanBoardViewModel optimistic merge. Holds optimisticOverrides: [taskId: status] for in-flight drags; the polled response merges with optimistic state until the server confirms the new status, so a stale poll arriving milliseconds after a drop can't snap the card back to its old column. On CLI failure the override is removed and the message lands in the inline banner.A diagnostic round driving real tasks end-to-end exposed a connected bug pattern that the polish pass closed:
+kanban dispatch --json output literally lists them under a skipped_unassigned key and moves on. Tasks created without an assignee sat in ready indefinitely and the user had no signal anything was wrong. The New Task sheet now defaults to the active Hermes profile, the inspector header shows a yellow "Unassigned" chip + warning banner, every ready / todo card without an assignee gets a ⚠ glyph + tooltip, and the inspector's inline assignee picker fixes it in one click.claim, which is a manual alternative to the dispatcher. Status flipped to running, but no worker spawned (Scarf doesn't host workers), and 15 minutes later the dispatcher reclaimed the task with a stale_lock outcome. Replaced with dispatch end-to-end so the gateway-running dispatcher actually does the spawning.hermes kanban assignees empty-state was leaking into the picker. The CLI prints a literal sentinel (no assignees — create a profile with hermes -p <name> setup) when the table is empty; the parser was tokenizing it on whitespace and offering (no as a profile in the menu. Parser now skips the sentinel, validates each candidate against ^[a-zA-Z0-9_-]+$, and falls back cleanly to the active local profile when the table is empty.spawn_failed from "executable not found on PATH" — most subtle of the lot. macOS GUI apps inherit a launch-services PATH (/usr/bin:/bin:/usr/sbin:/sbin) that doesn't include ~/.local/bin (where pipx installs hermes) or /opt/homebrew/bin. Scarf was finding hermes for its own invocation via the absolute-path resolver in HermesPathSet.hermesBinaryCandidates, but when the dispatcher then spawned a worker process, that worker inherited Scarf's GUI PATH and couldn't find hermes by name — recording an outcome=spawn_failed run with the exact "executable not found on PATH" message. LocalTransport now grows an environmentEnricher static (mirroring SSHTransport.environmentEnricher) wired by scarfApp.swift to the same HermesFileService.enrichedEnvironment() login-shell probe the SSH transport uses. Every local subprocess Scarf spawns now sees the user's full PATH and credential env, so a spawned-from-Scarf hermes can spawn its children by name without reaching for absolute paths. Defense-in-depth: subprocessEnvironment(forExecutable:) also unconditionally prepends the executable's parent directory to PATH, so the fix works even if the enricher hasn't been wired (early startup, tests).Sparkle will offer the update automatically. No config migration, no schema changes — ~/.hermes/kanban.db is shared across all Hermes clients and Scarf only reads/writes through the documented CLI surface. Existing Scarf projects pick up the new project Kanban tab on first open; the tenant slug is minted lazily on first kanban interaction inside the project, so projects with no kanban activity stay byte-identical until the user opens the tab.
If you have an existing project with a Scarf-managed manifest.json, the new optional kanbanTenant field is added on next mint and lives alongside any template-author config schema without touching it. Templates do not ship kanbanTenant (it's user-machine-scoped state); the export pipeline strips it.
If you've been running tasks via the v2.6 read-only list and your Hermes host already runs the gateway dispatcher, your existing kanban tasks should appear on the board automatically — there's no migration step. Tasks created without an assignee in v2.6 will now show the yellow "Unassigned" warning until you fix them through the inline picker.
+update verb and no position column on the tasks table — priority is write-once at create time. Sort order inside each column is priority DESC, created_at DESC, matching the dispatcher's actual run order. We considered a client-side ordering sidecar; rejected because the on-screen order would diverge from what runs next, which is worse than no manual order. Will revisit if Hermes ships an update --priority verb.watch streaming yet. The board polls every 5 s; the inspector polls detail on the same cadence and the Log tab on a 2 s cadence while running. hermes kanban watch --json event streaming + reconnect-with-backoff lands in v2.8 along with iOS write surfaces.tenant mutation verb post-create, so retagging would be archive + recreate — too destructive to ship in this release.claim vs dispatch distinction, the silent skipped_unassigned behavior, the (no parse leak, the integer-vs-ISO timestamp shape, and the stale "Last run" banner during a fresh attempt all surfaced from driving real tasks and watching what actually happened.