Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,7 @@ native-build
designs
.hallmark
site-dist
# Canonical serde output, compared byte-for-byte by Rust contract tests.
tests/fixtures/ipc-contract.ts
# Test-only ts-rs declarations and parsed Rust command signatures.
tests/fixtures/ipc-wire.ts
5 changes: 5 additions & 0 deletions docs/explanation/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The path is `query → SearchManager → providers → ranking → top 30 result
- Currency lookup reads a shared, immutable rate table in memory. Network requests and SQLite writes run outside the search lock. `currency.rs` contains the source request and parser; the calculator does not depend on the HTTP response format. The HTTP client reuses the reqwest version already required by Tauri and uses the OS TLS stack.
- Clipboard capture, copy, delete, and clear use the same storage lock. A generation number rejects reads already in progress when an entry is removed. Search never waits for disk access. Linux coalesces pending clipboard observations in a bounded queue. Results include short text summaries; a separate request fetches the selected entry's preview.
- SolidJS keeps UI state and sends queries without a debounce timer. It sends one search request at a time and retains only the newest waiting query. This prevents IPC arrival order from cancelling the current query when startup events overlap. Request numbers prevent late replies from replacing newer results. `src/search.ts` holds this queue and the rule that keeps the selected result across refreshes; it has no Solid dependency and has its own tests. Enter cannot execute an old result while a new query is pending.
- `src/launcherController.ts` owns the frontend search session, result reconciliation, selection, pending state, and visibility gating. The view keeps focus, keyboard navigation, dialogs, and rendering. `src/nativeSubscriptions.ts` owns native listeners, including registrations that finish after a window is disposed; both windows use it. `src/settingsDraft.ts` merges saved settings into local drafts without discarding local field edits or incomplete folder input.
- Hiding the window stops frontend search requests, drops waiting input, and ignores any late search reply. File indexing and clipboard capture continue in Rust. Reopening requests current results. A background refresh preserves the user's latest selection for the same query; a new query selects its first result.
- The frontend sends a result ID and an action. Rust resolves app paths, indexed file paths, emoji values, and calculation values. It checks that a file still exists before opening it. A bounded cache holds the last 32 calculation results so copying an issued result does not evaluate it again. The webview has no general shell, opener, filesystem, clipboard, or global-shortcut permissions.
- System command IDs resolve against the Rust catalog. Rust owns command aliases, confirmation text, and the confirmation rule. The frontend supplies explicit consent after the dialog. Platform modules contain the native API calls; no shell command text comes from the webview.
Expand All @@ -19,6 +20,10 @@ The providers use direct methods. No provider trait is needed. The shared result

The plugin system and public SDK are deferred. Preserve the current module responsibilities so a future SDK can use them. Follow the [future plugin support decision](plugin-readiness.md) when changing search, actions, storage, or frontend communication. Add plugin infrastructure only when implementation starts for a concrete command.

The internal IPC contract is checked without a schema framework or public SDK. Rust serializes canonical examples in `launcher/contract_tests.rs`; the checked-in `tests/fixtures/ipc-contract.ts` is consumed by TypeScript and browser tests. Examples cover results, all tool details, settings, optional fields, and targeted structured warnings. Full verification rejects serialization drift, missing enum examples, and mismatches between real bridge calls and registered Rust command arguments. See [verification](../how-to/verify.md#check-the-internal-ipc-contract) for the update procedure.

Startup warnings and search storage warnings carry a stable code, display message, and retryability. Settings updates clear only repaired warning categories, never warnings matched by English text. Other display-only command errors remain strings. Storage health stays in Rust; the launcher does not infer retryability from a storage message.

The frontend lives in `src/`. The Rust application lives in `src-tauri/`. Shared frontend styles use `tokens.css`. Tests and build scripts are separate from application code.

See the [feature catalog](../reference/features/README.md) for supported behavior.
21 changes: 21 additions & 0 deletions docs/how-to/verify.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,27 @@ The focused wrapper selects its own port and output directory. It accepts test-f

Use focused tests during diagnosis. After the final relevant edit, repeat affected proofs and run `verify:full` for source changes. For each task, record expected behavior, required platforms, outcomes, and evidence paths under `.local/` or `test-results/`. A command pass does not establish behavior outside that command's coverage. Missing required evidence prevents a verified result.

## Check the internal IPC contract

`verify:full` compares actual Rust serialization with [canonical examples](../../tests/fixtures/ipc-contract.ts) and test-only `ts-rs` declarations with [checked wire types](../../tests/fixtures/ipc-wire.ts). Recursive TypeScript equality checks reject extra optional keys, missing keys, changed optionality, and narrowed or widened nullable/enum domains. Rust command signatures are parsed with test-only `syn`; the bridge's argument domains and success returns must match. Runtime probes independently check each wrapper's command binding, registration, argument names, and representative values.

The bridge represents Rust `Option` arguments by omission instead of explicit null; `revealSettings` additionally supplies a default for Rust's required boolean. These are explicit test adaptations, not general assignability exceptions. Serialized response keys remain required unless serde omits them. Platform fields are strings because Rust currently declares strings, not a platform enum. Unsupported serialization rules and command types require review rather than silently generating a partial contract. Conditional `Option::is_none` serialization has a checked test-only `ts(optional)` annotation.

These are internal contracts, not a public SDK or runtime validator. Command errors that are only displayed remain strings. [Drift tests](../../tests/ipc-drift.spec.ts) compile isolated mutations and require contract-specific failures; they do not modify the checkout.

When intentionally changing the wire contract, regenerate its examples, review the diff, and run the normal checks:

```sh
TINYDASH_UPDATE_CONTRACTS=1 bun run test:rust -- ipc_
bun run typecheck
bun run verify:browser tests/ipc-contract.spec.ts tests/ipc-drift.spec.ts
bun run verify:full
```

Do not set `TINYDASH_UPDATE_CONTRACTS` in CI. Normal Rust tests compare both generated files byte-for-byte without updating them; both are excluded from Prettier. Add representative samples for new optional fields or variants, including present and null cases. Examples complement, but do not replace, exact shape checks. Changes to a command signature may require a new wrapper binding, probe, or supported type in the command consistency tests. `ts-rs` and `syn` are dev-dependencies only; neither ships in the application.

These checks prove serialization and bridge consistency, not native IPC authorization or OS effects. Use the desktop procedures for those.

## CI triggers

The Checks workflow always starts for pull requests, merge queues, pushes to `main`, version-tag pushes, and manual requests. Feature-branch pushes do not duplicate the pull request's desktop builds. No workflow-level path filter can leave its required status pending on a documentation-only change.
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/features/launcher.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ Run this browser recipe from the repository root. It retains successful traces i

```sh
bun run verify:browser tests/welcome.spec.ts tests/categories.spec.ts tests/launcher.spec.ts
bun run verify:browser tests/launcher-controller.spec.ts tests/native-subscriptions.spec.ts tests/launcher-warnings.spec.ts tests/ipc-contract.spec.ts tests/ipc-drift.spec.ts
```

The standalone controller checks cover result reconciliation, refresh selection, hidden/disposed replies, reopening before an old reply settles (the original single-flight queue must ignore the old result and keep the new search pending), failure state, and browser-preview gating. Subscription checks cover late registrations and teardown. Warning tests use changed display text to prove that settings repair follows structured codes rather than English prefixes; unrelated platform warnings remain visible. A storage warning must leave search results usable.

For affected backend behavior:

```sh
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/features/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,11 @@ Run this browser recipe from the repository root. It retains successful traces i

