Skip to content

[Agent] feat(logs): PVLogFileManager + session log browser (#3612)#3615

Merged
github-actions[bot] merged 6 commits intodevelopfrom
agent/issue-3612-log-sessions
Apr 3, 2026
Merged

[Agent] feat(logs): PVLogFileManager + session log browser (#3612)#3615
github-actions[bot] merged 6 commits intodevelopfrom
agent/issue-3612-log-sessions

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

Summary

  • Adds PVLogFileManager to PVLogging — subscribes to PVLogPublisher and writes rotating date-stamped log files to Library/Logs/Provenance/
    • Max 10 files, 2 MB each; oldest pruned automatically on rotation
    • startLogging() / stopLogging() API
  • Adds Settings › Debug › Session LogsPVLogSessionBrowserView
    • Live status indicator (active/inactive)
    • Toggle logging on/off from within the settings view
    • List of session log files with size and relative time
    • Per-file: inline viewer, share via iOS share sheet, swipe-to-delete
    • Delete All with confirmation

Files Changed

File Change
PVLogFileManager.swift New in PVLogging — rotating file logger subscribing to PVLogPublisher
PVLogSessionBrowserView.swift New in PVSwiftUI — session log list + viewer + share + delete
SettingsSwiftUI.swift Added "Session Logs" navigation link in Debug section

Test plan

  • Settings → Debug → Session Logs — verify row appears
  • Tap "Start" — status indicator turns green
  • Generate some logs (navigate around the app)
  • Return — verify a session log file appears in the list
  • Tap view icon — log content displays in the viewer sheet
  • Tap share icon — iOS share sheet opens with the log file
  • Swipe to delete a file — it disappears from the list
  • Tap "Delete All" — confirm dialog — all files deleted
  • Tap "Stop" — status indicator turns gray

Part of #3612

🤖 Generated with Claude Code

@github-actions github-actions Bot requested a review from JoeMatt as a code owner March 30, 2026 23:38
@github-actions github-actions Bot enabled auto-merge (squash) March 30, 2026 23:38
@github-actions github-actions Bot added agent-work PR or issue being worked on by the AI agent ai-reviewing AI code review in progress labels Mar 30, 2026
@github-actions github-actions Bot mentioned this pull request Mar 30, 2026
@JoeMatt
Copy link
Copy Markdown
Member

JoeMatt commented Mar 30, 2026

@claude please do a self-review pass on this PR:

Verify all changed files compile (check for unresolved symbols, missing imports, type mismatches)
Check for any dead code, unused variables, or missing test coverage
Ensure multi-platform compatibility (iOS + tvOS + macOS Catalyst)
Fix all issues
Be throgough

@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented Mar 30, 2026

✅ Self-Review Complete

Completed

  • 🚀 Agent started
  • 📖 Checked out branch agent/issue-3612-log-sessions
  • 🔍 Reviewed all 5 changed files
  • 🔨 Applied 2 fixes
  • ✅ 56 tests pass
  • 📤 Pushed to branch

Completed: 2026-04-03 02:22 UTC

@github-actions
Copy link
Copy Markdown
Contributor Author

Self-Review Complete ✅

Ran a thorough self-review pass. Found and fixed 5 issues across 3 files.


Issues Fixed

PVLogFileManager.swift — 4 fixes

  1. Linux compile failureprivate var cancellable: AnyCancellable? was declared unconditionally but AnyCancellable requires Combine, which is unavailable on Linux. Wrapped in #if canImport(Combine).

  2. Data races on isLogging / currentSessionURL — Both computed properties read cancellable / currentFileURL from any thread, while those vars are only written on the serial queue. Changed both to use queue.sync { ... } for safe cross-thread reads.

  3. Non-Combine guard logicstartLogging() guarded against double-start using cancellable == nil — missing on non-Combine platforms. Fixed to check currentFileHandle == nil in the #else branch.

  4. Force-unwrap crash on LinuxlogsDirectory used urls(for: .libraryDirectory).first!, which crashes on Linux (returns empty array). Changed to a safe if let with a temporaryDirectory fallback.

PVLogSessionBrowserView.swift — 1 fix

  1. Dead codelet fm = FileManager.default was declared but never used (only referenced by a _ = fm suppression line). Both lines removed.

Tests Added

Added 7 new tests in PVLogFileManagerTests suite covering:

  • Singleton identity
  • isLogging is false before start
  • startLogging creates a file and sets isLogging
  • stopLogging clears state
  • logFiles() returns created files
  • deleteFile(at:) removes from disk
  • deleteAllFiles() clears all

All 56 tests pass (swift test on Linux).


Multi-platform Check

Platform onDelete .navigationBarTrailing ActivityShareSheet
iOS ✅ (#if !os(tvOS) guarded)
tvOS ✅ compiles (no-op) ✅ (used elsewhere without guard) ✅ excluded
macOS/Catalyst

No new platform guard issues introduced.

@github-actions github-actions Bot removed the ai-reviewing AI code review in progress label Mar 31, 2026
@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch from 96dae36 to 9e67fb4 Compare March 31, 2026 00:32
@JoeMatt
Copy link
Copy Markdown
Member

JoeMatt commented Mar 31, 2026

@claude please do a self-review pass on this PR:

Verify all changed files compile (check for unresolved symbols, missing imports, type mismatches)
Check for any dead code, unused variables, or missing test coverage
Ensure multi-platform compatibility (iOS + tvOS + macOS Catalyst)
Fix all issues
Be throgough

@github-actions
Copy link
Copy Markdown
Contributor Author

Self-Review Results

Reviewed all changed files. All 56 tests pass. Two issues found and fixed:

Issues Fixed

1. FileHandle.write(_:) deprecated (iOS 13.4+)

  • PVLogFileManager.swiftopenNewFile() and write(entry:) were calling the deprecated write(_ data: Data) method on FileHandle
  • Fixed by switching to try? currentFileHandle?.write(contentsOf: data) (the non-deprecated API available since iOS 13.4)
  • Also silenced an unused-result warning on FileManager.createFile(atPath:contents:) with _ =

2. @ObservableObject should be @Observable (CLAUDE.md mandate)

  • PVLogSessionBrowserView.swiftPVLogSessionViewModel was using ObservableObject + @Published, which is the pre-iOS-17 pattern
  • Fixed: migrated to @MainActor @Observable class with plain var properties; view updated from @StateObject@State
  • Added import Observation

Verified

  • swift build — clean (no errors, no new warnings)
  • swift test — all 56 tests pass
  • Multi-platform: tvOS-unavailable APIs (DragGesture, haptics, orientation) not used; share sheet correctly guarded with #if !os(tvOS)
  • No dead code, no unused imports
  • RetroTheme.retroPink/retroBlue resolve from PVUIBase (used across 50+ files)
  • LogEntry.formattedTimestamp and LogLevel.name both exist in PVLogPublisher.swift

@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch 2 times, most recently from 593bc02 to fbbbe10 Compare April 2, 2026 01:41
@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented Apr 2, 2026

🤖 Merge conflict detected — Claude has been dispatched to resolve the conflicts automatically.

If conflicts cannot be resolved cleanly, the workflow will post a comment explaining what needs manual attention.

@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch from fbbbe10 to 952599e Compare April 2, 2026 02:20
@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented Apr 2, 2026

✅ Rebase conflicts resolved — kept both RetroArchLogBrowserView (from develop) and PVLogSessionBrowserView (from this PR) as separate navigation links in SettingsSwiftUI.swift.

@JoeMatt JoeMatt force-pushed the agent/issue-3612-log-sessions branch from 952599e to ea6a276 Compare April 2, 2026 03:49
@JoeMatt
Copy link
Copy Markdown
Member

JoeMatt commented Apr 2, 2026

@claude please do a self-review pass on this PR:

Verify all changed files compile (check for unresolved symbols, missing imports, type mismatches)
Check for any dead code, unused variables, or missing test coverage
Ensure multi-platform compatibility (iOS + tvOS + macOS Catalyst)
Fix all issues
Be throgough

new pvlog code recently added too, react to that

@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch from ea6a276 to 55af798 Compare April 2, 2026 03:54
@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented Apr 2, 2026

Self-Review Pass — Findings & Fixes

Reviewed all 5 changed files. PVLogFileManager.swift and PVLoggingTests.swift are clean. Three issues were found and fixed in PVLogSessionBrowserView.swift:

Issues Fixed

1. ActivityShareSheet (UIViewControllerRepresentable) → ShareLink (CLAUDE.md violation)

  • CLAUDE.md explicitly requires ShareLink over UIActivityViewController wrapped in UIViewControllerRepresentable for iOS 16+/17+ targets.
  • Replaced both the list row share button and the viewer sheet share button with ShareLink(item: entry.url).
  • Removed shareItems: [Any], showingShareSheet, showingShare state vars, the .sheet(isPresented:) for sharing, and the ActivityShareSheet struct entirely.
  • Removed now-unused #if canImport(UIKit) import UIKit #endif.

2. ToolbarItem(placement: .navigationBarTrailing).topBarTrailing

  • .navigationBarTrailing is deprecated in iOS 16+. The rest of the codebase consistently uses .topBarTrailing (e.g. AppearanceView.swift, HomeView.swift, CloudSyncSettingsView.swift).

3. Broken .constant() alert binding

  • .alert("Error", isPresented: .constant(viewModel.errorMessage != nil)).constant() freezes the boolean at view evaluation time; the alert would never dismiss when errorMessage was set to nil.
  • Fixed with Binding(get: { viewModel.errorMessage != nil }, set: { if !$0 { viewModel.errorMessage = nil } }).

Verified Clean

  • PVLogFileManager.swift — No issues. Thread safety via serial queue, @unchecked Sendable is justified, file rotation and pruning logic is correct.
  • PVLoggingTests.swift — 56 tests, all pass (swift test on Linux). Test isolation is correct: .serialized suite + unique temp directories per test.
  • SettingsSwiftUI.swift — Single NavigationLink addition is correct.
  • Multi-platform: All platform guards (#if !os(tvOS)) are in place. ShareLink is available tvOS 16+; share is intentionally iOS-only per the existing guards.

Build & Test

swift build  ✅  Build complete
swift test   ✅  56/56 tests passed

@github-actions github-actions Bot added needs-fixes AI review found issues; auto-fixer dispatched ai-reviewing AI code review in progress and removed needs-fixes AI review found issues; auto-fixer dispatched labels Apr 2, 2026
@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch from dbc2eab to 4300f03 Compare April 2, 2026 04:26
@github-actions github-actions Bot removed the ai-reviewing AI code review in progress label Apr 2, 2026
@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch from 4300f03 to f88a90e Compare April 2, 2026 04:48
@JoeMatt
Copy link
Copy Markdown
Member

JoeMatt commented Apr 2, 2026

@claude please do a self-review pass on this PR:

Verify all changed files compile (check for unresolved symbols, missing imports, type mismatches)
Check for any dead code, unused variables, or missing test coverage
Ensure multi-platform compatibility (iOS + tvOS + macOS Catalyst)
Fix all issues
Be throgough

@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch from f88a90e to 05f8f70 Compare April 2, 2026 05:02
@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented Apr 2, 2026

Self-Review Pass — Results

What Was Checked

Compilation

  • PVLogging module builds cleanly (swift build) — no errors, no new warnings
  • All 56 tests pass (swift test)
  • PVLogSessionBrowserView.swift imports verified: PVUIBase (for RetroTheme) and PVLogging are both declared dependencies of the PVSwiftUI target in PVUI/Package.swift

Type Safety

  • LogEntry.formattedTimestamp, LogEntry.level.name, LogEntry.category, LogEntry.message — all confirmed in PVLogPublisher.swift
  • PVLogPublisher.shared.logPublisher — confirmed AnyPublisher<LogEntry, Never>
  • Observation import for @Observable — consistent with codebase pattern (RetroArchLogBrowserView, RipperViewModel, etc.) ✅

Thread Safety

  • All mutable state in PVLogFileManager is accessed on the serial queue
  • isLogging / currentSessionURL use queue.sync correctly — no reentrancy risk (neither is called from within queue blocks) ✅
  • pruneOldFiles()logFiles() call chain: no queue.sync involved, no deadlock risk ✅
  • ViewModel is @MainActor @Observable — correct usage ✅

Multi-Platform

  • ShareLink guarded with #if !os(tvOS)
  • tvOS uses .onTapGesture instead of share buttons ✅
  • navigationBarTitleDisplayMode(.inline) and edgesIgnoringSafeArea — consistent with all other settings views in the codebase ✅
  • No use of DragGesture, haptics, or other tvOS-unavailable APIs ✅

Dead Code / Unused Imports

  • None found ✅

Bug Fixed

pruneOldFiles() was only called on file rotation, not on session start

The maxFileCount limit (default: 10) was only enforced when a single file grew past maxFileSize (2 MB). Starting many small sessions — the typical dev/QA workflow — would never trigger rotation, so files accumulated without bound.

Fix: call pruneOldFiles() in startLogging() before openNewFile(), so the limit is enforced at every session start. Test updated to verify files.count <= maxFileCount rather than just files.count > 0.

Files changed:

  • PVLogging/Sources/PVLogging/PVLogFileManager.swift — add pruneOldFiles() call in startLogging()
  • PVLogging/Tests/PVLoggingTests.swift — assert files.count <= mgr.maxFileCount

@github-actions github-actions Bot added needs-fixes AI review found issues; auto-fixer dispatched ai-reviewing AI code review in progress and removed needs-fixes AI review found issues; auto-fixer dispatched labels Apr 2, 2026
@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch 2 times, most recently from f2ff087 to df3c623 Compare April 2, 2026 05:52
@github-actions github-actions Bot removed the ai-reviewing AI code review in progress label Apr 2, 2026
@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch 3 times, most recently from 0a77a24 to 7024b2c Compare April 3, 2026 02:13
@JoeMatt
Copy link
Copy Markdown
Member

JoeMatt commented Apr 3, 2026

@claude please do a self-review pass on this PR:

Verify all changed files compile (check for unresolved symbols, missing imports, type mismatches)
Check for any dead code, unused variables, or missing test coverage
Ensure multi-platform compatibility (iOS + tvOS + macOS Catalyst)
Fix all issues
Be thorough

@JoeMatt JoeMatt force-pushed the agent/issue-3612-log-sessions branch from 011f75c to 143e90a Compare April 3, 2026 02:22
@github-actions
Copy link
Copy Markdown
Contributor Author

github-actions Bot commented Apr 3, 2026

Self-Review Results

Reviewed all 5 changed files. The PR is clean — here's what was found and fixed:

Issues Fixed

1. Deprecated API — edgesIgnoringSafeArea (2 locations, PVLogSessionBrowserView.swift)
Color.black.edgesIgnoringSafeArea(.all) was used in both PVLogSessionBrowserView.body and LogFileViewerSheet.body. This was deprecated in iOS 14 in favour of .ignoresSafeArea(). Replaced both.

2. Test Isolation — pruneOldFilesKeepsMaxCount (PVLoggingTests.swift)
The test set mgr.maxFileCount = 3 on the shared singleton without resetting it afterward. Added defer { mgr.maxFileCount = originalMaxFileCount } to prevent state leakage if tests are reordered or new tests are added.

No Issues Found

  • Symbols — All types, methods, and properties referenced compile correctly: PVLogFileManager, PVLogPublisher, LogEntry, LogLevel, RetroTheme, PVUIBase, SettingsRow, ShareLink, @Observable
  • Multi-platformShareLink and .onTapGesture are correctly guarded with #if !os(tvOS) / #if os(tvOS); retroFocusButtonStyle guard is consistent with other rows
  • Thread safetyPVLogFileManager uses a serial DispatchQueue throughout; isLogging/currentSessionURL use queue.sync correctly from the main thread
  • Swift buildPVLogging builds cleanly on Linux (only one pre-existing warning in PVLogChannel.swift, unrelated to this PR)
  • Tests — All 56 tests pass after the fix

Commit Pushed

fix: self-review — deprecation + test isolation

@qodo-code-review
Copy link
Copy Markdown

CI Feedback 🧐

A test triggered by this PR failed. Here is an AI-generated analysis of the failure:

Action: Build & test SPM leaf modules

Failed stage: Checkout [❌]

Failed test name: ""

Failure summary:

The action failed during git submodule checkout/update because a nested submodule is referenced but
not configured:
- Git reports fatal: No url found for submodule path Cores/4DO/ThirdParty/libchdr in
.gitmodules, meaning Cores/4DO/ThirdParty/libchdr exists as a submodule entry (e.g., in
.gitmodules/.git/config/gitlink) but has no corresponding url defined in the repo’s .gitmodules.
-
As a result, Git could not recurse into Cores/4DO submodules and aborted with exit code 128 (fatal:
Failed to recurse into submodule path Cores/4DO).

Relevant error logs:
1:  ##[group]Runner Image Provisioner
2:  Hosted Compute Agent
...

332:  Cloning into '/Users/runner/work/Provenance/Provenance/Cores/melonDS/melonDS'...
333:  Cloning into '/Users/runner/work/Provenance/Provenance/Cores/melonDS/melonds-libretro'...
334:  Cloning into '/Users/runner/work/Provenance/Provenance/Cores/opera/opera-libretro'...
335:  Cloning into '/Users/runner/work/Provenance/Provenance/Cores/pcsx_rearmed/pcsx_rearmed'...
336:  Cloning into '/Users/runner/work/Provenance/Provenance/Cores/sm64ex/sm64ex-ios'...
337:  Cloning into '/Users/runner/work/Provenance/Provenance/Cores/snes9x/libretro-snes9x'...
338:  Cloning into '/Users/runner/work/Provenance/Provenance/Cores/supergrafx/libretro-supergrafx'...
339:  Cloning into '/Users/runner/work/Provenance/Provenance/CoresRetro/RetroArch/RetroArch'...
340:  Cloning into '/Users/runner/work/Provenance/Provenance/CoresRetro/RetroArch/libretro-super'...
341:  Cloning into '/Users/runner/work/Provenance/Provenance/Dependencies/HexColors'...
342:  Cloning into '/Users/runner/work/Provenance/Provenance/Dependencies/ZipArchive'...
343:  Cloning into '/Users/runner/work/Provenance/Provenance/PVRcheevos/rcheevos'...
344:  From https://github.com/Provenance-Emu/4DO-Core
345:  * branch            75eee5eef650e6fdc98803c2d686515e015d1197 -> FETCH_HEAD
346:  Submodule path 'Cores/4DO': checked out '75eee5eef650e6fdc98803c2d686515e015d1197'
347:  ##[error]fatal: No url found for submodule path 'Cores/4DO/ThirdParty/libchdr' in .gitmodules
348:  ##[error]fatal: Failed to recurse into submodule path 'Cores/4DO'
349:  ##[error]The process '/opt/homebrew/bin/git' failed with exit code 128
350:  Post job cleanup.
351:  [command]/opt/homebrew/bin/git version
352:  git version 2.53.0
353:  Copying '/Users/runner/.gitconfig' to '/Users/runner/work/_temp/5266bbc9-d756-43ee-90b5-365095c84224/.gitconfig'
354:  Temporarily overriding HOME='/Users/runner/work/_temp/5266bbc9-d756-43ee-90b5-365095c84224' before making global git config changes
355:  Adding repository directory to the temporary git global config as a safe directory
356:  [command]/opt/homebrew/bin/git config --global --add safe.directory /Users/runner/work/Provenance/Provenance
357:  [command]/opt/homebrew/bin/git config --local --name-only --get-regexp core\.sshCommand
358:  [command]/opt/homebrew/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :"
359:  Entering 'Cores/4DO'
360:  fatal: No url found for submodule path 'Cores/4DO/ThirdParty/libchdr' in .gitmodules
361:  fatal: run_command returned non-zero status while recursing in the nested submodules of Cores/4DO
362:  .
363:  ##[warning]The process '/opt/homebrew/bin/git' failed with exit code 128
364:  Cleaning up orphan processes

@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch 2 times, most recently from dcae79e to 320d365 Compare April 3, 2026 03:06
github-actions Bot and others added 6 commits April 3, 2026 03:36
PVLogFileManager subscribes to PVLogPublisher and writes rotating
date-stamped log files to Library/Logs/Provenance/ (max 10 files × 2 MB).
PVLogSessionBrowserView in Settings › Debug lets users view, share,
and delete session log archives and toggle file logging on/off.

Part of #3612

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Guard AnyCancellable property with #if canImport(Combine) to fix
  Linux compile failure
- Fix thread-safety: isLogging and currentSessionURL now use
  queue.sync to safely access queue-protected state
- Fix startLogging/stopLogging non-Combine guard to use
  currentFileHandle instead of missing cancellable
- Fix logsDirectory force-unwrap crash on Linux (no libraryDirectory)
- Remove dead `fm`/`_ = fm` code from PVLogSessionViewModel.refresh()
- Add PVLogFileManagerTests suite (7 tests) covering start/stop,
  file listing, delete, and prune behaviour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace deprecated FileHandle.write(_:) with write(contentsOf:) to
  eliminate Xcode deprecation warnings on iOS 13.4+/iOS 17+ targets
- Discard unused Bool from FileManager.createFile(atPath:) call
- Migrate PVLogSessionViewModel from ObservableObject+@published to
  @observable (iOS 17+ mandate per project conventions); update view
  to use @State instead of @StateObject

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace ActivityShareSheet (UIViewControllerRepresentable) with
  ShareLink per CLAUDE.md minimum-deployment-target guidelines (iOS 17+)
- Fix ToolbarItem placement .navigationBarTrailing → .topBarTrailing
  (deprecated; rest of codebase uses .topBarTrailing)
- Fix broken .alert binding: .constant(expr) is frozen at eval time and
  won't dismiss; replaced with Binding(get:set:) computed from errorMessage
- Remove now-unused UIKit import and shareItems/showingShareSheet state vars

All 56 PVLogging tests pass.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Without this, creating many small sessions (<2 MB each) never
triggers pruneOldFiles(), so maxFileCount is silently ignored.
Now pruneOldFiles() runs at the start of every session.
Test updated to verify the limit is actually enforced.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Replace deprecated edgesIgnoringSafeArea(.all) with ignoresSafeArea() in PVLogSessionBrowserView (two locations)
- Add defer in pruneOldFilesKeepsMaxCount test to reset maxFileCount, preventing singleton state leakage between test runs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions github-actions Bot force-pushed the agent/issue-3612-log-sessions branch from 320d365 to b7e2f29 Compare April 3, 2026 03:36
@github-actions github-actions Bot merged commit d189f51 into develop Apr 3, 2026
11 checks passed
@github-actions github-actions Bot deleted the agent/issue-3612-log-sessions branch April 3, 2026 03:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agent-work PR or issue being worked on by the AI agent

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant