


Change a Multi-Browser Extension Without Leaking Secrets or Breaking Saved Data
Use when changing an extension built for several browsers. Keeps secrets local, regenerates dependency safety policies, adds numbered upgrades for saved data, tests at the right level, and runs Git steps only when asked.
To use this AI rule
## Find the Affected Extension Layer and Its Local Rules First
Before changing code, identify the affected layer: background logic, user interface, shared utilities, browser declarations, build tooling, tests, or stored-state upgrades. Read the nearest layer-specific instructions before editing; workflow automation files often have additional wiring and merge-queue rules.
Use the repository-managed runtime and package-manager wrapper. Keep configuration in the designated local environment file, never in tracked source. Prefer the current typed language for every new implementation file; legacy untyped files may remain until a requested change justifies conversion.
Use the formatter assigned to each file family. The code formatter owns typed and component files, while the structured-data formatter owns package and configuration data. Run changed-file formatting, static types, linting, and circular-dependency checks before handoff.
Keep functions focused, reduce nesting with early returns, remove repeated logic only when extraction clarifies ownership, and leave no debug logging or commented-out code.
## Keep Secrets Local and Regenerate Policies After Dependency Changes
Treat API credentials, monitoring endpoints, test passwords, and signing material as local secrets. Provide documented placeholders, but never commit real values or include sensitive state in diagnostic logs.
Every dependency addition, upgrade, or removal is a supply-chain change. Complete the full sequence:
1. Update the package declaration and lockfile.
2. Deduplicate the lockfile through the supported command.
3. Recalculate the allowlist of packages permitted to run install scripts.
4. Regenerate capability policies that restrict dependency access to host APIs.
5. Regenerate third-party attribution.
6. Build and test with the production security policy enabled.
Include all generated policy and attribution changes in the same review. A policy-free development build may be used for fast iteration, but it never substitutes for the protected final build. Regenerate platform-sensitive policies on the target platform instead of weakening them to make a build pass.
## Build Background Controllers With Declared State and Domain Actions
Use controllers for background business logic and message-based communication between subsystems. For every stored property, declare whether it is anonymous, safe for diagnostic snapshots or state logs, persistent, and consumed by the interface.
Follow these state rules:
- Export a default-state factory that returns a fresh object, never a shared mutable default object.
- Accept constructor dependencies through one named options bag rather than positional arguments.
- Model domain actions such as add, remove, approve, or reject; avoid generic field setters.
- Store only essential state and derive counts, flags, and projections through exported selectors.
- Validate action inputs before updating state.
- Implement lifecycle cleanup for timers, subscriptions, listeners, and message channels, then delegate to parent cleanup.
Keep controller source, types, colocated tests, registration, and affected selectors synchronized. A state-shape change also requires a numbered upgrade step.
## Put Code in the Layer That Owns It and Update Linked Files
Put reusable interface components with their styles, types, tests, stories, documentation, and public export. Put page-only components at the page layer, background-only helpers with background code, and truly shared utilities, constants, and types in shared modules.
When behavior changes, inspect the directly coupled files:
- Controller changes may require tests, types, registration, and state upgrades.
- Component prop changes may require tests, stories, styles, documentation, and exports.
- Global-state changes may require selectors and consuming components.
- Build or browser-declaration changes may require target-specific fixtures and end-to-end coverage.
Do not copy shared logic merely to avoid choosing an owner. Move it to the narrowest common layer, use dependency injection where needed, and resolve circular imports rather than accepting an expanded exception list.
## Build and Test Each Browser Target and Feature Flag Separately
Maintain a distinct build and test path for every supported browser declaration generation. Account for differences such as persistent background pages versus service workers, permission models, and namespace availability.
Use the cross-browser API abstraction in shared code. Isolate unavoidable target differences in a small compatibility layer rather than scattering conditions through features.
Feature flags must be explicit build inputs:
- Use the documented local configuration for repeatable development.
- Use one-command environment overrides for isolated experiments.
- Use a local declaration override only for remote-flag test scenarios.
- Build and test the same target and flag combination that will ship.
If the user does not name a browser, use the project's documented default target. When the request names a target, choose its matching development, production, and end-to-end commands rather than assuming compatibility from another build.
## Write Interface Components and Optimize Only After Measuring
Use functional components and hooks, destructure props at the boundary, keep local-only state local, and use the shared state layer only for cross-feature data.
For rendering correctness and performance:
- Use stable domain identifiers as keys for dynamic lists, never array positions.
- Compute derived display values during rendering instead of copying them into state through an effect.
- Memoize expensive sorting, filtering, or aggregation only when repeated work is material.
- Stabilize callbacks passed to memoized children when reference churn causes rerenders.
- Define truly static objects and styles outside the component.
- Consider virtualization for large lists after measuring the rendering cost.
Copy before sorting when inputs are immutable. Profile frequently rendered or computationally expensive components first; do not add memoization to simple, rarely rendered components without evidence.
## Reproduce Bugs With a Failing Test, Then Fix and Retest
For a bug, first add a focused test that reproduces the failure, run it red, implement the smallest fix, run it green, and then run directly related coverage. Colocate unit tests with source and test behavior through public interfaces rather than private methods.
Use integration tests for boundaries between subsystems. Use end-to-end tests for real browser workflows, building the dedicated test artifact first. During iteration a faster unprotected build may be acceptable; final verification must use the production security policy and the requested browser target.
When visual verification is explicitly requested:
1. Launch the actual extension test environment.
2. Inspect the current screen before acting.
3. Drive the user flow with browser controls.
4. Capture a screenshot or final screen description as evidence.
5. Clean up the test session even after failure.
If the required visual tool is unavailable, report that limitation and do not claim visual verification from code inspection alone. Review snapshot changes before accepting them; update a snapshot only when the behavior change is intentional.
## Write a Numbered Upgrade Whenever Saved Data Changes Shape
Create a numbered upgrade whenever persisted state shape changes. Generate it through the repository tool so ordering and registration remain consistent.
An upgrade must:
- Accept older, incomplete, and partially corrupted input gracefully.
- Clone the input and return a new state object rather than mutating the caller's value.
- Transform only the version range and properties it owns.
- Record the destination version explicitly.
- Preserve unrelated user data.
Test representative old states, missing fields, malformed optional values, idempotence expectations, and the exact new output. Run both the upgrade test and the controller or feature tests that consume the upgraded state.
## Sort Errors by Type and Fix Them Without Turning Off Checks
Read the first actionable error before changing the environment. Classify it as formatting, type, dependency, capability-policy, browser-build, test-data, stale-artifact, port, or circular-import failure, then run the narrowest diagnostic for that class.
When a previously green test fails after the change, determine whether the implementation regressed or the expected behavior intentionally changed. Update the test only in the latter case and document the behavioral reason.
For end-to-end failures, verify that the correct and current test build exists, use debug output, and leave the browser running only for active inspection. For policy failures, regenerate the policy from declared dependencies and host access; do not permanently disable enforcement.
Remove caches, build outputs, installed packages, browser data, or snapshots only after resolving the exact target and confirming that regeneration is safe. Do not use broad cleanup as the first troubleshooting step.
## Run Git Commits, Pushes, and Review Requests Only When Asked
Do not change repository configuration or run destructive version-control commands. Do not stage, commit, push, or open a review request unless the user explicitly asked for that specific action.
When authorized:
- Stage only files belonging to the requested change.
- Format and test the changed surface before committing.
- Use the repository's structured commit convention.
- Push only when push was requested.
- Open a draft review request unless the user explicitly requested ready status.
- Follow the review template and fully hide non-applicable sections.
Manual-testing sections must describe human actions and visible outcomes, not automated lint or test commands. Include before-and-after evidence for visible interface changes, explain non-obvious decisions, link related work, and identify user-facing change notes when required.
During review, answer feedback and add follow-up commits instead of rewriting reviewed history. Before merge, require resolved conversations, approvals, passing automation, and the repository's expected squash title.
Finish with a proportional checklist: changed-file formatting, types, circular dependencies, focused tests, affected builds, related files, documentation, production capability policies when dependencies changed, and real visual evidence when requested.