```sh
bun run verify:browser tests/settings.spec.ts tests/tinycast-features.spec.ts tests/release-config.spec.ts
bun run verify:browser tests/settings-draft.spec.ts tests/native-subscriptions.spec.ts
```

Standalone draft tests check local edits against incoming saved values, nested application preferences, remote deletion of default preferences while aliases are dirty, local deletions, category normalization, and incomplete folder input. Browser tests additionally exercise clean and dirty forms receiving changes from another window. A saved-settings event must not discard unrelated local edits.

For affected backend behavior:

```sh
Expand Down
1 change: 1 addition & 0 deletions scripts/perf/file-index/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ fn main() {
("result", "src-tauri/src/launcher/result.rs"),
("query", "src-tauri/src/launcher/query.rs"),
("pins", "src-tauri/src/launcher/pins.rs"),
("warning", "src-tauri/src/launcher/warning.rs"),
];
let mut modules = String::new();
for (name, relative) in files {
Expand Down
33 changes: 33 additions & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ tauri-plugin-updater = { version = "2.11.0", default-features = false, features
[dev-dependencies]
# Exercise the real IPC authorization path without opening native windows.
tauri = { version = "2.11.5", features = ["test"] }
ts-rs = { version = "12", features = ["no-serde-warnings"] }
syn = { version = "2", features = ["full", "visit"] }

[target.'cfg(target_os = "macos")'.dependencies]
plist = "1.10.1"
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/appearance.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use tauri::{AppHandle, Emitter, EventTarget, Runtime};
use crate::launcher::window::LauncherAppearance;

#[derive(serde::Deserialize)]
#[cfg_attr(test, derive(ts_rs::TS))]
#[serde(tag = "kind", content = "value", rename_all = "camelCase")]
pub enum AppearanceChange {
Appearance(LauncherAppearance),
Expand Down
Loading