Feat/zeppelin data layer 1 - #978
Conversation
Add zeppelin-embed 0.3.0 as a static XCFramework dependency for the app and integration-test targets. FluidVoice needs lastAsPrefix on QueryOptions for type-ahead search. Add FluidZeppelinRoot, the single Zeppelin database under Application Support. Namespaces are created on demand, independent of each other, and opened under one cached task so concurrent first callers share the open instead of racing on the writer lock. Every namespace is a derived copy of a store the app already keeps, so the library's default derived durability applies and one that cannot be read is reset and rebuilt from its source. Namespaces are closed at termination so their logs are checkpointed rather than replayed.
Mirror dictation history, file transcripts, and Command Mode chats into separate record-only Zeppelin namespaces while keeping the existing stores authoritative. Reconcile each namespace from source records so launch backfill, write-through updates, eviction cleanup, and reset recovery share one idempotent path. Reconciles for one kind run in order so two cannot interleave. Search the namespaces with last-word prefix matching and combine them with in-memory matches for dictionary entries, prompts, vocabulary, punctuation rules, and settings. Preserve group ordering, rank only within a group, cancel stale queries, and build highlighted snippets from the source text.
A chat hit now loads that session before opening Command Mode instead of showing whichever chat was current. When a reconcile adds or removes rows, the open query is re-run, so a search made before launch backfill finishes fills in on its own and a dictation made while the results are open appears in them.
A changed query left stale rows actionable during debounce. Global settings search exposed hidden controls, and transcript hits scrolled to their list rows instead of the selected details. Invalidate published groups whenever a query changes. Share the existing availability rules across both search surfaces, and give each selected transcript detail an explicit scroll target. Cover query invalidation, conditional settings filtering, and transcript detail targeting with focused regression tests.
History selection tried to scroll before clearing a local filter had rendered the target row. Self-target dictation also treated every FluidVoice field editor as a sidebar search destination, even when the recording began in another app. Defer filtered history scrolling to the next main-loop turn. Mark sidebar search controls explicitly, and insert into their field editor only when the captured recording focus is still exact and owned by FluidVoice.
Chat search derived its revision from updatedAt. A clock correction could therefore make later saves look older than the indexed document and leave stale message text searchable. Persist a per-session search revision and advance it on every save. Legacy sessions seed the counter from their existing timestamp revision, so the first update remains newer without requiring Zeppelin to accept a lower revision.
Up and Down moved the custom search cursor without moving the result list, so the highlighted row could leave the visible sidebar while Return still targeted it. Wrap the grouped results in a ScrollViewReader and reveal each new cursor target using the row identifiers already shared with AppSearchHit.
Settings search exposed Dictation controls that SettingsView omits until Accessibility permission is enabled. Opening those results selected a reveal target with no rendered row or scroll anchor. Apply the same permission gate to every nested Dictation target while keeping the visible permission and Global Hotkey rows searchable. Preserve the existing history conditions for audio storage. Validation after rebasing onto main: AppSearchServiceTests (8) and SettingsNavigationStateTests (20) passed. Swift parsing and strict SwiftLint passed for the resolved views and settings-search files.
History initially publishes an empty array while its asynchronous load is pending. Reconciling that snapshot can delete existing Zeppelin records before loading finishes, and edits after a failed load still describe incomplete history. Gate the history publisher on hasLoaded so indexing starts only after disk entries and pending edits are merged. Replay the current snapshot to late subscribers and resume indexing after successful retries. Keep the 250 ms debounce and serialized reconciliation, including deletion for successfully loaded empty history and explicit clears. Add five regressions using isolated history storage and seeded indexes. Observe the delayed-startup failure before the fix and its passing result afterward. All 47 focused XCTest cases pass with parallel execution disabled, along with the history persistence and presentation harnesses. Apply SwiftFormat across Sources and use equivalent SwiftUI aspect-ratio helpers to satisfy strict SwiftLint. Full Sources formatting, strict lint on all changed files, and diff whitespace checks pass.
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fbb6373a6a
ℹ️ 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".
|
|
||
| func start(historyStore: TranscriptionHistoryStore = .shared) { | ||
| guard self.cancellables.isEmpty else { return } | ||
| self.mirror(.history, historyStore.loadedEntriesPublisher.map { $0.map(\.searchRecord) }) |
There was a problem hiding this comment.
Refresh active searches after history finishes loading
If a user searches during the asynchronous history load on a normal relaunch, indexed hits are discarded because AppSearchService.historyGroup joins them against the still-empty entries. This publisher now waits until loading succeeds, but the coordinator refreshes the active query only when reconciliation changes the already-persisted index; when that index is already current, the report is empty and the history results remain missing until the user edits the query. Refresh the active search when this authoritative snapshot arrives even if reconciliation is a no-op.
Useful? React with 👍 / 👎.
|
The startup indexing fix included a Sources-wide formatter pass, expanding the review across 84 unrelated source files. Restore those files and the existing search tests to their previous formatting. Keep the history readiness publisher, serialized indexing behavior, test isolation, and all five startup regression tests unchanged. The remaining functional changes are confined to two implementation files and the search test file. Scoped strict SwiftLint and diff whitespace checks pass.
|
Want your agent to iterate on Greptile's feedback? Start a greploop in Codex and it will work through the open comments and keep going until this PR reviews clean. |
0.4.1 rebuilds the macOS static library without embedded LLVM bitcode and with std/core/alloc recompiled from source (-Z build-std), cutting the SPM download from the 0.3.0 xcframework to 4.45MB. No API, search behavior, or on-disk format changes -- verified against zeppelin-embed's full test suite and this app's own build/launch.
c91a6a7 to
a9b06ba
Compare
History results join the Zeppelin index against TranscriptionHistoryStore.entries, which is empty until the asynchronous load finishes. The coordinator only refreshed the visible results when reconcile reported a change, so on a relaunch where the on-disk index already matched history, a query typed while loading kept showing no history rows until the query changed. Refresh on the first authoritative history snapshot as well, leaving transcripts and chats on the reconcile-diff gate. History entries were always indexed at revision 1, and reconcile only replaces a row whose revision rose. A backup restore that puts different text under an existing id was therefore skipped, and the index kept serving the pre-restore text. Persist a searchRevision on TranscriptionHistoryEntry and bump it past the restored, the indexed and the wall-clock value on restore, as ChatSession already does. File transcriptions stay at revision 1: each one is stored under a fresh id, the store only appends and deletes, and no backup restores them. Opening a dictionary, vocabulary, punctuation or prompt hit navigated to the destination screen and dropped the identifier the hit carried, leaving the user to find the row again. Carry the target into the screen the way the settings reveal target already is, and open the matched entry, custom word, punctuation rule or prompt there. Add a coordinator regression for the restore: it fails against the fixed revision 1 and passes with the persisted one. The search, app search, Zeppelin data layer, dictionary and history persistence suites pass (56 tests), the app builds, and scoped SwiftLint and SwiftFormat report no new issues.
FluidVoice PR build readyDownload FluidVoice-PR-978-5d97eed6f144 The artifact contains the ad-hoc-signed app ZIP, Xcode archive, build manifest, and installation instructions. It expires 5 days after the build. Install the app
This build has its own app identity, so its permissions are separate from the release version of FluidVoice. |
Description
Adding general search to FluidVoice.
Users will now be able to search across their conversation history, transcripts, settings, everything that is text within the app.
Clicking the results will take them there directly.
Type of Change
Related Issue or Discussion
Previous PR: #954
Discussed this with maintainers in person.
Testing
swiftlint --strict --config .swiftlint.yml Sources Tests Package.swiftswiftformat --config .swiftformat SourcesScreenshots / Video
Attach screenshots or a video for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes.


Notes