Skip to content

v1.10: On-device summarization, iCloud archive, summary notes & attachments - #88

Merged
bisonbet merged 24 commits into
mainfrom
v1.10
May 10, 2026
Merged

v1.10: On-device summarization, iCloud archive, summary notes & attachments#88
bisonbet merged 24 commits into
mainfrom
v1.10

Conversation

@bisonbet

@bisonbet bisonbet commented May 9, 2026

Copy link
Copy Markdown
Owner

Summary

Major release with several new user-facing features and significant under-the-hood improvements.

Highlights

  • MLX on-device summarization — New AI engine using Apple's MLX framework with Bonsai 4B/8B models (2-bit quantized) for fully on-device, offline summaries. Memory-safe inference with pre-flight checks, capped Metal cache, and 4-bit KV quantization to prevent OOM crashes on iOS.
  • Archive to iCloud Drive — Offload audio files to iCloud while preserving transcripts, summaries, and metadata. Tokenized filenames enable seamless restore on reimport.
  • Summary notes & attachments — Attach files (PDFs, images, documents) and add personal notes to any summary, with in-app QuickLook preview and export integration.
  • Recording title editing — Rename recordings directly from the audio player and transcript views via a shared editor component.
  • Clean audio export — Separate export pipeline produces human-readable filenames for sharing, with single and multi-select bulk export.
  • Accurate recording timestamps — Persist explicit recording start timestamps scoped to recording URL, fixing date corruption from late-completing async merges.

Privacy & Stability

  • Redacted user content (AI responses, recording names, GPS, filenames, URLs) from logs across 15 files; retained diagnostic metadata.
  • Fixed audio offloading bug where archived recordings vanished from the list and transcript views.
  • Fixed experimental mode toggle to fully disable MLX and migrate active engine selection.
  • Swapped Transcripts/Summaries order in iPad sidebar.
  • Cleaned up .recordingmeta sidecars on deletion to prevent silent storage growth.

Test plan

  • Verify MLX summarization on a 6GB+ RAM device with both 4B and 8B Bonsai models
  • Verify experimental mode toggle: disabling fully removes MLX from engine list and migrates active selection
  • Archive a recording to iCloud Drive, confirm local audio is removed and transcripts/summaries remain
  • Re-import an archived audio file and verify it restores to the original recording entry
  • Add notes and attachments to a summary; verify QuickLook preview and export include them
  • Regenerate a summary and confirm notes/attachments migrate to the new summary
  • Delete a summary/recording and confirm attachment files are cleaned up from disk
  • Edit a recording title from both the audio player and transcript views
  • Bulk export audio recordings and confirm filenames are human-readable
  • Record back-to-back sessions and verify each gets the correct recordingDate
  • iPad: verify Transcripts appears above Summaries in the sidebar

🤖 Generated with Claude Code

bisonbet and others added 20 commits April 15, 2026 11:27
- P1: Fix silent no-op when renaming back to original title in
  AudioPlayerView — guard now compares against currentSavedTitle
  instead of the immutable recording.name
- P3: Extract RecordingTitleEditorView shared component to eliminate
  duplicate title-editor UI between AudioPlayerView and
  EditableTranscriptView
- P4: Fix indentation regression in TranscriptViews.swift — re-indent
  the if/else block inside VStack to match recordingTitleEditor
- P2: Add comments clarifying that both save paths update the display
  name only (consistent with SummaryDetailView pattern)

Co-authored-by: Tim Champ <bisonbet@users.noreply.github.com>
…d, QuickLook fallback, debounce, trim bug

- EnhancedSummaryData: add custom init(from:) with decodeIfPresent for
  `attachments` and `userNotes` so legacy serialized summaries (missing
  those keys) still load without keyNotFound errors (P1 Codex review)
- SummaryAttachmentStore: fix saveUserNotes storing untrimmed `notes`
  instead of `trimmed`; add deleteAll(for:) to remove per-summary
  folder; populate contentType via UTType(filenameExtension:)
- SummaryDetailView: call deleteAll before deleteSummary to prevent
  orphaned attachment files on disk; wrap text file read in
  Task.detached so large files do not block the main thread; replace
  UIApplication.shared.open (broken for sandboxed files) with
  .quickLookPreview SwiftUI modifier; debounce saveUserNotes with a
  cancellable Task (500 ms) and guard against spurious saves during
  loadSupplementalSummaryData using isLoadingSupplemental flag

