Skip to content

feat(search): save encrypted local search filters - #142

Merged
johnny4young merged 3 commits into
mainfrom
codex/saved-filters
Sep 17, 2026
Merged

johnny4young merged 3 commits into
mainfrom
codex/saved-filters

Conversation

@johnny4young

@johnny4young johnny4young commented Sep 12, 2026

Copy link
Copy Markdown
Owner

Summary

Save recurring searches as editable dynamic filters in the existing Library, preserving text, mode, type, app, board and independent pinned-only predicates.

Changes

  • Store definitions locally in the encrypted database, outside the CloudKit feed; no materialized clip copies.
  • Migrate legacy preference definitions atomically and idempotently, preserving the source after decode/write failures and preserving destination edits on retry.
  • Apply predicates before result limits, distinguish pinned clips from board membership and fail closed when a referenced board disappears. Every competing Library browse scope checks request ownership before publishing, so old broad results cannot overwrite a newly selected saved filter.
  • Support save, apply, edit/rename, update and delete with English/Spanish strings and related documentation.
  • Isolate native tests from production Keychain, StoreKit and clipboard state; fix their one-shot launch fallback and Library panel handoff. Combine legacy card accessibility children for one logical result per clip.

Testing

  • Local format/lint, 1,119 Swift Testing tests / 169 suites, 82.1% package coverage, macOS+iOS builds, release/site checks and unchanged strict compiler-warning checks passed.
  • Focused native flow passed without skips: four original synthetic clips, save/apply one matching result, rename, delete filter, all four clips remain. Clean app-window screenshot inspected.
  • Durable reopen, migration retry, Unicode/quotes, invalid regex, missing boards, pre-limit pinned results and independent board predicates covered by unit tests.
  • Existing 100,000-clip scale harness passed unchanged 150 ms cold / 50 ms warm p95 budgets: FTS 23.450/26.124 ms; pinned saved filter 15.304/17.256 ms, 100 warm samples each. No speedup claim.
  • Full signed local macOS suite passed on final source: 57 tests, three existing environment skips, zero failures; saved-filter lifecycle executed without skips. Skips are typed denylist focus and two display/Space status-item actions. Final synthetic app-window evidence was exported and visually inspected.
  • Exact final SHA: 7e8d54617f95dbc4673d2314beb874fc0a3d7df9.
  • CI passed on that SHA: 1,119 tests / 169 suites / 82.0% package coverage, macOS+iOS builds and four StoreKit tests.
  • Explicit UI workflow passed on the same SHA; logs inspected. Hosted macOS: 57 tests / four skips / zero failures, with the saved-filter lifecycle executed. Hosted iOS: 11 tests / two skips / zero failures.
  • Hosted Mac skips: typed denylist keyboard focus, two external-helper cases needing Apple Development signing rather than ad-hoc signing, and status-menu Settings not surfacing. Signed helper cases passed locally. Hosted iOS skips the unreachable board-appearance context action and unexposed PasteControl. These are explicitly unexecuted scenarios, not passes.
  • Native evidence and synthetic screenshots: macOS artifact, iOS artifact. Existing retention is 14 days.

Risks and boundaries

  • No definition sync, new dependencies, merge, installation or release.
  • Actual VoiceOver interaction is not certified by automated accessibility checks alone.
  • Xcode UI-test dependency scanning emits a separately reproduced main-baseline GRDB diagnostic; it is not suppressed.

@johnny4young johnny4young left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Reviewed 7e8d54617f95dbc4673d2314beb874fc0a3d7df9 against its PR merge base. Four actionable P2 findings are attached inline; no repository code was changed.

Validation:

  • make lint passed on the unmodified PR sources: 451 files, zero violations; git diff --check was clean.
  • The unmodified package suite passed: 1,119 tests / 169 suites.
  • The focused search, saved-filter, migration and bootstrap run passed: 64 tests / 8 suites.
  • Two review-only regression tests in an isolated source snapshot failed with three assertions, reproducing the fallback and migration findings below. Those tests were not committed or pushed.
  • The remaining two findings are verified from the Library/controller call paths, not from native UI execution.
  • Local native macOS/iOS UI, app builds and VoiceOver were not rerun in this review; earlier CI/UI results in the PR description are not new runtime evidence.

Integration note: GitHub currently reports this PR as CONFLICTING / DIRTY against main. The review above is for the stated feature head, not a certification of a resolved merge with current main.

Comment thread Packages/GanchoKit/Sources/GanchoAppCore/PanelSearchModel.swift Outdated
Comment thread Packages/GanchoKit/Sources/GanchoAppCore/SavedFiltersController.swift Outdated
Comment thread Apps/GanchoMac/LibraryView.swift
Comment thread Apps/GanchoMac/LibraryView.swift Outdated
…, refresh live scopes

Four review findings on saved filters:

- A type, pin, or search-mode filter routed every refresh through the
  durable-only `search`, which answers [] on the in-memory fallback, so
  picking Links emptied a populated panel after a store open failure.
  `ClipListCore.fallbackPage(matching:)` narrows the bounded client-side
  scan with `SmartCollectionRule.matches`, the SQL predicate's twin.
- Legacy preference import and the definitions read shared one `do`, so
  invalid legacy bytes hid every valid definition in the store on each
  launch. The import now fails on its own (`legacyImportFailed`, source
  kept for recovery) and the stored definitions still load.
- "Retry by reopening the Library" had no retry behind it: the window is
  cached and the view's `.task` runs once. Presenting the Library now
  reloads the controller (migration retry included) and the failed-load
  message carries a Retry button.
- A selected saved filter never reran after local history changed. The
  Library reloads the scope when the recents' identity, edit time, or
  pin state moves (`LibraryScopeReload`), leaving the paged scopes to
  their own next page.

The snippet editor pane moves to `LibraryView+SnippetEditor.swift` so the
view stays within the file-length budget after the merge.
@johnny4young
johnny4young merged commit faaff29 into main Sep 17, 2026
7 of 8 checks passed
@johnny4young
johnny4young deleted the codex/saved-filters branch September 17, 2026 03:36
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