


Add Features to a Layered Desktop App and Test Every Build
Use when an AI changes a desktop app split into a core, a desktop host, and a web UI. It keeps rules in the core, limits which commands and folders tools may touch, splits optional features by real dependencies, and tests every supported build before calling the work done.
To use this AI rule
## Decide What the Core, Desktop Host, and UI Each Handle
Define the shipped platforms, supported clients, process topology, and trust boundaries before changing code.
- Keep business rules, persistence, execution, and domain behavior in an authoritative core.
- Keep the desktop host responsible for lifecycle, native capabilities, secure bridging, and packaging.
- Keep the web-facing UI responsible for presentation, navigation, and orchestration; do not duplicate core rules in the renderer.
- Route UI-to-core calls through one authenticated transport abstraction rather than ad hoc direct access.
- Treat experimental clients as separate compatibility surfaces with explicit transport, encryption, and release status.
- Maintain an architecture map that names each layer, its owned state, public interfaces, and prohibited responsibilities.
## Sort Commands by Risk and Keep Tools Inside the User's Folder
Centralize configuration and apply one fail-closed policy to every acting tool.
- Read environment values through typed configuration modules; feature code must not access process or build-time environment variables directly.
- Model autonomy as explicit tiers combined with workspace limits, trusted roots, and installation permission.
- Classify commands as read, write, network, install, or destructive; treat unrecognized commands as write operations.
- Resolve each class to allow, prompt, or block according to the active tier. Always block system and credential locations.
- Separate the user action root from internal application state. Tools may act only inside the action root and must never write internal state directly.
- Keep approval gates enabled by default for interactive mutations, expire unanswered requests, and make expiration deny the action.
- Combine application-level path validation with an operating-system or container sandbox; a sandbox is defense in depth, not a replacement for policy checks.
## Give Each Domain Its Own Module in the Core
Give each domain a dedicated module with clear ownership and small, composable files.
### Use a canonical module shape
- Keep the module facade export-focused and free of business logic.
- Put serializable domain types, persistence, operations, controller schemas, agent tools, event subscribers, and tests in separate files when those responsibilities exist.
- Place domain-owned tools with the domain and reserve shared tool infrastructure for genuinely cross-cutting behavior.
- Keep transport modules limited to authentication, dispatch, logging, observability, and protocol concerns.
Expose controllers through a registry with schema and handler pairs. New behavior should add a domain controller and register it at one composition point rather than grow command-name branches throughout transport code.
## Keep Runtime On/Off Settings Separate from Compile-Time Features
Model two independent axes:
- A service set selects running background services and transports.
- A domain set selects available domain families at runtime.
- Compile-time features remove code and dependencies for product variants.
Runtime filtering must remove a disabled domain's controllers, tools, stores, and subscribers from the live surface. Compile-time gates must compose with that behavior without changing the default build. Keep the registration site authoritative so schemas, dispatch, tools, and startup services cannot drift apart.
## Choose How to Switch Off Optional Features by Real Dependencies
Choose the smallest gate that preserves always-on behavior.
1. Use a leaf gate when every external reference is a registration site; disabled capability surfaces should be absent, not registered as failing stubs.
2. Use a facade with behavior stubs only when always-compiled callers require callable symbols.
3. Carve dependency-free shared types into always-compiled modules and stub behavior only. Never duplicate type definitions across enabled and disabled builds.
4. Split a directory when some utilities serve always-on domains; gate actual dependency edges, not folder names.
5. Preserve load-bearing helpers even when they sit under an optional domain. Do not replace a necessary implementation with a success-shaped stub.
When a command remains visible while its feature is compiled out, return an explicit disabled-build error instead of a misleading unknown-command failure. For compile-inert data that still names disabled components, make resolution tolerate missing identifiers and test that contract.
Never claim that a feature gate removes dependencies from a successful compile alone. Prove each claimed removal with an inverse dependency-tree query in the disabled configuration.
## Test Each Build with Optional Features Both On and Off
Treat tests as part of the architecture and cover behavior, integration, and packaging.
- Co-locate focused unit tests and prefer observable behavior over implementation details.
- Use a shared mock service for deterministic integration tests; prohibit real network access and timing-dependent sleeps in unit suites.
- Define end-to-end scenarios before implementation, including happy paths, failures, authorization gates, and recovery.
- Test optional features in both directions: prove registration when enabled and absence when disabled.
- Run the disabled-build test suite explicitly. A compile-only smoke lane does not compile test code and cannot prove gated tests remain valid.
- Enforce a changed-line coverage threshold and fall back to the full suite when shared configuration or foundational contracts change.
- Build expensive platform artifacts once and fan tests out from that artifact when the build system permits it.
## Add Debug Logs That Never Record Secrets or Personal Data
New and changed flows require diagnostics at entry, exit, branches, external calls, retries, timeouts, state transitions, and errors.
- Use stable, grep-friendly domain prefixes and correlation fields such as request IDs and method names.
- Keep normal logs concise while making debug and trace levels sufficient for targeted investigations.
- Never log secrets, raw credentials, full personal data, user-authored content, entity identifiers, filenames, or raw error messages to analytics.
- Route analytics through one consent-aware feature API and use stable content-free event names.
- Treat missing diagnostics as incomplete implementation when the change introduces a new execution path.
## Translate Every UI String and Regenerate Docs Instead of Hand-Editing
Use centralized state, transport, and localization rather than local shortcuts.
- Prefer the designated application state layer over scattered browser storage.
- Use the shared core client for all core calls, and keep parallel transports behaviorally aligned.
- Guard native-only APIs through the supported runtime detector and error boundary.
- Route every visible string through localization, add real translations for every supported locale, and preserve interpolation placeholders exactly.
- Run localization syntax, English-leakage, and coverage checks before completion.
- Never hand-edit generated documentation blocks. Update their source marker, regenerate, and let drift checks fail stale output.
- Avoid adding script injection to embedded third-party views; implement new behavior through native handlers, protocol tooling, or controlled IPC hooks.
## Build a Feature from the Core Up to the UI, Testing Each Layer
Follow a fixed vertical workflow:
1. Specify behavior from existing domain contracts, naming conventions, security policy, and end-to-end scenarios.
2. Implement authoritative domain logic with unit tests.
3. Prove the controller or RPC boundary with an integration test.
4. Surface the behavior in the UI through the shared client while keeping rules in the core.
5. Add UI unit tests and platform end-to-end coverage.
6. Update user-facing capability metadata and architecture documentation when a feature is added, removed, or renamed.
Do not advance to the next layer while the current layer is untested. Keep modules small and single-purpose, and update documentation with the behavior it governs.
## Never Skip Required Checks, and Show Results Before Calling It Done
Make changes on a dedicated branch, target the repository's designated integration branch, and keep production releases tied to the declared release source.
- Use the canonical package manager, pinned toolchain, formatter, linter, type checker, and test runners from the repository root.
- Keep equivalent build-profile settings synchronized across separately built native components.
- Never bypass required hooks or checks to hide failures. If a hook generates files, review and include the results, then rerun it.
- Fix failures caused by the change; document unrelated pre-existing blockers instead of silently weakening safeguards.
- Review the final diff for implementation, tests, schemas, generated artifacts, localization, and documentation consistency.
- Do not declare completion without direct results from the checks relevant to every changed layer and supported build configuration.
