mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-10-28 09:12:08 +08:00
d60ded5b3fbe9e8a7ba8f69c351f8007abede371
173
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ef179fcf61 |
fix(home): manual update check not working when auto_check_update disabled (#7585)
* fix(config): prevent cleanup_orphaned_files from deleting active profiles When IProfiles::new() fails to parse profiles.yaml (e.g., corrupt file), it returns Self::default() with items: None. The subsequent cleanup_orphaned_files() call then sees no active files and deletes all profile files in the profiles/ directory except the two hardcoded protected files (Merge.yaml and Script.js). Guard cleanup_orphaned_files() against this by checking if items is None or empty before proceeding. When items is unavailable, skip the cleanup and log a warning instead of blindly deleting user data. Fixes #7577 * fix(home): manual update check not working when auto_check_update disabled When auto_check_update is false, useUpdate() sets enabled=false for the SWR query, which sets the SWR key to null. Calling swr.mutate() on a null key does not execute the queryFn, returning undefined instead. This caused the home page's "last check time" click to instantly show "already up to date" without actually checking for updates. Fix: use revalidateQuery() for the manual checkUpdate function, which uses the global SWR mutate with the actual query key, bypassing the enabled: false state and always triggering a real network request. Fixes #7584 * fix: preserve manual update and profile data --------- Co-authored-by: Evan Gong <evan@users.noreply.github.com> Co-authored-by: wonfen <96291150+wonfen@users.noreply.github.com> Co-authored-by: wonfen <eiwonfen@gmail.com> |
||
|
|
4b3bc54b2d |
fix(ws): assign websocket after onConnected succeeds (#7639)
* feat(layout): add logging of MihomoWebSocket instances in dev mode * refactor(hooks): defer ws assignment in use-mihomo-ws-subscription * chore: update tauri-plugin-mihomo-api dependency source to use github protocol * docs: update changelog with websocket connection fix |
||
|
|
3d8d7731e1 |
fix: node selection persistence (#7620)
* fix(core): put the user's node choices back when the core starts mihomo restores the node picked in each group from the `cache.db` it keeps in the directory it was started against. The app relied on that entirely, and it does not hold: `profile.store-selected` comes from a merge template a user is free to replace, and a service older than the durable runtime generation starts every core in a directory nothing has ever run in. Both end the same way — every `select` group falls back to the first entry in its `proxies:` list. The app already knows better. It records each selection in the profile, and `activate_selected_nodes` already reconciles that record against the running core — it was only ever called when switching profiles, never when a core came up. Call it from the three places a core process becomes ready. Repeat calls supersede each other, so no coordination is needed between the start paths. That makes the profile authoritative, which turns any selection it does not know about into one the next start silently undoes. The chain proxy moved a group without recording it, so recording is now one shared hook that both it and the proxy list go through. Restoration is spawned, not awaited: it polls the core until its groups are readable, and a start must not wait for that. * fix(tray): record the node the tray switched to Restoring the profile's selections when a core starts made the profile authoritative, which turns any selection it never learned about into one the next start silently undoes. The proxy list and the chain proxy both record; the tray did not. So switching node from the tray survived only until the next restart, and then reverted to whatever the profile last knew — a regression the restoration itself introduced, and only for the tray. `record_selected_node` is the backend counterpart of the frontend's `useRecordSelection`. Failing to record does not fail the switch: the core is already on the new node by then, and reporting the switch as failed would be wrong. It is logged instead. * fix(core): restoring selections must not prune what the core has not loaded Putting the profile's selections back when a core starts reused the reconciliation written for profile switches, and that one also *repairs*: a record whose group is absent from two snapshots a second apart is dropped, and the reduced list is written to profiles.yaml. After a profile switch the configuration is loaded, so absence is evidence. After a core start it is not — a provider-backed group can be empty or missing for seconds — and a record pruned on that evidence is gone for good, because the provider finishing later cannot bring it back. Restoring now keeps the records and only applies what it can match; pruning stays with the caller entitled to conclude it. The restore is also asynchronous: it reads the profile, polls the core until its groups are readable, then puts its selections. A choice the user makes inside that window is newer than what it captured, so letting it finish pushed the core back to the older node while the profile already held the newer one. Recording a selection now supersedes any activation in flight — from the tray, and from the UI and chain proxy through `patch_profile`. * fix(core): activate the selections of the profile that is actually running The activation worker read the profiles draft. A profile switch stages its target there before validating it, so a switch that fails — leaving the previous profile running — could still have handed an activation the rejected profile's selections, which it would then apply to whichever same-named groups the running profile has. Reading the committed state removes the window. The switch that succeeds commits before it activates, so nothing changes for that caller; what changes is that a restore running at core start can no longer observe a switch that never happened. * fix(core): stop a restoration from undoing a choice made while it runs Two gaps in making the profile authoritative, both found by adversarial review of the commits that introduced it. A restoration reads the profile's selections once, then polls the core until its groups can be read — seconds, on a cold start. A selection made inside that window is newer than what the restoration is carrying, and nothing told the restoration so: it went on to apply the older choice over the user's. The UI accepted the new node and traffic snapped back to the old one, with the profile and the running core then disagreeing. Recording a selection now abandons any restoration still in flight, through the same generation counter every other superseding caller uses. The other gap is the home card in global mode, which passed `skipConfigSave` and so recorded nothing. That was harmless while nothing re-applied the profile; now it means the next start undoes the choice. It was the only caller that ever set the flag, so the flag is gone rather than flipped — a parameter whose one use was a bug is not worth keeping. * fix(core): wait for the selections that can be put back, keep trying for the rest Two gaps in restoring the profile's selections when a core starts, both found by adversarial review. Restoring re-checked once, a second later, and stopped. That is enough after a profile switch, which happens with the configuration already loaded. A core start is the opposite case: a provider-backed group is present but empty until its provider finishes loading, and a `select` can fail while the core is still settling. Neither produces an activation, so the restore reported success having applied nothing and the group stayed on the first entry of its `proxies:` list — the outcome restoring exists to prevent. It now keeps looking, on a deadline, at what the core is actually on rather than at what it managed to send. Restoring was also spawned, and the caller enables the system proxy the moment the start returns — so traffic could be pointed at a core still on the wrong node. The first pass is now awaited, bounded, and the rest keeps settling in the background: a core that will not answer delays a start rather than blocking it, because being proxied through the wrong node beats not being proxied at all. TUN is deliberately not covered, and cannot be by anything here: it is configured in the core's own file, so it carries traffic from the moment the process starts. What puts a TUN user on the right node from the first packet is the core's own `cache.db`, which is why the service keeps an owner's runtime generation across restarts. `supersede_selected_activation` and `cancel_selected_node_activation` were the same function under two names, added independently for the same reason, and `patch_profile` called both. One survives, called once, after the profile is written — the only placement that needs to exist, since an activation that read the older profile is cancelled by it and one starting afterwards reads the newer one. * fix(core): record a selection by naming it, not by resending the list Recording a node selection sent the whole selection list, built from the profile the frontend had rendered. Two selections made before that list refreshed were therefore built from the same stale snapshot, and the later write dropped the earlier group's choice. Since a core start re-applies whatever the profile holds, the dropped one came back on the next restart — the profile and the running core disagreeing, in the direction the user would least expect. The tray already avoided this, because it went through `record_selected_node`, which merges one group into the profile inside `with_data_modify`. Exposing that as a command and pointing the frontend at it leaves one way to record a selection instead of two, and removes the list from the wire: what a caller knows is the group and the node, and that is now all it says. `useRecordSelection` no longer reads the profile at all, which also makes it stable across renders rather than changing identity whenever the profile does. * test(core): make every way of declining to stage answer for itself `CoreRestarted` was added to `StageRejection` upstream and reached production without a test. It inherited the right verdict — every `RestartRequired` is matched together — but nothing said so, and nothing would have noticed if it had inherited the wrong one. The list of rejections now goes through a wrapper whose `match` is exhaustive, so the next variant added upstream fails to compile here. That is the part worth having: it does not prove the list is complete, but it does mean nobody can add a way to decline without being sent to this test to say what it should lead to. * build(deps): pin the service protocol to v2.6.1 The pin tracked `feat/stage-runtime` because the protocol change it carried had no tag yet. It has one now, and a branch pin makes the build irreproducible: an upstream force-push silently changes what this compiles against. v2.6.1 carries the runtime generation an owner keeps across restarts, which is what lets the core hold on to the `cache.db` its node selections and fake-ip leases live in. The protocol itself is untouched — epoch, revision and both compatibility floors are byte-identical to v2.6.0 — so this is a fix release in both directions: an older service still works with this app, and this app still works with an older service, only without the durable generation. The lockfile change is the two lines that name it. Reaching for `cargo update -p` instead re-resolved a subtree and quietly downgraded `windows-sys`, `windows-core` and `getrandom` along the way. |
||
|
|
c90de1355e |
refactor: core runstate (#7602)
* refactor(core): introduce core::runstate as the owner of Run State
"Is the Service available?" had five caches and three predicates, and
prepare_startup consulted all three within thirty lines. This lands the
foundation for collapsing that, with no behaviour change: the old owners
now delegate into one store.
The state model splits observation from intent. Five of ServiceStatus's
ten variants were never observations — cmd/service.rs passed
ServiceStatus::InstallRequired as the "which operation do you want"
argument — which is why narrowing to ServiceInstallState had to be lossy.
ServiceHealth now records what we saw, PendingAction records what the
session asked for, and ServiceStatus::from_run_state flattens the pair
for call sites that still expect the single slot.
Reads differ only in freshness: state() is cached, settled() waits for an
in-flight privileged operation, probe() forces IPC. What an answer means
lives on the snapshot (service_usable, tun_capable), so callers stop
writing their own formulas — the tray now asks tun_capable() instead of
recomputing "admin or ready".
Effects sit behind RunStateEnv with two adapters, RealEnv and FakeEnv.
That retires the seven tests that hand-built a private ServiceManager
struct literal, and makes the probe and detection paths testable for the
first time.
Two distinctions are now explicit rather than incidental:
- A single failed probe is not an observation and leaves confirmed
health intact; exhausting a poll's whole retry budget is one and
records unavailable. Both are tested, protecting
|
||
|
|
389c5342e5 | fix(service): harden transient IPC handling | ||
|
|
e599a9d02c | fix(service): avoid sticky availability probes | ||
|
|
7796259b03 |
feat: automatic mixed port fallback (#7579)
* feat: automatically select an available mixed proxy port * fix: coordinate listener port changes * fix: detect Windows listener port conflicts |
||
|
|
bce83d5c6f | chore: update i18n | ||
|
|
23dda47641 |
feat: add launch guard and secure multi-user service ownership (#7569)
* feat(service): isolate macOS launch and owner runtime * ci(windows): run owner security tests * chore(deps): use kode-bridge v0.4.1 tag * fix(macos): classify installed service state * fix(macos): classify marked unavailable service * fix(macos): gate core startup on service migration * fix(macos): scope service init to supported platforms * feat(macos): prompt before migrating installed service * fix(macos): shorten sidecar IPC path * build(service): pin helper protocol and artifacts * fix(core): require confirmed service and sidecar readiness * fix(macos): harden launch roots and singleton state * fix(ui): refresh migration state before restart * fix(macos): linearize service migration lifecycle * fix(ui): keep service migration state fresh * feat(service): track authenticated owner sessions * fix(service): preserve newer owner sessions * feat(macos): route service proxy changes through helper * fix(macos): serialize service proxy guard * fix(macos): close service proxy owner lifecycle * fix(macos): close remaining proxy stop races * fix(app): do not cancel controlled service stop * fix(lifecycle): keep controlled updates cancellation-safe * fix(macos): stage dev core for service * test(macos): exercise staged core cleanup * fix(dev): await controlled macOS exit * chore: update changelog * fix(macos): recover sidecar after service install failure Allow the existing sidecar to continue when service installation leaves the manager in InstallRequired, then settle the session state as SidecarAllowed. Show neutral status text while the migration dialog waits for its final state. * fix(macos): stage dev service tools before elevation * fix(macos): localize launch guard prompts * fix(service): address cross-platform integration issues * feat: support seamless service migration * fix: pass Windows service installer path safely * fix(prebuild): derive service release from Cargo dependency * fix(dev): preserve service installation state * fix: resolve clippy warnings in service lifecycle |
||
|
|
17fe92352e | refactor(frontend): centralize tun availability guard | ||
|
|
304f5bade9 |
perf(proxy): consolidate proxy data into a single IPC (#7542)
* feat(proxy): build deterministic proxy view * feat(proxy): expose ordered proxy view command * feat(proxy): add proxy view client contract * fix(lint): allow Node test files * feat(proxy): switch UI to ordered proxy view * fix(proxy): harden ordered proxy view consumers * fix(proxy): compare chain duplicates by current id * fix(proxy): release mihomo guard after fetch * fix(proxy): preserve semantic selections across refresh * fix(proxy): block global chain without GLOBAL * fix(proxy): handle proxy view edge states |
||
|
|
d144e78aeb | perf: streamline profile switching | ||
|
|
7ca20fc4ed |
refactor: knip cleanup (#7468)
* chore(frontend): expand Knip reports * chore: remove unused Mihomo changelog sync script * refactor: remove unused value exports * refactor: remove unused type exports * refactor: remove circular route dependencies * chore(frontend): move Knip scope to config * chore(i18n): remove stale portable update message |
||
|
|
14bc636ee8 | refactor(frontend): remove unused modules | ||
|
|
562828e14d |
fix: provider proxy delay check (#7456)
* refactor(proxy): support delay testing for provider-based proxies * style: remove debug console logs * docs: update changelog * feat(services): merge provider nodes into records in calcuProxies --------- Co-authored-by: Tunglies <tunglies.dev@outlook.com> |
||
|
|
1d7462a021 | fix: stop idle mihomo websocket subscriptions | ||
|
|
c2ddb5c111 |
refactor: activate selected proxies (#7285)
* feat(profiles): add automatic node activation after profile switch * refactor(use-profiles): simplify patchCurrent logic by always mutating profiles * refactor(use-profiles): remove unused activateSelected function and related imports * refactor: rename worker handle to activate selected task * fix: clippy * refactor(config): replace log statements with structured logging * refactor(config): fix incorrect retry logic in profile activation * feat(profiles): update proxy cache when switching profiles * docs: update changelog with bug fixes and logic improvements * refactor(profiles): remove unused imports and redundant cache update |
||
|
|
fa712b59c9 | fix: reuse preloaded verge config | ||
|
|
520746428c | fix: dedupe initial websocket subscriptions | ||
|
|
b9da0875cd | refactor: optimize swr data refresh paths | ||
|
|
e71e701fd0 | refactor: migrate query cache to swr | ||
|
|
5a96fdd9a2 |
fix: harden home clash-mode card against /configs read failures
Recover from controller read/switch failures on the mode card instead of showing a bare "communication error": - fall back to the tolerant get_clash_mode command (then runtime config) when the live /configs mode is unavailable, - optimistically reflect the clicked mode and write it through to the getClashConfig cache so a failed refetch can't flash back the old value, - surface patch failures via showNotice on both the home card and the proxies page (patchClashMode can now reject), and - revalidate getClashMode/getRuntimeConfig on refresh-clash-config so a once-empty fallback can recover. |
||
|
|
fd8c852698 | fix: update mihomo plugin log handling | ||
|
|
1e57f41190 | fix: use mihomo traffic totals | ||
|
|
e1380b9bab | fix: frontend build after bump version for tauri-plugin-mihomo | ||
|
|
45afb1c15a |
fix(frontend): reduce long-running memory pressure in connections (#7227)
* fix(connections): stabilize table memory usage Render the connections table and list from stable row view models, and replace the TanStack row/cell instantiation path with a lightweight virtualized table body. The leak was isolated to the frontend table render path: websocket data without the table stayed flat, while the normal table path kept producing large per-snapshot object graphs under active/closed churn. Introduced-by: |
||
|
|
2a397d7ef6 | fix(settings): load network interfaces in allow LAN dialog | ||
|
|
c18c23963d |
refactor: frontend (#7097)
* refactor: remove unused window show/hide event listeners * refactor: deduplicate profile-changed event handling * refactor: remove unused setupCloseListener from use-listen * refactor: remove unused removeAllListeners from use-listen * fix(backend): dedupe profile changed notification on import * fix: avoid duplicate timer update completed event * refactor(frontend): simplify proxy switch fallback path |
||
|
|
6e0647f7b2 | perf: narrow app data subscriptions | ||
|
|
5b738a9d7a | fix(logs): preserve logs when pausing (#6902) | ||
|
|
b85b435e00 |
refactor: structure config validation outcomes
- Replace boolean/string validation results with structured ValidationOutcome values across config validation, profile switching, DNS validation, and script validation. Callers can now distinguish valid, invalid, skipped, and busy states, with invalid results carrying an error kind and message. - Route core config updates through structured outcomes and checked forced updates. Generation and validation failures now discard runtime drafts and surface validation failures instead of relying on empty success messages. - Preserve existing frontend ergonomics by keeping boolean service wrappers where callers only need success, while direct DNS and script validation consumers receive ValidationOutcome. Failed profile editor saves reload the backend-restored file content and synchronize Monaco models so reopened editors show the restored state. |
||
|
|
549f21961c | perf: reduce frontend subscription and raf churn | ||
|
|
d9ae7578ce | perf: reduce traffic update allocations | ||
|
|
e0b9f8a48b | perf(frontend): optimize traffic graph rendering | ||
|
|
4d408a8c1a | fix: serialize OS state reads with in-progress update_sysproxy | ||
|
|
d203121abe |
perf(ui): reduce CPU and memory pressure on hot frontend paths
WebSocket hooks (use-connection-data, use-log-data) - Single-pass merge for connection snapshots; reuse previous row reference when upload/download are unchanged so downstream memos can bail out - Single-allocation cap on closedConnections (no double concat+slice) - Single-allocation clamp in appendLogs - Cache dayjs().format result within each 50ms log flush window Connection table (connection-table.tsx) - Decouple 5s relative-time tick from columnDefs via shared module-level tickStore + memoised RelativeTimeCell so the tick no longer rebuilds the table - RowComponent memo with custom comparator (relies on stable row reference) - Lift sx objects to module scope - Stable onShowDetail via ref App data provider (app-data-provider, app-data-context) - Split monolithic context into six sub-contexts (Proxies/Rules/ClashConfig/ System/Uptime/Refreshers); 3s uptime ticks no longer invalidate unrelated consumers - useAppData adapter aggregates sub-contexts; consumer files unchanged - useStableFn helper stabilises all refresh* callback identities - Extract useCtx helper to remove repeated null-check boilerplate Canvas traffic graph (enhanced-canvas-traffic-graph, use-traffic-monitor) - Lift Y-axis scale (max/min) to drawGraph layer; calculateY allocations per frame: ~600 -> 1 - Document visibility gate stops RAF and the setNow interval when the window is hidden; appendData unaffected - Reuse the existing visibilitychange listener instead of adding a second one; isDocumentVisibleRef guards animate() inside the frame - Convert forEach arrow shorthands to block bodies - Drop unnecessary state-setter deps from a useCallback Proxy groups (proxy-groups, proxy-render, use-render-list) - Narrow proxyColItemsMemo dep: headState -> headState.showType - Per-group fingerprint cache + prevListRef in use-render-list; steady-state refetch ticks become O(0) and the list reference stays stable when nothing changed - useStableCallback wraps handleCheckAll / handleLocation |
||
|
|
b1eb011050 |
refactor(ui): extract useProxyDelayState hook and tighten types
- Extract shared delay state logic from ProxyItem and ProxyItemMini into `useProxyDelayState` hook (src/hooks/use-proxy-delay-state.ts); JSX display thresholds preserved per-component. - Deduplicate rAF/setTimeout/Promise scheduling in DelayManager via a private `scheduleOnNextFrame` helper. - Drop no-op `useMemo(() => MODES, [])` in proxies page. - Replace `any` on `setClashLog` prev params and layout-traffic sx objects with proper types (`pre!`, `Pick<BoxProps|...>`). |
||
|
|
b8fbabae04 |
fix: frontend memory leaks — Monaco dispose, TQ cache eviction, useEffect cleanup
- Dispose Monaco editor instances on dialog close to prevent cycle leak - Replace gcTime: Infinity with finite TTLs and evict orphaned subscription queryKeys - Add missing useEffect cleanup for timers, move setTimeout out of useMemo |
||
|
|
5da9f99698 |
fix: prevent TUN from being falsely disabled during startup
- Add 10s startup grace period before TUN auto-disable logic activates; service IPC may not be ready when the frontend first queries, causing a transient isServiceOk=false that incorrectly persists - Replace placeholderData (which set isLoading=false with stale data) with a proper isStartingUp guard; query now polls every 2s during startup to catch service readiness quickly - Add 'getSystemState' to refresh-verge-config invalidation keys to fix key mismatch that prevented event-driven refetches from working |
||
|
|
a73fafaf9f |
refactor: migrate SWR to TanStack Query v5 (#6713)
Replace swr with @tanstack/react-query v5 across all hooks, providers, and components. Introduce singleton QueryClient, WS subscription pattern via useQuery+useEffect, and enforce component-layer cache access contract. |
||
|
|
36624aff49 |
fix(logs): preserve log data and eliminate blank flash on page navigation
Preserve SWR cache in onConnected to avoid replacing accumulated logs with kernel buffer on reconnect. Add KeepAlive for the logs page so its DOM stays mounted across route changes, removing the visible blank window when navigating back. |
||
|
|
824bcc77eb |
fix: throttle WebSocket subscriptions to prevent UI freeze on profile switch (#6683) (#6686)
Add leading-edge throttle to useMihomoWsSubscription, reduce SWR retry aggressiveness, and increase WebSocket reconnect delay to prevent event storms when switching profiles under poor network conditions. |
||
|
|
9bcb79465c |
fix: resolve frontend data race conditions in hooks
- use-system-state: convert module-level `disablingTunMode` to useRef to isolate state per hook instance, fix no-op clearTimeout, add proper effect cleanup - use-profiles: convert forEach to for..of so selectNodeForGroup is properly awaited, remove fire-and-forget setTimeout around mutate - use-clash: add useLockFn to patchInfo for concurrency safety |
||
|
|
7a06a5a069 |
fix: remove dead localStorage writes and hardcoded key
- Remove unused `dns_settings_enabled` localStorage writes in setting-clash.tsx — state is sourced from verge config, these writes were never read anywhere. - Replace hardcoded `'last_check_update'` localStorage read in system-info-card.tsx with exported `readLastCheckTime()` from the useUpdate hook, keeping the key in a single source of truth. |
||
|
|
99bbd7ee5a |
fix(home): unify last check update timestamp across settings and home page (#6605)
The settings page "Check for updates" did not update the homepage "last check update time" because each page managed the timestamp independently. Centralizes the timestamp in the useUpdate hook via SWR + localStorage so both pages share a single data source. Closes https://github.com/clash-verge-rev/clash-verge-rev/issues/6605#issuecomment-4147144987 |
||
|
|
56141e6dfa | feat: use optimistic updates for system proxy toggle | ||
|
|
68ca01cfea | refactor: streamline proxy availability checks and remove redundant methods | ||
|
|
13538914be |
chore(deps): update dependency @eslint-react/eslint-plugin to v3 (#6522)
* chore(deps): update dependency @eslint-react/eslint-plugin to v3 * chore: remove deprecated rules * chore: remove eslint from pre-push * chore: update lint-staged config --------- Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Slinetrac <realakayuki@gmail.com> |
||
|
|
c672a6fef3 |
refactor: lint (#6511)
* refactor: lint * chore: remove eslint-plugin/config-prettier |
||
|
|
837508c02c |
feat(monaco): reintroduce meta-json-schema (#6509)
* refactor(editor): make EditorViewer controlled and unify document state handling * fix(monaco-yaml): add patchCreateWebWorker * feat(monaco): reintroduce meta-json-schema * fix(editor): reset document state on target change |