Skip to content

Replace GRDB/SQLite with JSON file persistence#5

Merged
vaayne merged 2 commits intomainfrom
refactor/json-persistence
Mar 20, 2026
Merged

Replace GRDB/SQLite with JSON file persistence#5
vaayne merged 2 commits intomainfrom
refactor/json-persistence

Conversation

@vaayne
Copy link
Copy Markdown
Owner

@vaayne vaayne commented Mar 20, 2026

Summary

  • Drop GRDB dependency — replace SQLite with a single mori.json file
  • Add JSONStore (NSLock thread safety, atomic writes via Data.write(.atomic))
  • Nest worktrees inside projects: {projects: [{project, worktrees[]}], uiState}
  • Rewrite repositories to wrap JSONStore — same public API, minimal WorkspaceManager changes
  • Delete Database.swift, all GRDB record types, and Records/ directory
  • Link libc++ for GhosttyKit (was transitively provided by GRDB)
  • Net -185 lines (203 added, 388 removed)

Why

Mori's data is tiny — a handful of projects/worktrees and one UI state singleton. All operations are simple CRUD with no complex queries, joins, or transactions. A human-readable JSON file is simpler, easier to debug, and has zero external dependencies.

Test plan

  • 47 persistence test assertions pass
  • mise run dev builds and launches
  • Manual: add project, quit, relaunch — verify state persists
  • Manual: inspect ~/Library/Application Support/Mori/mori.json is valid nested JSON

- addProject now calls gitBackend.status() to get the real current branch
- selectWorktree refreshes branch name on switch via refreshWorktreeBranch()
- Non-git repos fall back to "main" safely
Copy link
Copy Markdown

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

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: aa5cbab3b1

ℹ️ 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 +50 to +51
let storeURL = appSupport.appendingPathComponent("mori.json")
let store = JSONStore(fileURL: storeURL)
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 Migrate existing mori.sqlite data before reading mori.json

Pointing the app at mori.json here breaks every upgrade from the previous SQLite-backed build: JSONStore only reads the new JSON file, so users who already have projects/worktrees/UI state in ~/Library/Application Support/Mori/mori.sqlite will launch into an empty workspace after updating. Because this commit removes all GRDB-based loading code, there is no one-time import or fallback path to preserve existing data.

Useful? React with 👍 / 👎.

Comment on lines +85 to +87
try data.write(to: tmp, options: .atomic)
_ = try? FileManager.default.removeItem(at: url)
try FileManager.default.moveItem(at: tmp, to: url)
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 Replace the JSON file without deleting the last good copy first

The write path is not actually atomic here. If removeItem(at: url) succeeds and the subsequent moveItem(at: tmp, to: url) fails, or the process is interrupted between those calls, the previous persisted store is already gone. In that case the next launch hits load's empty-store fallback and the user loses all saved projects/worktrees/UI state from a transient I/O error during save.

Useful? React with 👍 / 👎.

Drop GRDB dependency and replace SQLite with a single JSON file at
~/Library/Application Support/Mori/mori.json.

- Add JSONStore (NSLock thread safety, atomic writes via Data.write)
- Nest worktrees inside projects: {projects: [{project, worktrees[]}]}
- Rewrite repositories to wrap JSONStore (same public API)
- Delete Database.swift, GRDB record types, and Records/
- Link libc++ for GhosttyKit (was transitively provided by GRDB)
- 47 test assertions pass, net -180 lines
@vaayne vaayne force-pushed the refactor/json-persistence branch from e557948 to befe30d Compare March 20, 2026 07:10
@vaayne
Copy link
Copy Markdown
Owner Author

vaayne commented Mar 20, 2026

@codex review

Copy link
Copy Markdown

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

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: befe30ddbc

ℹ️ 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 +48 to +50
).first!.appendingPathComponent("Mori", isDirectory: true)
try? FileManager.default.createDirectory(at: appSupport, withIntermediateDirectories: true)
let storeURL = appSupport.appendingPathComponent("mori.json")
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 existing mori.sqlite data during upgrade

Upgrading an existing install will now appear to wipe all persisted state: startup always opens ~/Library/Application Support/Mori/mori.json, but the previous builds stored projects/worktrees/UI state in mori.sqlite and there is no fallback or import path anywhere in this change. On first launch after upgrading, loadAll() will see an empty store and users have to re-add every project manually. Please migrate the old SQLite file or keep reading it until a migration has run.

Useful? React with 👍 / 👎.

Comment on lines +89 to +90
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
try? data.write(to: url, options: .atomic)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Propagate JSON write failures instead of reporting success

These try?s make every repository save/delete effectively succeed even when the Application Support directory is unwritable or the disk is full. In that case _data is still mutated in memory, so the UI looks correct for the rest of the session, but the change is silently lost on restart. The old GRDB-backed code surfaced write errors through the throwing repository APIs, so this is a regression in persistence guarantees.

Useful? React with 👍 / 👎.

@vaayne vaayne merged commit 1aba6a1 into main Mar 20, 2026
1 check passed
@vaayne vaayne deleted the refactor/json-persistence branch March 20, 2026 07:23
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