Releases: MasterAlanLab/avalon
Release list
v1.0.7 -- "生命中,再无聊的时光,也都是限量版。"
-
chore(release): bump version to 1.0.7
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01GXxfHB38J4dvd7j4vEnWGA
-
refactor: strip dead code and the redundant CoreInterface layer
-
An ablation pass over lib/ (core/ excluded): remove each candidate, then
-
require
flutter analyzeandflutter testto stay at baseline. -
CoreInterface was a mixin declaring 35 abstract methods that
-
CoreHandlerInterface then implemented one for one, and nothing in the tree
-
ever used it as a type. Merged it into CoreHandlerInterface, keeping the
-
four lifecycle methods the platform backends supply.
-
Also gone: two files nothing imported (common/archive.dart, common/store.dart),
-
27 unreferenced top-level declarations, and ~25 dead members. Three of those
-
were hiding defects rather than just weight -- putProfileDisabledRule wrote
-
RuleScene.added instead of disabled, Script.saveWithPath copied a file onto
-
itself, and importUri was a bare alias for importText. Dropping
-
getMaterialColorShades made _createPrimarySwatch, value32bit, the four *8bit
-
getters and lighten unreachable; dropping Windows.registerTask left
-
String.encodeUtf16LeWithBom with no caller but its own test, so that pair went
-
too (hence 862 tests, down from 864).
-
Kept, because the ablation showed they carry weight: the six abstract
-
interfaces under core/desktop each pair a production implementation with a
-
test fake, and NodeCodec has eight implementations.
-
Verified beyond the suite by installing a debug APK on an arm64 emulator --
-
core start/stop/restart, theme switching, node import and the settings screens
-
all behave as they did before.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01XSQDyYNbJPMa7e3Bkxhgrt
Download based on your OS:
v1.0.6 -- "到最后遗憾的是我,还是你。"
-
fix(ci): survive the flaky hdiutil detach that fails the macOS dmg
-
The v1.0.6 build produced Avalon.app fine and then died in appdmg's last
-
steps:
hdiutil detach /Volumes/Avalonreturned "No such file or directory", -
appdmg treated that as fatal and deleted both the temporary image and the
-
target dmg. Twelve seconds passed between "Blessing image" and the failure,
-
so appdmg had been retrying: the first detach reported an error, the volume
-
went away underneath it anyway, and every retry after that found nothing left
-
to unmount. The image was complete the whole time.
-
This is the runner, not the build - a volume held a moment too long by a
-
background process, or unmounted asynchronously by DiskArbitration. appdmg
-
gives us no way to tell it that an already-unmounted volume is a success, so
-
put a shim in front of hdiutil on the PATH of the packaging process. It only
-
touches
detach: an absent mount point exits 0, a busy one is retried, and -
the last attempts sync and force it out so the image content is still
-
flushed. Five failed attempts still exit non-zero, so a genuinely stuck
-
volume is not swallowed.
-
The shim is only prepended for macOS packaging, so no other platform's
-
environment changes.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_013ChdTt9BG4wAfqY35MDr1o
-
chore(release): bump version to 1.0.6
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01NSAj59bHG8PDrWLncUW8fC
-
fix(nodes): default udp on, the real cause of the WebRTC leak
-
The virtual adapter was never the problem. With the tun up and its routes
-
installed, a STUN probe still left through the physical interface carrying the
-
real address, and 1.0.4's strict-route did nothing about it.
-
mihomo gates UDP on a per-outbound flag: adapter/outbound/*.go declares it as
-
UDP boolwith theproxy:"udp,omitempty"tag, so a proxy that omits the key -
is parsed as not supporting UDP. tunnel.go's match() then skips every rule that
-
resolves to such an outbound and, once the rules run out, returns
-
proxies["DIRECT"]. The packet leaves through the physical interface with the
-
real source address and nothing is logged. WebRTC's STUN probes and QUIC both
-
take that path, which is why ipcheck.ing kept reporting the real address while
-
everything else went through the proxy.
-
Share links do not carry udp; only Clash YAML subscriptions write it. So this
-
hit exactly the nodes this app exists for - the ones imported by URI and
-
combined into relay chains - and left subscription users untouched.
-
Default it to true, in the URI codec so the value is visible in the node editor
-
and travels with exported links, and again in effectiveNodeConfig and
-
effectiveStoredNodeConfig so nodes already in the library are covered without a
-
migration. Profile proxies get the same treatment in the chain runtime: a
-
subscription that omits the key leaks the same way and never reaches the node
-
library. Only the fifteen types the core actually reads the key for; hysteria,
-
hysteria2, tuic and shadowquic set UDP in Base themselves, http and ssh are
-
always false. The default is applied before the overlay merge, so an explicit
-
udp: false and an explicit removal both still win.
-
A server that cannot carry UDP now fails to dial instead of quietly going
-
direct, which is the failure this needs.
-
Drop the plain udp flag from the relay-hop compatibility check. It is the
-
default now and carries no signal; leaving it in would have put a warning on
-
every multi-hop chain, and setup.dart logs each one on every apply.
-
Hide the strict-route switch on macOS. sing-tun implements it in tun_linux.go
-
as unreachable rules and in tun_windows.go as WFP filters; tun_darwin.go never
-
reads the field. The switch and its description promised a protection that does
-
not exist there. The field, the bridge and the config writer stay - they are
-
real on the other two desktops.
-
Turn the tun switch back off when authorization fails. The state stayed at
-
unauthorized, _getEffectiveTunEnable rewrote tun.enable to false for the core,
-
and the switch kept showing on: the user believes traffic is going through the
-
virtual adapter while only the system proxy is running. Log it too, because the
-
state was otherwise invisible from outside the process. Authorization is still
-
asked for once per app lifecycle.
-
Stop escaping spaces in checkIsAdmin's core path. It goes to Process.run as an
-
argv element, not through a shell, so an escaped path makes stat miss the file
-
and the app reports itself as unauthorized.
-
Verified on device: importing a share link without a udp parameter now stores
-
udp: true, while nodes imported before this change still have the key absent.
-
For anyone reading the 1.0.4 notes: "stop DNS and WebRTC leaking the real
-
address" was wrong about WebRTC. strict-route is a no-op on macOS and the leak
-
had a different cause entirely. The IPv6 takeover and fake-ip-range6 changes
-
from that release are unrelated and still stand.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01NSAj59bHG8PDrWLncUW8fC
Download based on your OS:
v1.0.5 -- "就像那句狠话:有一天,我一定会后悔的。"
-
chore(release): bump version to 1.0.5
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01LKYUwNq8JMjiGfvPQWekST
-
fix(nodes): send XHTTP transport settings to xhttp-opts
-
Importing a VLESS or VMess link with type=xhttp put path and host into
-
ws-opts. mihomo reads XHTTP settings from xhttp-opts only (XHTTPOptions
-
in adapter/outbound/vless.go), so path was lost, mode was dropped on
-
the floor, and host fell back to the TLS servername. The node could not
-
connect at all, and nothing in the app ever wrote xhttp-opts, so only
-
raw YAML nodes could carry an XHTTP transport.
-
Route them to xhttp-opts on import, keep mode, and read them back on
-
export so the URI round-trips. httpupgrade keeps using ws-opts, which
-
is where mihomo reads it from.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01LKYUwNq8JMjiGfvPQWekST
-
fix(tun): put the IPv6 takeover behind an explicit switch
-
The virtual adapter took over IPv6 whenever it was enabled, overriding
-
the IPv6 switch the user had turned off in settings. That also handed
-
::/0 to the tun, so direct traffic started depending on the host having
-
a working IPv6 uplink — the shape of failure where a half-broken v6
-
(address and default route present, packets dropped) makes dual-stack
-
sites time out.
-
Add tun.ipv6, on by default, and let it decide. With it off,
-
inet6-address is not written either, so sing-tun never installs the
-
::/0 route, and the global ipv6 switch is left exactly as the user set
-
it. Taking over ::/0 is safe to keep as the default: verified on a
-
device with deliberately broken IPv6 that domestic sites still load,
-
because mihomo restores the domain in preHandleMetadata and re-resolves
-
it, so the outbound address family is mihomo's choice and
-
tcp-concurrent races the dead v6 leg away. Xray honours what the client
-
picked and has no such fallback, which is why the same setup breaks
-
there.
-
Android builds the adapter in VpnService, where tun.enable is always
-
false, so the takeover lives in the VPN IPv6 switch instead. That
-
switch never reached the core: VpnService pointed ::/0 at the adapter
-
while the core still had ipv6 off, so the route was taken over but no
-
AAAA was ever resolved. Both platforms now decide through
-
SetupAction._tunTakesIpv6, and the result is passed into
-
makeRealProfileTask rather than derived there — that runs in a compute
-
isolate and should not reach for the platform singleton.
-
Also move the fake-ip-range6 gate out of the overrideDns branch. A
-
profile shipping its own dns block skips that branch entirely, so a
-
subscription setting ipv6: true silently overrode the local DNS IPv6
-
switch. Outside the branch the dns map may still be the read-only one
-
the profile arrived with, so copy it before writing.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01LKYUwNq8JMjiGfvPQWekST
-
chore(release): bump version to 1.0.4
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01YWcTvHimUrxTUtVBRVTcZn
-
fix(tun): stop DNS and WebRTC leaking the real address
-
With the virtual adapter on, DNS queries and WebRTC candidates still
-
exposed the real address. mihomo implements the defences for both, but
-
the app could not reach them: the Tun model, the config writer and the
-
Go bridge each carried the same six tun fields, so strict-route had no
-
way out of the app.
-
Add strict-route, on by default. On Windows sing-tun installs WFP
-
filters that permit only the tun interface and this process and block
-
remote port 53 everywhere else, which is what Windows' multihomed name
-
resolution needs; on Linux it adds the unreachable ip rules.
-
Make the tun carry IPv6. mihomo clears tun.inet6-address whenever ipv6
-
is false (config.parseIPV6), so a tun that does not take over IPv6
-
leaves the system routing it out of the physical interface. Force ipv6
-
on whenever the tun is enabled and write inet6-address explicitly.
-
Whether AAAA is resolved stays with dns.ipv6, so this does not change
-
which address family applications prefer.
-
Both settings also travel through the bridge, because core_manager
-
patches the running config instead of rewriting the profile: enabling
-
the tun after launch would otherwise recreate it without the IPv6
-
address it needs.
-
Add dns.fake-ip-range6 and tie it to dns.ipv6. Under fake-ip, AAAA is
-
answered by the withFakeIP middleware, which returns an empty answer
-
when there is no v6 pool and never reaches the dns.ipv6 check in
-
withResolver, so that switch did nothing in the default mode. It now
-
provisions the pool. Hosts without global IPv6 are unaffected:
-
parseIPV6 runs before parseDNS and parseTun and clears both the v6 pool
-
and the tun address.
-
Verified by parsing the generated config with mihomo's own config.Parse
-
in both environments. With IPv6 the tun keeps fdfe:dcba:9876::1/126 and
-
FakeIPPool6 is created; without it both are dropped and parsing still
-
succeeds.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01YWcTvHimUrxTUtVBRVTcZn
-
chore(release): bump version to 1.0.3
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
feat(profiles): give each tab its own action button
-
The Profiles scaffold showed one Add Profile button for all three tabs,
-
while the node and chain actions sat inline above their lists. The
-
button that matters depends on the tab you are looking at.
-
Drive the floating action button from the tab index: Add Profile on
-
Profiles, Create node and Import node on Nodes, Create chain on Chains.
-
The inline buttons in the node toolbar and above the chain list are gone
-
now that the actions live in the scaffold; the empty states keep theirs
-
as a call to action.
-
The two node buttons sit at opposite edges, which the end-aligned slot
-
cannot express, so CommonScaffold gained a floatingActionButtonLocation
-
and that tab uses centerFloat with the edge inset added back.
-
To reach the actions from the scaffold, the node import/create handlers
-
and the chain create handler become top-level functions in their own
-
files; the views delegate to them.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
feat(nodes): import nodes without a preview step
-
Removing only the overwrite-or-copy radios left the preview dialog in
-
place, so importing still interrupted the user to confirm a list they
-
did not ask to review. Importing a link means "add this": parse it, add
-
what is valid, and report what is not.
-
Drop the preview entirely from both import paths. NodeLibraryService
-
already skips drafts that fail to parse and returns their issues, so the
-
Nodes tab now surfaces those issues instead of showing them in a list
-
nobody needed to approve. Binding follows the caller as before: imports
-
bind to the current profile when there is one.
-
Delete NodeImportPreview, NodeImportSelection and their test, along with
-
the importPreview and bindCurrentProfile messages that only that dialog
-
used. _previewAndCommitNodes becomes _commitNodes, since it no longer
-
previews anything.
-
Verified on an Android emulator: a node deep link and the Nodes tab
-
import both create the node with no dialog, and importing the same link
-
twice yields two copies.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
feat(nodes): always add on import instead of asking about duplicates
-
The import preview asked the user to choose between updating the
-
existing node and creating a copy. The choice is not worth a prompt:
-
importing a link means "add this", and re-importing the same link should
-
simply yield another copy.
-
Drop the radio group and commit imports with createCopy, so the preview
-
only asks what it still needs, which drafts to keep and whether to bind
-
them to the current profile.
-
Clean up what the choice left behind:
-
- NodeImportSelection.createCopy, now that nothing reads it.
-
- The createCopy parameter on importNodeText and _previewAndCommitNodes,
-
which no caller ever passed.
-
- The updateExisting message, whose only reference was the removed radio.
-
NodeLibraryService.commit keeps its createCopy parameter: the node menu
-
still duplicates a node with it, and manual creation still dedupes by
-
fingerprint without it.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
fix(android): keep warm-start deep links out of Flutter routing
-
Deep links are handled by app_links, but the Flutter engine's own deep
-
link handling is on by default and pushes the incoming URI into the
-
navigation system. This app uses a plain MaterialApp with no route
-
generator, so a link delivered to an already running instance raised
-
"Could not find a generator for route" and never reached LinkManager.
-
Cold start worked only because getInitialLink reads the intent
-
directly. Warm start is the common case here, since the app is meant to
-
stay running in the background while a subscription link is opened from
-
a browser or chat app.
-
Set flutter_deeplinking_enabled to false so the intent reaches
-
app_links in both cases.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
fix(nodes): treat every http(s) URI as a subscription
-
isHttpProxyUri classified any http(s) URI with an explicit port as an
-
HTTP proxy node, so...
v1.0.4 -- "那些过去,从未过去。"
-
chore(release): bump version to 1.0.4
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01YWcTvHimUrxTUtVBRVTcZn
-
fix(tun): stop DNS and WebRTC leaking the real address
-
With the virtual adapter on, DNS queries and WebRTC candidates still
-
exposed the real address. mihomo implements the defences for both, but
-
the app could not reach them: the Tun model, the config writer and the
-
Go bridge each carried the same six tun fields, so strict-route had no
-
way out of the app.
-
Add strict-route, on by default. On Windows sing-tun installs WFP
-
filters that permit only the tun interface and this process and block
-
remote port 53 everywhere else, which is what Windows' multihomed name
-
resolution needs; on Linux it adds the unreachable ip rules.
-
Make the tun carry IPv6. mihomo clears tun.inet6-address whenever ipv6
-
is false (config.parseIPV6), so a tun that does not take over IPv6
-
leaves the system routing it out of the physical interface. Force ipv6
-
on whenever the tun is enabled and write inet6-address explicitly.
-
Whether AAAA is resolved stays with dns.ipv6, so this does not change
-
which address family applications prefer.
-
Both settings also travel through the bridge, because core_manager
-
patches the running config instead of rewriting the profile: enabling
-
the tun after launch would otherwise recreate it without the IPv6
-
address it needs.
-
Add dns.fake-ip-range6 and tie it to dns.ipv6. Under fake-ip, AAAA is
-
answered by the withFakeIP middleware, which returns an empty answer
-
when there is no v6 pool and never reaches the dns.ipv6 check in
-
withResolver, so that switch did nothing in the default mode. It now
-
provisions the pool. Hosts without global IPv6 are unaffected:
-
parseIPV6 runs before parseDNS and parseTun and clears both the v6 pool
-
and the tun address.
-
Verified by parsing the generated config with mihomo's own config.Parse
-
in both environments. With IPv6 the tun keeps fdfe:dcba:9876::1/126 and
-
FakeIPPool6 is created; without it both are dropped and parsing still
-
succeeds.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01YWcTvHimUrxTUtVBRVTcZn
Download based on your OS:
v1.0.3 -- "宇宙是蚂蚁的梦。"
-
chore(release): bump version to 1.0.3
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
feat(profiles): give each tab its own action button
-
The Profiles scaffold showed one Add Profile button for all three tabs,
-
while the node and chain actions sat inline above their lists. The
-
button that matters depends on the tab you are looking at.
-
Drive the floating action button from the tab index: Add Profile on
-
Profiles, Create node and Import node on Nodes, Create chain on Chains.
-
The inline buttons in the node toolbar and above the chain list are gone
-
now that the actions live in the scaffold; the empty states keep theirs
-
as a call to action.
-
The two node buttons sit at opposite edges, which the end-aligned slot
-
cannot express, so CommonScaffold gained a floatingActionButtonLocation
-
and that tab uses centerFloat with the edge inset added back.
-
To reach the actions from the scaffold, the node import/create handlers
-
and the chain create handler become top-level functions in their own
-
files; the views delegate to them.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
feat(nodes): import nodes without a preview step
-
Removing only the overwrite-or-copy radios left the preview dialog in
-
place, so importing still interrupted the user to confirm a list they
-
did not ask to review. Importing a link means "add this": parse it, add
-
what is valid, and report what is not.
-
Drop the preview entirely from both import paths. NodeLibraryService
-
already skips drafts that fail to parse and returns their issues, so the
-
Nodes tab now surfaces those issues instead of showing them in a list
-
nobody needed to approve. Binding follows the caller as before: imports
-
bind to the current profile when there is one.
-
Delete NodeImportPreview, NodeImportSelection and their test, along with
-
the importPreview and bindCurrentProfile messages that only that dialog
-
used. _previewAndCommitNodes becomes _commitNodes, since it no longer
-
previews anything.
-
Verified on an Android emulator: a node deep link and the Nodes tab
-
import both create the node with no dialog, and importing the same link
-
twice yields two copies.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
feat(nodes): always add on import instead of asking about duplicates
-
The import preview asked the user to choose between updating the
-
existing node and creating a copy. The choice is not worth a prompt:
-
importing a link means "add this", and re-importing the same link should
-
simply yield another copy.
-
Drop the radio group and commit imports with createCopy, so the preview
-
only asks what it still needs, which drafts to keep and whether to bind
-
them to the current profile.
-
Clean up what the choice left behind:
-
- NodeImportSelection.createCopy, now that nothing reads it.
-
- The createCopy parameter on importNodeText and _previewAndCommitNodes,
-
which no caller ever passed.
-
- The updateExisting message, whose only reference was the removed radio.
-
NodeLibraryService.commit keeps its createCopy parameter: the node menu
-
still duplicates a node with it, and manual creation still dedupes by
-
fingerprint without it.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
fix(android): keep warm-start deep links out of Flutter routing
-
Deep links are handled by app_links, but the Flutter engine's own deep
-
link handling is on by default and pushes the incoming URI into the
-
navigation system. This app uses a plain MaterialApp with no route
-
generator, so a link delivered to an already running instance raised
-
"Could not find a generator for route" and never reached LinkManager.
-
Cold start worked only because getInitialLink reads the intent
-
directly. Warm start is the common case here, since the app is meant to
-
stay running in the background while a subscription link is opened from
-
a browser or chat app.
-
Set flutter_deeplinking_enabled to false so the intent reaches
-
app_links in both cases.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
fix(nodes): treat every http(s) URI as a subscription
-
isHttpProxyUri classified any http(s) URI with an explicit port as an
-
HTTP proxy node, so a subscription link served on a non-default port
-
was imported as a node instead of creating a profile. Self-hosted
-
panels commonly use ports such as 8443 or 2096, and the
-
avalon://install-config deep link routes through the same path, so its
-
explicit intent was overridden by the heuristic.
-
http and https are no longer node schemes, matching what the deep link
-
dispatcher and the Android intent filters already assumed. Drop
-
HttpCodec, its https alias and the port heuristic, and let importText
-
treat any http(s) URI as a subscription URL.
-
Raw Mihomo configs carrying
type: httpproxies keep working; only the -
URI codec is gone.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
perf(telemetry): gate UI telemetry on a unified activity state
-
Traffic sampling ran on a fixed 1s global timer that only stopped when
-
the proxy stopped, so a hidden window still paid for Dart scheduling,
-
two IPC round trips and provider updates every second.
-
Introduce a process-wide AppActivityState fed by the Flutter lifecycle,
-
window visibility and window focus, and drive telemetry from it:
-
- SetupAction picks 1s, 5s or no sampling from the current activity,
-
the dashboard traffic widgets and the tray title need, resamples
-
immediately on resume, and skips a tick while a request is in flight.
-
- The tray speed title is macOS-only, so its need is platform-gated;
-
otherwise showTrayTitle (default true) kept background sampling alive
-
for a consumer that does not exist off macOS.
-
- Pages and the app root wrap their subtree in TickerMode so animations
-
stop when the page or the window is inactive.
-
- NetworkSpeed and TrafficUsage drop their provider subscription while
-
inactive; Logs and Requests buffer through ActiveSnapshotMixin and
-
commit one snapshot once page and window are both active again.
-
- Tray titles dedupe on the formatted string, and getTraffic /
-
getTotalTraffic bypass the debug RPC timing logs.
-
Frame pausing now distinguishes an on-screen window from a hidden one:
-
blur only gates telemetry and tickers, while hide and minimize pause
-
frames after 300ms instead of 5s. Losing focus must not stop frames, as
-
an unfocused window is still scrollable and would appear frozen.
-
Verified on an Android emulator: 1 tick/s in the foreground, zero ticks
-
across 25s backgrounded, and an immediate resample on resume.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01THm54YvuV1sjPqEMGFrgxq
-
fix(ui): show chain names and align node actions
Download based on your OS:
v1.0.2 -- "经过上千年的修炼,你学到的就是这个吗?"
-
ci: retry Flutter SDK setup after transient downloads
-
ci: use Maven Central mirror for Android builds
-
chore(release): bump version to 1.0.2
-
Start Flutter splash from a blank first frame
-
Add animated Flutter startup splash
-
Refresh primary icon and status generator
-
Update Avalon branding assets across platforms
Download based on your OS:
v1.0.1 -- "很多人宁可否认事实,也不愿面对真相。"
v1.0.0 -- "无心忆本失心意,忆心失本心无意。"
-
fix(windows): resolve installer locale path
-
ci: drop unsupported build architectures
-
chore: organize branding sources
-
chore(release): bump version to 1.0.0
-
ci: optimize release workflow
-
feat: refresh cross-platform branding assets
-
fix: update project links and About page
-
fix: correct packaging paths and platform naming
-
docs: rewrite README and sync translations
-
chore: license Avalon additions under AGPLv3
-
fix(android): update JNI exports for Avalon package
-
refactor: drop the dead crash-reporting plumbing and unused strings
-
Removing Firebase left a settings field, a shared-state field and four
-
localized strings behind, kept alive only by a localization test. This drops
-
them end to end: AppSettingProps.crashlytics/crashlyticsTip, SharedState.crashlytics
-
on both the Dart and Kotlin sides, and the crashlytics, crashlyticsTip,
-
dataCollectionTip and dataCollectionContent strings in all four locales. The
-
shared-state provider now watches two fields instead of three.
-
Also removes chainLibrary, importNodeDesc and chainHopHint, three strings that
-
were never referenced, and renames plugins/rust_api/CLAUDE.md to NOTES.md so the
-
flutter_rust_bridge integration notes survive without carrying an assistant
-
config filename.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
chore: remove the remaining agent tooling files
-
AGENTS.md and CLAUDE.md only routed into the deleted .agents directory, and
-
.mcp.json and opencode.jsonc configured a codegraph MCP server this project
-
no longer uses.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
chore: remove the JetBrains run configuration
-
.run/main.dart.run.xml only provides a ready-made Flutter run entry for
-
JetBrains IDEs and takes no part in building, testing or packaging.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
chore: stop ignoring docs/
-
The directory now holds the translated READMEs, so new files under it must
-
be trackable. Also drops the .claude/settings.local.json rule, which no
-
longer has a directory to apply to.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
docs: move the translated READMEs under docs/
-
Keeps the Chinese README at the repository root and relocates the seven
-
translations to docs/. Language navigation, image sources and the LICENSE,
-
NOTICE and CHANGELOG links are rewritten so every relative path still
-
resolves from its new depth.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
chore: remove the .codegraph directory
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
chore: remove agent tooling configs
-
Drops the .gemini, .codex, .claude, .agents and .zed directories, which
-
carry per-tool assistant configuration rather than anything the project
-
builds or ships.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
chore(release): bump version to 0.8.97
-
Artifact file names come from the pubspec version while the release body
-
template builds its download links from the tag, so the two have to match.
-
v0.8.96 is already taken by an upstream FlClash tag, hence 0.8.97 for
-
Avalon's first release.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
ci: put a hitokoto quote in the release title
-
Every release is named
<tag> -- "<quote>", matching how emailbox names -
its releases. The quote is fetched from v1.hitokoto.cn with a 5s timeout and
-
falls back to a fixed string when the API is unreachable or returns something
-
jq cannot parse. The value is stripped of newlines and quotes and capped at 80
-
characters so remote content cannot inject extra step outputs or break the
-
title.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
refactor!: rename FlClash fork to Avalon and trim the release pipeline
-
Rebrand this FlClash fork as Avalon, repoint every project-owned identifier,
-
and reduce the inherited release pipeline to building installers and attaching
-
them to a GitHub Release.
-
Rename:
-
- Dart package fl_clash -> avalon
-
- application id com.follow.clash -> com.masteralanlab.avalon, with the Kotlin
-
package directories moved to match
-
- executables FlClashCore / FlClashHelperService -> AvalonCore /
-
AvalonHelperService, keeping the Windows pipe name, unix socket path and
-
helper protocol header in sync between the Dart and Rust sides
-
- custom URL scheme flclash:// -> avalon:// and generated chain group prefix
-
_flclash -> _avalon
-
- a fresh Inno Setup app id so the Windows installer is a distinct application
-
Upstream references are deliberately left untouched: the Clash.Meta submodule
-
branch, the flutter_distributor git ref and the chen08209 forks in pubspec.yaml
-
still point at the upstream projects.
-
Docs:
-
- rewrite the README for Avalon in 8 languages, replacing README_zh_CN.md
-
- add NOTICE carrying the GPL-3.0 attribution to FlClash and the list of
-
modifications made in this fork
-
- state in every README that the project is for study and research only and
-
that any illegal use is prohibited, and add a recommended-resources section
-
CI:
-
- drop the F-Droid, Homebrew tap and Telegram publishing steps
-
- skip Android signing when no keystore secret is configured instead of writing
-
an empty keystore over the build
-
- export ANDROID_NDK from the setup-ndk output, which the Go core build tool
-
requires under that exact name
-
- fall back to the runner default Xcode when the pinned version is absent
-
Android:
-
- remove Firebase Crashlytics and Analytics; crash-loop protection now reads
-
ActivityManager.getHistoricalProcessExitReasons on API 30+ and degrades to
-
no protection below it
-
BREAKING CHANGE: the application identifiers changed, so Avalon installs
-
alongside FlClash instead of upgrading it. Settings, profiles and the WebDAV
-
backup directory are not migrated.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_018PvMz7SJGTDgSFovM66xPm
-
Fix three codec outputs the Core rejects
-
Device testing surfaced three configs that the Dart layer considered valid
-
but the bundled Core refuses at apply time:
-
- SOCKS nodes were emitted as
type: socks. Mihomo registerssocks5
- SOCKS nodes were emitted as
-
only, so adapter.ParseProxy failed with "unsupport proxy type: socks"
-
and no SOCKS node could start. SOCKS4/4a keep their variant in
-
version, which _socksScheme already uses to restore the scheme. -
scheme://base64(user:password)@hostshare links kept the undecoded
-
blob as the username and left the password empty, so servers rejected
-
the auth. Decode it in a shared _credentialParts, but only when the
-
decoded value carries a separator, so a literal username that happens
-
to be valid base64 is left alone. SOCKS and HTTP shared the bug.
-
h2transport was written tohttp-opts, and header values were
-
written as plain strings. Mihomo splits these: HTTPOptions takes
-
pathas a list andheadersvalues as lists, HTTP2Options takes -
hostas a list with a plainpath, and WSOptions takes plain -
strings throughout. Fixed on URI parse, VMess JSON parse and export.
-
None of these were caught by the existing suite because it validates only
-
within Dart. Two of them were in fact pinned as expected behaviour by
-
codec tests asserting
type == 'socks'. Those assertions are corrected -
here, and hub_validate_test.go now pins the SOCKS naming contract on the
-
Go side.
-
Found by feeding parsed sample URIs for every supported protocol to the
-
real handleValidateConfig; the other 15 protocol/transport combinations
-
tested clean.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01GuY5bCnM5DDp2Qb5LHKCkS
-
Replace the node import entry with create-from-chain
-
Add Profile offered "Import node", which duplicated the button already on
-
the Nodes tab, while there was no way at all to create a profile without a
-
subscription URL or a config file. Nodes and chains are global, but binding
-
either one needs a profile, so the no-subscription single-node flow was
-
unreachable from the UI.
-
Add Profile now lists the chain library instead: picking a chain creates a
-
stub profile, binds the chain as default and sets the chain entry group.
-
The stub ships an empty PROXY group on purpose. _attachChainEntry appends
-
the generated selector and keeps the first member in place, so an empty
-
group leaves the chain as the only member and therefore the default
-
outbound; a DIRECT placeholder would silently win instead. Core validation
-
accepts a select group with no members, verified against the bundled Core.
-
Verified on an Android emulator: the entry lists the chain library, creates
-
the profile and makes it current.
-
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
-
Claude-Session: https://claude.ai/code/session_01GuY5bCnM5DDp2Qb5LHKCkS
-
Fix R1-R9 from the node library and chain review
-
- R1: keep old files, database and settings on restore failure
-
- R2: preserve SOCKS4/4a version across export round-trip
-
- R3: include chains and generated groups in export
-
- R4: show diagnostics and confirmation be...