Search/Change a Large Desktop Media App Without Freezing the UI or Breaking Undo

Change a Large Desktop Media App Without Freezing the UI or Breaking Undo

For a large desktop app that handles files and media: the AI keeps heavy work off the UI thread, handles cancellation and edge cases, keeps undo exact, and reports only the tests it really ran.

To use this AI rule
## Trace the Full Call Path and Give Each State One Owner - Understand the owning feature and existing abstractions before editing, and trace the full call path through UI, automation, undo, persistence, and background work. - For nontrivial changes, identify the source of truth, state owner, isolation domain, invariants, and failure and cancellation behavior before writing code. - Design the architecture before implementation details, and prefer one coherent change over patches scattered across unrelated layers. - Keep one authoritative owner for each piece of mutable state; derived state is computed or maintained by a cache with an explicit invalidation contract. - Reuse existing domain operations; do not add a second implementation because the caller is a preview, view, controller, automation tool, or test. - Place code with the feature that owns it, keep shared utilities for infrastructure used by several independent features, and name types and files so ownership is clear. ## Keep Comments Short and Remove Dead Code - Keep comments minimal: write one only when the reason, invariant, safety constraint, or workaround is non-obvious, and keep it to one short line. - Do not narrate code, restate names, describe the current patch, leave removal breadcrumbs, or keep commented-out code. - Prefer precise names, small types, and extracted operations over explanatory comments. - A calculation or business rule has a single source of truth; never copy it into another file or surface. - Remove dead code, unused state, obsolete compatibility paths, and temporary diagnostics before finishing. - Do not add compatibility code for platforms, versions, or configurations the project does not support. ## Keep Heavy Work Off the UI Thread and Recheck State After Each Await - Treat the UI thread as a scarce resource: it may hold UI and light coordination state but must not perform file work, decoding, model inference, image processing, indexing, export, blocking calls, or large transforms. - Isolation is not performance: marking work async or wrapping it in a task does not move it off the current thread. Follow the actual executor. - Make thread hops explicit; snapshot the minimum immutable input before leaving a thread, return a value, then apply it after confirming it is still current. - After every suspension point, assume shared state changed: revalidate identity, generation, selection, and preconditions before committing a result. - Prefer structured concurrency; an unstructured task needs a clear owner, a stored handle when cancellation matters, and defined teardown. - Cancellation is cooperative: long loops check it at useful boundaries and never commit partial or stale results. - Bound parallel work by the real scarce resource, deduplicate identical in-flight requests, acquire and release gates in matched scopes, and resume each continuation or callback exactly once. - Never block the UI thread with synchronous waits, semaphores, locks, or polling for asynchronous work. ## Do File Work Off the UI Thread and Replace Files Atomically - Every filesystem operation runs off the UI thread: reads, writes, existence and metadata checks, enumeration, copy, move, replace, delete, and directory creation. - Assume any volume can be slow, removable, externally modified, or network-backed; a small size or a prior successful access does not make synchronous access safe. - Snapshot owned model data before file work, and do not mutate observable state from the file work executor. - Stage complete output outside the live target, prepare the replacement on the destination, then atomically install it; route live writes through one coordinator instead of writing directly. - Serialize operations that target the same file or destination so save, import, generation, thumbnail, delete, export, and close never race. - Use unique temporary paths and clean them on success, failure, and cancellation; never delete or replace a destination until the full replacement is ready. - Surface user-requested file failures; do not hide them behind ignored errors, empty results, or success-shaped responses. ## Measure Before Claiming a Speedup and Keep Hot Paths Light - Identify performance-sensitive paths such as rendering, playback, input handling, view updates, import, indexing, save, restore, and export, and treat them as hot. - Do not perform filesystem access, logging, serialization, model or decoder setup, or other blocking setup inside per-item, per-frame, or repeated interaction paths. - Measure before claiming an improvement: profile or benchmark the same workload before and after the change. - Fix algorithmic complexity and unnecessary work before low-level tuning; watch for nested scans, repeated sorting, copy-in-loop mutation, intermediate allocations, and repeated thread hops. - Batch and coalesce bulk mutations, flushing pending state before snapshots, undo, export, close, and reads that must return current data. - Load and hydrate data lazily, and cache expensive reusable work only with an explicit key, capacity, invalidation rule, and stale-result policy. - Reuse expensive objects where their lifecycle permits and invalidate them on relevant configuration changes; keep high-frequency state such as progress and meters from invalidating whole views; bound retained memory. ## List Edge Cases and Failure Paths Before Implementing - Working on the happy path is not enough: before implementing, enumerate the applicable boundary, lifecycle, concurrency, and failure cases and decide which layer owns each. - Validate empty, nil, zero, negative, maximum, overflowing, non-finite, malformed, duplicated, missing, stale, and unsupported inputs as applicable. - Validate before arithmetic, indexing, or numeric conversion; never rely on a later catch to absorb an arithmetic trap, and define exact rounding and clamping instead of silently clamping. - Cover no-op and repeated operations: they must report accurately and must not create mutations, history entries, duplicate work, or misleading success. - Consider cancellation before start, during each phase, and after work completes but before commit, and consider stale completion after selection, target, or configuration changes. - Consider teardown paths: close, quit, sleep, wake, deactivation, device changes, and save-as while work is active. - Preserve invariants on every failure path: partial success is either safely resumable and reported as such, or rolled back. ## Make One User Action Undo in a Single Step - Route user actions and automated actions through the same domain mutation operations and the same shared history. - One coherent user intent produces one undoable action; do not expose internal substeps as separate history entries unless each is independently meaningful. - Validate arguments and preconditions before opening a history group; failed, cancelled, refused, and unchanged operations must not create empty entries. - Nested work coalesces into the outer action without closing groups owned by the platform or another subsystem. - Undo restores exact state without cumulative rounding, derived-state drift, orphaned relationships, or stale selection. - When a change touches history, test interleaving between user edits, automated edits, platform event grouping, and concurrent requests. ## Design Automation Tools Around What the User Wants Done - Design tools from user intent, not from internal APIs, storage operations, or service boundaries; define the outcome, success criteria, warnings, failure, cancellation, idempotency, and undo semantics before the schema. - A tool performs one coherent action; one call normally completes one atomic, understandable, undoable workflow. Do not build a broad tool with unrelated modes, and do not force callers to chain low-level tools for work the system can do safely itself. - Express parameters in user-facing domain concepts and hide storage layout and implementation details; use stable entity identifiers for automation, treating positional indexes and labels as context only, not durable identity. - Treat every argument as untrusted: require exact types, finite numbers, explicit bounds, and valid identifiers; resolve and validate the full request before mutating, and apply multi-entity changes atomically. - Reuse the same domain operations as the UI, and do not duplicate placement, linking, or validation logic. - Return structured receipts describing what changed, stable identifiers, explicit no-op state, warnings, and actionable errors; never return a success-shaped response when the outcome was adjusted or not achieved, and never silently clamp, retarget, or fall back unless the contract promises and reports it. - Long-running tools expose a durable job or terminal result the caller can inspect; keep machine-facing contracts stable and separate from localized copy; descriptions explain when, why, and how a tool interacts with others. ## Keep UI State on the UI Thread and Preserve Native Platform Behavior - Keep observable UI state on the UI thread and let background results cross the boundary as immutable values. - Scope observation to the smallest view that needs a value; high-frequency progress, hover, and playback state must not invalidate unrelated view trees. - Do not start persistent side effects from a render body; use lifecycle-aware tasks or controllers with explicit cancellation and teardown, and keep render bodies fast and free of side effects. - Preserve native platform behavior for focus, keyboard, escape, menus, window restoration, undo, drag state, and close confirmation. - Delegate and completion-handler contracts complete exactly once on every success, failure, cancellation, and missing-target path. - Do not assume a framework or completion callback arrives on the UI thread unless the API guarantees it. ## Process Media Without Blocking and Keep Time and Frame Values Exact - Use asynchronous property loading for external assets, and do not call deprecated synchronous accessors that may block the calling thread. - Keep values in their exact domain, such as precise time, frame, or unit integers, as long as possible, and convert to floating point only at explicit UI or external-format boundaries. - Define and preserve source semantics such as timing, scale, transforms, color, alpha, channel layout, and metadata, unless the feature explicitly changes them. - Reuse readers, render contexts, and pipelines where their lifecycle permits, and do not rebuild them during continuous interaction unless invalidation requires it. - Bound concurrent decoders, readers, exports, inference, and extraction, and propagate cancellation through every decode, render, and export loop, checking between chunks when an underlying step cannot be cancelled. - Test with missing tracks, unusual containers, zero or indefinite duration, rotated or alpha inputs, nonstandard rates, and cancellation. ## Use Design Tokens Instead of Hardcoded Style Values - Style everything through the shared design-token system; never hardcode spacing, sizes, font weights, radii, border widths, opacity, colors, shadows, or animation durations. - If a needed token does not exist, add it to the token system first instead of hardcoding a value. - Resource lookup must work in the packaged application, local runs, and tests; use the shared resource-lookup abstraction instead of feature-specific probing, and treat each bundle layout as a distinct configuration to verify. - Do not use debug-only flags to select a fundamentally different resource path or behavior unless the difference is intentional and tested. - When adding a bundled resource, update the package manifest, build scripts, and tests, verify the final packaged layout, and keep optional feature-flag code buildable both with and without the flag. ## Give Every Operation a Visible End State and Useful Logs - Every user-initiated or automation-initiated operation reaches an observable success, failure, refusal, or cancellation state. - Do not ignore an error at an interaction boundary when failure changes the outcome; convert it into UI state, a tool error, a job failure, or an actionable log. - Fire-and-forget work is allowed only when it is explicitly best-effort; it must not own required persistence, and its failures must be safely ignorable or recorded. - Log failures with the operation, stable identifiers, and lifecycle phase; never write secrets, credentials, or raw user input into logs. - Preserve enough diagnostics to distinguish cancellation, validation failure, unavailable resource, framework failure, and internal invariant violation. - Use assertions for programmer invariants only, not for recoverable user input, external files, lifecycle races, or automation requests. ## Write Small, Deterministic Tests That Can Run in Parallel - Unit tests are concise and behavior-focused: one test establishes one invariant or regression with only the setup that makes it clear, using descriptive names and no step-narrating comments. - Use parameterized tests for boundary matrices and shared fixture builders instead of duplicating setup across suites. - Test through the smallest stable seam that proves the behavior, and avoid coupling to private details or incidental call counts. - Every bug fix gets a regression test that fails for the original defect when practical, and covers negative paths: validation, no-op, partial failure, cancellation, stale completion, and cleanup. - Concurrency tests coordinate deterministically with gates, continuations, or injected hooks, and do not rely on sleeps, timing luck, or scheduling order. - Tests run in parallel: do not share mutable global state, fixed temporary names, ports, or directories; use unique temporary directories and clean them up, and keep real credentials, external services, and real user files out of unit tests. ## Review Correctness First and Report Exactly Which Tests Ran - Review for correctness before style: trace changed behavior through callers, shared state, background work, persistence, undo, UI, and automation surfaces. - Prioritize findings that cause data loss, corruption, crashes, hangs, races, security exposure, incorrect results, broken undo, or misleading success, then performance, maintainability, and polish. - Look for UI-thread blocking, concurrency mistakes such as state committed after a suspension without revalidation, unowned tasks, missing cancellation, unbounded fan-out, unbalanced gates, and double-resumed callbacks, plus duplicated domain logic, unsafe caches, and unvalidated boundary arithmetic; scope the review to the change and what it directly affects. - Verify claims: run the build and the tests appropriate to the change, exercise cancellation and lifecycle paths for concurrency changes, and measure performance changes under a representative workload. - Report exactly what was and was not run; do not claim full build, test, or runtime verification when only a subset completed. - Keep commits focused with a narrow, accurate category prefix and no unrelated cleanup, and describe a change by its summary, approach, key design decisions, and testing evidence rather than the sequence of edits made.