Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion agents/review-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Review state (marks and notes) is persistent across app restarts, local to the u

- Canonical implementation: `jayjay_review::ReviewStore` (marks in `marks.rs`, notes in `note_store.rs`, reconciliation in `reconcile.rs`).
- Persistence and file format: [Storage Guide](storage.md).
- SwiftUI: `Shared/ReviewStore.swift` is an `@Observable` UniFFI facade with a per-file marks cache and a one-time UserDefaults legacy import.
- SwiftUI: `Shared/ReviewStore.swift` is an `@Observable` UniFFI facade with a per-file marks cache.
- GPUI: one process-global store; mutate only through `window/review.rs::mutate` (refresh from disk first). Render-path reads use `refresh_if_stale`. Note reconciliation loads asynchronously on the view model (`loaders/review_notes.rs`, generation-guarded).

## Marks
Expand Down
2 changes: 1 addition & 1 deletion agents/storage.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Rust stores resolve platform-native directories through `directories::ProjectDir
| Pinned repositories | `jayjay-core`; SwiftUI via UniFFI; GPUI directly | `repositories.json` in the shared config directory | An ordered `repositories` array of canonical absolute UTF-8 repository paths. New pins are inserted first; empty paths and exact duplicates are removed on load. |
| Review marks and notes | `jayjay-review`; SwiftUI via UniFFI; GPUI and CLI directly | `review_store.json` in the shared config directory | File/hunk review marks keyed by `change_id|path`, content identities, and local review notes including path, side, line, anchor context, body, timestamps, and resolution state. |
| SwiftUI settings and history | SwiftUI-only `AppSettings` | `UserDefaults` for bundle `dev.hewig.jayjay` | Appearance and font, diff options, layout, confirmations, onboarding, editor/terminal choices, update channel, sponsorship state, up to 12 recent repositories, and the last opened repository. |
| SwiftUI auxiliary state | SwiftUI components | The same `UserDefaults` domain | Command-palette position. A legacy `jayjay.reviewedFiles` blob is imported once into the shared review store and then removed. |
| SwiftUI auxiliary state | SwiftUI components | The same `UserDefaults` domain | Command-palette position. |
| GPUI settings and history | GPUI-only Rust `AppConfig` | `config.toml` in the platform config directory | Appearance and font, diff options, layout, tools, feature confirmations, onboarding, update channel, window bounds/maximized state, and up to 12 recent repositories. |

Recent repositories are history, not projects. Each shell owns its own recent list. Pins are persistent projects and are intentionally shared by both shells.
Expand Down
29 changes: 0 additions & 29 deletions shell/mac/Sources/JayJay/Shared/ReviewStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import JayJayCore
final class ReviewStore {
typealias ReviewNote = NoteEntry

private static let legacyStorageKey = "jayjay.reviewedFiles"

let storeURL: URL?
var notes: [ReviewNote]
// Observable stand-in for the cache's contents: SwiftUI views read marks during render (gutter stripes, file rows), and without a tracked read a toggle would not re-render them until something else invalidated the view.
Expand All @@ -17,7 +15,6 @@ final class ReviewStore {
init() {
storeURL = reviewStorePath().map { URL(fileURLWithPath: $0) }
notes = []
importLegacyMarks(from: .standard)
}

/// Test seam: persist to an explicit file instead of the shared store path.
Expand Down Expand Up @@ -149,30 +146,4 @@ final class ReviewStore {
marksCache.removeAll()
marksVersion &+= 1
}

// MARK: Legacy migration

/// One-time import of marks the old UserDefaults-backed store left behind; runs only while no shared store file exists yet, then drops the legacy blob.
func importLegacyMarks(from defaults: UserDefaults) {
guard let storeURL, !FileManager.default.fileExists(atPath: storeURL.path),
let data = defaults.data(forKey: Self.legacyStorageKey),
let raw = try? JSONSerialization.jsonObject(with: data) as? [String: Any]
else { return }
for (key, value) in raw {
guard let separator = key.firstIndex(of: "|"),
let dict = value as? [String: Any],
let identity = dict["identity"] as? String,
!identity.isEmpty
else { continue }
let changeId = String(key[..<separator])
let path = String(key[key.index(after: separator)...])
let hunks = (dict["hunks"] as? [Int])?.compactMap { $0 >= 0 ? UInt32($0) : nil } ?? []
if dict["file_marked"] as? Bool ?? false {
markReviewed(changeId: changeId, path: path, identity: identity)
} else if !hunks.isEmpty {
setReviewedHunks(changeId: changeId, path: path, identity: identity, hunkIndices: hunks)
}
}
defaults.removeObject(forKey: Self.legacyStorageKey)
}
}
24 changes: 0 additions & 24 deletions shell/mac/Tests/JayJayTests/ReviewStoreTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -171,30 +171,6 @@ final class ReviewStoreTests: XCTestCase {
)
}

func testLegacyDefaultsImportOnFirstRun() throws {
let url = tempStoreURL()
let suiteName = "review-migration-\(UUID().uuidString)"
let defaults = try XCTUnwrap(UserDefaults(suiteName: suiteName))
defer { defaults.removePersistentDomain(forName: suiteName) }
let legacy: [String: Any] = [
"c1|a.txt": ["identity": "idA", "file_marked": true],
"c1|b.txt": ["identity": "idB", "file_marked": false, "hunks": [1]]
]
try defaults.set(JSONSerialization.data(withJSONObject: legacy), forKey: "jayjay.reviewedFiles")

let store = ReviewStore(storeURL: url)
store.importLegacyMarks(from: defaults)

XCTAssertTrue(store.isReviewed(changeId: "c1", path: "a.txt", identity: "idA"))
XCTAssertTrue(store.isHunkReviewed(changeId: "c1", path: "b.txt", identity: "idB", hunkIndex: 1))
XCTAssertNil(defaults.data(forKey: "jayjay.reviewedFiles"))

// A later run with an existing store file must not re-import or drop the blob.
try defaults.set(JSONSerialization.data(withJSONObject: legacy), forKey: "jayjay.reviewedFiles")
ReviewStore(storeURL: url).importLegacyMarks(from: defaults)
XCTAssertNotNil(defaults.data(forKey: "jayjay.reviewedFiles"))
}

func testMalformedStoreIsPreservedBeforeWrite() throws {
let url = tempStoreURL()
try FileManager.default.createDirectory(
Expand Down
Loading