mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 10:36:35 +00:00
v2.7.0
373 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
f9a288ac6c |
fix(ios-chat): dismissable keyboard via swipe + toolbar button (#51)
Pre-fix the iOS composer's TextField had no keyboard dismissal: no @FocusState, no scrollDismissesKeyboard, no keyboard accessory. With axis: .vertical + submitLabel: .send the Return key inserts a newline rather than committing, so once the keyboard rose it stayed up — hiding the top-trailing toolbar button on small phones. Three additive changes: - @FocusState private var composerFocused on ChatView, bound to the TextField via .focused($composerFocused). - .scrollDismissesKeyboard(.interactively) on the message list ScrollView so dragging the messages downward collapses the keyboard with the gesture (the standard iOS chat pattern the reporter explicitly named — "swipe away"). - ToolbarItemGroup(placement: .keyboard) accessory with a keyboard.chevron.compact.down "Done" button so dismissal is also available without a scrollable area (e.g. fresh empty-state chat before any messages exist). ScarfGo iOS only. Mac unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
bb33a39b42 |
fix(profiles): respect Hermes v0.11 active_profile (#50)
Hermes v0.11's `hermes profile` feature gives each profile its own
HERMES_HOME directory: the default profile is ~/.hermes, named
profiles live at ~/.hermes/profiles/<name>/. Each has its own
state.db, sessions/, config.yaml, .env, memories/, cron/, etc.
The active profile is recorded in ~/.hermes/active_profile.
Pre-fix Scarf hardcoded ~/.hermes and ignored active_profile, so
`hermes profile use coder` followed by a Scarf relaunch left Scarf
reading the wrong state.db — the new profile's chat sessions
silently never appeared.
Add HermesProfileResolver in ScarfCore that reads active_profile
and returns the effective home path. HermesPathSet.defaultLocalHome
becomes a static var backed by the resolver; every derived path
(stateDB, sessionsDir, configYAML, memoriesDir, cron paths, plugins,
gateway state, auth.json, etc.) automatically follows the active
profile through the existing `home + suffix` plumbing — no
downstream call sites need to change.
Resolver semantics:
- Absent / empty / "default" file → ~/.hermes (today's behavior)
- Valid profile name pointing to an existing dir → that dir
- Invalid name OR missing target → fall back to ~/.hermes with a
one-line os.Logger warning (so worst case is "Scarf shows what
it always showed")
Validation regex mirrors Hermes's hermes_cli/profiles.py exactly
([a-z0-9][a-z0-9_-]{0,63}). 5-second cache via OSAllocatedUnfairLock
keeps hot-path filesystem hits negligible.
SessionInfoBar gains a leftmost profile chip when not "default" so
users can see which profile Scarf is reading from. Tooltip explains
how to switch (`hermes profile use <name>` + relaunch).
Out of scope (deferred):
- In-app profile picker that writes to active_profile. Switching
mid-session is messy (open ACP processes are bound to whichever
HERMES_HOME spawned them); the reporter's "switch + restart" flow
is what we fix here.
- Remote SSH profile awareness. defaultRemoteHome stays "~/.hermes"
— remote profile selection is a separate, larger feature needing
its own UI.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
e828538a2d |
docs(privacy): correct sandbox claim — Scarf macOS is unsandboxed by design
The privacy policy claimed "the macOS app is sandboxed where possible" and that uninstall removes "~/Library/Containers/com.scarf". Both wrong: - Per scarf/CLAUDE.md "Sandbox disabled. Scarf needs to read ~/.hermes/ directly." Scarf cannot ship App-Sandboxed because it needs direct filesystem access to ~/.hermes/ and the ability to spawn the hermes CLI — both forbidden by the App Sandbox. - ~/Library/Containers/com.scarf doesn't exist for an unsandboxed app; data lives at ~/Library/Caches/scarf/, ~/Library/Preferences/com.scarf.app.plist, and ~/Library/Application Support/com.scarf/. Replaced both with accurate text. Also clarified that ScarfGo on iOS DOES run inside the standard iOS sandbox — no special entitlements beyond Keychain. The wiki mirror at .wiki-worktree/Privacy-Policy.md got the same fix in the corresponding wiki audit commit. Caught during the v2.5 wiki audit pass. Will re-publish to gh-pages in v2.5.1 alongside other queued doc updates. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
051f3bf80c |
feat(chat): density preferences for tool cards, reasoning, font (#47, #48)
Three Scarf-local @AppStorage-backed preferences in
Settings → Display → Chat density. All defaults match today's UI;
existing users see no change until they opt in.
- Tool calls: Full card (today) / Compact chip / Hidden
- Compact: one-line tappable chip per call (icon + name + status
dot). Tap focuses the call so the right-pane inspector opens
with full args + result, same as today's inline expand.
- Hidden: per-call rows skipped entirely. The MessageGroupView
toolSummary pill ("Used 5 tools (3 read, 2 edit)") becomes
the only chrome AND becomes tappable — clicking focuses the
first call so per-call duration / exit code remain reachable
via the inspector. Pill is now shown for any call count > 0
in hidden mode (was > 1) so the inspector path is always
available. Issue #47.
- Reasoning: Disclosure box (today) / Inline (italic) / Hidden
- Inline: italic foregroundFaint caption inline above the reply
with a 9pt brain prefix. No box, no border. Same data, far
less vertical space.
- Hidden: reasoning text not rendered. Per-message tokenCount
(which the disclosure label was duplicating) stays in the
metadataFooter so token telemetry isn't lost. Issue #48.
- Chat font size: 85%–130% slider (5% step) applied via
.environment(\.dynamicTypeSize, ...) on RichChatView's root,
scaling message list / input bar / session info bar / inspector
pane together. Reset button restores 100%. Issue #48.
Telemetry preservation (the user-stated constraint):
- Per-turn stopwatch, per-message tokenCount, finish reason, and
message timestamp remain in the bubble metadataFooter in every
mode.
- SessionInfoBar input/output/reasoning tokens, cost USD, model,
project, git branch, and started-at relative time are unchanged
by every density setting.
- Per-call duration + exit code stay reachable via the inspector
pane in compact and hidden modes.
Out of scope (called out in the plan):
- Context-fill widget — Hermes v0.11 doesn't expose context_used
/ context_total per session. Approximating from messages.tokenCount
+ a static window table would be wrong-on-purpose; defer until
Hermes ships the canonical field.
- iOS — ScarfGo already renders both surfaces compactly. Both
issues reference Mac.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
558970a09a |
perf(chat-ios): mirror Mac equatable short-circuit on ScarfGo bubbles (#46)
ScarfGo's chat is a separate rendering path: LazyVStack + ForEach(controller.vm.messages) with a private MessageBubble struct (not the shared MessageGroupView/RichMessageBubble used on Mac). The Mac fix's Equatable conformances therefore didn't propagate. Without short-circuiting, every visible bubble re-evaluates body on each streamed ACP chunk because the @Observable VM's `messages` mutation invalidates anyone reading it — and each bubble's `ChatContentFormatter.segments` + `AttributedString(markdown:)` are both O(content) per render. LazyVStack already keeps off-screen bubbles dormant on iOS, but the 5–10 visible bubbles re-parsing on every chunk is enough to bog down a long turn on phone hardware. Add Equatable to MessageBubble (id-keyed, with content/reasoning/ toolCalls.count compared only for the streaming bubble id==0) and apply .equatable() at the ForEach call site. Settled bubbles short- circuit body re-eval; the streaming bubble still redraws per chunk. Note: the trailing-group patch helper (Mac fix part 2) already benefits iOS as a side effect — buildMessageGroups() is no longer called per chunk, and even though iOS doesn't read messageGroups directly, the elided rebuild is still wasted work avoided. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8d9de4c576 |
perf(chat): stop O(n)-per-token re-render of settled bubbles (#46)
Long chats progressively bog down and eventually crash because every streamed ACP token triggers a full messageGroups rebuild plus a body re-evaluation of every MessageGroupView and RichMessageBubble — even the n-1 settled groups that haven't changed. Three changes cap per-chunk work at "patch the trailing group + re-render the streaming bubble": - MessageGroupView and RichMessageBubble are now Equatable, applied via .equatable() in the ForEach. Settled groups (no streaming message inside) short-circuit body re-evaluation entirely; the streaming group compares content/reasoning/toolCalls.count so it still redraws on every chunk. - RichChatViewModel.upsertStreamingMessage no longer calls buildMessageGroups() per chunk. New patchTrailingGroupForStreaming mutates only the trailing group's assistant entry in place. The 9 other call sites of buildMessageGroups() are untouched — they cover structural events (user message, tool-call complete, finalize, session resume) where group boundaries can actually change, and a full rebuild is correct there. - MessageGroup.toolKindCounts is now a model property (was a MessageGroupView computed prop that re-walked O(m × k) per body render). Lives behind the Equatable short-circuit. - ToolCallCard.formatJSON cached via .task(id: call.callId) so JSON pretty-printing runs once per card lifetime instead of on every expand/collapse + every neighbour's re-render. Seeded with raw arguments to avoid a first-frame empty-text flicker. - ToolResultContent.lines/preview cached via .task(id: content) — the prior pair of computed properties split content on \n twice per render, expensive on long command/file output. Skipped from the original plan: the per-message parse cache (rendered moot once Equatable already short-circuits settled bubbles) and the LazyVStack switch (deferred — RichChatMessageList comments flag scroll-anchor regression risk; revisit separately if needed). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
e0f0fad192 |
fix(release): post-package verification + non-destructive recovery docs
Add codesign --verify --strict --deep + spctl --assess on the extracted distribution zip inside build_variant() so any seal regression introduced by ditto / staple / future pipeline tweaks fails the release before users see "damaged" errors. Document the non-destructive recovery path in README and explicitly warn against `xattr -rc` and `codesign --force --deep --sign -` (issue #49 — both corrupt Sparkle.framework's nested XPC service / Updater.app signatures even when the outer app remains intact). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
80a4d23974 |
docs(readme): shrink ScarfGo gallery thumbs 180->140px so 5 fit in one row
GitHub's README content column is ~770px wide. 180px x 5 + spacing overflowed and wrapped 4+1 (the System tab dropped to its own line), breaking the gallery's "thumbnail strip" reading. 140px x 5 lands at ~700px including spacing, comfortably within the column. No content change to the screenshots or paths — just the width attr. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d95ef61e13 |
docs(readme): ScarfGo screenshot gallery under the v2.5 What's New section
Five 1284x2778 simulator captures from the iPhone 17 Pro Max stock sim, dropped in at assets/screenshots/scarfgo-*.png. The README gallery is HTML inside the existing Markdown — five thumbnails at 180px wide, centered, each wrapped in an <a href> pointing back at the same file so a click opens the full-resolution PNG via GitHub's asset viewer (the closest thing the README format supports to a lightbox). Order matches the user flow: Servers list -> Chat with Hermes -> Project dashboard (Site Status Checker template, dogfooding the catalog) -> Skills browser -> System tab. One italic caption underneath labels the screens in order. 3.4 MB total. iPhone 17 Pro Max is the canonical capture device for v2.5; the App Store listing will use the same shots once they need cropping/framing for Apple's screenshot specs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
988ce5df5a |
docs(readme): rename hero icon to bust GitHub's raw-asset CDN cache
The previous commit replaced icon.png on disk with the rust v2.5 artwork, but GitHub's raw-asset CDN was still serving the cached purple PNG to README viewers (~5 min TTL — but in practice longer under sustained traffic). Renaming the asset forces a fresh fetch on every README render, which is the reliable cache-bust. icon-v2.5.png is bit-identical to the prior icon.png (md5 match against the Mac app icon set's 512x512). The version in the filename is intentional — when v2.6 ships with a different icon, we'll cycle to icon-v2.6.png and the same cache-bust applies. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
3bca8a6e55 |
docs(readme): swap home-page hero icon for the v2.5 rust app icon
icon.png at the repo root drives the centered hero block on the GitHub README. It was still the pre-rust design from v2.0; replaced with the rust ScarfDesign 512x512 sourced from the Mac app icon set so the home page matches the in-app branding now that v2.5.0 has shipped. Also bumps the source resolution from 256x256 to 512x512 — the README displays it at 128x128, so retina + HiDPI displays now render crisply without losing the asset's intent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b5f4f65ffe | chore: Bump version to 2.5.0 v2.5.0 | ||
|
|
b474286bfe |
build(ios): iPhone-only — drop iPad, macCatalyst, Designed-for-iPhone-iPad
Locks the `scarf mobile` target to iPhone before TestFlight submission: - TARGETED_DEVICE_FAMILY 1,2 -> 1 (iPhone only) - SUPPORTED_PLATFORMS constrained to iphoneos + iphonesimulator - SUPPORTS_MACCATALYST = NO - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO - SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO Applied symmetrically to Debug + Release configs. iPad layout via .tabViewStyle(.sidebarAdaptable) hasn't been smoke-tested and was explicitly out of scope for v2.5; flipping the device-family flag prevents Apple's review tooling from picking up an unsupported device class. Mac and visionOS are similarly excluded — ScarfGo is an iPhone-only companion in v2.5; the iPad / Catalyst story is its own future release. Both targets build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b1e2fc5dcd |
docs(v2.5): home-page focus + RELEASE_NOTES under-the-hood + App Store metadata
README: strip the "Previously, in 2.3" subsection per release direction — the home page is now a single-version forward-looking surface with prior releases linked off to the wiki Release Notes Index. Promote the ScarfGo TestFlight callout to its own subsection with the public link (testflight.apple.com/join/qCrRpcTz) embedded inline. Add a "Connect ScarfGo to your Hermes server" five-step walkthrough between What's New and Multi-server, mirroring OnboardingRootView's state machine so users can follow it cold without opening the wiki first. RELEASE_NOTES: extend the Under-the-hood section with the iOS-side maintenance work that landed in the last 48h — Citadel executeCommandStream rewrite (preserves stdout on non-zero remote exit; was eating Skills hub Browse output), inline PATH= prepend on every iOS runProcess (Citadel's raw exec channel doesn't source shell rc files), fd-leak cleanup across the three transports + ProcessACPChannel, ServerLiveStatus 10/30/60/120/300s exponential backoff for unreachable remotes, and the print() -> os.Logger sweep. APP_STORE_METADATA.md: full App Store Connect copy ready for paste — app name, subtitle, promotional text (153/170 chars), 2873/4000-char description in three paragraphs (what / features / privacy), brand-safe keywords (85/100 chars; no competitor product names), support / marketing / privacy URLs, category, age rating, 1150/4000-char "What's New" text. Screenshots flagged as out-of-scope for this prep pass — user captures from the simulator before App Store submission. TestFlight checklist remains the canonical doc for the in-flight beta submission. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
87fcbad1ac |
fix(ios): App Store validation — strip 1024 alpha + drop iCloud entitlements
Apple's TestFlight upload validator rejected v2.5 with two errors; fixing both for the next archive. 1. **Invalid large app icon** (alpha channel). `AppIcon.appiconset/AW Mac OS Applications-macOS-Default-1024x1024@1x.png` was RGBA — Apple rejects any 1024×1024 marketing icon with an alpha channel. Composited the icon onto a solid white background via PIL and resaved as RGB PNG. `sips -g hasAlpha` now reports `no`. The file's design is solid edge-to-edge, so the white-fill is invisible — no visual change. 2. **Invalid Code Signing Entitlements** (`com.apple.developer.icloud-container-environment` empty string). `Scarf_iOS.entitlements` had `com.apple.developer.icloud-services = [CloudKit]` + `com.apple.developer.icloud-container-identifiers = []`. Xcode's signing phase synthesises `com.apple.developer.icloud-container-environment` from this combo, and with no container identifier the value lands as empty — which Apple's validator rejects. Per the privacy policy I drafted in v2.5 ("no iCloud Keychain sync, no cloud accounts") Scarf doesn't actually use iCloud, so removing the iCloud entries is the correct fix. Dropped both `com.apple.developer.icloud-services` and `com.apple.developer.icloud-container-identifiers`. Kept `aps-environment = development` — push capability stays declared but gated off via `NotificationRouter.apnsEnabled = false` until the cert + Hermes-side sender land. iOS scheme builds clean post-fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
63c5d13bec |
chore: fd-leak cleanup, os.Logger conversion, status-poll backoff
Pre-release maintenance pass picked up across both targets while
debugging the iOS Browse fix:
- LocalTransport / SSHTransport: close the parent's copy of every
pipe write end after spawn so EOF reaches the reader once the child
exits, and explicitly close read ends after draining. Was leaking
one fd per `runProcess`/`streamLines` invocation under load.
- ProcessACPChannel: also close stdout/stderr write-end fds on
channel teardown — same pattern, ACP sessions can churn on long
reconnect loops.
- HermesDataService / HermesLogService / ProjectDashboardService:
replace remaining `print("[Scarf] ...")` debug statements with
os.Logger calls (subsystem="com.scarf"), matching the global rule
that production code uses Logger and `print()` is reserved for
previews + test helpers.
- ProjectDashboardService / IOSCronViewModel: drop redundant
`fileExists` guards before `createDirectory` — the operation is
already mkdir -p across every transport, so the extra round-trip
was pure latency on remote hosts.
- scarfApp.swift: server-status sidebar probe now uses an exponential
backoff (10s → 30s → 60s → 120s → 300s) when consecutive probes
fail, resetting on the first full success. Previously a registered
remote going unreachable hammered pgrep + gateway_state.json every
10s indefinitely; now offline servers settle to a 5-min cadence
while live servers stay snappy.
- Localizable.xcstrings: routine .strings catalog refresh — stale
entries for removed UI strings, picked up new "Stored under
quick_commands:..." subtitle wording.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
850fa7a697 |
fix(ios): preserve hermes output on non-zero exit + extend remote PATH
Two related fixes that together restore Skills hub Browse / Search on iOS over Citadel SSH. CitadelServerTransport.asyncRunProcess was using `executeCommand`, which throws `CommandFailed` and discards the captured ByteBuffer when the remote process exits non-zero. `hermes skills browse` happens to print its full table and then exit non-zero on some hosts, so iOS got nothing while Mac (Foundation Process) got the full output with exitCode=1. Drive `executeCommandStream` directly so stdout + stderr are drained regardless of outcome, then catch `SSHClient.CommandFailed` to recover the actual exit code. Network/channel-level failures still report -1 so callers can distinguish them from a clean non-zero remote exit. Citadel's raw exec channel also doesn't source the user's shell rc files, so non-interactive sessions land with a stripped PATH (typically just /usr/bin:/bin). pipx installs `hermes` at `~/.local/bin/hermes`, and many of hermes's sub-tools (git, curl, python) live in homebrew prefixes that the remote sshd would otherwise add via login-shell init. Mac's OpenSSH sshd handles this transparently; Citadel does not. Inline PATH=$HOME/.local/bin:/opt/homebrew/bin:/usr/local/bin:$PATH on every runProcess invocation so bare `hermes` resolves AND any subprocess it spawns can still find its tools. SkillsViewModel.finishBrowse now surfaces the actual stderr/stdout snippet when the CLI exits non-zero, instead of a canned "Browse failed" banner. ANSI-stripped + box-drawing-stripped so the message stays readable in the one-line banner. Made diagnosing the underlying PATH issue much easier and is a net UX improvement going forward. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
21e3cc9361 |
feat(ios): rust page background + dashboard switch-server button
Sweeps the rust ScarfDesign page background onto the screens that were still rendering against the iOS default: Skills/Hub, Skills/Updates, all three project sub-views, and Skill Detail. Lists adopt .scrollContentBackground(.hidden) + ScarfColor.backgroundPrimary, with .listRowBackground(ScarfColor.backgroundSecondary) on rows so the Mac-style elevated-card density carries through. Adds a "Switch server" toolbar button to Dashboard's top-right, threaded through ScarfGoTabRoot from the connected-server host. One tap soft- disconnects and returns to the server list — same code path the System tab already exposes, just reachable without first navigating away from Dashboard. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
295f2dfefc |
feat(ios): Mac-style page backgrounds + Dashboard + Chat redesign
iOS now uses ScarfColor.backgroundPrimary throughout instead of the default iOS systemGroupedBackground. List-based screens add .scrollContentBackground(.hidden) + the rust background underneath; list rows use ScarfColor.backgroundSecondary as their card surface. Applied to: Projects, Memory, Cron, Settings, Skills/Installed, and the Servers root. Dashboard rewritten in Mac-style cards (no more native iOS list): - ScrollView + VStack with rust background - Activity stat grid (2-col LazyVGrid) with bordered rust-tinted cards: Sessions / Messages / Tool Calls / Tokens (with in/out sub- label). - Recent sessions card — bordered, ScarfColor.backgroundSecondary, inline session rows with 1px dividers, "See all" nav to Sessions sub-tab. - Error banner styled with ScarfColor.warning tinted card per Mac. - Sessions sub-tab keeps a List view but renders against rust background with ScarfColor.backgroundSecondary row backgrounds. Chat redesigned to match the Mac chat reference: - User bubble: rust accent fill with ScarfColor.onAccent text and uneven rounded corners (top/bottom-leading + top-trailing 14px; bottom-trailing 4px) — visually pinches to the sender side, same as Mac. - Assistant bubble: rust gradient sparkles avatar tile (24x24) alongside a ScarfColor.backgroundSecondary bordered card. - ToolCallCard: kind-tinted border + uppercase tracked label (READ/EDIT/EXECUTE/FETCH/BROWSER) using ScarfColor.success/info/ warning/Tool.web/Tool.search; expanded JSON in a bordered ScarfColor.backgroundSecondary panel. - ReasoningDisclosure: warning-tinted card with REASONING uppercase label. Both Mac (scarf) and iOS (scarf mobile) schemes build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
de611c5343 |
feat(ios): adopt ScarfDesign across the iOS app
AccentColor.colorset repointed to BrandRust hex (light + dark) so the tab bar, every .tint, every default button, and every navigation accent across all 5 tabs read rust automatically. Single-line fix, biggest visible change. ScarfDesign now imported across all 27 iOS view files. Color sweep applies the same patterns as the Mac side, with two iOS-specific deviations documented in CLAUDE.md: - ScarfPageHeader is NOT retrofitted onto iOS tab roots. iOS uses .navigationTitle(...) + .navigationBarTitleDisplayMode(.large) as its native page-header pattern; stacking ScarfPageHeader on top creates double titles. ScarfPageHeader is reserved for sub-views without a native large-title bar. - Only .borderedProminent → ScarfPrimaryButton. .bordered and .plain stay native because .bordered is the iOS convention for non-primary buttons and inherits rust through AccentColor automatically. Dynamic Type policy (locked + documented in CLAUDE.md): preserve .font(.headline)/.body/.caption semantic tokens for body copy, list rows, error messages, and chat content (anything read for content). Use ScarfFont only for status badges, chip labels, intentional fixed- size display elements. Mass-swapping ScarfFont on iOS would regress accessibility for users on .accessibility2 / .xSmall. Files touched (27 view files + AccentColor + CLAUDE.md): - Color sweep: .foregroundStyle(.secondary) → ScarfColor.foregroundMuted, Color(.secondarySystemBackground) → ScarfColor.backgroundSecondary, status colors (.orange/.green/.red) → ScarfColor.warning/success/danger in: Dashboard, Skills (root + Installed + Hub + Updates + Detail), Projects (root + Detail + Sessions + Site + 8 widgets), Memory (List + Editor), Cron, Settings (root + Editor), Servers, Chat (root + Picker + Slash browser), Onboarding. - Primary button swap (5 files): Chat, Projects/Sessions, Skills/ Updates, Skills/Hub, Onboarding. - CLAUDE.md: appended "iOS Dynamic Type policy" + "iOS page chrome" guidance under the existing Design System section. Both Mac (scarf) and iOS (scarf mobile) schemes build green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
23dd8becb9 |
polish: tokenize remaining sheets, page headers, and widgets
Phase 1 — Page headers for the 9 non-mockup feature views: Skills, Gateway, Platforms, Personalities, QuickCommands, CredentialPools, Plugins, Webhooks, Profiles. Each now ships a ScarfPageHeader with title + subtitle + tokenized trailing actions (ScarfPrimary / Secondary / Ghost buttons), wrapped in .fixedSize so labels can't wrap at narrow widths. Outer .background(ScarfColor.backgroundPrimary). Phase 2 — Modal sheets: ModelPickerSheet, NousSignInSheet, RenameProjectSheet, MoveToFolderSheet, the five Templates sheets (TemplateInstall / TemplateConfig / TemplateExport / TemplateUninstall / ConfigEditorSheet), three MCPServer sheets (AddCustom / Editor / PresetPicker), AddServerSheet, ManageServersView, MissingServerView. .font(.headline) -> .scarfStyle(.headline); .buttonStyle(.borderedProminent) -> ScarfPrimaryButton(); raw text fields where touched -> ScarfTextField; cancel buttons -> ScarfGhostButton. Phase 3 — All 12 platform setup views (Discord / Email / Feishu / HomeAssistant / IMessage / Matrix / Mattermost / Signal / Slack / Telegram / Webhook / WhatsApp). Connect buttons swapped to ScarfPrimaryButton. Phase 4 — All 7 project dashboard widgets (Chart / List / Progress / Stat / Table / Text / Webview). .font(.caption) -> .scarfStyle(.caption); .background(.quaternary.opacity(0.5)) -> ScarfColor.backgroundSecondary; RoundedRectangle(cornerRadius: 8) -> ScarfRadius.lg. Phase 5 — Project sub-views: ProjectSessionsView, ProjectsSidebar, ProjectSlashCommandsView. Same token sweep. Phase 6 — Common chrome: - LoadingOverlay: .font(.callout/caption) -> .scarfStyle; secondary foreground -> ScarfColor.foregroundMuted; window-background -> ScarfColor.backgroundPrimary. - ServerSwitcherToolbar: status dot + label tokenized. - ConnectionStatusPill: status colors -> ScarfColor.success/warning/ danger; error sheet header -> ScarfPrimaryButton retry. Build green on both Mac (scarf) and iOS (scarf mobile) schemes. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
41769e289c |
feat(chat): port the 3-pane chat layout + ScarfDesign telemetry
Sessions list (264 px) | transcript (flex) | inspector (320 px) per design/static-site/ui-kit/Chat.jsx and the ScarfChatView reference. Built over the real ChatViewModel + RichChatViewModel — live ACP streaming pipeline untouched. HermesToolCall gains optional duration / exitCode / startedAt fields (backwards-compatible, nil defaults; not Codable). RichChatViewModel populates them on ACP toolCallStart / toolCallUpdate; mutates the streaming entry before finalize so the persisted call carries telemetry. Sessions loaded from state.db gracefully render "—" when nil. ChatViewModel gains focusedToolCallId + a focusedToolCall computed helper. ToolCallCard takes onFocus / isFocused — single click both focuses the inspector and toggles inline expansion (two paths to the same data per locked decision). Border weight + tint bump signal the focused card. Sessions pane: project filter (matches Sessions feature semantics), search field, project chips per row, right-click rename + delete via hermes sessions rename / delete --yes. Recent-sessions limit bumped 10 -> 50 so the project filter has data. loadRecentSessions commits all four observables in a single MainActor batch to eliminate the brief flash on session switch. ChatView toolbar's redundant Session menu trimmed (left pane is canonical). ChatTranscriptPane wraps existing SessionInfoBar + RichChatMessageList + RichChatInputBar without owning new state. RichChatView body becomes a fixed 3-pane HStack — ViewThatFits was downgrading to transcript-only when transcript content widened mid-load. Inspector: STATUS / ARGUMENTS / TELEMETRY / PERMISSIONS in the Details tab; STDOUT in dark mono panel under Output; full JSON envelope under Raw. Footer Re-run is stubbed (TODO: /retry path); Copy puts the raw JSON envelope on the clipboard. ProjectSlashCommandsView: empty-state ContentUnavailableView now centers in the full pane via .frame(maxWidth/maxHeight: .infinity). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8a2d89654b |
feat(design): adopt ScarfDesign system across Mac UI
Add a typed design-system package (Packages/ScarfDesign) with rust-tone color tokens, type scale, spacing/radius tokens, ScarfPageHeader and component primitives (ScarfCard, ScarfBadge, ScarfTextField, ScarfSectionHeader, ScarfDivider, four button styles). Both Mac and iOS targets `import ScarfDesign`. Sidebar redesigned per design/static-site/ui-kit/Sidebar.jsx — glassy translucent background, 224 px width, app-icon header with server pill, custom tokenized rows with rust accent-tint when active, footer with live Hermes-running indicator (wired to ServerLiveStatusRegistry). 14 mockup-backed feature screens redesigned: Settings, Dashboard, Sessions, Memory, Chat (visual sweep), Activity, Cron, Insights, MCPServers, Health, Logs, Tools (full); Projects light-touch. Non-mockup features inherit rust through AccentColor.colorset repoint. Mac AppIcon.appiconset replaced with the rust set. AccentColor.colorset repointed to BrandRust hex (light + dark variants). Visual sweep: every multi-button page-header / action-bar cluster now wraps in .fixedSize(horizontal: true, vertical: false) so labels can't wrap letter-by-letter at narrow widths (regression seen on the MCP detail pane with 4 buttons). Follow-ups landed: - Sidebar Hermes-running probe wired to per-window ServerLiveStatusRegistry (no more placeholder green). - Sessions: today filter predicate (isDateInToday(startedAt)); pill count reflects real count. Starred stays a no-op pending an upstream pinned/starred field on HermesSession. - Dashboard: Recent activity column rendered alongside Recent sessions in a ViewThatFits 2-col grid. Populated from HermesDataService.fetchRecentToolCalls(limit:) flattened to ActivityEntry. ActivityEntry gains a public memberwise init. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f04d95c960 |
refactor(mac-skills): delegate loadSkills to shared SkillsScanner (Phase E)
Cleanup pass: HermesFileService.loadSkills() was duplicating walk logic that the new ScarfCore SkillsScanner now owns. Replaced the ~38-line implementation with a one-line delegation. Removed: - HermesFileService.loadSkills() walk body (38 lines). - HermesFileService.parseSkillFrontmatter (24 lines, supersedes by SkillFrontmatterParser.parseV011Fields). - HermesFileService.parseSkillRequiredConfig (24 lines, superseded by SkillFrontmatterParser.parseRequiredConfig). The remaining HermesFileService surface (loadSkillContent, saveSkillContent, isValidSkillPath) is unchanged — those are Mac- target-specific guards on file paths that don't fit ScarfCore. Tab enum audit: searched for orphan `.memory` / `.more` references under Scarf iOS/. None found — the worktree refactor cleanly migrated every selectedTab assignment to the new 5-tab vocabulary. Verified: ScarfCore 197 tests + 28 catalog tests + Mac + iOS builds all green (Phase F gate). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
26c034ea6f |
feat(ios-skills): port v0.11 features to new file structure (Phase D)
Re-port the four v0.11 iOS Skills features that lived in the now- deleted Skills/SkillsListView.swift into the new Installed/SkillDetailView + Skills/SkillsView surfaces. iOS Components/FlowLayout.swift (NEW, promoted helper): - 50-line struct conforming to SwiftUI's Layout protocol; wraps subviews onto multiple lines on overflow. Built-in API, no third- party dep. Originally inline in the deleted SkillsListView; promoted so multiple iOS views can share without duplicating ~30 lines. iOS Skills/Installed/SkillDetailView.swift (extend): - design-md npx prereq banner: yellow "Prerequisite missing" section triggered by .task(id: skill.id) probing `which npx` via SkillPrereqService when the selected skill is design-md. - Spotify info row: green "Authentication" section pointing users at the Mac sheet or shell for OAuth — phone OAuth flows are deferred in v2.5. - SKILL.md frontmatter chip rows: three sections (Allowed tools / Related skills / Dependencies) using a chipRow helper backed by the shared FlowLayout. Each section hides itself when its HermesSkill field is nil — old skills without v0.11 frontmatter show none of these. iOS Skills/SkillsView.swift (extend): - "What's New" pill at the top of the tab (above the sub-tab picker). Driven by SkillSnapshotService.diff() against the per- server last-seen snapshot. First-load primes silently so users don't see "everything is new!" noise on day one. - Recomputes on .task and .refreshable. - "Seen" button persists the current set + dismisses. Verified: iOS build succeeds. The chip-row data path is now end-to-end (SkillsScanner → HermesSkill → SkillDetailView chips) and the snapshot pill matches the Mac SkillsView placement. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
84b033814b |
feat(scarfcore): SkillsScanner populates v0.11 frontmatter (Phase C)
Post-merge follow-up: the new SkillsScanner constructed HermesSkill with `requiredConfig` only — leaving `allowedTools` / `relatedSkills` / `dependencies` (added in my v0.11 Phase 3.3) as nil. Detail-view chip rows would render empty. SkillFrontmatterParser: - New `parseV011Fields(_:) -> (allowedTools:, relatedSkills:, dependencies:)` reader. Reuses HermesYAML.parseNestedYAML to extract the three lists from the SKILL.md frontmatter region between `---` markers. Returns nil-everything when the file has no frontmatter or the fields are absent / empty — chip rows hide. - Existing `parseRequiredConfig(_:)` unchanged. SkillsScanner: - Reads `<skill>/SKILL.md` opportunistically (after the `<skill>/skill.yaml` read), parses v0.11 frontmatter, passes the three optional arrays into the HermesSkill constructor. - Old skills without SKILL.md or without frontmatter keep nil and scan keeps working. Tests: - 5 new SkillFrontmatterParserTests cases covering happy path, partial fields, no frontmatter, empty fields, empty input. - 10 total tests for the parser; all green. Verified: ScarfCore builds clean. The chip-row data path is now end-to-end (scan → HermesSkill → detail view) for both Mac and iOS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
3d4a6a3a75 |
Merge branch 'claude/pedantic-mcnulty-bac7cc' (iOS UI refactor)
Brings the major iOS UI refactor into scarf-mobile-development on top
of the v0.11 work that landed after the merge base (commit
|
||
|
|
a73025aba0 |
feat(ios): 5-tab nav + Projects/Skills feature parity with Mac
Major iOS UI refactor that brings ScarfGo to feature parity with the Mac app for Projects + Skills, on top of a ScarfCore consolidation that unifies the view-model + scanner/parser layer between platforms. Layout (ScarfGoTabRoot): - Old: Chat / Dashboard / Memory / More (4 tabs). - New: Dashboard / Projects / Chat / Skills / System (5 tabs, Chat centered). Memory + Cron + Settings consolidate under System. Projects (NEW iOS feature): - ProjectsListView, ProjectDetailView, ProjectSessionsView_iOS, ProjectSiteView. - Widgets/ subdir: 7 widget views (Chart, List, Progress, Stat, Table, Text, Webview) + WidgetHelpers + DashboardWidgetsView. - Tied to chat via ScarfGoCoordinator.startChatInProject() which sets pendingProjectChat + flips selectedTab to .chat. Skills (NEW iOS surface): - SkillsView is a 3-sub-tab switcher (Installed / Browse Hub / Updates). - Installed/: InstalledSkillsListView, SkillDetailView, SkillEditorSheet. - Hub/HubBrowseView for the skills hub catalog. - Updates/UpdatesView for hermes skills check / update. ScarfCore consolidation: - SkillsViewModel and ProjectSessionsViewModel lift from Mac target into ScarfCore so iOS and Mac share one type. - New SkillsScanner walks ~/.hermes/skills/ once for both platforms via the supplied transport. - New SkillFrontmatterParser handles required_config: parsing. - New HermesSkillsHubParser for the hub catalog format. - Tests for both new parsers. Mac touchpoints: - Features/Skills/Views/SkillsView.swift: .onAppear wraps the now- async load() in a Task. - Old Mac-target SkillsViewModel and ProjectSessionsViewModel deleted (replaced by ScarfCore). Coordinator + chat: - ScarfGoCoordinator gains pendingProjectChat: String? + startChatInProject(path:) helper. - iOS ChatView consumes pendingProjectChat (mirrors the existing pendingResumeSessionID pattern); resolves path → ProjectEntry via registry, falls back to a synthesized entry on miss. Tests: - M5FeatureVMTests renames 3 IOSSkillsViewModel references to the shared SkillsViewModel. - New SkillFrontmatterParserTests + SkillsHubParserTests. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
99f734bf0b |
feat(ios-memory): hermes memory reset on iOS too (cross-platform parity)
Mac shipped the toolbar Reset button in Phase 5; iOS gets it in the final verification pass for parity. iOS MemoryListView: - Toolbar button (counterclockwise icon) opens a destructive confirmation dialog matching the Mac copy. - resetMemory() shells out via context.makeTransport().runProcess, using the same PATH-prefix trick IOSSettingsViewModel.saveValue uses so non-interactive remote shells find hermes in ~/.local/bin / /opt/homebrew/bin / ~/.hermes/bin. - Success and failure both surface alerts (success message confirms the wipe; failure surfaces stderr+stdout combined). Verified: iOS build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
ca1eb54a5b |
docs(v2.5): extend release notes + README with Hermes v0.11 work (Phase 8)
Phase 8 of the v2.5 plan — fold the Hermes v2026.4.23 integration into the existing v2.5 release artifacts rather than creating a v2.6 set. releases/v2.5.0/RELEASE_NOTES.md: - Lead paragraph extended to mention slash commands, chat parity, Spotify, design-md. - Six new sections: Portable project-scoped slash commands, Hermes v2026.4.23 chat parity, Spotify + design-md skill onboarding, SKILL.md frontmatter chips, "What's New" pill, state.db deltas, hermes memory reset. - All inserted before the existing "Mac global Sessions" section so the Hermes-v0.11 work reads as the headline alongside ScarfGo. README.md: - "What's New in 2.5" lead bullets gain slash commands, Hermes v0.11 chat parity, Spotify+design-md, SKILL.md chips, snapshot pill. - Test count bumped 163 → 179. - Requirements: Hermes recommended bumped from v0.10.0+ to v0.11.0+ with feature attribution. - Compatibility table: v0.11.0 row added as the current target; v0.10.0 row demoted to "Tool Gateway introduced". - Targeting paragraph rewritten for v2.5/v0.11. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f35bc910e4 |
feat(memory): hermes memory reset toolbar action + v0.11 CLI doc (Phase 5)
Adopt the lowest-risk new CLI subcommand from Hermes v2026.4.23 — `hermes memory reset --yes` — and document the deferred ones for v2.6. Wholesale plugin/profile/webhook/logs adoption is forward- compatible work the existing services don't block on; deferring keeps v2.5 scope tight. MemoryView: - Toolbar button "Reset memory…" with .arrow.counterclockwise icon. - Confirmation dialog explaining the destructive semantics (no undo, wipes both MEMORY.md and USER.md). Routes through context.runHermes(["memory", "reset", "--yes"]); on non-zero exit shows the stderr in an alert. Refreshes the on-screen content on success. CLAUDE.md: - "Hermes Version" section now leads with v2026.4.23 (v0.11.0) and enumerates the v2.5-adopted features (slash steer, state.db deltas, new skills, frontmatter chips, memory reset) with file pointers. v2.6-deferred CLIs (plugins / profile / webhook / insights / logs) are flagged so future bandwidth knows where to pick up. Verified: Mac build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
8057beb001 |
feat(state.db): reasoning_content + api_call_count (Phase 4)
Hermes v2026.4.23 added two columns to state.db: - messages.reasoning_content — newer richer reasoning channel some providers emit alongside the legacy messages.reasoning blob. - sessions.api_call_count — distinct from tool_call_count; counts per-turn API round-trips so the user can see the cost breakdown. ScarfCore models: - HermesMessage gains reasoningContent: String? + computed preferredReasoning + updated hasReasoning to consider both channels. - HermesSession gains apiCallCount: Int (default 0 for old hosts). ScarfCore HermesDataService: - hasV011Schema flag detects both new columns via PRAGMA table_info; only flips true when BOTH are present (partial migrations stay on the v0.7 path to avoid runtime "no such column" errors). - sessionColumns / messageColumns / searchMessages SELECT lists conditionally append the new columns. - sessionFromRow / messageFromRow read them defensively (column index 20 / 11 respectively when v0.11 schema is on). UI surfacing: - Mac SessionDetailView shows "<N> API" label next to msgs/tools when apiCallCount > 0. - Mac Dashboard SessionRow + iOS Dashboard sessionRow add a network-icon chip with the API call count. - Mac RichMessageBubble + iOS MessageBubble switch to message.preferredReasoning for the disclosure body. Verified: ScarfCore + Mac + iOS build. 179/179 ScarfCore tests pass unchanged (existing tests didn't construct sessions/messages with the new fields; defaults preserve behaviour). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
751c9e6778 |
feat(skills): SkillSnapshotService + 'What's New' pill (Phase 3.4)
Per-server snapshot of skill signatures so the Skills tab can show "2 new, 4 updated since you last looked" — same pattern Hermes's `hermes skills update` CLI shows on the host. ScarfCore SkillSnapshotService: - [skillId: signature] map, signature is `<fileCount>:<sorted-files>`. New / removed / files-changed all show up as a delta. - diff(against:) returns SkillSnapshotDiff with counts + a label string for the pill. - markSeen(_:) persists the current set. - Backend abstraction: file-based on Mac, UserDefaults on iOS, in-memory for tests. - previousSnapshotEmpty silently primes first-load so users don't see "everything is new!" noise. Mac SkillsView: - whatsNewPill(diff:) tinted pill at the top with "Mark as seen". - recomputeSnapshotDiff() on .task and on totalSkillCount change. iOS SkillsListView: - Same pill rendered as a Section row with "Seen" button. - Recompute on .task + .refreshable. Verified: Mac + iOS builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
5c08c09dde |
feat(skills): SKILL.md frontmatter v0.11 fields (Phase 3.3)
Hermes v2026.4.23 SKILL.md files carry richer YAML frontmatter: allowed_tools, related_skills, dependencies. Surface them as chip rows in the skill detail view on both platforms. ScarfCore HermesSkill: - Three new optional fields: allowedTools, relatedSkills, dependencies. Default-nil so older skills (no SKILL.md, or SKILL.md without these fields) load unchanged. Mac HermesFileService.parseSkillFrontmatter: - Reads `<skill>/SKILL.md`, splits at `---` markers, parses the frontmatter via HermesYAML.parseNestedYAML, and extracts the three list fields. Tuple-of-optionals return; nil-everything when the file is absent or has no frontmatter. iOS IOSSkillsViewModel.parseFrontmatter: - Mirror with the iOS transport (over SFTP). Same parser, same return shape. Mac SkillsView: - skillChipSection(title:items:) helper renders a labelled chip row. Three rows added between the existing missing-config / Spotify / npx surfaces and the file list — only shown when the corresponding field is non-empty. iOS SkillDetailView: - chipRow(_:) helper using a small in-file FlowLayout (built-in Layout protocol, no third-party dep) so the chips wrap onto multiple lines on iPhone-narrow screens. Three sections matching Mac. Verified: ScarfCore + Mac + iOS builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7ec7282f36 |
feat(skills): design-md npx prereq check (Phase 3.2)
design-md (Hermes v2026.4.23) requires `npx` (Node.js 18+) on the host to invoke `npx @google/design.md`. Probe the host's PATH when the skill is selected; surface a yellow banner with an install hint when missing. ScarfCore SkillPrereqService: - probe(binary:installHint:) async -> Status — runs `/usr/bin/env which <binary>` via the transport with a 4s timeout. Returns .present / .missing(hint) / .unknown(reason). - installHints table for npx / node / gws / ffmpeg with terse per-OS install guidance. Skills can pass custom hints if their install path is more involved. Mac SkillsView: - @State designMdNpxStatus + .onChange(of: selectedSkill.name) triggers the probe whenever the user lands on the design-md skill. Banner renders only on .missing — present and unknown cases stay silent (avoids false-alarm noise on transient SSH errors). iOS SkillDetailView: - @State npxStatus + .task(id: skill.id) per-skill probe. - Same banner with the same hint copy; no install button (user is already on iPhone, fixing the host needs a shell anyway). Verified: ScarfCore + Mac + iOS builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
97aa988762 |
feat(skills): Spotify auth flow + sign-in sheet (Phase 3.1)
Hermes v2026.4.23 ships a `spotify` skill that needs OAuth via `hermes auth spotify`. Mirror the v2.3 Nous Portal in-app sign-in pattern so users don't have to drop to a shell. Mac (full sign-in flow): - SpotifyAuthFlow.swift in Core/Services — @Observable @MainActor, five-state machine (idle → starting → waitingForApproval(URL) → verifying → success | failure). Spawns `hermes auth spotify` via the transport, regex-detects the `https://accounts.spotify.com/authorize?...` URL on stdout/stderr, auto-opens it via NSWorkspace, and on subprocess exit polls `~/.hermes/auth.json` to confirm `providers.spotify.access_token` actually landed (exit code alone isn't proof). - SpotifySignInSheet.swift in Features/Skills/Views — five sub-views matching the state machine (starting / waiting / verifying / success / failure with retry). Auto-dismisses 1.2s after success. Mirrors NousSignInSheet shape. - SkillsView surfaces a "Sign in to Spotify" row in the skill detail pane when the selected skill is the spotify one. iOS (read-only documentation): - SkillsListView's SkillDetailView gains a "Authentication" section on the spotify skill explaining that OAuth needs to happen from Mac (or a shell). The credential lands in ~/.hermes/auth.json and ScarfGo picks it up automatically once the agent uses the skill. Editor sheet UX deferred to v2.6 — multi-line OAuth flows on iPhone are a separate UX problem. Verified: Mac + iOS builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
64bcea35a0 |
feat(chat): git branch indicator in chat header (Phase 2.4)
Hermes v2026.4.23's TUI shows the project's current git branch as a sidebar pill. Mirror it in the chat header on both platforms. ScarfCore GitBranchService: - branch(at projectPath: String) async -> String? — runs `git -C <path> rev-parse --abbrev-ref HEAD` via the transport (works on local + remote SSH projects). Returns nil for non-git dirs, missing git, detached HEAD, or transport errors. No throwing — chat header omits the chip on any failure. Mac: - ChatViewModel.currentGitBranch populated alongside currentProjectPath in startACPSession's resolution branch. - SessionInfoBar gains gitBranch: String? — renders a tinted `arrow.triangle.branch` chip after the project chip when set. - RichChatView wires chatViewModel.currentGitBranch through. iOS: - ChatController.currentGitBranch on the same lifecycle hooks (resetAndStartInProject + startResuming + cleared on resetAndStartNewSession). - projectContextBar renders the chip inline next to the project name. Verified: ScarfCore + Mac + iOS builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1fcd963019 |
feat(chat): numbered shortcuts on permission sheet (Phase 2.3)
Hermes v2026.4.23's TUI rewrite added 1-9 numbered shortcuts on approval prompts so power users approve/deny without reaching for the mouse. Mirror the pattern in Scarf: Mac PermissionApprovalView: - Each option button gets a "1. ", "2. ", … prefix on its label. - New private View extension `applyingNumberShortcut(index:)` binds the digit `idx + 1` (no modifiers) via .keyboardShortcut. Capped at 9; extra options stay tappable but unbound. iOS PermissionSheet: - Each row gets a monospaced "1." / "2." prefix as a hierarchy hint. - No keyboard binding (phones don't have hardware keyboards), but the numbering matches the Mac pattern so users transitioning between platforms see the same visual structure. Verified: Mac + iOS builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
70d4c97a6c |
feat(chat): per-turn stopwatch on assistant bubbles (Phase 2.2)
Wall-clock duration of each agent turn renders as a compact pill in the message metadata footer (Mac) / below the bubble (iOS). Mirrors the per-turn stopwatch Hermes v2026.4.23's TUI rewrite ships. ScarfCore RichChatViewModel: - currentTurnStart: Date? captured in addUserMessage when entering a fresh turn (skipped for /steer-style mid-run sends so the duration reflects the FULL turn). - turnDurations: [Int: TimeInterval] keyed by finalised assistant message id; populated in finalizeStreamingMessage and cleared on reset(). - formatTurnDuration(_:) static — "0.8s" / "4.2s" / "1m 12s". Mac: - RichMessageBubble gains turnDuration: TimeInterval?; renders via formatTurnDuration in the existing metadata footer. - RichChatMessageList + MessageGroupView thread the durations dict through; RichChatView wires richChat.turnDurations. iOS: - MessageBubble gains turnDuration parameter; renders below the bubble for assistant messages only. - ChatView's ForEach passes controller.vm.turnDuration(forMessageId:). Verified: Mac + iOS builds clean. Resumed sessions (loaded from state.db) show no pill — turnDurations only populates for live ACP turns, which is the correct behaviour. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
a9bd51bf05 |
feat(chat): /steer non-interruptive support (Phase 2.1)
Hermes v2026.4.23 introduces /steer — mid-run guidance the agent applies after the next tool call without interrupting the current turn. Surface it as a first-class slash command in both Mac and iOS chat menus with non-interruptive send semantics. ScarfCore RichChatViewModel: - nonInterruptiveCommands static (currently just /steer) merged into availableCommands at the end of the menu. - HermesSlashCommand.Source.acpNonInterruptive case carries the flag through to the menu UI. - transientHint: String? property for short-lived composer toasts. - isNonInterruptiveSlash(_ text: String) -> Bool helper for the send paths to detect /steer-shaped invocations. Mac ChatViewModel.sendViaACP: - /steer-shaped sends skip the "Agent working..." status update (the agent is already on its current turn) and set a 4-second transientHint "Guidance queued — applies after the next tool call." Mac RichChatView: - New steeringToast() above the input bar renders the hint when set; tinted pill with arrow icon, opacity transition. iOS ChatController.send + ChatView: - Same isNonInterruptiveSlash check surfaces the toast above the composer; auto-clears via the same 4s Task pattern. - steeringToast() helper view in ChatView. Verified: ScarfCore + Mac + iOS builds clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
79a350d793 |
test(scarfcore): M9 slash-command surfaces (Phase 1.10)
16 tests across name validation, frontmatter parsing, argument substitution (plain + default fallback + multiple occurrences), on-disk round-trip, missing-dir graceful handling, save invalidation, delete idempotency, and ProjectContextBlock surfacing (slash command list line + idempotency + omission when empty). 179 tests across 13 suites — green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
b247942e1f |
feat(slash-commands): .scarftemplate format extension + catalog validator (Phase 1.8-1.9)
Slash commands now travel with .scarftemplate bundles. Schema bumps to v3 when a manifest declares contents.slashCommands; v1/v2 bundles keep parsing unchanged. Swift side: - TemplateContents gains slashCommands: [String]? — names only. Bundle layout: slash-commands/<name>.md at the root. - ProjectTemplateService.buildInstallPlan copies each claimed name into <projectDir>/.scarf/slash-commands/<name>.md. - ProjectTemplateService.verifyClaims cross-checks: each name must pass ProjectSlashCommand.validateName, the file must exist, and the bundle can't contain unclaimed slash-commands/ files. - TemplateLock gains slashCommandFiles: [String]? (relative to project root). The uninstaller's existing tracked-file logic removes them; user-authored slash commands in the same dir survive (they're not in the lock). - ProjectTemplateExporter scans <project>/.scarf/slash-commands/ on export and copies each .md into the bundle root, populating the manifest contents claim. SchemaVersion bumps to 3 only when slash commands are present. Python catalog validator (tools/build-catalog.py): - SUPPORTED_SCHEMA_VERSIONS gains 3. - SLASH_COMMAND_NAME_RE mirrors the Swift validation pattern. - _validate_contents_claim picks up slashCommands: rejects malformed names, missing files, and unclaimed extras with the same error shapes the Swift verifier uses. Tests: - 4 new test_build_catalog cases. 28/28 catalog tests pass. - ProjectTemplateTests literal updated for the new TemplateContents field. Verified: Mac + iOS builds succeed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
7f5ff1946e |
feat(slash-commands): ScarfGo read-only browser sheet (Phase 1.7)
Read-only surface in iOS for browsing project-scoped slash commands. Editing on phones is its own UX problem (multi-line markdown + keyboard ergonomics) — Mac stays the canonical authoring surface in v2.5; iOS browses + invokes. When a project chat has at least one slash command loaded, projectContextBar grows a tinted "<N> slash" chip on the right side. Tapping opens ProjectSlashCommandsBrowser: - List of every command with /<name>, description, argument hint, optional model-override badge. - Tap a row → CommandDetailSheet with the full prompt-template body rendered in a monospaced block (text-selection enabled), plus metadata rows for argumentHint / model / tags. - Footer points authors back to Mac for editing. Verified: iOS build succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
9164e65cac |
feat(slash-commands): Mac authoring UI — Slash Commands tab + editor (Phase 1.6)
Adds a fourth per-project tab on Mac (alongside Dashboard / Site /
Sessions) for managing project-scoped slash commands. The whole
authoring story lives here: list, add, edit, duplicate, delete, with
a live-preview pane that expands {{argument}} substitutions against a
sample-arg field so authors see exactly what Hermes will receive.
- ProjectSlashCommandsViewModel — @Observable @MainActor, owns the
commands list + editor draft + dirty-tracking. Routes through
ScarfCore's ProjectSlashCommandService for all I/O. Save validates
name shape + collision detection before writing; rename cleans up
the previous file.
- ProjectSlashCommandsView — list with content menu (Edit/Duplicate/
Delete), empty state with CTA, error banner for transient failures.
- SlashCommandEditorSheet — HSplitView with form on the left
(identity / optional / monospaced body editor) and live preview on
the right (sample-argument field + expanded prompt). Save disabled
until name + description + body are non-empty.
- DashboardTab gains .slashCommands case alongside dashboard / site /
sessions; visibleTabs filter unchanged so it always shows for any
selected project.
iOS gets a read-only browser in the next commit (Phase 1.7) — phone
keyboards aren't great for multi-line markdown editing.
Verified: Mac build succeeds.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
8a87ff1922 |
feat(slash-commands): list project commands in AGENTS.md block (Phase 1.5)
The chat layer client-side-expands /<name> args, but the agent still needs to know what commands exist so it can answer "what slash commands does this project have?" and recognise the <!-- scarf-slash:<name> --> marker prepended to expanded prompts. ProjectContextBlock.renderMinimalBlock(...) gains an optional slashCommandNames parameter; when non-empty, a new "Project slash commands" bullet lists the names as backticked /<name> entries. Mac's ProjectAgentContextService.renderBlock(for:) reads the names via ProjectSlashCommandService.loadCommands(at:).map(\.name) and emits the same bullet, keeping Mac and iOS block output aligned where the content overlaps. iOS chat resetAndStartInProject splits the slash-command load into a synchronous read on a detached task BEFORE writing the block — needed because the block has to land on disk before `hermes acp` boots, and the async load that populates the chat menu would lose the race. Verified: ScarfCore, Mac, iOS all build clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6808adfa98 |
feat(slash-commands): portable project-scoped slash commands (Phase 1.1-1.4)
Net-new Scarf primitive — Hermes has no project-scoped slash command
concept. Commands live at <project>/.scarf/slash-commands/<name>.md as
Markdown files with YAML frontmatter; Scarf intercepts the chat slash
menu, expands {{argument}} substitution client-side, and sends the
expanded prompt as a normal user message. Works uniformly on Mac + iOS,
local + remote SSH, against any Hermes version (no upstream dep).
Lands the model + service + chat wiring; editor UI (Mac), read-only
browser (iOS), AGENTS.md block extension, .scarftemplate format
extension, and tests follow in subsequent commits.
What this commit ships:
- ScarfCore Models/ProjectSlashCommand.swift — Sendable struct
carrying name + description + argumentHint? + model? + tags? + body
+ sourcePath. Validates name shape (lowercase, hyphens, starts with
letter, ≤64 chars).
- ScarfCore Services/ProjectSlashCommandService.swift — transport-
based loadCommands(at:), loadCommand(at:), save(_:at:),
delete(named:at:), expand(_:withArgument:). Markdown-with-
frontmatter parser reuses HermesYAML so no new dep. Substitution
supports `{{argument}}` and `{{argument | default: "..."}}`.
- HermesSlashCommand.Source gains .projectScoped (full payload looked
up in RichChatViewModel by name) and .acpNonInterruptive (reserved
for /steer in Phase 2.1).
- RichChatViewModel.projectScopedCommands + projectScopedCommand(named:)
+ loadProjectScopedCommands(at:); availableCommands precedence is
ACP > project-scoped > quick_commands, all de-duped by name.
- Mac ChatViewModel: expandIfProjectScoped(_:) helper called in
sendViaACP; loads commands when currentProjectPath is set in
startACPSession's resolution branch.
- iOS ChatController: same pattern in send(); loads commands in both
resetAndStartInProject and startResuming(sessionID:); resume now
resolves both path AND name so we can read the slash-commands dir.
Verified: ScarfCore + Mac + iOS all build clean.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
bdc271c2b8 |
docs(readme): trim history + lead with v2.5
Drops "Previously, in 1.6 / 2.0 / 2.1 / 2.2" so the README's release history is just the lead (2.5) + one-level-back (2.3). Earlier history moves to the wiki's Release-Notes-Index, which is the canonical place for full version history anyway. New "What's New in 2.5" section leads with ScarfGo public TestFlight, the Mac Sessions parity (filter + badges), human-readable cron schedules, and the under-the-hood consolidation in ScarfCore. Requirements section gains an iOS row pointing at the ScarfGo wiki page for installation; the Hermes recommended-version bumps from v0.9.0+ to v0.10.0+ to match the v2.3 floor. No iOS-specific install instructions in the README — the TestFlight URL gets added later in Phase G once Apple's Beta Review issues it. For now, the link points at the wiki where the URL will land. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
d45de925ae |
docs(v2.5): privacy policy + TestFlight submission checklist
Authored locally (not pushed). Phase D of the v2.5 release plan needs: - A privacy policy at a stable URL before App Store Connect lets you submit for Beta App Review. - A pre-flight checklist so the Xcode + App Store Connect dance doesn't lose state. `scarf/docs/PRIVACY_POLICY.md` — minimal, accurate. The apps don't collect data on developer-controlled servers (no analytics, no telemetry, no ads, no IDFA). Covers SSH credentials, Hermes state cache, the project + attribution sidecars, the network connections the apps make. Ready to host on gh-pages at /privacy/ when the user opts to push it. `releases/v2.5.0/TESTFLIGHT_CHECKLIST.md` — step-by-step from Apple Developer Program prerequisites through Beta Review submission, with a beta-description copy block, "What to test" copy, and a rollback note. Explicitly calls out NOT bumping versions manually (release.sh does it in Phase G) and NOT enabling Push Notifications until APNs cert + sender land together. Both files stay local until the user pushes them — the checklist is the user's reference, the privacy policy gets copied into the gh-pages worktree when ready to submit. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1eb37771f9 |
docs(v2.5): release notes
Authored before `release.sh` so it gets included in the version-bump commit auto-generated by the script in Phase G. Highlights: ScarfGo iOS public TestFlight, Mac Sessions project filter + badges (parity with ScarfGo's Sessions tab), human-readable cron schedules cross-platform, shared-services refactor, silent-failure hardening on the iOS lifecycle, test-suite consolidation that fixes the cross-suite factory races we hit during pre-release verification. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
1174c5abc7 |
feat(mac-sessions): project filter + badges (v2.5 parity with iOS)
The Mac global Sessions feature rendered all sessions with no project context. ScarfGo's new Sessions tab added a project filter Menu and badge chips on each row in v2.5 — bring the same to Mac so v2.5 lands as a user-visible upgrade on both platforms, not just iOS. Changes: - `SessionsViewModel`: load `~/.hermes/scarf/session_project_map.json` + the project registry off the main actor (single batched read, matches the iOS Dashboard pattern). Exposes `sessionProjectNames`, `allProjects`, `projectFilter`, `filteredSessions`, and `projectName(for:)`. - `SessionsView`: filter bar above the list (shown only when at least one project is registered) with a Menu listing "All projects", "Unattributed", and each registered project. An xmark button clears the filter. The right side shows "X of Y shown" so the filter's effect is obvious. - `SessionRow` (shared with Dashboard): gains an optional `projectName: String?` parameter that renders a tinted folder chip alongside the relative date when set. Both services already lived in ScarfCore (moved there in v2.5's iOS work), so this is pure UI consumption — no new shared logic. Verified: Mac build succeeds. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |