Refine repository guidance

Signed-off-by: Loren Eteval <loren.eteval@proton.me>
This commit is contained in:
Loren Eteval
2026-08-25 13:52:25 +08:00
parent bdc32aaf72
commit 471fdbbd98
25 changed files with 390 additions and 716 deletions
+47 -67
View File
@@ -1,78 +1,58 @@
# Furious repository guidance
## Working method
## Work from the current tree
- Treat the checked-out tree as authoritative. Preserve unrelated staged and unstaged work; do not revive deleted
experiments or infer architecture from old history.
- If `.codegraph/` exists, use `codegraph explore` before broad text search or file reading. Use `rg` for exact
follow-up searches.
- Before Python work, inspect the repository root for `.venv*` or `venv*` and prefer its interpreter when usable. Do not
create or modify an environment unless required.
- Keep edits focused. Preserve GPL headers, `from __future__` placement, import grouping, repository naming style, and
public compatibility unless a deliberate migration is part of the task. Treat curated package `__init__` exports,
plugin API dataclasses, persisted keys, and semantic exit codes as compatibility surfaces.
- Treat the checked-out tree, including unstaged work, as authoritative. Preserve unrelated changes and do not revive
deleted experiments from history.
- If `.codegraph/` exists, use it for structural questions before broad searches; use `rg` for exact follow-up.
- Before Python work, inspect `.venv*`/`venv*` at the repository root and prefer its interpreter when usable. Do not
create or alter an environment without need.
- Keep edits focused. Preserve GPL headers, `from __future__` placement, import grouping, and established naming. Search
consumers before changing curated package exports, plugin contracts, persisted keys, serialized values, IDs, aliases,
migrations, or semantic exit codes.
## Pythonic design
## Design and boundaries
- Prefer the simplest design that makes ownership, state transitions, failure, and side effects explicit. Readability
and one canonical path beat clever indirection or parallel implementations.
- Keep policy close to the layer that owns it: models describe data, repositories persist domain collections,
`AppSettings` persists preferences, services own workflows and temporary resources, controllers own shared state
machines/orchestration, plugins/backends own protocol-specific behavior, `Application` composes the process, and UI
adapts those APIs.
- Make invalid states and boundary failures visible with specific return values, result objects, or exceptions. Catch
broadly only at a genuine isolation boundary, log actionable context, and do not silently convert explicit user input
into a different behavior.
- Bound external work where the workflow requires responsiveness: network requests, subprocess startup/shutdown,
thread joins, and host commands need caller-chosen timeouts or a documented non-GUI execution context. Low-level
wrappers such as `runExternalCommand()` intentionally do not invent a universal timeout. Cleanup must be idempotent,
own exact resources, and never search by process name.
- Prefer immutable metadata, pure transformations, dependency injection, and explicit runtime copies. Avoid global
mutable state, hidden mutation, duplicated caches, and UI-owned business state.
- Make each state authority, resource owner, mutation, and failure path explicit. Prefer one readable canonical path
over parallel implementations or clever indirection.
- Put policy in its owning layer: models describe data; repositories persist domain collections; `AppSettings` persists
preferences; services own workflows and temporary resources; controllers own shared state machines; plugins/backends
own protocol variation; `Application` composes the process; UI adapts those APIs.
- Existing global accessors and live repository collections are compatibility mechanisms, not invitations to add hidden
ownership. Prefer a narrow injected dependency or named operation for new code when practical.
- Treat persisted configuration as input. Build runtime, routing, testing, logging, TUN, and statistics state from
explicit copies unless an API is documented as mutating storage.
- Prefer plugin capabilities/factories to protocol conditionals in shared orchestration. Registries may own
process-lifetime plugins and metadata, never transient UI or active runtimes.
- Keep platform mutation behind `Frozenlib` and runtime boundaries. Own exact processes, threads, replies, timers, and
handles; cleanup is bounded where responsiveness requires it, idempotent, and never based on process-name searches.
## Repository invariants
## Errors and external input
- Treat persisted user configuration as input. Connection, routing, testing, logging, TUN, and statistics preparation
must not mutate it implicitly; use explicit runtime/derived state unless an API is documented as mutating storage.
- Prefer plugin capabilities/factories over protocol or core conditionals in shared managers. Registries may strongly
own process-lifetime plugins, capability providers, factories, descriptors, and metadata; they must not retain
transient UI or active runtime instances.
- A `ServerProfile` combines profile metadata with one persisted connection/configuration document. `CoreRuntime` means
one managed proxy-core lifecycle regardless of whether its implementation uses a subprocess, multiprocessing, or an
in-process binding. Reserve process terminology for actual operating-system processes and handles.
- Application-wide controllers and repositories may be process-lifetime. Transient UI, network replies, timers,
callbacks, and temporary processes must not become accidental global state.
- Keep platform mutation behind `Frozenlib`/runtime abstractions so unsupported platforms remain safe to import and
tests can fully mock host operations.
- Treat secrets, subscription payloads, paths, URLs, and plugin data as untrusted input. Do not log credentials or full
sensitive configurations; validate before host or process use.
- Internal invariant failures remain visible. Validate user/plugin/network input and return a controlled failure with
useful diagnostics. At OS/network/plugin boundaries, translate expected failures without discarding their cause.
- Cleanup may continue after one failure, but log the failed resource/stage. Narrow best-effort suppression is
acceptable only when the caller cannot act and the primary outcome remains observable.
- Treat secrets, subscription payloads, paths, URLs, and plugin data as untrusted. Do not log credentials or complete
secret-bearing configurations.
## Generated files and translations
## Generated and packaged artifacts
- `Furious/Frozenlib/AppResources.py` and `Furious/Externals/GenTranslation.py` are generated. Change their source
inputs and run the existing generator instead of hand-maintaining them.
- Add user-facing text through translation-aware controls and `_()` extraction conventions, then run `Translation.py`.
- `_()` normally receives a static string literal. The sole dynamic exception is an f-string made only from bare names
in `Furious.Frozenlib.Constants`; the extractor resolves those constants. Runtime values, attributes, calls, format
specifications, and `.format(...)` inside `_()` are unsupported.
- Curly braces in extracted strings are reserved for application-constant substitution, not ordinary runtime
placeholders.
- Keep both source execution and the `Deploy.py`/Nuitka build viable. Plugin discovery and optional heavy imports must
remain statically discoverable or explicitly included without introducing import-time application/UI construction.
- `Furious/Frozenlib/AppResources.py` and `Furious/Externals/GenTranslation.py` are generated. Modify their source inputs
and run the existing generator; do not hand-edit generated output.
- `_()` normally receives a static literal. Its only dynamic exception is an f-string composed solely of bare names
from `Furious.Frozenlib.Constants`; ordinary placeholders, attributes, calls, format specs, and `.format()` are not
extractable. Curly braces are reserved for constant substitution.
- Keep both source execution and `Deploy.py`/Nuitka builds viable. Runtime data belongs under `Furious/Data`; plugin and
optional imports must remain discoverable without constructing application or UI objects at import time.
## Verification and review
## Verification
- Run the narrowest relevant tests first, then the affected tier in `tests/README.md`. Tests must not touch production
settings, networking, routing, TUN, startup registration, or unrelated processes.
- Format only touched Python files with the repository Black configuration and run Black check mode on those files.
- For backend/process/platform work, verify error cleanup and bounded shutdown. For Qt ownership work, follow
`Furious/AGENTS.md` and `Furious/Qt/AGENTS.md`, use the `manage-qt-pyside6-lifetimes` skill, and run the relevant
lifetime tests.
- Windows 7 release bindings that require newer Go runtimes must use locally built wheels produced with the patched
`go-win7` toolchain; an ordinary modern PyPI wheel does not establish Windows 7 compatibility. A binding built with an
official Go release that still supports Windows 7 may use a verified prebuilt wheel.
- Architecture-specific Windows release jobs must verify the active Python, installed native extensions, and packaged
executable architecture before publishing uniquely named artifacts.
- Review for duplicated state authorities, mutation of persisted configuration during runtime preparation, broad
swallowed errors, unbounded waits/caches, generated-file edits without regeneration, and protocol branches that belong
in a capability.
- Run the narrowest relevant tests, then the affected tier documented in `tests/README.md`. Tests never touch production
settings, networking, routing, TUN, startup registration, unrelated processes, or external services.
- Format only touched Python files with the repository Black configuration and check those files afterward.
- Match verification to the boundary: round trips/migrations for models and persistence; transitions/signal counts for
controllers; stale/cancel/cleanup paths for async services; partial startup and bounded cleanup for runtimes; repeated
destruction for Qt lifetimes; fully mocked host operations for platform helpers.
- Review for duplicated state authorities, persisted-data mutation during preparation, swallowed diagnostics, unowned
resources, unbounded external-input caches, and shared-manager branches that belong in a capability.
+23 -44
View File
@@ -1,51 +1,30 @@
# Furious package guidance
## Layering and state
## Architecture
- `Application` is the deliberate broad composition layer. Elsewhere, depend on the narrowest lower-level contract and
avoid circular imports or reaching through a page/window when a controller, service, repository, or plugin capability
owns the operation.
- Existing UI code has compatibility paths that reach repositories or application globals. Do not extend that coupling
when a controller/service API can be introduced cleanly; migrate incrementally rather than creating a second state
authority.
- Long-lived objects may be exposed through `Frozenlib.Globals`, but accessors must tolerate partial startup and
shutdown. Never place transient dialogs, replies, workers, or editor instances in application globals.
- Keep GUI-thread work short. Worker callbacks publish immutable/result data to the GUI thread; they do not mutate
widgets directly.
- `Application` is the broad composition root. Elsewhere, depend on the narrowest owning model, repository, service,
controller, or plugin capability. Do not create a second authority merely to avoid an existing boundary.
- Process-lifetime accessors in `Frozenlib.Globals` must tolerate partial startup and shutdown. They may expose deliberate
application authorities, never transient dialogs, replies, workers, editors, or runtimes.
- Compatibility code still reaches global accessors and live collections. Do not extend that coupling when a narrow
API can be added without a competing cache or state path.
- Keep GUI-thread work short. Workers publish result data through the established Qt boundary and never mutate widgets
directly.
## Qt/PySide6 lifetime invariants
## Qt ownership
Classify every dynamic Qt object before choosing ownership:
- Classify Qt objects as application-lifetime, reusable, or transient. Give each one a durable Python owner, compatible
QObject parent, and explicit reuse or destruction path.
- Use the canonical `AppQ*` controls. One-shot dialogs use `AppQTransientDialog` or `AppQMessageBox`; managed `open()` is
safe for local variables, while `exec()` is reserved for genuinely synchronous flow. Reusable windows retain one
explicit owner.
- Parent or explicitly dispose timers, models, delegates, replies, event filters, menus, and actions. Never cache a
transient QObject or an instance method in a global/unbounded cache.
- For lifetime-sensitive changes, follow `Furious/Qt/AGENTS.md` and the `manage-qt-pyside6-lifetimes` skill, then run the
relevant native and packaged lifecycle checks.
- **Application-lifetime:** main window, navigation pages, shared controllers/managers. Give them one durable
application owner and idempotent cleanup.
- **Reusable:** intentionally hidden and shown again. Retain one explicit Python owner and release it deliberately at
final shutdown.
- **Transient:** editors, prompts, progress windows, and one-shot dialogs. Use a suitable parent plus normal
close/deferred deletion; do not retain them after completion.
## Presentation
Use `AppQTransientDialog` for one-shot dialogs. `AppQDialog.open()` and `AppQMessageBox.open()` retain asynchronous
dialogs until completion, so local variables are safe; connect `finished` before `open()`. Use `exec()` only where
synchronous control flow is required. Do not add `WA_DeleteOnClose` universally or use `gc.collect()` as a production
fix.
QObject parent ownership and Python references are separate concerns. Parent timers/models/delegates where appropriate,
stop/disconnect owned resources on teardown when auto-disconnection is insufficient, remove event filters when lifetimes
differ, and clear references after `destroyed`. Never cache a transient QObject instance or an instance method with
unbounded `lru_cache`.
## UI and translation
- Reuse `AppQ*` Fluent/theme/translation-aware primitives. Prefer layout composition over fixed geometry and preserve
keyboard, focus, shortcut, accessibility, and theme behavior.
- Pass source text at construction when an `AppQ*` control already retranslates it; avoid parallel manual
`retranslate()` logic.
- UI shows errors at the interaction boundary, while services/controllers provide structured, testable failures. Do not
broadly catch and hide deleted-wrapper or worker failures.
## Verification
- For changed transient/reusable UI, test open/close/show cycles, weak-reference clearing or deliberate retention,
signal/timer stability, and asynchronous completion.
- Use offscreen Qt and the isolated settings helpers documented in `tests/README.md`. Distinguish allocator high-water
behavior from linear live-object/resource growth.
- Reuse translation/theme-aware construction instead of parallel manual retranslation or one-off styling. Preserve
focus, keyboard, shortcut, accessibility, resize, high-DPI, and light/dark behavior.
- UI presents failures at the interaction boundary; owning services/controllers provide structured, testable results.
+14 -18
View File
@@ -1,21 +1,17 @@
# Actions guidance
# Action guidance
- Actions are thin presentation adapters: resolve current controller/service state at trigger time, invoke the owning
API, and present the result. Do not make an action a second owner of connection, routing, repository, or import state.
- Reuse shared `QAction` command logic for menus/buttons. Keep shortcuts, check state, enabled state, translation, and
callback semantics synchronized from one source.
- `AppQAction.callback` is intentionally a strong reference. Scope actions to a suitable QObject owner; an
application-lifetime action must not retain a transient bound method or closure.
- Import through `profileFromAny`/plugin capabilities. Treat clipboard, file, URI, QR, and subscription text as
untrusted; report per-item failures without logging credentials or whole secret-bearing payloads.
- Long or batched work must yield safely or use an owned worker/progress dialog. Do not sleep the GUI thread.
- Asynchronous prompts/editors use managed `open()` lifetime and connect completion before opening. Rebuilt dynamic
menus must release obsolete `QMenu`/`QAction` objects rather than accumulating them.
- Preserve structured error semantics in message boxes: error title becomes the visible `heading`, the main message
becomes `text`, and diagnostics become `informativeText`; native `windowTitle` remains metadata.
## Scope and contracts
## Code review rules
- Actions are thin presentation commands. Resolve current state when triggered, call its owning controller/service, and
present the result; do not own connection, routing, import, or persistence state.
- Share one `QAction` command between menus/buttons so callback, enabled/check state, shortcut, and translation cannot
diverge. `AppQAction.callback` is a strong reference, so its QObject owner must not outlive a captured receiver.
- Import through `profileFromAny` and plugin capabilities. Treat clipboard, file, URI, QR, and subscription content as
untrusted and avoid logging secret-bearing payloads.
- Managed asynchronous dialogs connect completion before `open()`. Long or batched work must yield or use one owned
worker/progress dialog; never sleep the GUI thread.
- Flag duplicated controller logic, captured transient widgets, unsanitized sensitive logging, and action-local
persistence changes.
- Verify repeated triggering does not multiply dialogs, callbacks, menus, or workers.
## Verification
- Verify command state and results plus repeated triggering: dialogs, dynamic menus, callbacks, and workers must return
to baseline.
+15 -23
View File
@@ -1,28 +1,20 @@
# Application composition guidance
- `DesktopApplication` is the composition root. It durably owns process-lifetime controllers, logging, the main window,
tray, thread pool, singleton IPC endpoint, cleanup stack, and plugin-registry lifecycle. `MainWindow` owns the built-in
page/widget tree and page-level managers through normal Qt parentage; do not duplicate those owners in the application.
- Keep bootstrap order explicit: environment/plugins and storage before consumers, controllers/services before UI, and
restoration only after all dependencies exist. Accessors must tolerate partial startup and cleanup.
- Each successful startup stage registers its exact cleanup immediately. Partial startup and final shutdown use the
same reverse-order, failure-isolating cleanup stack, and cleanup is idempotent without relying on destructor timing.
- Keep graceful shutdown distinct from the final base-Qt event-loop exit. Delayed forced-exit callbacks must never
recursively re-enter application cleanup.
- Serialize single-instance election with a short-lived cross-process lock. `QLocalServer.listen()` is the endpoint
claim on Unix, but Qt permits duplicate same-name local servers on Windows. Never remove a possibly stale endpoint
until the election lock is held and endpoint reachability and listening have both been rechecked.
- The tray owns its long-lived actions/menus and reflects controller state. Dynamic submenu rebuilds must not retain
stale actions or menus. A system tray is an optional desktop capability, not proof that the operating system is
supported; when it is unavailable, show the main window and let closing the last window quit the application.
- Application code may import higher layers to compose them; lower layers must not import `DesktopApplication` to obtain
dependencies when injection or a narrow global accessor suffices.
- Keep blocking startup checks bounded and event-loop safe. A startup failure must leave enough runtime available to
report the error and still clean up.
## Ownership and lifecycle
- `DesktopApplication` is the composition root. It owns process-lifetime controllers, plugin-registry lifecycle,
storage, singleton IPC, logging, platform integration, the main window/tray, and the cleanup stack. `MainWindow` owns
the persistent page tree through Qt parentage.
- Keep startup order explicit: settings/storage and plugins before consumers; controllers/services before UI;
restoration after dependencies exist. Register exact cleanup immediately after each successful acquisition.
- Partial startup and normal exit use the same reverse-order, failure-isolating, idempotent cleanup path. Keep graceful
resource cleanup separate from final Qt event-loop exit.
- Single-instance election is an atomic host operation: serialize stale-endpoint recovery and do not delete an endpoint
that another launch may have claimed.
- The tray owns its long-lived actions/menus. Dynamic rebuilds release obsolete objects, and absence of a system tray is
a supported desktop condition rather than an OS-support verdict.
## Verification
- Test partial initialization, repeated cleanup, singleton IPC commands, tray menu rebuilding, startup restoration, and
failure before the main window exists.
- Do not start the production singleton/host mutation in ordinary tests; compose fakes through the established
boundaries.
- Test partial initialization, singleton commands/races, tray rebuilding, restoration, and repeated cleanup with host
integration mocked.
+23 -41
View File
@@ -1,50 +1,32 @@
# Backend and core-integration guidance
# Backend guidance
- A backend owns protocol parsing/export, editor factories, runtime materialization, process integration, statistics,
validation, and native-TUN capability for its core. Register these through plugin capabilities instead of adding
shared-manager conditionals.
- The JSON/document submitted to a core is the runtime authority. Build it from a deep/runtime copy; never mutate the
persisted profile while preparing connection, routing, logging, testing, or TUN state.
- Preserve full user-authored core documents and unknown supported fields. Report a lossless-compatibility failure
instead of silently compiling or deleting unsupported configuration.
## Scope and extension
## Structured editor contract
- A backend owns its protocol parsing/export, structured editors, runtime factory, validation, statistics, routing, and
native-TUN behavior. Expose variation through plugin capabilities instead of shared-manager core-name branches.
- The full document submitted to the core is the runtime authority. Derive it from a copy and preserve the persisted
profile plus unknown supported fields; fail visibly when a lossless representation is impossible.
- Runtime modules remain importable without constructing Qt editors. Registrations/imports must be literal enough for
plugin discovery and Nuitka inclusion; factories create fresh widgets/runtimes and registries never retain them.
- `factoryToInput()` is normally observational: loading an editor must not add defaults or otherwise mutate the
configuration document unless that backend has a documented compatibility normalization, such as Xray transport
aliases.
- `inputToFactory()` writes only fields represented by the editor and returns whether it actually changed the
document. Do not materialize an absent effective default merely because the editor displays it.
- Display unknown future string values exactly and preserve them on an untouched load/save round trip. Editing one
known leaf must preserve unknown fields and unknown siblings elsewhere in the same object.
- A deliberate user switch to a supported tagged variant may replace the incompatible active variant. Keep unrelated
extension fields, and do not treat a fallback page used for display as a user selection.
## Structured editors
## Native TUN policy
- Loading is observational except for a documented compatibility normalization. Saving writes only represented fields
that changed, preserves unknown siblings, and does not materialize absent effective defaults.
- Unknown future string values remain visible and survive an untouched round trip. A deliberate switch to a known
tagged variant may replace only the incompatible variant data that control owns.
For a normal connection:
## Native TUN and runtime ownership
- If the Furious native-TUN option is enabled, replace any runtime native TUN with the generated TUN; mark TUN handled
and do not start application tun2socks.
- If the option is disabled and the user document contains native TUN, preserve it unchanged; mark TUN handled and do
not start tun2socks.
- If neither exists, do not inject native TUN; global TUN mode may use tun2socks.
- Proxy-only operations such as speed/latency tests explicitly strip native TUN from their own temporary copy.
Never remove an explicit user TUN merely because an application toggle is off, and never run native TUN plus application
tun2socks together.
## Runtime and UI
- Core runtimes expose actionable `startError()`, exact resource ownership, bounded startup/shutdown, and deterministic
cleanup. Process-backed runtimes additionally own and reap their exact child process. Keep platform exit codes
semantically intact.
- Keep runtime modules importable without constructing editor widgets. Use literal, discoverable lazy
imports/registrations so Nuitka includes every editor family without per-editor command changes.
- Backend editors follow `Furious/Qt/AGENTS.md`; factories create fresh transient editors and registries retain
factories/classes, not editor instances.
- Normal connection preparation operates on a runtime copy. With the backend native-TUN option enabled, generated TUN
replaces runtime native TUN and suppresses application tun2socks. With it disabled, an existing user native TUN is
preserved and also suppresses tun2socks. Without either, global TUN mode may use tun2socks.
- Proxy-only tests explicitly strip native TUN from their own copy. Never run two TUN implementations or silently turn
malformed explicit TUN into another networking mode.
- A `CoreRuntime` owns exact resources, reports an actionable `startError()`, and has bounded, idempotent startup failure
and shutdown cleanup. Process-backed implementations additionally reap their exact child.
## Verification
- Test URI/mapping round trips, malformed input, runtime document equality, original-document immutability, all
native-TUN matrix cases, proxy-only stripping, failed core startup, and cleanup.
- Test mapping/URI round trips, malformed and unknown input, original-document immutability, runtime document equality,
TUN matrices/proxy-only stripping, failed startup, and cleanup. Editor changes also require lifetime tests.
+8 -24
View File
@@ -1,25 +1,9 @@
# External Core backend guidance
# External Core guidance
## Configuration contract
- External Core is protocol-agnostic. Keep executable path, working directory, argument vector, environment mapping,
proxy endpoints, shutdown timeout, and application-tun2socks choice as distinct fields.
- The editor projects these known fields but must preserve unknown top-level fields. Loading is observational and an
untouched save must not normalize paths, arguments, environment variables, or future fields.
- Execute with `shell=False`. Never concatenate arguments into a shell command or log credentials/environment secrets.
## Runtime and TUN ownership
- Own the exact `Popen` instance and its reader/watcher resources. Startup failure and shutdown must close callbacks,
terminate, kill if necessary, and reap the exact child within configured bounds.
- Application tun2socks is explicit and requires a valid remote address. This backend does not inject a native core TUN
block or infer protocol-specific behavior.
- Validation dialogs are transient asynchronous UI: use the established `open()` ownership path and release them on
destruction without retaining them in backend or plugin registries.
## Code review and verification
- Flag shell execution, ambiguous string arguments, inherited transient callbacks, unknown-field loss, and unbounded
child-process cleanup.
- Use fully mocked subprocess and host-network operations. Test mapping round trips, unknown-field preservation,
validation failures, output-reader shutdown, tun2socks validation, and repeated editor/dialog destruction.
- This backend is protocol-agnostic. Keep executable, working directory, argument vector, environment, proxy endpoints,
shutdown timeout, and application-tun2socks choice as distinct fields; preserve unknown top-level fields.
- Execute with `shell=False`, own the exact `Popen` plus readers/watchers, and terminate/kill/reap it within the configured
shutdown contract. Never concatenate a shell command or log environment secrets.
- Application tun2socks is explicit and requires a valid remote address; this backend never invents a native core TUN.
- Verify mapping/unknown-field round trips, mocked spawn/start/stop failures, output-reader cleanup, TUN validation, and
repeated editor/validation-dialog destruction.
+7 -21
View File
@@ -1,22 +1,8 @@
# Hysteria1 backend guidance
# Hysteria1 guidance
## Compatibility boundary
- Hysteria1 is an independent legacy backend with its own flat client schema and `hysteria://` share-link behavior.
Do not import Hysteria2 nested configuration, obfuscation, or native-TUN assumptions into it.
- Its structured editor follows the shared observational-load/minimal-write contract. Unknown future string values in
combo-backed fields remain visible and survive an untouched round trip.
- Preserve existing tolerant handling of malformed legacy field types unless a deliberate compatibility migration is
part of the task; do not silently rewrite valid user values while loading.
## Runtime and assets
- Runtime setup owns Hysteria1-specific MMDB, geosite, and rule assets. Keep asset preparation and failure cleanup in
this backend rather than shared connection managers.
- Proxy-only tests operate on a copy, set their temporary proxy listener, and must not alter the stored profile.
## Code review and verification
- Flag accidental reuse of Hysteria2 keys, loss of unknown flat fields, or editor factories retained as live widgets.
- Test URI and mapping compatibility, unknown combo values, runtime document immutability, asset failures, process
cleanup, and transient editor destruction.
- Hysteria1 is a distinct legacy flat schema with `hysteria://` links. Do not import Hysteria2 nested fields,
obfuscation rules, or native-TUN assumptions into it.
- Loading preserves tolerated legacy types and unknown combo values; an untouched editor round trip does not normalize
valid user data.
- This backend owns its MMDB, geosite, and rule assets. Proxy-only tests alter a copy and never the stored profile.
- Verify legacy URI/mapping compatibility, unknown values, asset/startup failure cleanup, and editor destruction.
+10 -28
View File
@@ -1,29 +1,11 @@
# Hysteria2 backend guidance
# Hysteria2 guidance
## Native document and structured editor
- The persisted Hysteria2 client document is the configuration submitted to the embedded core. The compact editor is
a partial projection, not a compiler or schema normalizer.
- Keep upstream field names and values exact. In particular, `realm.ipMode` uses the native values `dual`, `v4`, and
`v6`; an absent value has the effective dual-stack default. Unknown future strings remain visible and untouched.
- Optional nested controls edit only their leaf. Preserve unknown siblings, and do not create optional groups or
effective Gecko packet defaults until the user actually changes the represented value.
- `obfs.type` selects a tagged subtype object. An unknown type must remain visible and preserve its subtype on an
untouched round trip. An explicit user switch to a known type may remove the previously active incompatible subtype,
while retaining unrelated extension data.
## Runtime, statistics, and TUN
- Runtime materialization passes a derived full Hysteria2 document to `startFromJSON`; do not translate it through an
Xray-shaped intermediate representation.
- With native TUN enabled, replace the runtime `tun` block with the generated block. With it disabled, preserve and
recognize a user `tun` block. Download/probe configurations explicitly omit TUN.
- Keep management/statistics requests bounded and separate from GUI objects. Runtime and worker ownership follows the
parent backend and Qt lifetime guidance.
## Code review and verification
- Flag `_modified` shadow state, dynamic combo-item accumulation, fallback pages mistaken for user selections, and
nested writes that replace an entire user object.
- Test known and unknown values, nested sibling preservation, obfs subtype switching, absent-default preservation,
native-TUN matrices, proxy-only stripping, and editor destruction.
- The persisted native client document is submitted to the embedded core; the GUI editor is a partial projection, not
an Xray-shaped compiler or schema normalizer.
- Preserve upstream names/values. `realm.ipMode` uses `dual`, `v4`, or `v6`; absent effective defaults and unknown future
strings survive untouched. Optional controls update only their leaf and preserve unknown siblings.
- `obfs.type` selects a tagged subtype. Display an unknown subtype without rewriting it; an explicit switch to a known
type may replace only the incompatible subtype data.
- Runtime preparation and native-TUN behavior follow the parent backend contract; probe/download copies omit TUN.
- Verify nested sibling/default preservation, known/unknown values, subtype switching, runtime document equality, TUN
matrices, statistics cleanup, and transient editor destruction.
+11 -26
View File
@@ -1,27 +1,12 @@
# Xray backend guidance
# Xray guidance
## Documents and editors
- The full Xray JSON document is authoritative. Structured editors project only the selected `proxy` outbound,
transport, and TLS fields; preserve every unrelated inbound, outbound, routing field, and extension.
- Transport and security loading preserves unknown `network` or `security` strings so they remain visible and survive
an untouched round trip.
- `http`, `gun`, and `mkcp` are legacy transport aliases that the editor intentionally normalizes to `h2`, `grpc`, and
`kcp` while loading. Keep this compatibility rewrite explicit and covered by tests.
- Selecting a different known transport or security mode may replace incompatible represented settings. Preserve
unknown sibling settings that the selected editor does not own.
## Runtime, routing, and TUN
- Prepare routing, logging, tests, and native TUN only on runtime copies. Keep the selected profile document intact.
- With Xray native TUN enabled, replace runtime TUN inbounds with the generated inbound. With it disabled, preserve and
recognize user TUN inbounds. Proxy-only tests explicitly strip TUN.
- Xray API statistics, routing assets, and transient asset/routing windows belong to this backend. Keep window owners
explicit and never register live editor or dialog instances globally.
## Code review and verification
- Flag editor loading that mutates configuration outside the documented transport-alias normalization or materializes
unrelated defaults.
- Test URI/mapping round trips, unknown transport/security preservation, routing and TUN runtime-copy behavior, asset
failure cleanup, and transient editor/window destruction.
- The full Xray JSON document is authoritative. Editors project selected outbound/transport/TLS fields and preserve
unrelated inbounds, outbounds, routing, extensions, and unknown transport/security values.
- Loading may intentionally normalize legacy transport aliases `http`, `gun`, and `mkcp` to `h2`, `grpc`, and `kcp`;
keep this sole compatibility mutation explicit and tested.
- Routing, logging, testing, and native TUN are prepared on copies. Native-TUN replacement/preservation and proxy-only
stripping follow the parent backend contract, including multiple user TUN inbounds.
- This backend owns API statistics, routing assets, and its transient asset/routing UI; retain owners explicitly and
never register live windows/editors globally.
- Verify document/URI round trips, unknown and alias behavior, routing/TUN copies, asset failures, process cleanup, and
transient window destruction.
+13 -18
View File
@@ -1,22 +1,17 @@
# Controller guidance
- Controllers are process-lifetime state authorities. Expose observable state/transitions and orchestrate
repositories/services; do not retain transient widgets or duplicate service resource ownership.
- `ConnectionController` owns the connection state machine and interaction gating. Stable and transitional states,
failures, unexpected exits, reconnect preference, `runtimes` snapshots, and signal ordering must remain explicit.
Runtime managers are injected for tests.
- `RoutingController` owns selected/active routing semantics. Distinguish repository selection from the routing actually
applied to a live connection; derive runtime routing through backend capabilities.
- `SettingsController` validates, persists, and applies settings through service-level APIs where practical. Host
registration or administrator-dependent changes persist only after success; UI pages should not implement settings
policy.
- Controller signals carry state/results, not widget callbacks. Long work belongs in services/workers, and GUI-thread
entry points must not perform unbounded waits.
- Treat partial startup/shutdown as normal: global dependencies may be absent and cleanup methods must be repeatable.
## Scope and ownership
## Code review rules
- Controllers are process-lifetime authorities for shared state and transitions. They orchestrate repositories/services
and publish results; they do not own transient widgets or duplicate service resources.
- `ConnectionController` owns connection state, interaction gating, active profile, failure/exit transitions, and signal
ordering. `RoutingController` distinguishes repository selection from routing applied to a live connection.
- `SettingsController` validates, persists, and applies preferences. A setting whose host side effect fails must not be
persisted as successful; UI pages do not reimplement that policy.
- Protocol-specific behavior goes through capabilities. Long work belongs in an owned service/worker, not an unbounded
GUI-thread controller call. Global dependencies may be absent during partial startup and shutdown.
- Flag direct ownership of dialogs/pages, duplicated connection/routing state, settings saved after failed side effects,
and protocol branches that belong in a capability.
- Test success, validation failure, startup failure, unexpected exit, signal order, restoration, cancellation, and
idempotent shutdown.
## Verification
- Test transitions and signal counts for success, validation/start failure, cancellation, unexpected exit, restoration,
failed settings effects, and repeatable shutdown.
+13 -18
View File
@@ -1,22 +1,17 @@
# Process-backed core-runtime guidance
# Process-backed runtime guidance
- This package provides low-level process-backed `CoreRuntime`, queue, output-redirection, and tun2socks primitives. It
must not own controller, repository, page, or protocol policy.
- Own exact child `Process`/handle objects. Startup validates launch specs and readiness; shutdown uses bounded
terminate/join/kill escalation, reaps the child, stops timers/queues, clears callbacks, and is idempotent.
- Preserve platform exit codes and expose actionable startup errors. Do not convert serialization/start failures to an
unexplained success or “Unknown error” when context exists.
- Child-output transports are bounded and non-blocking for producers. Drain them in bounded batches regardless of page
visibility: use a short interval while messages flow and back off to a finite maximum interval while idle. Truncate
oversized messages and drop excess burst output at the documented queue boundary rather than retaining it
indefinitely. Presentation may remain lazy after collection.
- Process targets do not touch GUI objects. Queue/monitor callbacks cross to the owning Qt thread through the
established timer/signal boundary.
- A timer without a QObject parent requires an explicit durable Python owner and `dispose()` path. Never rely on wrapper
finalization for process or timer cleanup.
- Temporary output files/streams close on every success and error path; avoid unbounded queues and blocking reads.
## Scope and ownership
- This package supplies low-level process-backed `CoreRuntime`, output transport, and tun2socks primitives. It does not
own controller, repository, page, or backend protocol policy.
- Own and reap exact child/handle objects. Startup validates launch/readiness; shutdown performs bounded
terminate/join/kill escalation, stops queues/timers, clears callbacks, and is idempotent.
- Preserve semantic exit codes and actionable startup diagnostics. Child targets never touch GUI objects; callbacks
cross through the established Qt timer/signal boundary.
- Bound producer queues/messages and drain independently of page visibility. Presentation may be lazy; process pipes
cannot be. Parentless timers require a durable Python owner and explicit `dispose()` path.
## Verification
- Test invalid specs, failed spawn, early exit, normal output, bounded stop escalation, repeated dispose, exact-child
cleanup, and no residual timers/threads/handles.
- Test invalid launch, failed spawn, early exit, burst output bounds, normal stop, forced escalation, repeated disposal,
and absence of residual children, timers, queues, or handles.
-15
View File
@@ -1,15 +0,0 @@
# Bundled extension guidance
- Extensions are bundled plugins, not exceptions to the plugin contract. Declare stable metadata and contribute
capability objects through `Furious.Plugins` APIs.
- Store factories, handlers, descriptors, and immutable metadata; never retain transient UI or active runtime instances.
- Subscription decoders recognize and normalize one representation. Treat remote payloads as untrusted, return “not
matched” distinctly from “matched but invalid,” and leave profile materialization/import policy to the shared
pipeline.
- Imports must stay lightweight and deterministic so plugin discovery and Nuitka inclusion work without side effects.
- Initialization/shutdown must support registry rollback and repeated process cleanup.
## Verification
- Test discovery order, malformed/ambiguous payloads, capability registration, rollback, and shutdown without network or
production state.
+9 -17
View File
@@ -1,19 +1,11 @@
# Generated translation catalog guidance
- `GenTranslation.py` is generated by the repository-root `Translation.py`; do not hand-edit it. Change source strings
or the translation data and regenerate.
- Each catalog entry uses this key order: `source`, supported language keys in the generator's canonical order, then
`isReviewed`.
- `source` is a deduplicated list of fully qualified Python module names discovered by the generator. Preserve generated
ordering and remove stale sources through regeneration, not manual cleanup.
- `_()`/`gettext()` extraction accepts static literals. The only f-string exception consists exclusively of bare names
defined in `Furious.Frozenlib.Constants`; the extractor substitutes those values before catalog lookup.
- Do not put runtime values, attribute expressions, calls, format specifications, `.format(...)`, or ordinary brace
placeholders inside a translatable expression.
- New or changed translations remain unreviewed until a human verifies meaning. Preserve Unicode, HTML/newline
semantics, and natural RU/ZH wording rather than literal word order.
## Verification
- Run `Translation.py` for the affected languages, inspect the generated diff for source/key ordering and stale entries,
and ensure a second generation is stable.
- `GenTranslation.py` is generated by repository-root `Translation.py`; edit source strings or translation data and
regenerate. Never hand-edit the catalog.
- Entry key order is `source`, language keys in generator order, then `isReviewed`. `source` contains deduplicated fully
qualified modules and stale sources disappear only through regeneration.
- Extraction accepts static `_()`/`gettext()` literals and the root-documented constants-only f-string exception. Keep
runtime formatting outside the translatable expression.
- Preserve HTML/newline semantics and natural RU/ZH meaning. New or changed wording remains unreviewed until a human
verifies it.
- Run affected language generation, inspect ordering/stale entries, and verify a second run is stable.
+20 -31
View File
@@ -1,37 +1,26 @@
# Frozenlib foundation guidance
# Frozenlib guidance
- `Frozenlib` is the compatibility/platform foundation and a broad public import surface. Keep imports cheap,
cross-platform, and free of GUI/application construction side effects. Preserve exported names unless a migration
covers all wildcard consumers.
- `Constants`, enums, version, and pure helpers stay dependency-light. `Globals` contains narrow accessors for
deliberate application-lifetime owners and must return safely during partial startup/shutdown.
- `AppSettings` is the centralized settings boundary. Preserve defaults, value types, migrations, and compatibility
aliases; write requested state only after required host side effects succeed.
## Foundation and compatibility
## Platform and process safety
- `Frozenlib` is the low-level platform/compatibility foundation and a broad import surface. Keep imports cheap,
cross-platform, and free of application/UI construction. Preserve exports unless all wildcard consumers migrate.
- `Constants`, enums, and pure helpers remain dependency-light. `Globals` exposes only deliberate application-lifetime
owners and returns safely during partial startup/shutdown.
- `AppSettings` is the preference boundary. Preserve key/value/default/migration compatibility and write a requested
state only after any required host side effect succeeds.
- Host mutation (proxy, DNS, startup registration, session, routing) is platform-isolated, returns/logs actionable
failure, and can be fully mocked. `runExternalCommand()` intentionally delegates timeout policy to each caller;
callers that require bounded execution must pass a timeout or run in a documented non-GUI context. Never test against
real host state.
- Own exact daemon threads/processes. Clear dead references so restart is possible; cleanup is bounded and idempotent.
Do not suppress a failed shutdown or leave persisted state claiming success.
- `parseHostPort` and other externally keyed caches must be bounded. Unbounded caches are limited to finite application
metadata and must not capture QObject instances or bound methods.
- Rate limiting on GUI-callable paths must not sleep the caller. Network helpers support IPv4/IPv6 and use bounded
resolution/connect operations.
## Host and resource ownership
## Mixins and resources
- Isolate proxy, DNS, routing, TUN, startup, session, and process mutation here so callers can mock it completely.
Return/log actionable failure and never test against real host state.
- `runExternalCommand()` deliberately has no universal timeout; each caller supplies one when responsiveness requires
it or documents a non-GUI execution context.
- Own exact threads/processes/handles, clear dead daemon references, and make cleanup bounded and repeatable. Externally
keyed caches are bounded; finite metadata caches never capture QObject instances or bound methods.
- Context managers restore prior state when nested. Weak pools/callbacks must not have a parallel strong owner.
- `AppResources.py` is generated from resource inputs; update inputs and regenerate.
- Weak instance pools must not be paired with hidden strong references or callbacks that capture the target. Remove dead
entries and tolerate already-deleted Qt wrappers.
- Context managers restore the exact prior enabled/signal-block state and remain safe when nested.
- `AppResources.py` is generated from resource inputs. Do not hand-edit generated bytes or bypass the
resource-generation workflow.
## Verification
## Code review rules
- Flag GUI/high-level imports, caller paths that can block the GUI or shutdown without an intentional bound, state
persisted after host failure, broad process-name cleanup, sensitive logging, unbounded external-input caches, and
globals holding transient objects. Do not add a universal timeout to `runExternalCommand()`.
- Run direct mocked platform/helper tests plus affected controller/service tests.
- Use direct mocked helper tests plus affected controller/service tests. Review GUI blocking, persisted success after
host failure, process-name cleanup, sensitive logs, stale daemon references, and unbounded external-input caches.
+12 -19
View File
@@ -1,20 +1,13 @@
# Interface contract guidance
# Interface guidance
- This package defines small, low-level contracts shared across layers. Keep imports cheap and avoid depending on UI,
controllers, services, repositories, or concrete backends.
- Abstract methods state observable behavior, ownership, mutation, and failure contracts; concrete implementations may
add detail but must not weaken them.
- `CoreRuntime` is the canonical mechanism-neutral lifecycle contract. Preserve its semantic exit codes, `startError()`,
callback order, serialization behavior, and bounded stop expectations.
- Do not introduce mechanism-specific aliases for `CoreRuntime` or mistake the semantic runtime contract for an
operating-system process.
- Serialization uses the shared `Furious.Models` encoders. Preserve supported dict subclasses such as
`CoreConfiguration`; return/raise behavior must be documented and failures must retain useful diagnostics.
- Storage contracts intentionally expose live mutable collections. State that explicitly—do not label them copies—and
keep persistence/ordering semantics in concrete repositories.
- Editor bindings translate between widgets and configuration but do not decide process/runtime policy.
## Verification
- Contract tests cover representative implementations, serialization failures, lifecycle/error behavior, mutable-storage
semantics, and import independence.
- This package defines small contracts shared by lower layers. Keep it cheap to import and independent of UI,
controllers, services, repositories, and concrete backends.
- Contracts state observable lifecycle, ownership, mutation, and failure semantics; implementations may strengthen but
not weaken them.
- `CoreRuntime` is mechanism-neutral. Preserve semantic exit codes, `startError()`, callback order, serialization, and
bounded stop expectations; use process terminology only for actual OS processes.
- Storage contracts intentionally expose live mutable collections. Editor bindings translate widget/configuration
values but do not own runtime policy.
- Shared encoders support `CoreConfiguration` and compatible mappings; serialization failures retain useful context.
- Verify import independence and representative implementations for lifecycle/error, serialization, and live-storage
semantics.
+11 -18
View File
@@ -1,20 +1,13 @@
# Model guidance
- Models are core-neutral Python data and transformations. Do not import Qt, application globals, controllers,
repositories, services, or concrete backend plugins.
- Preserve meaningful user configuration and unknown metadata fields across load/save/copy. Promote legacy fields
through explicit, backward-compatible migrations without ambiguous duplicates.
- `ServerProfile` separates connection data from metadata. Stable profile IDs, subscription ownership/key fields,
display metadata, and connection fingerprints each have distinct semantics; do not substitute row position or display
text for identity.
- Fingerprints must be deterministic for supported JSON-compatible connection documents. If compatibility requires a
fallback, make its stability/diagnostics explicit rather than silently changing identity across runs.
- Encoders are the canonical JSON/ujson boundary. Preserve supported mappings/dict subclasses and surface useful failure
context at the caller boundary.
- `toURI()` dispatch belongs to plugin protocol capabilities; model methods may remain compatibility shims but must not
grow new protocol-specific branches.
## Verification
- Test legacy/current mappings, unknown-field round trips, copies, deterministic identity, subscription migration,
malformed serialization, and plugin-based export.
- Models are core-neutral Python data and transformations. Do not import Qt, globals, controllers, repositories,
services, or concrete backends.
- Preserve user connection data, unknown metadata, and legacy migrations across load/save/copy without duplicate
meanings. `ServerProfile` keeps metadata and connection documents distinct.
- Stable profile IDs, subscription ownership/profile keys, display data, and connection fingerprints have separate
semantics; row position and display text are not identity.
- Fingerprints are deterministic for supported JSON-compatible documents. Export dispatch belongs to plugin protocol
capabilities; model methods may be compatibility shims but gain no new protocol branches.
- Use the shared JSON/ujson encoders and surface serialization context at the caller boundary.
- Verify legacy/current/unknown-field round trips, copy identity, subscription migration, deterministic fingerprints,
malformed serialization, and capability-based export.
+16 -24
View File
@@ -1,28 +1,20 @@
# Plugin architecture guidance
# Plugin guidance
- `Plugins.API` defines stable capability contracts; `Registry` owns registration, selection, materialization,
initialization, rollback, and reverse-order shutdown.
- Use `CoreRuntimeFactory`, `CoreRuntimeRequest`, and `CoreRuntimeLaunch` consistently across capabilities, registry
dispatch, statistics providers, and tests.
- Plugins contribute factories, handlers, descriptors, immutable metadata, and service providers—not live transient
widgets, active core instances, or controller state.
- The registry intentionally keeps registered plugin and capability-provider objects strongly reachable for the
registry lifetime. Their initialization/shutdown contract must release any resources they acquire; this ownership is
not permission to cache factory-created UI or runtimes.
- Protocol parse/export/editor, backend runtime, routing/TUN, statistics, subscription decoding, and navigation behavior
belongs behind capabilities. Shared code must not add core-name conditionals when capability dispatch can express the
policy.
- Registration is deterministic and validates every capability family through focused validators before committing
any index changes. A plugin failure is isolated, logged with plugin/capability context, and does not corrupt already
registered providers.
- Initialization is transactional: partially initialized plugins are rolled back; shutdown is reverse-order and
idempotent.
- Keep discovery imports literal and side-effect-light for source and Nuitka builds. Avoid circular dependencies from
API/model layers into concrete plugins.
- UI factories create fresh owned widgets. Invalid QObject results are explicitly destroyed; registries never retain
rejected objects.
## Registry and extension contract
- `Plugins.API` defines capability contracts; `Registry` owns validation, deterministic registration/selection,
initialization rollback, and reverse-order idempotent shutdown.
- Use current `CoreRuntimeFactory`, `CoreRuntimeRequest`, and `CoreRuntimeLaunch` vocabulary. Add protocol, runtime,
routing/TUN, statistics, subscription, or navigation variation through its capability family rather than central
conditionals.
- Registries strongly own process-lifetime plugins, providers, factories, descriptors, and immutable metadata. They do
not own factory-created UI, active runtimes, or controller state; rejected QObject results are destroyed explicitly.
- Registration validates before committing index changes. A failed plugin is isolated with useful plugin/capability
context and cannot corrupt existing providers. Partial initialization is rolled back.
- Discovery imports stay literal, deterministic, and side-effect-light for source and Nuitka builds. API/model layers
never import concrete plugins.
## Verification
- Test order, duplicate/invalid registration, dynamic dispatch, rollback, shutdown, compiled discovery, and factories
returning invalid objects.
- Test discovery/order, duplicates and invalid capabilities, dispatch, rollback/shutdown, compiled inclusion, and
factories returning invalid objects.
+30 -56
View File
@@ -1,64 +1,38 @@
# Qt foundation guidance
Use the `manage-qt-pyside6-lifetimes` skill for any Qt ownership or lifecycle change.
Use the `manage-qt-pyside6-lifetimes` skill for Qt ownership or lifecycle work.
## Public UI primitives
## Canonical UI surface
- `Furious.Qt` is the canonical Fluent/theme/translation-aware UI surface. Extend/reuse `AppQ*` controls instead of
styling raw Qt controls at call sites.
- Construction-time source text should be retained by the control for retranslation. Do not duplicate manual
`retranslate()` code where an `AppQ*` widget/action/menu already supports it.
- `AppQMessageBox.windowTitle()` is native window-manager/accessibility metadata and is not rendered by the frameless
Fluent surface. Put an optional visible semantic title in `heading`, the primary explanation in `text`, and supporting
details in `informativeText`. Never rely on `setWindowTitle()` alone for user-visible information, and do not promote a
generic application-name title into a visible heading.
- Preserve public exports and wildcard-import compatibility carefully; keep optional/heavy facilities lazily imported
where practical.
- `AppStyleSheet` remains the sole public stylesheet authority. Internal `StyleSheets` modules are data-oriented QSS
fragments that consume the centralized semantic palette; application consumers must not import fragments directly.
- For composite controls, let the outer widget own its frame, radius, and focus indication. Sub-control hover/pressed
fills must remain inside that frame (normally through padding/content origin), and state-order changes should be
verified in both application themes.
- Extend/reuse `Furious.Qt` `AppQ*` controls for Fluent styling, themes, translation, dialogs, menus/actions, inputs,
tables/lists, and top-level windows. Do not create call-site styling or parallel lifetime registries.
- Pass source text at construction when a control retains it for retranslation. Preserve focus, keyboard, shortcuts,
accessibility, translated-text growth, high-DPI, responsive layout, and both themes.
- `AppQMessageBox.windowTitle` is native metadata. Visible hierarchy is `heading`, `text`, then `informativeText`.
`AppQMessageBox.open()` bypasses `AppQDialog.open()`, so its separate message-box registry is its actual async owner;
keep that registry and the base destroyed cleanup balanced.
- `AppStyleSheet` is the public style authority. `StyleSheets` contains internal QSS fragments that consume the semantic
palette; application code does not import fragments directly.
## Ownership and destruction
## Lifetime and threading
- `AppQDialog` owns platform-neutral presentation geometry: simple subclasses declare `DEFAULT_DIALOG_SIZE` or
`FIXED_DIALOG_SIZE`; procedural `prepareInitialGeometry()` runs only at first presentation after subclass construction.
Do not call subclass geometry hooks from constructors or manipulate private first-show state. Dialogs retain the
established center-on-each-presentation behavior unless a specialized class explicitly owns another centering policy.
- `AppQDialog` provides an asynchronous open-dialog registry. `AppQMessageBox` has an additional registry cleanup layer;
it is redundant but harmless. Keep both registries balanced if either implementation changes.
- `AppQTransientDialog` is delete-on-close. Connect completion before `open()` and never access it after destruction.
Reusable dialogs/windows instead need a durable owner and must not be delete-on-close.
- In Nuitka/PySide6 builds, compiled bound methods passed directly to `SignalInstance.connect()` may be protected for the
process lifetime. Connect signals to transient receivers with `connectWeakly(signal, receiver, 'methodName')`; do not
replace this with a closure that strongly captures the receiver.
- `AppQAction.callback` is a deliberate strong reference; owner scope must be no longer than the callback receiver.
Application-lifetime actions cannot capture transient bound methods.
- Parent child widgets/models/delegates/timers where their lifetimes match. Explicitly stop/disconnect timers, remove
mismatched event filters, abort/delete network replies, and clear references when wrappers can outlive C++ objects.
- Do not cache QObject instances or instance-bound methods in global/unbounded caches. Weak callbacks must not close
over their target.
- Application-lifetime Qt objects have one durable owner; reusable windows retain one deliberate owner; transient
dialogs use `AppQTransientDialog`/`AppQMessageBox` and remain retained through deferred native destruction.
- Direct compiled bound-method signal connections can retain transient receivers in Nuitka builds. Use
`connectWeakly(signal, receiver, 'methodName', sender=...)` when the sender is independent/longer-lived; never replace
it with a closure or partial capturing the receiver.
- `AppQAction.callback` is a strong reference. Its owner cannot outlive the callback receiver. Parent or explicitly
dispose timers, models, delegates, replies, event filters, menus, animations, and effects.
- Only the GUI thread mutates widgets. Slots do not sleep or perform unbounded host/network/process work. Coalescing
timers are created once and do not multiply across show/hide cycles.
- Each `QNetworkReply` has one manager/context owner, one terminal cleanup, and a freshness rule when superseded.
Schedule deletion on every terminal path; do not attach ad-hoc application attributes to third-party Qt objects.
## Threading and event loops
## Presentation and verification
- Only the GUI thread mutates widgets. Workers return immutable/result data through queued signals or established
managers.
- Never sleep or perform unbounded host/network/process work in a slot. Timers and debounce/coalescing must have one
owner and must not multiply across show/hide cycles.
- `exec()` is reserved for genuinely synchronous control flow; prefer managed `open()` when continuation logic can live
in `finished` callbacks.
## Network replies and presentation
- Give each reply one manager/context owner. Where a newer request supersedes an older one, use a generation/version or
equivalent identity check; independent requests do not need a synthetic generation. Handle success/error/abort once
and call `deleteLater()` on every terminal path. Do not attach ad-hoc application attributes to third-party Qt
objects when a manager mapping suffices.
- Preserve keyboard, focus, shortcuts, accessibility, light/dark theme, layout responsiveness, and translated-text
growth.
## Verification
- Run focused UI behavior and lifetime tests. Repeated open/close/show/refresh cycles must stabilize object, signal,
timer, reply, handle, and callback counts.
- Top-level subclasses use the canonical first-show geometry hooks and centering/retention behavior; do not call
overridable geometry hooks from constructors or alter private first-show state.
- Use `exec()` only when synchronous control flow is required; otherwise connect completion before managed `open()`.
- Run focused behavior tests plus repeated native lifecycle cycles. For compiled-signal or transient-dialog changes,
also run the Nuitka probe and verify destroyed signals, weak references, registries, callbacks, timers, replies, and
native resources return to baseline.
+11 -22
View File
@@ -1,24 +1,13 @@
# Repository guidance
- Repositories are the persistence authority for domain collections and documents such as profiles, subscriptions,
routing configurations, and TUN settings. Application preferences and current selections may use `AppSettings` at
their owning controller/application boundary. Keep Qt/UI/workflow concerns out of repository implementations.
- Preserve stable IDs, ordering, unknown compatible fields, legacy migrations, and subscription ownership. Display names
and row indexes are not identities.
- Subscription synchronization may update only profiles explicitly managed by that subscription and identified by stable
profile keys. Never delete user-created or another subscription's data.
- Public collection access currently returns live mutable application-owned collections for compatibility. Document this
honestly and avoid introducing a second cached copy; new mutations should flow through named repository operations
when practical.
- Load failures retain recoverable data where possible and log actionable context. Never silently replace malformed
explicit configuration with unrelated defaults when doing so loses user intent.
- If persisted data cannot be decoded, automatic shutdown cleanup must not overwrite the unreadable value with an empty
fallback. A deliberate non-empty repository mutation or explicit sync may replace it as a recovery action.
- Prepare fallible reconciliation work—identity calculation, metadata normalization, and the final collection—before
mutating live repository objects. The commit phase should contain only deterministic assignments.
- Singleton repository caches are application-lifetime finite objects and must be reset/sandboxed in tests.
## Verification
- Test old/current schemas, unknown fields, ordering, stable identity, subscription isolation, malformed persisted data,
round trips, and isolated temporary settings.
- Repositories persist profiles, subscriptions, routings, and TUN settings. They do not own Qt presentation, network
workflows, or controller state; application preferences remain in `AppSettings` at their owning boundary.
- Preserve stable IDs, ordering, unknown fields, legacy schemas, and subscription ownership. Display names and row
indexes are not domain identity.
- Public access currently returns application-owned live mutable collections for compatibility. Do not create a second
cached copy; prefer named repository mutations for new behavior when practical.
- Decode failures remain observable and must not be overwritten with an empty fallback during automatic shutdown.
Prepare fallible transformations before committing deterministic assignments to live collections.
- Subscription reconciliation can change only profiles explicitly owned by that subscription and stable profile key.
- Verify old/current/unknown-field round trips, ordering/identity, subscription isolation, malformed persisted bytes,
failed pre-commit transforms, and temporary-settings isolation.
+27 -55
View File
@@ -1,62 +1,34 @@
# Service guidance
- Services own workflows and temporary resources; controllers own shared application state. Services may use Qt for
asynchronous I/O/signals but must not own pages or encode presentation policy.
- Never instantiate `QObject` services such as network-access managers at module import time. Acquire them after the
application exists, give them one explicit service/application owner, and release them during that owner's cleanup.
- Inject repositories, runtimes, clients, clocks, and callbacks where practical. One service owns each worker, reply,
timer, executor, process, and cache; cleanup is bounded and idempotent.
## Scope and ownership
## Connection and configuration
- Services own workflows and temporary resources; controllers own shared application state. A service may use Qt for
async I/O/signals but does not own pages or presentation policy.
- Construct QObject services only after the application exists. Give each worker, reply, timer, executor, runtime,
process, and cache one explicit service/application owner with bounded, idempotent cleanup.
- Inject repositories, providers, clients, clocks, and runtime factories where practical. Services stage results and
commit through the owning repository/controller rather than creating a parallel state cache.
- `ConnectionManager` coordinates plugin capabilities, core runtimes, proxy/TUN/routing, and rollback. Its `runtimes`
collection contains semantic runtime owners, not necessarily operating-system processes. It consumes runtime copies
and never mutates persisted profiles.
- Preserve the backend native-TUN decision reported by capabilities. Start application tun2socks only when no native TUN
is handled; proxy-only operations strip TUN explicitly.
- Connection startup is one private staged attempt over a runtime configuration copy. Track each exact runtime as soon
as it is acquired; failures roll back only that attempt in reverse order, and commit transfers ownership to the
manager's normal lifecycle.
- Startup either reaches a valid running state or rolls back every process/host mutation with an actionable error.
Shutdown and unexpected-exit paths are bounded and safe to repeat.
## Runtime and asynchronous work
## Async, network, and background work
- `ConnectionManager` consumes runtime copies, coordinates capabilities and exact runtime ownership, and rolls back
only resources acquired by the failed staged attempt. It respects the backend TUN decision before any tun2socks
fallback.
- Every async workflow defines supersession: generation/version checks when stale completion is possible, or exact
object ownership when requests are independent. Terminal paths remove context, abort/finish once, and schedule Qt
replies for deletion once.
- Bound network/DNS/process/worker operations where their API permits it. Callbacks must not retain a shut-down manager;
GUI effects cross through signals.
- Collection continues independently of page visibility. Log/process transports and metric history remain bounded;
hidden pages may defer rendering but never draining. Raw metric samples remain immutable and display buckets derived.
- `SubscriptionManager` owns download, decode/filter/reconcile/persist, stale-request rejection, and stable-ID schedules.
Reconciliation commits current data before optional reconnect effects; one subscription failure does not cancel other
current results. Reapplying unchanged schedule policy does not restart timers or duplicate connections.
- Endpoint inspection enforces the active proxy, rejects stale connection results, uses neutral request metadata, and
discloses actual providers without profile secrets.
- Each asynchronous workflow defines one explicit ownership and supersession policy: a generation/version where stale
completion is possible, or exact reply/future ownership where requests are independent. Partial results may publish
independently; terminal reply paths abort or finish once and schedule deletion once.
- Bound network, DNS, process, host, and worker work where the provider permits it. Executor callbacks use weak or
otherwise bounded ownership and must not retain a manager forever after shutdown; GUI updates cross via signals.
- Log storage has independent count, total-character, and per-entry hard bounds even when automatic clearing is
disabled. High-rate producers must coalesce GUI notifications; hiding a page may defer rendering but must never defer
draining a process pipe or bounded transport queue.
- Metric history has both a time horizon and a defensive sample-count ceiling. Derive graph buckets on demand rather
than retaining a second ever-growing history.
- Every network reply has a finite transfer timeout unless a documented caller supplies a stricter one. Track replies
by exact object identity, remove every context on the terminal path, and schedule the reply for deletion exactly once.
- Per-subscription versions exist only while the persisted subscription, its timer, or an active reply needs them;
repeated create/delete cycles must not grow bookkeeping dictionaries.
- Logging and metrics collect while pages are hidden but avoid hidden-page rendering. Raw time-series samples are
immutable; stable timestamp buckets are derived display data.
- Log retention is owned by `LogManager`, not a text document. Category pruning/reset notifications must keep lazy UI
sequence state valid.
- Endpoint inspection must enforce the active proxy path, reject stale connection results, use neutral request metadata,
and disclose actual external providers without sending profile secrets.
- `SubscriptionManager` owns download, decoding, filtering, reconciliation, persistence effects, stale-request
rejection, and stable-ID auto-update timers. Subscription views invoke commands and render semantic results; they do
not own this workflow. Remote data is untrusted.
- Long-lived service timers are created and connected once, then reconciled idempotently. Reapplying unchanged policy
must not restart a periodic countdown, reconnect its timeout, or emit a lifecycle transition log.
- Page visibility and presentation refreshes must not control application-level background scheduler lifecycles.
Reconcile schedules at service startup and when the corresponding persisted scheduling policy changes.
- Subscription reply callbacks stage decoded results only. Persist group status and reconcile profiles after the final
request-version check; one group's failure must not abort other current groups in the same completion batch.
- Treat reconnect/disconnect after subscription reconciliation as a post-commit effect. Failure there must be logged
without reporting the already-committed repository update as rolled back.
## Verification
## Code review rules
- Flag widgets retained by services, unbounded executor/network/process work, callbacks that capture service owners
after shutdown, UI-only cache invalidation, and swallowed provider failures.
- Test success, partial/stale/timeout/cancel/failure, hidden-page behavior, repeated cleanup, and worker/reply lifetime
with fakes—never real host mutation.
- Test success plus partial, stale, timeout, cancel, failure, hidden-page, and repeated-cleanup paths with fake
providers/host operations. Review retained callbacks, widgets owned by services, unbounded transports, and
persistence performed before final freshness checks.
+7 -14
View File
@@ -1,16 +1,9 @@
# Utility process guidance
- This package contains the outer application-process wrapper, not general miscellaneous helpers. Keep its surface small
and avoid adding business/UI policy that belongs in application, controller, or service layers.
- `AppMainProcess` owns its exact child/application runner, signal handlers, exit-code translation, and crash-log
attempt. Signal/exception paths must tolerate partial application startup.
- Crash reporting is best effort but must not hide the original exception; avoid logging secrets from application
history and bound all cleanup before exit.
- Do not introduce process-name cleanup or unmanaged `multiprocessing.Manager`/thread resources. Explicitly shut down
auxiliary process resources when no longer needed.
- Preserve semantic `ApplicationRunner.ExitCode` values and cross-platform spawn behavior.
## Verification
- Test normal exit, assertion/unknown exception mapping, crash-log write failure, signals before/after app creation, and
no orphaned manager/child resources.
- This package owns the outer application-process wrapper and exit-code translation; it is not a miscellaneous helper
namespace and does not own application business/UI policy.
- `AppMainProcess` owns its exact child/runner, signal handlers, auxiliary manager resources, and best-effort crash-log
attempt. Signal and exception paths tolerate partial startup, preserve the original failure, avoid sensitive logs, and
perform bounded exact-resource cleanup.
- Verify normal/exception/signal exits, crash-log failure, pre/post application signals, cross-platform spawn, and no
orphaned child or manager resources.
+12 -23
View File
@@ -1,25 +1,14 @@
# Reusable widget guidance
- Widgets are reusable presentation components below full pages/windows. Prefer controller/service/repository
dependencies passed explicitly; do not add new `AppMainWindow()` reach-through or duplicate application state.
- Subscription views delegate download, decoding, reconciliation, persistence effects, and recurring scheduling to
`SubscriptionManager`; keep view code limited to user intent, presentation, and repository-backed table refresh.
- Models, delegates, menus, actions, spinners, animations, timers, and network-backed helpers need explicit owners.
Persistent page widgets are constructed once; refresh/show cycles toggle state rather than recreate/connect
indefinitely.
- Resolve table/list actions through the model mapping that is current after sort/filter. Use stable repository IDs for
persisted or cross-refresh identity; preserve existing row-index compatibility only where the repository contract
still requires it. Keep model begin/end notifications and repository ordering synchronized.
- Use `AppQ*` controls and responsive layouts. Preserve translated-text growth, shortcuts, focus, accessibility, theme
changes, high-DPI rendering, and hidden-page lazy behavior.
- Expensive parsing, map/network work, metrics aggregation, and subscription synchronization must not freeze the GUI.
Batch, defer, or offload work according to the Qt API involved; publish worker results through owned signals and
reject stale completions where requests can be superseded.
- Transient editors/progress dialogs use managed `open()` lifetime; reusable top-level windows retain one explicit
owner.
## Code review rules
- Flag direct global/window reach-through, duplicated persistence logic, row-index identity, repeated signal/timer
creation, hidden-page rendering, and callbacks retaining closed widgets.
- Run relevant behavior plus Qt lifetime tests for repeated refresh/show/open/close cycles.
- Widgets are presentation components below pages/windows. Prefer explicit controller/service/repository dependencies;
do not add new `AppMainWindow()` reach-through or duplicate application state.
- Subscription views issue commands to `SubscriptionManager` and render repository-backed results; they do not own
download, decode, reconciliation, persistence, or scheduling workflows.
- Models, delegates, menus, actions, spinners, animations, timers, and network helpers have explicit owners. Persistent
widgets are constructed and connected once; refresh/show toggles state rather than accumulating objects.
- Resolve actions through the current model/proxy mapping after sort/filter. Use stable IDs across persistence/refresh
and keep model begin/end notifications synchronized with live repository ordering.
- Reuse `AppQ*` controls and keep expensive parsing, aggregation, mapping, and synchronization off blocking GUI paths.
Reject stale completions where work can be superseded.
- Verify behavior and repeated refresh/show/open/close lifetime, including sorted/filtered model actions and hidden-page
rendering.
+15 -31
View File
@@ -1,33 +1,17 @@
# Window and page guidance
- `MainWindow` owns long-lived built-in pages, navigation, page-level managers, and compatibility aliases. Pages remain
stable for the window lifetime; plugin pages are owned by navigation plus `PluginNavigationManager`.
- Main-window page selection and navigation expansion are session-local. Register Home first as the canonical initial
page and keep new navigation views collapsed; never persist or restore either state from a previous process.
- Pages and dialogs adapt controllers/services/repositories to user interaction. Do not create a competing state
authority; prefer controller/service APIs over new cross-page reach-through.
- Long-lived pages create controls, timers, and signal connections once. Show/hide activates lazy rendering or refresh
intent without multiplying objects or background work.
- One-shot editors/prompts inherit `AppQTransientDialog`/`AppQMessageBox`, connect `finished` before `open()`, and are
not retained afterward. Reusable `TextEditorWindow` keeps an explicit owner, survives normal close/show, and is
destroyed only by that owner.
- Close buttons route through the normal `close()`/`closeEvent` path so save/discard/cancel and cleanup remain
canonical. Base event handlers are called unless intentionally documented.
- Keep file/network/core work bounded and off the GUI thread when it can block. Present structured failures without
swallowing them or exposing secrets.
- Use Fluent `AppQ*` controls, normal layouts, translation-aware construction, and theme callbacks. Preserve shortcuts,
default/escape actions, focus, resizing, navigation overlay behavior, and light/dark presentation.
- `AppQMainWindow` owns platform-neutral first-show preparation, centering, and shown-wrapper retention. Subclasses
declare stable defaults through `DEFAULT_WINDOW_SIZE`; persistent windows override `prepareInitialGeometry()` only
for product-owned restoration/migration and call `restoreInitialGeometry()` so a valid saved position suppresses
centering. Never call overridable geometry hooks during construction or manipulate private first-show flags.
- Restore composed top-level window geometry only after its persistent child layout exists. Treat Qt's explicit
`restoreGeometry()` result as authoritative; missing or invalid data uses the canonical application default and must
not be inferred from magic window dimensions. Repeated show/hide cycles preserve the live window geometry.
## Code review rules
- Flag local top-level windows shown without a durable owner, transient dialogs cached by pages/controllers, repeated
show-time connections, direct worker-thread UI mutation, and manual translation already handled by controls.
- Test navigation/page restoration, async dialog continuations, unsaved-close flow, hidden-page behavior, and repeated
lifecycle stability.
- `MainWindow` owns built-in pages/navigation and their persistent Qt tree. Plugin pages are registered through
`PluginNavigationManager`; pages adapt owning controllers/services/repositories rather than becoming state authorities.
- Home is the initial page. Page selection and navigation expansion are session-local, with navigation initially
collapsed; do not persist either state.
- Long-lived pages create controls, timers, services, and connections once. Visibility controls lazy presentation, not
application-level collection or scheduler lifecycles.
- One-shot editors/prompts use managed transient dialogs. `TextEditorWindow` is reusable and needs a deliberate durable
owner. Close buttons call the normal `close()`/`closeEvent` path so confirmation and cleanup remain canonical.
- Use normal layouts and `AppQ*` controls. Blocking file/network/core work leaves the GUI thread, and worker results
return through owned signals without exposing secrets.
- Top-level windows use `AppQMainWindow` first-show preparation and `DEFAULT_WINDOW_SIZE`. Restore saved geometry only
after the persistent child layout exists; a successful `restoreGeometry()` is authoritative, otherwise use the
canonical default/centering path.
- Verify navigation defaults, page lazy behavior, async continuations, unsaved-close flow, geometry restore, and
repeated open/show/hide/destroy stability.
+7 -15
View File
@@ -1,16 +1,8 @@
# Icon asset guidance
# Icon source guidance
- This tree contains source assets consumed by the Qt resource workflow. Reuse an existing semantic icon before adding a
near-duplicate.
- Keep SVGs vector, compact, theme-compatible, and free of embedded raster data, scripts, remote resources, editor
metadata, or hard-coded backgrounds.
- Follow the established monochrome/current-color convention for UI icons; add explicit light/dark variants only when
the design cannot be expressed through tinting.
- Preserve upstream license/attribution requirements and stable resource paths. Renaming/removing an asset requires
updating all references and regenerating `Furious/Frozenlib/AppResources.py` through the existing workflow.
- Do not hand-edit the generated resource module.
## Verification
- Search all references, regenerate resources, test light/dark and high-DPI rendering, and visually check icon
size/alignment in the target Fluent control.
- Reuse an existing semantic icon before adding another. Source SVGs remain compact vectors without scripts, remote
resources, embedded rasters, editor metadata, or hard-coded backgrounds.
- Follow the established monochrome/tint convention; add theme variants only when tinting cannot express the design.
- Preserve resource paths and licensing. A rename/add/remove updates every consumer and regenerates
`Furious/Frozenlib/AppResources.py`; never hand-edit that generated module.
- Verify both themes, high-DPI rendering, and icon size/alignment in the actual Fluent control.
+29 -48
View File
@@ -2,57 +2,38 @@
## Isolation
- Tests must never affect a running Furious instance or production data. Set `QT_QPA_PLATFORM=offscreen` before Qt
import and use `tests/support.py` for one small application and temporary INI-backed settings.
- Never mutate real proxy, DNS, routing, TUN, startup registration, tray, network interfaces, or external services.
Patch host/network APIs and inject fake controllers, runtimes, repositories, clocks, and clients.
- Own exact subprocess handles/PIDs and threads. Use bounded waits and clean up only resources the test created; never
discover or terminate by process name.
- Normal tests require no external network or installed proxy core. Keep packaged/manual smoke procedures explicit and
disposable.
- Tests never affect a running Furious instance or production data. Set `QT_QPA_PLATFORM=offscreen` before Qt import,
use `tests/support.py` for one small application and temporary INI-backed settings, and mock every external service.
- Never mutate real proxy, DNS, routing, TUN, startup registration, tray, interfaces, or host processes. Own exact test
threads/children/handles, give waits bounded timeouts, and clean up only resources created by the test.
- Normal tests need no network or installed proxy core. Keep packaged/manual smoke procedures explicit and disposable.
## Test design
## Test contracts
- Test public behavior and architectural contracts, not implementation trivia. Cover success, validation failure,
timeout, cancel, partial/stale result, cleanup, and backward-compatible persisted input.
- For multi-step mutations, inject a failure immediately before the commit point and assert that live objects and
persisted bytes remain unchanged; separately test post-commit side-effect failures without pretending rollback.
- Use canonical `CoreRuntime` vocabulary in runtime factories, registry fixtures, and traffic-statistics providers.
- Separate persisted configuration from runtime-copy assertions. Normal connection, generated native TUN, preserved user
TUN, and proxy-only stripping must not share a helper that erases their differences.
- Keep deterministic logic/UI behavior in the normal tier. Put repeated live-object, native-handle, thread, subprocess,
and RSS trends in explicit stress tiers.
- Scale cheap fake/model operations into the thousands where useful. Keep real Qt, WebEngine, and subprocess counts
proportional to their cost; workloads beyond the normal stress tier require the explicit
`FURIOUS_VERY_HEAVY_TESTS=1` opt-in.
- Warm up allocators before interpreting RSS, sample only at bounded batch boundaries, and prefer exact live-object,
registry, thread, handle, and child-process ownership assertions over arbitrary memory thresholds.
- Qt lifetime tests use real close/deferred-delete paths, weak references, destroyed signals, and live counts.
`gc.collect()` is diagnostic at batch boundaries only; threshold increases or production collection are not leak
fixes.
- Avoid timing-only assertions. Drive events deterministically and assert final ownership/state plus absence of
duplicate connections/timers/replies.
- Update `tests/README.md` when modules, tiers, or environment requirements change. Documentation commands use the
active `python` environment.
- Test public behavior and architecture contracts rather than private layout trivia. Cover success, invalid input,
timeout/cancel, partial/stale completion, cleanup, and compatible persisted input where applicable.
- For staged mutations, fail immediately before commit and assert live plus persisted state is unchanged. Test
post-commit side-effect failure separately without claiming rollback.
- Keep persisted configuration and runtime-copy assertions distinct. TUN preservation, generated TUN, fallback, and
proxy-only stripping must not share a helper that erases their differences.
- Deterministic logic/UI belongs in normal tiers. Put repeated object/handle/thread/process/RSS trends in stress tiers;
very heavy work requires `FURIOUS_VERY_HEAVY_TESTS=1`.
- Prefer exact ownership, destroyed-signal, registry, thread, handle, and child assertions over timing or arbitrary RSS
thresholds. `gc.collect()` is diagnostic at batch boundaries, never a production fix or per-cycle requirement.
## Real lifecycle and process-boundary regressions
## Real lifecycle boundaries
- Apply the highest-fidelity safe test boundary throughout the suite. Unit tests remain appropriate for deterministic
logic, but bugs involving Qt event loops, native callbacks, interpreter finalization, subprocess teardown, or wrapper
destruction need a focused integration test that exercises the real boundary that failed.
- For application-lifecycle regressions, start a clean child Python process, force `QT_QPA_PLATFORM=offscreen` before
importing Qt, construct the smallest real `QApplication`/`DesktopApplication` and real Qt window needed, run the real
event loop, request shutdown through the public path, and assert the child process exits normally. Repeat the cycle
when the defect is intermittent.
- A lifecycle child must be hermetic: bypass single-instance discovery and IPC, use temporary settings and application
identity where relevant, disable tray and startup restoration, mock all host/network integrations, and never attach
to, signal, inspect, or change a potentially running Furious instance.
- Child-process tests own only the windows, threads, handles, and processes they create. Give every wait and child a
bounded timeout, capture diagnostics, and terminate only that exact child on timeout. Never use process-name cleanup.
- Visible diagnostic windows are for an explicit manual smoke procedure only. Automated tests always use the offscreen
platform so they cannot flash, steal focus, or interact with the desktop session.
- Bugs involving Qt event loops, native callbacks, interpreter finalization, subprocess teardown, or wrapper destruction
require a focused integration test using the smallest real boundary that failed.
- Run lifecycle regressions in a clean child process with offscreen Qt, temporary identity/settings, singleton IPC and
tray/restoration disabled, and all host/network integration mocked. Exercise the public shutdown path and repeat when
intermittent.
- Automated tests cannot flash, focus, inspect, signal, or modify the desktop or a potentially running Furious process.
Visible windows are limited to explicit manual smoke procedures.
- Update `tests/README.md` when modules, tiers, commands, or environment requirements change; examples use active
`python`.
## Code review rules
## Review
- Flag production settings/host mutation, broad process cleanup, unbounded waits, external network dependence, shared
mutable fixtures, and tests that only check storage when runtime output is the contract.
- Flag production state/host mutation, external network dependence, process-name cleanup, unbounded waits, shared
mutable fixtures, and tests that assert storage when runtime output is the actual contract.