Co-authored-by: Tim Champ <bisonbet@users.noreply.github.com>
…e-to-summaries

Add attachments & user notes to summaries with in‑app preview and export integration
Privacy: Strip user content (AI responses, recording names, GPS
coordinates, file names, URLs) from all log statements across 15 files.
Replace Watch app print() calls with os.Logger using privacy annotations.
Retain diagnostic metadata (byte counts, HTTP status codes, UUIDs,
boolean flags) so logs remain useful for troubleshooting.

Features: Refactor summary note editor into a sheet, migrate
supplemental data (notes/attachments) on summary regeneration, clean up
attachment files on summary and recording deletion, update delete
confirmation text to mention notes and attachments.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Export audio files to iCloud Drive, Dropbox, Google Drive, or any
provider via the iOS Files picker. After export, optionally remove
local audio while keeping transcripts, summaries, and metadata.

- Add isArchived, archivedAt, archiveNote attributes to RecordingEntry
- Add RecordingArchiveService for archive/restore/query operations
- Add DocumentExportPicker (UIDocumentPickerViewController for export)
- Add ArchiveConfirmationView with remove-local toggle
- Add .archived case to FileAvailabilityStatus
- Add archive fields to RecordingFile model
- Add getStoredURL to CoreDataManager/AppDataCoordinator
- Update RecordingsListView: archive selection mode, older-than picker,
  archive indicators on rows, info alert for offloaded recordings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…anscript views

- cleanupRecordingsWithMissingFiles() now skips archived recordings so their
  recordingURL is never cleared on app relaunch (was causing them to vanish
  from the recordings list after the file was intentionally offloaded)
- TranscriptViews.loadRecordings() now falls back to getStoredURL for archived
  recordings so transcripts remain visible even when the local audio is gone

https://claude.ai/code/session_01Wu4tB7fuskn3ZuzhWQxdDP
…olver

- RecordingWorkflowManager: sort existing summaries by generatedAt desc so
  the "primary" one used for attachment migration is deterministically the
  most recent, preventing data loss when duplicate summary rows exist.
- RecordingArchiveService: replace hasPrefix("/") path builder with a
  resolveLocalURL helper that also handles file:// URLs and percent-encoded
  relative paths, so archive cleanup actually removes legacy-formatted
  audio files instead of leaving them on disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…KMOam

Fix audio offloading bug: preserve archived recordings in list and transcript views
- Export: stage audio to Library/Application Support/ArchiveStaging with
  names of the form <SanitizedName>-<8hex>.<ext>. Staging uses a fresh copy
  (not hardlink) so the iCloud File Provider sees clean xattrs, which fixes
  the "permission denied" error when archiving to iCloud Drive. Staged
  files are stamped with the original recordingDate as mtime so timestamps
  survive an iCloud round-trip.
- Import: parse the archive token from the incoming filename, match it
  against the first 8 hex chars of RecordingEntry.id, and either copy and
  restore, clear archive flags (when local audio is still present), or
  throw ImportError.alreadyImported for true duplicates. New-entry imports
  now prefer the file's modification date for recordingDate.
- Fix: convertToTranscriptData no longer returns nil for archived
  recordings whose local audio is gone — it falls back to getStoredURL so
  the Transcripts list keeps showing "Edit Transcript" rather than
  "Generate Transcript" after archiving.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add archive-location metadata so offloaded audio keeps a restorable pointer

- Limit new archive destinations to iCloud Drive and leave local audio untouched for unsupported providers

- Restore archived audio from the saved location, validate it, and delete the archived copy after restore

- Add UI recovery for archived recordings that still have local audio

- Document the iCloud-only archive workflow in README and WordPress guide
…estamp

Address review feedback: the new .recordingmeta sidecar files were not
being cleaned up when recordings were deleted, causing silent storage
growth. Add cleanup to all four deletion paths (EnhancedFileManager,
TranscriptViews, RecordingArchiveService, orphan cleanup). Also fix
generateAppRecordingDisplayName() to use recordingStartedAt so the
display name timestamp matches the persisted recordingDate.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Introduce a separate audio export pipeline that stages files with
human-readable names (no archive tokens), for user-facing sharing and
bulk export. Single-file export from AudioPlayerView and multi-select
export from RecordingsListView, with proper cleanup and error handling
for recordings missing local audio.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Change recordingStartedAt from Date? to (url: URL, date: Date)? so
currentRecordingDate(for:) only returns the in-memory start time when
the provided URL matches the active recording URL. Previously, an async
merge that completed after a new recording started would pick up the new
session's timestamp for the old merged file, corrupting its recordingDate.

