Skip to content

Feat/zeppelin data layer 1 - #978

Merged
altic-dev merged 12 commits into
altic-dev:mainfrom
Ghatage:feat/zeppelin-data-layer-1
Sep 21, 2026
Merged

altic-dev merged 12 commits into
altic-dev:mainfrom
Ghatage:feat/zeppelin-data-layer-1

Conversation

@Ghatage

@Ghatage Ghatage commented Sep 16, 2026

Copy link
Copy Markdown
Contributor

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

  • 🐞 Bug fix
  • ✨ New feature
  • 💥 Breaking change
  • 🧹 Chore
  • 📝 Documentation update

Related Issue or Discussion

Previous PR: #954
Discussed this with maintainers in person.

Testing

  • Tested on Intel Mac
  • Tested on Apple Silicon Mac
  • Tested on macOS version: 27 beta
  • Ran linter locally: swiftlint --strict --config .swiftlint.yml Sources Tests Package.swift
  • Ran formatter locally: swiftformat --config .swiftformat Sources
  • Ran tests locally:

Screenshots / Video

Attach screenshots or a video for UI, UX, settings, onboarding, overlay, menu bar, or visual behavior changes.
Screenshot 2026-09-09 at 1 56 35 PM
Screenshot 2026-09-09 at 1 57 06 PM

  • No UI/visual changes; screenshots/video are not applicable.

Notes

  • Adds Zeppelin-Embed for lightening fast search, opens the door to more unique features and owning the entire stack from application to infra

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.
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 16, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-17T00:00:17.893884Z fbb6373 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@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: 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) })

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 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 👍 / 👎.

@greptile-apps

greptile-apps Bot commented Sep 17, 2026

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

The PR appears safe to merge with no outstanding actionable findings.

Summary

This PR adds app-wide search backed by Zeppelin, indexes persisted history, transcripts, and chats, and connects results to their corresponding UI destinations.

  • Adds search indexing, reconciliation, lifecycle management, and persistence revisions.
  • Adds sidebar search results for history, transcripts, chats, dictionary data, prompts, and settings.
  • Adds direct reveal behavior for matched dictionary rows, prompt profiles, settings, and history records.
  • Adds integration coverage for the Zeppelin data layer, search service, navigation, and restore reindexing.

Reviews (4) · Last reviewed commit: "Fix stale sidebar search results after s..."

Comment thread Sources/Fluid/Persistence/Search/SearchIndexRecord.swift
Comment thread Sources/Fluid/Persistence/Search/SearchIndexRecord.swift
Comment thread Sources/Fluid/ContentView.swift
Comment thread Sources/Fluid/Theme/Components/OnboardingComponents.swift Outdated
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.
@greptile-apps

greptile-apps Bot commented Sep 17, 2026

Copy link
Copy Markdown

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.
@Ghatage
Ghatage force-pushed the feat/zeppelin-data-layer-1 branch from c91a6a7 to a9b06ba Compare September 18, 2026 03:22
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.
kirolos-esmat pushed a commit to kirolos-esmat/FluidVoice that referenced this pull request Sep 19, 2026
@github-actions

Copy link
Copy Markdown

FluidVoice PR build ready

Download 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

  1. Extract the downloaded artifact, then extract FluidVoice-PR-978.app.zip.

  2. Move FluidVoice Feat/zeppelin data layer 1 #978.app into the /Applications folder.

  3. Open Terminal and remove the download quarantine marker:

    xattr -dr com.apple.quarantine "/Applications/FluidVoice #978.app"
    
  4. In Applications, Control-click FluidVoice Feat/zeppelin data layer 1 #978.app and choose Open.

  5. If macOS still blocks it, open System Settings → Privacy & Security, click Open Anyway, and confirm.

This build has its own app identity, so its permissions are separate from the release version of FluidVoice.

View workflow run

@altic-dev
altic-dev merged commit c391411 into altic-dev:main Sep 21, 2026
6 checks passed
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