Files
Allofit/sources/ui/SyntaxHelpView.swift
Bitsy 876122ff98 Audit fixes: security hardening, index correctness, performance, UX
Security
- Elevated copies: root only reads the original; the copy is written by
  the user (sudo -u tee), so root never chowns / chmods a user-controlled
  path. Staging folder forced to 0700.
- Service logs moved from fixed /tmp names to /Library/Logs/Allofit
  (root-owned) and ~/Library/Logs/Allofit; Diagnostics reveals instead of
  opening the log.
- Index files are owner-only (0600; the root daemon's belongs to the
  installing user), set on the temp file before an atomic rename.
- Root install passes the plist inline (base64, plutil -lint) instead of a
  user-writable temp file; the binary comes from Bundle.main.
- Cache loader caps and checks the declared payload size; each save uses
  its own temp file.
- Release action pinned to a commit; non-system LC_RPATHs stripped.

Correctness
- Move to Trash removes the files from the index (the watcher ignores the
  app's own operations) and registers Undo (Put Back); trashed folders are
  matched with the original URLs; failures are shown.
- The saved event id stays below pending subtree walks (GUI and service).
- Roots / exclusions changes restart the watcher from the snapshot's id.
- Case-only renames no longer leave a ghost entry.
- Service mode is saved only after a successful install; a saved but
  missing service falls back to the in-process indexer.

Performance
- New folders are merged without the O(n) removal pass.
- Size / date sorts use a compact key array (539 -> 47 ms for 630k).
- Selection, preview and actions use the selected records directly.
- Service saves at most every 15 s; reader reloads pause while hidden and
  are deferred instead of dropped; window close saves only when dirty.

Usability
- Results appear during the first index; empty-list explanations.
- Down arrow moves to the results, Up on the first row back; history on
  Up / Option-Up / Option-Down.
- Search syntax popover and Help menu; shortcuts shown in the context menu;
  confirmations for Clear Cache and Uninstall; privacy usage strings;
  Group Containers excluded by default; wording, VoiceOver labels, plural.
2026-10-02 14:44:49 +02:00

54 lines
2.0 KiB
Swift

import SwiftUI
// SyntaxHelpView is the search syntax cheat sheet shown by the "?" button
// next to the search field and by Help > Search Syntax.
struct SyntaxHelpView: View {
// one example query and what it matches
private struct Example: Identifiable {
let query: String
let meaning: String
var id: String { query }
}
// the rows of the cheat sheet, same order as the README
private let kExamples: [Example] = [
Example(query: "report", meaning: "names containing \"report\" (case and accents ignored)"),
Example(query: "annual report", meaning: "names containing both words (AND)"),
Example(query: "*.png | *.jpg", meaning: "either one (OR, binds tighter than AND)"),
Example(query: "report !draft", meaning: "without \"draft\" (NOT)"),
Example(query: "\"my file\"", meaning: "quotes keep spaces in one term"),
Example(query: "Start*.pdf", meaning: "* any characters, the whole name must match"),
Example(query: "IMG_????.heic", meaning: "? exactly one character"),
Example(query: "ext:pdf;docx", meaning: "by extension"),
Example(query: "file: folder:", meaning: "files or folders only (also as a prefix: folder:build)"),
Example(query: "src/main", meaning: "a term with / matches the full path"),
Example(query: "\"photos/**/\" *.heic", meaning: "** spans folders, * stays in one; trailing / = inside"),
]
var body: some View {
VStack(alignment: .leading, spacing: 10) {
Text("Search syntax")
.font(.headline)
Grid(alignment: .leading, horizontalSpacing: 14, verticalSpacing: 6) {
ForEach(kExamples) { vExample in
GridRow {
Text(vExample.query)
.font(.system(.body, design: .monospaced))
.textSelection(.enabled)
Text(vExample.meaning)
.foregroundColor(.secondary)
}
}
}
Divider()
Text("↓ moves from the search field to the results, ↑ on the first row comes back. Hold ⌥ to freeze the list while files change.")
.font(.caption)
.foregroundColor(.secondary)
.fixedSize(horizontal: false, vertical: true)
}
.padding(16)
.frame(width: 560)
}
}