Co-authored-by: Tim Champ <bisonbet@users.noreply.github.com>
…-to-recordings

Persist stable recording timestamps and use them for recordingDate
…erence

New AI engine using Apple's MLX framework and PrismML's Ternary Bonsai models
(4B and 8B, 2-bit quantized) for fully on-device transcript summarization.

Engine (MLXSwiftEngine.swift):
- MLX Swift service actor with model loading, chunked inference, and
  Markdown-based prompt matching the proven Mac summarize.py approach
- Metal buffer cache capped at 32MB (Memory.cacheLimit) to prevent the
  multi-GB cache accumulation that caused OOM crashes on iOS
- Pre-flight memory check via os_proc_available_memory() before model load
- 4-bit KV cache quantization (kvBits:4) to reduce inference overhead
- Memory warning detection with graceful abort between chunks
- Model unloaded and Metal cache cleared after each summarization run
- Download manager with Hub API integration for pre-downloading models

Settings (MLXSwiftSettingsView.swift):
- Model picker with 4B and 8B options, RAM-gated (6GB+ / 8GB+)
- Download/delete per model with progress tracking
- Temperature, Top-K, Top-P, repetition penalty, max output controls
- Advanced settings with context size display and custom model ID field

Crash prevention:
- BackgroundProcessingManager skips auto-resume after crash detection
- EnhancedLoggingSystem tracks session lifecycle for crash detection
- CoreDataManager orphan recording logs reduced to debug level

Integration:
- AIEngineType.mlxSwift added with full settings/factory/metadata support
- SummaryMetadataCodec recognizes MLX/Bonsai engine names
- SimpleSettingsView preserves MLX selection in on-device flow
- Version bumped to 1.10

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…xperimental engines

- Set MLXSwiftSettingsKeys.enabled = false when experimental is disabled so MLX
  no longer appears available anywhere (not just deselected as active engine)
- Filter MLX Swift out of the AI engine list in AISettingsView when experimental
  is off; add @AppStorage observer so the list updates reactively
- Guard MLXSwiftSettingsView from opening when experimental is disabled
- Migrate selected on-device model to first available non-experimental model when
  experimental is disabled; preserve stored ID for re-enable
- On <6GB devices where all on-device models are experimental, fall back to Apple
  Native when experimental is disabled (both MLX and On-Device AI become unusable)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: da4ad78b3e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +1713 to +1714
return EnhancedSummaryData(
recordingURL: summaryData.recordingURL,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve summary identity when rebuilding fallback data

When summaryData.recordingId is nil, this fallback path rebuilds the model with the legacy initializer, which generates a new id instead of preserving the existing summary ID. That breaks attachment/note continuity for legacy summaries because subsequent reads/writes target a different folder/key, and cleanup on delete can miss the original supplemental data.

Useful? React with 👍 / 👎.

Comment on lines +388 to +390
let bookmarkData = try? url.bookmarkData(
options: [.minimalBookmark],
includingResourceValuesForKeys: nil,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Store security-scoped bookmark for archived destination

Archive locations are persisted with .minimalBookmark only, so the saved bookmark does not reliably retain sandbox access across app launches. In the restore flow this can leave startAccessingSecurityScopedResource() without valid permissions, making previously archived files unreadable until the user reselects the location.

Useful? React with 👍 / 👎.

bisonbet and others added 4 commits May 9, 2026 19:14
Same fix as claude-code-review: pull-requests and issues need to be
write so Claude can actually reply when @-mentioned in a PR or issue.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The install-github-app PR (#89) reverted permissions back to read.
Re-apply write so the action can post review output.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revert the earlier write-permission change — the Claude Code Action
authenticates as the GitHub App, so the workflow's GITHUB_TOKEN perms
weren't the cause of silent reviews. Restore the original read perms.

Add display_report: true so the review summary always appears on the
PR, even when there are no inline comments to post.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add use_sticky_comment: true so the review summary is posted directly
on the PR (and updated on subsequent runs), not just hidden in the
Actions step summary.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bisonbet
bisonbet merged commit a5dcdc1 into main May 10, 2026
1 check passed
@bisonbet
bisonbet deleted the v1.10 branch May 10, 2026 00:33
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.

2 participants