mirror of
https://github.com/awizemann/scarf.git
synced 2026-05-10 18:44:45 +00:00
1174c5abc724a6e8a0746ea334f9966bd0a759a7
4 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
5f9343be5d |
M8: list density tokens (scarfGoCompactListRow + scarfGoListDensity)
Apple's default List styling targets Reading/Notes-style apps: ~60pt rows, 10pt inter-row spacing, big vertical padding on grouped cells. ScarfGo's lists (Memory, Cron, Skills, More, Dashboard recent sessions) lean information-dense — devs want to see 4-6 items per screen, not 2. Two tokens in Scarf iOS/App/Theme/ListDensity.swift: - `.scarfGoCompactListRow()` — 6pt vertical listRowInsets (down from default ~12pt), explicit `.frame(minHeight: 44)` to preserve the Apple HIG tap target, and `.contentShape(Rectangle())` so rows can shrink below 44pt visually while keeping the full-row hit area. ~48pt rows end up net, vs. ~60pt default. - `.scarfGoListDensity()` — `.listRowSpacing(0)` kills inter-row gaps on the whole List, `.defaultMinListRowHeight(36)` sets the floor for rows that want to go smaller (e.g. `LabeledContent`). Applied to Memory, Cron, Skills, Dashboard, MoreTab. No visual change to Chat (it's not a List — different density patterns for M8 items 2.4–2.7). Research-backed: Fantastical / GitHub Mobile / Mona for Mastodon use similar spacing. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f2f6c4e50b |
M7 #9+#10: Memory editor keyboard + Saved pill above keyboard
Pass-1 complaints: - Typing near the bottom of MEMORY.md → keyboard covered the cursor, user lost track of where they were editing (M7 #9). - Tapping Save → "Saved" pill was never visible because it sat at .bottom with a fixed 16pt padding, behind the still-raised keyboard (M7 #10). Fixes: - `.scrollDismissesKeyboard(.interactively)` on the TextEditor so scrolling the editor drags the keyboard down smoothly. - Move the error banner + Saved pill into `.safeAreaInset(edge: .bottom)` so SwiftUI draws them above whatever is presenting the keyboard. The pill is now a full-width material strip (easier to hit/notice) instead of a floating capsule. - Saved pill holds for 2.5s (up from 1.5s — the old timer was too tight to read mid-thought). - Any in-flight hide task is cancelled when a new save lands, so rapid-fire saves don't produce stacked fade timers. No Mac equivalent needed — Mac memory editor is a separate MemoryView with different layout and a non-mobile keyboard concern. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
f41ac1c84e |
M7: pass-1 quickfixes (PATH, SFTP tilde, SOUL.md, ScarfGo bundle id)
Four fixes surfaced during the 2026-04-24 pass-1 smoke test of the iOS companion against a local Hermes host. All discovered while collaboratively driving the Simulator + tailing os.Logger. 1. ACPClient+iOS.swift — ACP exec command prepends common install paths to PATH. SSH RFC 4254 exec uses a non-interactive shell whose PATH is sshd's default (`/usr/bin:/bin:/usr/sbin:/sbin`); `.zshrc` doesn't source, so `~/.local/bin/hermes` (pipx default) was invisible and the agent died with "command not found: hermes". Mirrors HermesPathSet.hermesBinaryCandidates (the Mac-side local probe list) inline in the exec command. 2. CitadelServerTransport.swift — SFTP tilde expansion. Every Memory/Cron/Skills/Settings read used paths like `~/.hermes/memories/MEMORY.md`. SFTP treats `~` as a literal character, not a home-dir alias — so every read silently returned nil and the UIs showed "empty file" instead of the real content. Added a per-connection cached `resolveHome()` + a `resolveSFTPPath` helper applied to every SFTP entry point (readFile / writeFile / fileExists / stat / listDirectory / createDirectory / removeFile). This was the single biggest blocker on pass-1. 3. IOSMemoryViewModel.swift + MemoryListView.swift — SOUL.md added as a third Memory row. SOUL.md lives in the Personalities feature on Mac; folding it into Memory on iOS matches the on-the-go scope (all agent prompt inputs in one place). Uses the existing `HermesPathSet.soulMD` path; no new plumbing. 4. project.pbxproj — bundle id rename for ScarfGo branding: - CFBundleDisplayName: "Scarf Mobile" -> "ScarfGo" - PRODUCT_BUNDLE_IDENTIFIER: com.scarf-mobile.app -> com.scarfgo.app Xcode target name stays "scarf mobile" internally (rename surgery isn't worth the PBX churn). Home-screen label + bundle id now match the product name. Both schemes build green. Phase 1 starter commit — per-item M7 fixes follow in subsequent commits. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
6b731ddfb8 |
iOS port M5: Chat polish + Memory + Cron + Skills features
Fleshes out the iOS app from "Chat + placeholder Dashboard" into a
real on-the-go Hermes companion: Chat now renders tool calls + tool
results + permission sheets + markdown + chain-of-thought, and the
Dashboard gains three new feature surfaces.
## Chat polish
scarf/Scarf iOS/Chat/ChatView.swift — several new small SwiftUI
view types:
- ToolCallCard: expandable card for each HermesToolCall on an
assistant message. Tool-kind icon in the header (from
HermesToolCall.toolKind.icon), arguments summary collapsed,
full JSON on tap.
- ToolResultRow: compact "Tool output" disclosure for messages
with role == "tool", shown indented beneath the preceding
assistant bubble.
- PermissionSheet: SwiftUI .sheet(item:) presentation of
RichChatViewModel.pendingPermission. Tapping an option
dispatches ChatController.respondToPermission → ACPClient.
- ReasoningDisclosure: DisclosureGroup for HermesMessage.reasoning,
collapsed by default so chatty thinkers don't dominate scroll.
MessageBubble now renders assistant content through
AttributedString(markdown: options: .inlineOnlyPreservingWhitespace).
User messages stay plain Text (no reason to parse what the user
just typed). Unknown markdown falls through as literal text — worst
case, no formatting.
ChatController gains respondToPermission(requestId:optionId:) that
forwards to ACPClient and clears vm.pendingPermission on the
MainActor.
## New feature surfaces
### Memory (read + edit)
ScarfCore/ViewModels/IOSMemoryViewModel.swift:
- Kind enum (.memory / .user) → maps to paths.memoryMD / .userMD
- text (mutable) + originalText (pristine) + hasUnsavedChanges
- load() / save() / revert()
- async file I/O via ServerContext.readText / writeText — run on
a detached task so the MainActor doesn't hang on remote SFTP
scarf/Scarf iOS/Memory/:
- MemoryListView: two-row NavigationLink (MEMORY.md, USER.md)
- MemoryEditorView: TextEditor bound to vm.text, toolbar Save +
Revert, "Saved" bottom toast on success.
### Cron (read-only)
ScarfCore/ViewModels/IOSCronViewModel.swift:
- Loads ~/.hermes/cron/jobs.json via transport.readFile + decodes
into CronJobsFile (Codable, shipped in M0a)
- Missing file = empty list (no error — common on fresh installs)
- Sort: enabled-first, then nextRunAt ascending, disabled last
- Surfaces decode errors via lastError
scarf/Scarf iOS/Cron/CronListView.swift:
- Row: state-icon + name + schedule.display + next-run-at.
- Detail: prompt, schedule, state, delivery route (via
job.deliveryDisplay), skills, model.
Editing is deferred — needs atomic jobs.json rewrites. Shipped the
read path so users can at least audit their cron config on the go.
### Skills (read-only)
ScarfCore/ViewModels/IOSSkillsViewModel.swift:
- Scans ~/.hermes/skills/<category>/<name>/ via transport.listDirectory
+ transport.stat for directory-ness
- Filters dotfiles. Skips empty categories. Swallows per-category
listing errors (permissions etc.) rather than failing the whole
load.
- requiredConfig stays empty — YAML frontmatter parsing deferred
(would need a parser in ScarfCore; see M5 plan note).
scarf/Scarf iOS/Skills/SkillsListView.swift:
- Grouped by category, tap → SkillDetailView (path + file list).
## Supporting tweaks
- RichChatViewModel.PendingPermission: fields + public init promoted
from `let`/internal to `public let` / `public init(...)` so
PermissionSheet can read title/kind/options and tests can construct
one directly.
- LocalTransport.writeFile refactored to use Data.write(options: .atomic)
instead of FileManager.replaceItemAt. replaceItemAt is Apple-only;
Linux swift-corelibs doesn't fully implement it, which was breaking
the M5 save-path tests on Linux CI. Data.write(atomic) is cross-
platform and has identical semantics (temp-file + rename). Also
auto-creates the parent directory if missing, folding in the one
bit of the old logic that wasn't atomicity-related.
- DashboardView: single Chat Section → "Surfaces" Section with four
NavigationLinks (Chat / Memory / Cron / Skills).
## Tests (ScarfCoreTests/M5FeatureVMTests, 10 new)
.serialized suite — tests install a `withLocalTransportFactory`
helper that swaps ServerContext.sshTransportFactory to produce a
LocalTransport against real tmp files (so .ssh contexts in the
test resolve to local FS paths). Restored in defer. Serialized
because the factory is a static.
- memoryLoadsEmptyWhenFileMissing
- memoryRoundTripsFileContent — seed file → load → edit → save
→ reload via fresh VM → confirm persistence
- memoryRevertRestoresOriginal
- memoryKindPathRouting — pin .memory → memoryMD etc.
- cronEmptyWhenJobsFileMissing — missing file is not an error
- cronLoadsAndSortsJobs — 3-job fixture, verify sort:
enabled-before-disabled and
nextRunAt-ascending within
- cronSurfacesDecodeErrors — garbage jobs.json
- skillsEmptyWhenDirMissing
- skillsScansCategoryAndSkillStructure — 2 categories, dotfile
filter check
- skillsSkipsEmptyCategories
- pendingPermissionMemberwise — SQLite3-gated (RichChatViewModel
is gated)
**108 / 108 passing on Linux** (98 → 108).
## Manual validation needed on Mac
1. Xcode compile clean against M5 source additions.
2. Chat: trigger a tool call + a permission request. Verify cards
render, options dispatch, markdown looks right.
3. Memory: edit MEMORY.md on phone → save → confirm via `cat` on
the remote.
4. Cron: existing jobs show sorted + detail view useful.
5. Skills: browse matches `ls ~/.hermes/skills/<cat>/<name>/`.
Updated scarf/docs/IOS_PORT_PLAN.md with M5's scope, rationale
for the LocalTransport.writeFile refactor (Linux CI), and the M6
Settings-blocker (needs YAML parser port).
https://claude.ai/code/session_019yMRP6mwZWfzVrPTqevx2y
|