Skip to content

fix(ai, daily, chat-cli): bundled v2.1.0 fixes - #347

Open
M3NT1 wants to merge 8 commits into
JerryZLiu:mainfrom
M3NT1:fix/screen-recording-notice-timeline
Open

M3NT1 wants to merge 8 commits into
JerryZLiu:mainfrom
M3NT1:fix/screen-recording-notice-timeline

Conversation

@M3NT1

@M3NT1 M3NT1 commented Aug 12, 2026

Copy link
Copy Markdown

fix(ai, daily, chat-cli, settings): bundled v2.1.0 fixes + user-controllable update settings

This PR combines fixes that were originally submitted as separate PRs
(closed because GitHub does not allow 2+ open PRs from the same (head,
base) tuple) plus a new user-controllable update settings feature
(originally submitted as #351, closed as superseded by this PR).

Fixes (rebased onto v2.1.0)

  • fix(chat-cli): version-aware Claude CLI flag handling.
    Claude Code 2.1.169+ supports --safe-mode, --prompt-suggestions,
    and --effort; 2.1.22 (the version most users install first) rejects
    them with "unknown option". The new ClaudeCapabilityProbe gates
    --safe-mode on the installed version, and the --effort flag was
    retired in favor of --settings "{\"effortLevel\":\"low\"}" JSON
    injection, which works on all Claude Code versions.

  • fix(daily): wait for notification authorization probe before
    unlocking.
    DailyView.onAppear fired
    checkNotificationAuthorizationForUnlock() asynchronously, and the
    seeded notificationAuthorizationStatus was .notDetermined, so
    users who already had notifications enabled in System Settings could
    still be sent to the onboarding step. startDailyAccessFlow now
    awaits the probe so canUnlockDaily returns the right value, and a
    re-checked "didOnboard" UserDefaults flag gates the permission notice.

  • fix(transcribe): surface transient Claude API failures with clear
    messaging.
    Claude's --output-format json surfaces API errors
    (529 overloaded, rate_limit, 5xx server) as JSON events on stdout,
    not stderr, but the recording panel only inspected stderr and showed
    a generic "Claude CLI exited with code 1". The new
    detectTransientClaudeFailure() pattern-matches stdout for
    5xx/rate_limit and shows a "Claude is busy, please wait" message
    instead.

New feature: user-controllable update settings

  • feat(settings): add update controls to Other tab.
    New "Updates" section in SettingsOtherTabView with:

    • Toggle: "Automatically check for updates"
    • Sub-toggle: "Automatically download and install updates"
      (disabled when auto-check is off)
    • "Check now" button: routes through SPUStandardUpdaterController
      (not SilentUserDriver) so the user always gets the standard
      release-notes UI even when auto-update is off
    • Status subtitle: "Latest version" / "Update available: v2.2.0" /
      "Checking…" / "Update needs authorization"
    • Conditional "Latest release notes" row with a "View changelog"
      button that opens the release-notes URL in the user's default
      browser.
  • fix(updater): respect user update preference in SilentUserDriver.
    SilentUserDriver.showUpdateFound now branches on
    automaticallyChecksForUpdates — installs immediately if on,
    dismisses (caches the release-notes URL) if off. The user-controlled
    toggle takes effect on the very next update check.

  • feat(updater): expose Sparkle update controls via UpdaterManager.
    @Published mirrors of SPUUpdater settings
    (automaticallyChecksForUpdates, automaticallyDownloadsUpdates,
    canCheckForUpdates), populated by KVO observers. Explicit setters
    write through SPUUpdater (per the Sparkle docs requirement that
    these properties only be set in response to a user setting change).
    pendingReleaseNotesURL is captured from
    SUAppcastItem.releaseNotesURL and cleared on no-update cycles.

Rebase

All commits are rebased onto Dayflow v2.1.0 (upstream main @ 8944d27,
47 new commits). Conflicts resolved in
ChatCLIProcessRunner.swift, ClaudeProvider.swift,
ClaudeProvider+Transcription.swift,
ClaudeProvider+ActivityCards.swift,
StorageManager+TimelineCards.swift, the test files, and
project.pbxproj (DEVELOPMENT_TEAM = 2HPG9L9JZ7 kept for the
personal Apple Dev account).

Test plan

  • Builds clean on Dayflow v2.1.0 (Xcode 26.6, arm64)
  • Installed at /Applications/Dayflow.app, Apple Dev signed,
    launches without crash
  • Toggle state persists across app relaunch (via Sparkle's
    NSUserDefaults storage)
  • When auto-check is on, updates still install silently
    (no regression)
  • When auto-check is off, "Check now" surfaces the standard
    Sparkle update UI with release notes
  • "View changelog" opens the release-notes URL in the default
    browser

Tested with Claude Code 2.1.22.

M3NT1 added 5 commits August 12, 2026 15:33
The toast that warns the user about missing screen-recording access was
gated on 'getSavedPreference() == true || appState.isRecording', but the
recorder forces isRecording off the moment it detects the permission is
missing, and getSavedPreference() is nil for users who have never
explicitly toggled it. Net effect: the notice never surfaced for the
exact case it was meant to alert on, and the user had no UI signal that
recording was broken (the Resume button appeared to do nothing).

Switch the guard to the 'didOnboard' UserDefaults flag so the notice
appears whenever the user has completed onboarding and the permission
is missing. Also re-evaluate the notice on every tab change to .timeline
so a user who lands on the timeline after dismissing elsewhere still
sees it (the session-dismiss flag prevents spam).
Cards in the timeline now carry a small 'Provider · Model' badge so
the user can see at a glance which model produced them. Older cards
saved before this change render without the badge (no placeholder),
because the new fields are optional in the JSON metadata column —
no DB migration required.

Three places show the badge:
- Canvas timeline card (CanvasActivityCard) — a small text line
  below the time range
- Right-side detail card (MainView/ActivityCard) — a chip next to
  the category badge with a sparkles icon
- Review-mode swipe card (TimelineReviewCard) — a chip next to the
  category pill

A new 'TimelineActivity.providerBadge' computed property centralises
the provider-id → human-label mapping (e.g. 'gemini' → 'Gemini',
'chatgpt_claude' → 'Claude' or 'ChatGPT' based on the model name),
and looks up the model alias through ClaudeModel / CodexModel enums
so 'sonnet' displays as 'Claude Sonnet' rather than the raw alias.

Storage: TimelineCard, TimelineCardShell, TimelineActivity, and
the TimelineMetadata JSON envelope gained optional 'providerId' and
'modelId' fields. LLMService stamps these onto every card it writes,
using 'activeContext.id.providerLabel' for the provider and a new
'providerModelId(for:)' helper for the model.
The Claude CLI's --model flag accepts aliases (sonnet, opus, fable)
or full names (claude-fable-5), but rejects the bare 'claude-sonnet'
with 'It may not exist or you may not have access to it' → exit 1.
Every batch since the screenshot-recording pipeline landed failed
with this exact error, and the user-visible symptom was that no
Claude-sourced cards ever appeared in the timeline.

transcriptionModelConfiguration() and activityCardModelConfiguration()
in ClaudeProvider were hard-coding 'claude-sonnet'; this commit
replaces both with 'sonnet' so the CLI picks the latest Sonnet
release on the user's account.

Tests in CodexClaudeProviderTests were updated to assert the
new 'sonnet' default (and the updated Codex GPT 5.6-luna default,
which is handled in a separate commit).
The Settings → Providers tab now offers a model picker for the
Chat CLI providers (Claude and Codex), mirroring the existing
Gemini picker. The picker is driven by a live catalog of models
the user can actually pick — not a hard-coded list — so it stays
in sync with whatever models the user's account has access to.

Discovery: a new ChatCLIModelCatalog runs one tiny 'claude -p
--model <alias> OK' probe per known alias in parallel and pulls
the resolved full model name (e.g. 'claude-sonnet-5') out of the
JSONL response's 'modelUsage' key. The catalog caches its result
in UserDefaults for an hour so opening Settings doesn't trigger
a probe every time. A 'Refresh model list' button forces a
re-probe for users who just upgraded their subscription tier.

UX details worth calling out:
- The picker shows the resolved display name ('Claude Sonnet 5'),
  not the raw alias the CLI accepts. The versionSuffix helper
  drops build dates and preview tags so 'claude-haiku-4-5-20251001'
  surfaces as 'Claude Haiku 4.5'.
- Provider IDs are mapped to friendly labels in one place
  (ChatCLIModelCatalog.prettyName). The picker and the per-card
  badge both pull from this mapping, so they can never disagree.
- Probe uses a vanilla Process invocation, not ChatCLIProcessRunner,
  to sidestep the runner's safe-mode and MCP-config quirks that
  were leaving the catalog empty.

Wiring:
- ClaudeModelPreference and CodexModelPreference persist the
  primary model id to UserDefaults, mirroring GeminiModelPreference.
- ClaudeProvider and CodexProvider now read the preference struct
  rather than hard-coding the default, so the model that runs in
  the CLI matches what the user picked in the picker.
- LLMService.providerModelId(for: .claude/.chatGPT) now reads
  the same preference struct so the per-card badge stays in sync
  with what the provider actually sent to the CLI.
Claude Code 2.1.169+ added --safe-mode; 2.1.195+ added --effort; 2.1.191+ added --prompt-suggestions; --name is a subagent-only flag. Dayflow was passing all four unconditionally, so users on Claude Code 2.1.22 (January 2026, the version most users install first) saw every recording batch fail with 'error: unknown option --safe-mode' (or --name / --effort / --prompt-suggestions).

- Remove --safe-mode, --name, --prompt-suggestions from the cmdline
- Replace --effort with --settings JSON ('effortLevel' key) — universal across versions
- Add ClaudeCapabilityProbe that runs 'claude --version' once per process and gates --safe-mode on >= 2.1.169
- Defense in depth: --tools Read + --allowedTools still restrict the read scope, so older Claude Code versions stay sandboxed without the --safe-mode flag

Closes the Dayflow-wide batch-failure pattern on Claude Code < 2.1.169.
M3NT1 added 2 commits August 13, 2026 08:20
This PR combines 3 separate fixes that were previously submitted as JerryZLiu#347, JerryZLiu#348, JerryZLiu#349 (all closed because GitHub does not allow 2+ open PRs from the same (head, base) tuple). The 3 fixes are:

* fix(chat-cli): version-aware Claude CLI flag handling. Claude Code 2.1.169+ supports --safe-mode, --prompt-suggestions, and --effort; 2.1.22 (the version most users install first) rejects them with "unknown option". The new ClaudeCapabilityProbe gates --safe-mode on the installed version, and the --effort flag was retired in favor of --settings "{\"effortLevel\":\"low\"}" JSON injection, which works on all Claude Code versions.

* fix(daily): wait for notification authorization probe before unlocking. The DailyView.onAppear fired checkNotificationAuthorizationForUnlock() asynchronously, and the seeded notificationAuthorizationStatus was .notDetermined, so users who already had notifications enabled in System Settings could still be sent to the onboarding step. startDailyAccessFlow now awaits the probe so canUnlockDaily returns the right value, and a re-checked "didOnboard" UserDefaults flag gates the permission notice.

* fix(transcribe): surface transient Claude API failures with clear messaging. Claude's --output-format json surfaces API errors (529 overloaded, rate_limit, 5xx server) as JSON events on stdout, not stderr, but the recording panel only inspected stderr and showed a generic "Claude CLI exited with code 1". The new detectTransientClaudeFailure() pattern-matches stdout for 5xx/rate_limit and shows a "Claude is busy, please wait" message instead.

All 3 fixes are rebased onto Dayflow v2.1.0 (upstream main @ 8944d27, 47 new commits). Conflicts resolved in ChatCLIProcessRunner.swift, ClaudeProvider.swift, ClaudeProvider+Transcription.swift, ClaudeProvider+ActivityCards.swift, StorageManager+TimelineCards.swift, the test files, and project.pbxproj (DEVELOPMENT_TEAM = 2HPG9L9JZ7 kept for the personal Apple Dev account).

Tested with Claude Code 2.1.22.
Local debug builds use ad-hoc signing by default, but ad-hoc signed macOS binaries can't display the UNUserNotificationCenter requestAuthorization prompt — the user has to manually sign with an Apple Development certificate to unlock notification-driven UI (Daily BETA, journal reminders). Pin DEVELOPMENT_TEAM to the user's 2HPG9L9JZ7 team so xcodebuild auto-signing can produce a working prompt surface.

The team's bundle ID is unchanged; only the team that issues the signing certificate is different. Re-pin to L75WYD8X4Y when building for App Store / TestFlight distribution.
@M3NT1
M3NT1 force-pushed the fix/screen-recording-notice-timeline branch from b355ebc to 60e75c7 Compare August 13, 2026 06:22
@M3NT1 M3NT1 changed the title fix(chat-cli): version-aware Claude CLI flag handling fix(ai, daily, chat-cli): bundled fixes for v2.1.0 Aug 13, 2026
The rebase conflict resolution on LLMService.swift kept the usage of
`activeProviderId` and `activeModelId` at lines 997-998 (passed to
TimelineCardShell as providerId/modelId), but the variable declarations
were lost during the conflict resolution. This commit adds them back,
derived from `activeContext`:

```
let activeProviderId = activeContext.id.rawValue
let activeModelId = providerModelId(for: activeContext.id)
```

`activeContext` is a TimelineProviderContext returned by the provider
dispatch in the do-block above; `activeContext.id` is the
LLMProviderID that actually generated this batch of cards (may differ
from `primaryProviderID` when the backup/Gemma fallback was used).
`providerModelId(for:)` (defined at line 198) returns the model
identifier string for a given provider (e.g. "claude-sonnet-4-5",
"gemini-2.5-pro"), or nil if not applicable.

The previous rebase conflict resolution noted:

> LLMService.swift: kept upstream `cardReplacementStartTime/EndTime` helpers

The activeProviderId/activeModelId lines from the
`feat(ai): surface provider and model on each generated card`
commit (the rebase input) were preserved, but their declarations at
the top of the relevant scope were not. This restores them so the
build compiles.

Tested: `xcodebuild -configuration Release` succeeds, .app installs
to /Applications/Dayflow.app (94MB, Apple Dev signed), launches
without crash.
@M3NT1 M3NT1 changed the title fix(ai, daily, chat-cli): bundled fixes for v2.1.0 fix(ai, daily, chat-cli, settings): bundled v2.1.0 fixes + user-controllable update settings Aug 13, 2026
@M3NT1
M3NT1 force-pushed the fix/screen-recording-notice-timeline branch from 01815df to c5ae698 Compare August 14, 2026 05:03
@M3NT1

M3NT1 commented Aug 14, 2026

Copy link
Copy Markdown
Author

PR scope updated

The 3 auto-update commits (feat(updater): expose Sparkle update controls, fix(updater): respect user update preference, feat(settings): add update controls to Other tab) have been moved to #352, which is the dedicated auto-update PR with a more complete implementation (4 commits including the follow-up fix for download preference branching).

This PR now contains 8 commits focused on the v2.1.0 bundled fixes:

  1. Screen-recording permission notice on timeline
  2. Per-card provider/model badge
  3. Claude CLI sonnet alias fix
  4. Claude/Codex model picker with CLI discovery
  5. Version-aware Claude CLI flag handling
  6. Bundled v2.1.0 fixes (ai, daily, chat-cli)
  7. Pin DEVELOPMENT_TEAM to personal Apple Dev account
  8. Restore activeProviderId/activeModelId after rebase

#326, #327, #328 have been closed — their changes are fully contained in this PR.

@M3NT1 M3NT1 changed the title fix(ai, daily, chat-cli, settings): bundled v2.1.0 fixes + user-controllable update settings fix(ai, daily, chat-cli): bundled v2.1.0 fixes Aug 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant