diff --git a/.github/workflows/frontend-ci.yml b/.github/workflows/frontend-ci.yml index 9a87bf94..a575a9c4 100644 --- a/.github/workflows/frontend-ci.yml +++ b/.github/workflows/frontend-ci.yml @@ -27,7 +27,9 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v4 with: - node-version: 20 + # 22+: the test script passes --no-experimental-webstorage, which Node 20 rejects as an + # unknown option. Node 20 reached end of life in April 2026. + node-version: 24 cache: npm cache-dependency-path: cardinal/package-lock.json diff --git a/AGENTS.md b/AGENTS.md index 4ad23657..c349786f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -4,7 +4,8 @@ - Desktop app lives in `cardinal/` (React UI in `src/`, Tauri/native glue in `src-tauri/`, build output in `cardinal/dist/`). - Workspace crates (root `Cargo.toml`): `lsf/` (CLI), `cardinal-sdk/` (shared types), `fswalk/`, `fs-icon/`, `namepool/`, `query-segmentation/`, `search-cache/`, `search-cancel/`, `cardinal-syntax/`, `slab-mmap/` (mmap-backed slab for the cache), `was/` (CLI that streams FSEvents via the SDK). - Tests sit next to code; cross-crate cases belong in each crate’s `tests/` directory. Generated outputs (`target/`, `cardinal/dist/`, vendor bundles) stay out of commits. -- Toolchain pinned via `rust-toolchain.toml` (`nightly-2025-05-09`); install with `rustup toolchain install nightly-2025-05-09`. +- Toolchain pinned via `rust-toolchain.toml` (`nightly-2025-12-11`); install with `rustup toolchain install nightly-2025-12-11`. Let the rustup shim pick it up rather than putting a toolchain's `bin/` on `PATH`: an older rustc still builds here from cache, but the proc-macro dylibs it emits are rejected by recent macOS dyld with `mis-aligned LINKEDIT string pool` as soon as anything has to be rebuilt. +- Keep `target/` out of iCloud-synced folders (`CARGO_TARGET_DIR`). If iCloud evicts a fingerprint file, cargo blocks forever in `read()` waiting for a download that never lands. ## Build, Test, and Development Commands - `cargo check --workspace` — fast compile validation for all crates. diff --git a/CHANGELOG.md b/CHANGELOG.md index cbaa1e34..aa1140a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## 0.2.0 — 2026-08-01 +- Add an accessible name to the search input, so assistive technologies can announce it. Thanks to [@dkattan](https://github.com/dkattan) ([#220](https://github.com/cardisoft/cardinal/pull/220)). +- Add a file-type dropdown next to the search bar. Picking a type writes it into the query (`type:image`), so the control and the search bar never disagree — and the syntax stays visible instead of hidden behind a menu. +- Add the `type:email` category (`.eml`, `.emlx`, `.emlxpart`, `.msg`, `.mbox`), with `mail`/`message` as synonyms. +- Rename "folder scope" to plain "Search in" across all 15 languages. +- Add a context column for `content:` searches, showing the matching text inside each file with the searched term highlighted. +- Derive the highlighted content terms from the query parser itself, so negated terms (`!content:`) no longer highlight and `content:"Bearer "` keeps its trailing space. +- Hydrate result rows in parallel, so icon and content-snippet reads no longer queue up behind each other. + ## 0.1.23 — 2026-03-25 - Reduce power consumption by expanding the default ignored paths to cover more macOS cache, log, metadata, and runtime directories. - Further reduce background work by making the filesystem event watcher honor ignored paths. diff --git a/README.md b/README.md index cd68fab2..2e28a121 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,12 @@ Cardinal icon

Cardinal

Fastest and most accurate file search app for macOS.

+

+ Download Cardinal 0.2.0 for macOS +

+

+ Signed and notarized · Apple silicon · macOS 12+ +

Using Cardinal · Building Cardinal @@ -17,13 +23,9 @@ ### Download -Use homebrew: - -```bash -brew install --cask cardinal-search -``` +[**Download Cardinal 0.2.0 for macOS**](https://github.com/franciscoxc/cardinal/releases/download/v0.2.0/Cardinal_0.2.0_aarch64.dmg) — signed with a Developer ID and notarized by Apple, so it opens without Gatekeeper warnings. -You can also grab the latest packaged builds from [GitHub Releases](https://github.com/cardisoft/cardinal/releases/). +Every build lives in [Releases](https://github.com/franciscoxc/cardinal/releases). Open the DMG, drag Cardinal to Applications, and grant Full Disk Access when macOS asks — Cardinal needs it to index and watch your files. ### i18n support diff --git a/cardinal/app-icon.png b/cardinal/app-icon.png index 7034823c..97fac2cc 100644 Binary files a/cardinal/app-icon.png and b/cardinal/app-icon.png differ diff --git a/cardinal/mac-icon_1024x1024.png b/cardinal/mac-icon_1024x1024.png index 0e4a2119..97fac2cc 100644 Binary files a/cardinal/mac-icon_1024x1024.png and b/cardinal/mac-icon_1024x1024.png differ diff --git a/cardinal/package.json b/cardinal/package.json index e5fe8f2d..0a62b646 100644 --- a/cardinal/package.json +++ b/cardinal/package.json @@ -8,8 +8,8 @@ "build": "vite build", "preview": "vite preview", "tauri": "tauri", - "test": "vitest --watch=false", - "test:watch": "vitest --watch", + "test": "NODE_OPTIONS=--no-experimental-webstorage vitest --watch=false", + "test:watch": "NODE_OPTIONS=--no-experimental-webstorage vitest --watch", "typecheck": "tsc --noEmit", "format": "prettier --write .", "format:check": "prettier --check ." diff --git a/cardinal/src-tauri/Cargo.lock b/cardinal/src-tauri/Cargo.lock index e19f1ca0..798d827f 100644 --- a/cardinal/src-tauri/Cargo.lock +++ b/cardinal/src-tauri/Cargo.lock @@ -382,7 +382,7 @@ dependencies = [ [[package]] name = "cardinal" -version = "0.1.23" +version = "0.2.0" dependencies = [ "anyhow", "base64 0.22.1", diff --git a/cardinal/src-tauri/Cargo.toml b/cardinal/src-tauri/Cargo.toml index 386b1944..a113e525 100644 --- a/cardinal/src-tauri/Cargo.toml +++ b/cardinal/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "cardinal" -version = "0.1.23" +version = "0.2.0" description = "Cardinal is a cross platform file searching tool" authors = ["ldm2993593805@163.com"] edition = "2024" diff --git a/cardinal/src-tauri/icons/128x128.png b/cardinal/src-tauri/icons/128x128.png index 9c9ea0f8..df1b3ad5 100644 Binary files a/cardinal/src-tauri/icons/128x128.png and b/cardinal/src-tauri/icons/128x128.png differ diff --git a/cardinal/src-tauri/icons/128x128@2x.png b/cardinal/src-tauri/icons/128x128@2x.png index db741556..c13417df 100644 Binary files a/cardinal/src-tauri/icons/128x128@2x.png and b/cardinal/src-tauri/icons/128x128@2x.png differ diff --git a/cardinal/src-tauri/icons/32x32.png b/cardinal/src-tauri/icons/32x32.png index bb6d7bf5..9e6e4975 100644 Binary files a/cardinal/src-tauri/icons/32x32.png and b/cardinal/src-tauri/icons/32x32.png differ diff --git a/cardinal/src-tauri/icons/Square107x107Logo.png b/cardinal/src-tauri/icons/Square107x107Logo.png index cfe1b543..eb21b4d3 100644 Binary files a/cardinal/src-tauri/icons/Square107x107Logo.png and b/cardinal/src-tauri/icons/Square107x107Logo.png differ diff --git a/cardinal/src-tauri/icons/Square142x142Logo.png b/cardinal/src-tauri/icons/Square142x142Logo.png index 8dee4711..bc2d6942 100644 Binary files a/cardinal/src-tauri/icons/Square142x142Logo.png and b/cardinal/src-tauri/icons/Square142x142Logo.png differ diff --git a/cardinal/src-tauri/icons/Square150x150Logo.png b/cardinal/src-tauri/icons/Square150x150Logo.png index 80047483..41ea6eed 100644 Binary files a/cardinal/src-tauri/icons/Square150x150Logo.png and b/cardinal/src-tauri/icons/Square150x150Logo.png differ diff --git a/cardinal/src-tauri/icons/Square284x284Logo.png b/cardinal/src-tauri/icons/Square284x284Logo.png index 2bfa6d48..7dbb8635 100644 Binary files a/cardinal/src-tauri/icons/Square284x284Logo.png and b/cardinal/src-tauri/icons/Square284x284Logo.png differ diff --git a/cardinal/src-tauri/icons/Square30x30Logo.png b/cardinal/src-tauri/icons/Square30x30Logo.png index a036fe0a..356f48d4 100644 Binary files a/cardinal/src-tauri/icons/Square30x30Logo.png and b/cardinal/src-tauri/icons/Square30x30Logo.png differ diff --git a/cardinal/src-tauri/icons/Square310x310Logo.png b/cardinal/src-tauri/icons/Square310x310Logo.png index 4bc2ea01..edf3403e 100644 Binary files a/cardinal/src-tauri/icons/Square310x310Logo.png and b/cardinal/src-tauri/icons/Square310x310Logo.png differ diff --git a/cardinal/src-tauri/icons/Square44x44Logo.png b/cardinal/src-tauri/icons/Square44x44Logo.png index ca3eaacb..dd815fef 100644 Binary files a/cardinal/src-tauri/icons/Square44x44Logo.png and b/cardinal/src-tauri/icons/Square44x44Logo.png differ diff --git a/cardinal/src-tauri/icons/Square71x71Logo.png b/cardinal/src-tauri/icons/Square71x71Logo.png index e2c439a5..12e9480f 100644 Binary files a/cardinal/src-tauri/icons/Square71x71Logo.png and b/cardinal/src-tauri/icons/Square71x71Logo.png differ diff --git a/cardinal/src-tauri/icons/Square89x89Logo.png b/cardinal/src-tauri/icons/Square89x89Logo.png index 379dc71f..e88f9ff9 100644 Binary files a/cardinal/src-tauri/icons/Square89x89Logo.png and b/cardinal/src-tauri/icons/Square89x89Logo.png differ diff --git a/cardinal/src-tauri/icons/StoreLogo.png b/cardinal/src-tauri/icons/StoreLogo.png index f45cf5fe..641c2661 100644 Binary files a/cardinal/src-tauri/icons/StoreLogo.png and b/cardinal/src-tauri/icons/StoreLogo.png differ diff --git a/cardinal/src-tauri/icons/icon.icns b/cardinal/src-tauri/icons/icon.icns index d4192237..15905057 100644 Binary files a/cardinal/src-tauri/icons/icon.icns and b/cardinal/src-tauri/icons/icon.icns differ diff --git a/cardinal/src-tauri/icons/icon.ico b/cardinal/src-tauri/icons/icon.ico index acac2af1..edcf2b27 100644 Binary files a/cardinal/src-tauri/icons/icon.ico and b/cardinal/src-tauri/icons/icon.ico differ diff --git a/cardinal/src-tauri/icons/icon.png b/cardinal/src-tauri/icons/icon.png index 81dbaa62..06e9cc46 100644 Binary files a/cardinal/src-tauri/icons/icon.png and b/cardinal/src-tauri/icons/icon.png differ diff --git a/cardinal/src-tauri/src/commands.rs b/cardinal/src-tauri/src/commands.rs index ae863611..5ebb8d0a 100644 --- a/cardinal/src-tauri/src/commands.rs +++ b/cardinal/src-tauri/src/commands.rs @@ -19,8 +19,10 @@ use objc2::{ use objc2_app_kit::{NSPasteboard, NSPasteboardItem, NSPasteboardTypeString, NSPasteboardWriting}; use objc2_foundation::{NSArray, NSString, NSURL}; use parking_lot::Mutex; +use rayon::prelude::*; use search_cache::{ SearchOptions, SearchOutcome, SearchQuery, SearchResultNode, SlabIndex, SlabNodeMetadata, + content_snippet, content_terms_of_query, }; use search_cancel::CancellationToken; use serde::{Deserialize, Serialize}; @@ -217,6 +219,8 @@ pub struct NodeInfo { pub path: String, pub metadata: Option, pub icon: Option, + #[serde(rename = "contentContext")] + pub content_context: Option, } #[derive(Serialize, Default)] @@ -224,6 +228,9 @@ pub struct NodeInfo { pub struct SearchResponse { pub results: Vec, pub highlights: Vec, + /// `content:` terms of this query, so the UI highlights in a snippet exactly what the search + /// looked for inside files. + pub content_terms: Vec, pub status_code: u8, } @@ -291,6 +298,7 @@ pub async fn search( search_activity::note_search_activity(); let options = options.unwrap_or_default(); + let content_terms = query.as_deref().map(content_terms_of_query).unwrap_or_default(); let cancellation_token = CancellationToken::new_search(); let (result_tx, result_rx) = bounded(1); if let Err(e) = state.search_tx.send(SearchJob { @@ -325,6 +333,7 @@ pub async fn search( SearchResponse { results, highlights, + content_terms, status_code, } }) @@ -335,6 +344,8 @@ pub async fn search( pub fn get_nodes_info( results: Vec, include_icons: Option, + content_terms: Option>, + case_insensitive: Option, state: State<'_, SearchState>, ) -> Vec { if results.is_empty() { @@ -342,11 +353,17 @@ pub fn get_nodes_info( } let include_icons = include_icons.unwrap_or(true); + let content_terms = content_terms.unwrap_or_default(); + let case_insensitive = case_insensitive.unwrap_or_default(); let nodes = state.request_nodes(results); + // Rows are independent, and each one may read a file for its icon and its content snippet. nodes - .into_iter() + .into_par_iter() .map(|SearchResultNode { path, metadata }| { + let content_context = content_terms + .iter() + .find_map(|term| content_snippet(&path, term, case_insensitive)); let path = path.to_string_lossy().into_owned(); let icon = if include_icons { fs_icon::icon_of_path_ns(&path).map(|data| { @@ -362,6 +379,7 @@ pub fn get_nodes_info( path, icon, metadata: metadata.as_ref().map(NodeInfoMetadata::from_metadata), + content_context, } }) .collect() diff --git a/cardinal/src-tauri/tauri.conf.json b/cardinal/src-tauri/tauri.conf.json index 6b674bef..40d1f524 100644 --- a/cardinal/src-tauri/tauri.conf.json +++ b/cardinal/src-tauri/tauri.conf.json @@ -1,8 +1,8 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "Cardinal", - "version": "0.1.23", - "identifier": "com.cardinal.one", + "version": "0.2.0", + "identifier": "com.franxc.cardinal", "build": { "beforeDevCommand": "npm run dev", "devUrl": "http://localhost:1420", @@ -26,7 +26,7 @@ "targets": "all", "category": "Utilities", "macOS": { - "signingIdentity": "-", + "signingIdentity": "Developer ID Application: Francisco Carrasco (993439K8XB)", "minimumSystemVersion": "12.0" }, "icon": [ diff --git a/cardinal/src/App.css b/cardinal/src/App.css index 5d6af384..94bd6407 100644 --- a/cardinal/src/App.css +++ b/cardinal/src/App.css @@ -356,6 +356,57 @@ main, font-size: 0.78rem; } +/* Native + + {FILE_TYPE_VALUES.map((option) => ( + + ))} + {fileType === CUSTOM_FILE_TYPE ? ( + + ) : null} + + + ) : null}