Repository navigation
Logging system rebuild, profile management, and connection improvements - #2333
Merged
Merged
Conversation
Update the hiddify-core submodule to db74dfc (main HEAD), which moves hiddify-sing-box onto the testing-merge-base line (AWG/amnezia option API, public RedirectStderr, removed broken client submodules). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Return a CupertinoPage from customTransition() on iOS so pushed pages get the native interactive edge-swipe (pop) gesture, which CustomTransitionPage does not support. Other platforms keep the existing slide/fade transitions. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the success heavy-impact from the Connected() transition to the moment the tunnel has a valid url-test ping, so it fires when the connection actually works. Add a medium then heavy buzz when a live connection times out, and a heavy buzz when it recovers. Extract the delay-validity threshold into ConnectionConst.maxDelay / isValidDelay as the single source of truth, and route the connection button, system tray, proxy tile, and delay indicator through it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Replace the leading action icon with a live status: a spinning indicator while updating, then a transient success/failure icon (cross-faded). The result is held for 2s inside UpdateProfileNotifier so it survives list rebuilds/reordering. Also tidy ProfileTile (extract body/title/tap helpers, drop commented-out code and the unused color param) and remove the never-used ProfileTileMain widget. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Route bulk updates through UpdateProfileNotifier.updateProfile so each tile shows its status, and report one aggregate toast (N success, M failed with names) instead of one per profile. Single user-initiated updates no longer toast on success. Toast titles now wrap to multiple lines and error toasts stay longer to fit the failed names. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Key profile tiles by id so their update indicator and animation aren't lost when the list reorders (it sorts by last-update, so an updated profile jumps position). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fix swapped sort labels, replace the ambiguous up/down arrow with explicit direction labels (A -> Z / Z -> A, Newest/Oldest first) shown as a subtitle, and default to the natural direction per sort type. Persist the sort choice via a new general preference so it survives app restarts. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add translations for bulkSuccess, bulkPartial and the sort direction labels (newestFirst/oldestFirst) across all locales, matching the keys added to en. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove pages.profiles.msg.update.successNamed (single updates no longer toast on success) and components.subscriptionInfo.{remainingTime, remainingDurationNew, remainingUsage, profileSite, profileSupport} (only used by the deleted New*SubscriptionInfo widgets) from en and all locales.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
English base-locale strings for the profile pin/unpin action. Kept as its own commit (source locale); other languages follow separately. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Add a pinned column (DB schema v7 + migration; drift steps/schema/test helpers regenerated) plus a README documenting the migration workflow. The profiles list now orders active -> pinned -> sorted; Pin/Unpin is the first profile-menu item and pinned list tiles show a pin badge. Pin state persists and a subscription refresh no longer clobbers it (toUpdateEntry omits pinned). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Translate common.pin / common.unpin into all non-English locales. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Remove the aider-based translation helper and migrate its workflow into Claude Code project instructions. - delete .aider.conf.yml and SLANG.md - remove aider history/cache entries from .gitignore - add CLAUDE.md documenting the slang translation workflow - git-ignore slang analyze reports (_missing/_unused_translations*.json) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `?url=` deep-link form documented as `hiddify://install-config?url=<uri>#<name>` never picked up the name: the parser only read a `name=` query parameter and ignored the fragment. Prefer an explicit `name=` and fall back to the fragment. Adds tests covering both deep-link forms and the other panel schemes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a `showLeading` flag to LanSharingPreferenceWidget and turns it off in the quick settings modal, where the leading icon is redundant. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Switches the profiles modal from a Column to a Stack so the buttons float above the list instead of taking their own row. Bottom list padding is increased to keep the last profile reachable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Strings for the profile web page / support chips and for the warning shown when a subscription-provided link is not in the trusted list. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`profile-web-page-url` and `support-url` were stored on SubscriptionInfo, so they were only kept when the `subscription-userinfo` header was also present. They are independent headers, so they move onto RemoteProfileEntity and are now parsed unconditionally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Scammers publish look-alike channels and sites (e.g. t.me/Hiddiify vs the real t.me/hiddify) inside fake subscriptions to collect donations meant for real providers. This adds the list used to tell a verified provider link from an unverified one. Matching is exact on the whole url, after normalising the scheme, a leading `www.`, host case, a trailing slash and any fragment — so a look-alike handle never matches. The list is managed by the project manager and holds Hiddify only for now; there is no public request flow to be added to it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reworks the unverified-link dialog so it informs rather than alarms: an unverified link is not necessarily malicious, so it drops the icon and the error styling and states plainly that the link cannot be verified. The Open button stays disabled for 10 seconds with a visible countdown, the same friction used for sensitive permissions, so the note is actually read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Renders the provider's web page and support links as a single chip below the main profile card. With both present the chip cycles every 10s: it slides up behind the card, then the next one slides down into place. The card's bottom corners square off so the chip reads as attached to it. A tapped link is checked against the trusted list first: a trusted link opens after a plain confirmation, anything else goes through the countdown warning. Hidden behind ProfileTileConst.showProviderLinks while the design is decided. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The header was split on `;` and every segment was assumed to be a `key=value` pair, reading index 1 unconditionally. A trailing `;`, a stray `;;` or any malformed token therefore threw — and because parse() is wrapped in a single try/catch, the whole profile failed to load: no name, no traffic, nothing. A trailing semicolon is common in the wild. Segments without a `key=value` pair are now skipped instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The interval was read with int.parse, so a decimal or any non-numeric value threw and — like the subscription-userinfo case — failed the entire profile parse rather than just the auto-update setting. The convention is a positive integer number of hours, so anything else (garbage, a decimal, zero or negative) is now ignored and the profile keeps its default update behaviour. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two gaps in the name hierarchy: content-disposition was matched with `filename="..."` only, so the RFC 5987 extended form `filename*=UTF-8''<percent-encoded>` — how servers send a non-ASCII name — was skipped and the provider's real name was lost. The extended form is now preferred and decoded, falling back to the quoted form. The steps also mixed `isEmpty` with `isBlank`: a whitespace-only result was treated as a name by the later steps but as blank by the final fallback, so a real url fragment or filename could be dropped in favour of "Remote Profile". Every step now treats whitespace as blank. Also documents that the url-based steps are remote-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…eader Replaces the `enable-fragment` boolean with `fragment`, which carries the same value as a proxy link's own `fragment` parameter, so a provider can set the size and sleep instead of only switching fragmentation on. The value is read exactly like a proxy link: a full `size,sleep,method` triplet, or `tlshello,size,sleep`. Anything shorter, or a size or sleep that isn't a valid range, is incomplete and leaves fragmentation off rather than enabling it with half the values. The user's own setting still wins, and enables fragmentation with the size and sleep from their settings — it carries no values of its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Warp and psiphon are mutually exclusive — the core runs one chain mode at a time — so the two boolean headers become a single `extra-security` header holding the mode. UserOverride follows, replacing `enableWarp`/`enablePsiphon` with `extraSecurity`, bumped to v2 with a read-time migration; psiphon wins when both were set, matching v1 where it overrode warp. The value is matched strictly against ChainMode: it must equal a documented mode key exactly or it is ignored, so no case folding and no lists. `profile` is never accepted from a subscription, since chaining to another profile is the user's choice. ChainMode and ChainStatus also replace the hardcoded strings. The raw header is consumed rather than passed through — `extra-security` is on the output allow-list, so an unrecognised value would otherwise reach the built config as a string where the core expects an object. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The tile no longer shows or edits a password, so the title itself has to tell the user the shared link is protected. Drop the two keys the old password UI needed: - lanSharingPassword (input dialog title) - lanSharingPasswordNotSet (subtitle placeholder) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mirror the English change in the other ten locales: lanSharingPassword and lanSharingPasswordNotSet no longer exist. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The user had to invent and maintain the LAN sharing password, and an empty one silently shared the VPN with no authentication at all. Generate a 16-character password with Random.secure the first time sharing is turned on, store it, and reuse it from then on. Sharing that was already enabled without a password gets one as soon as the tile is shown. Visually ambiguous characters are left out of the alphabet so the password survives being typed by hand from the QR code. With the password out of the user's hands, the edit dialog, the subtitle and the no-password link branch are gone, and the tile is a plain SwitchListTile again. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The protobuf/gRPC output under lib/hiddifycore/generated/ is produced by protoc and json_editor.dart is a vendored copy of json_editor_flutter. Neither is ever hand-edited, yet together they accounted for 148 of the 243 analyzer diagnostics, burying the findings in our own code. Compilation still covers both; only the analyzer stops reporting them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
FlutterError.onError defaults to presentError, which prints the full formatted
report. Assigning a handler replaced it, so an error that used to arrive as a
complete report with the widget context became a single terse line — which is
why crashes have been hard to read.
- presentError is called again in debug builds
- details.library and details.context are now logged; exceptionAsString()
leaves both out, and they are the parts that say which subsystem failed and
what it was doing
Also completes log_level_compat with trace() and fatal(), so all six levels the
engine can produce have a short name, not just the four loggy happened to have.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every log line that carries a stack now ends with the method, file and line it
came from, taken from the first frame belonging to this package:
could not save the profile <- ProfileRepository.save (profile_repository.dart:88)
It is appended to the message rather than kept in a separate field, because the
message is the one thing DevTools, the logs page and app.log all display.
error() and fatal() capture StackTrace.current when the caller does not pass
one, so a bare loggy.error('...') still knows its origin. Errors are rare
enough to afford the stack walk.
The stack handed to dev.log is trimmed to this package's frames as well. A raw
trace is about twenty frames, nearly all of them inside Flutter, and the
console printed every one — three lines instead of twenty-two, and the ones
that survive are still clickable in the terminal.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The step that moves every app log at once. PresLogger, AppLogger and InfraLogger now hand out a package:logging Logger. The getter keeps the name `loggy`, and log_level_compat is re-exported from this file, so the ~200 classes that write logs are untouched. Only five call sites needed editing — the ones that passed loggy's own LogLevel as an argument, which is a type the extension cannot alias away. Logger names gain a layer prefix: ui., app. and infra. That is what will let the logs page filter a whole layer, and taking the rest from $runtimeType means a renamed class cannot leave a stale name behind. Note app.log receives nothing until the next step. loggy's file printer still owns the file but no longer has any writers, and the replacement file sink is not attached yet — two IOSinks on one path would truncate each other. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nothing outside custom_loggers.dart builds a Logger any more. A name without a category would quietly break the logs page filter, so the only way to get one is through a mixin or a factory, and both stamp the prefix. Classes use a mixin. Static and top level code cannot — a mixin needs an instance — so it uses the matching factory. UriUtils, the FFI interface and the preferences provider were all constructing loggers by hand; they now go through infraLogger() and appLogger(). Adds CoreLogger, so the engine bridge is a category like the rest rather than a bare name, and boot., which separates startup from the app layer. Startup logs are what you read first when the app fails to start, and they were mixed in with everything else. Drops LoggerMixin. It declared "this class has a loggy" for ExceptionHandler, which never used it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every LogMessage from the engine already carries a type — CORE, SERVICE or CONFIG — and hiddify dropped it, so a failed profile import and a thousand connection lines landed in the same pile. core.core the proxy engine: connections, routing, dns core.service the background tunnel: start, stop, permissions core.config reading and validating a profile Nothing new is transmitted; the field was already on the wire. When a profile fails to import you can now ask for core.config alone. Also renames the boot logger to boot.bootstrap, after the file it reports on. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The last step of the switch. Sentry becomes a listener like every other sink,
the file sink is attached, and loggy's plumbing is deleted.
sinks/sentry_sink.dart replaces analytics_logger.dart. Same rule as before —
info and up becomes a breadcrumb, severe and up
becomes a reported event — but as a subscription the
analytics toggle attaches and cancels, rather than an
entry in a printer map. Levels map onto Sentry's five
properly now: SHOUT is fatal instead of collapsing
into error.
bootstrap.dart the three LoggerController calls become logStart,
logAddFile and logFinish. This reattaches app.log,
which had no writer since the mixins switched.
Deleted: logger_controller.dart, custom_logger.dart, analytics_logger.dart, and
the dependency itself. Nothing imports loggy any more.
Whether to keep the log file is unchanged in effect but clearer to read: worth
its cost on desktop or while debugging, pure waste on a phone in release.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A phone in release keeps no app.log — writing one continuously costs storage nobody reads — so the share menu was hidden there entirely and the user had no way to send app logs at all. The history is in memory anyway. Sharing now writes the ring out to app-logs.txt at the moment the user asks, so the file exists exactly when it is needed and never otherwise. Continuous cost stays zero, and the menu is useful on every platform except web. The export uses its own file name rather than app.log, which the file sink is still writing to on desktop. Engine logs were never affected: box.log is written by the engine itself on every platform. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two defects the first real run exposed, neither visible to the analyzer. The origin pointed at the logger itself. error() captures StackTrace.current inside the extension, so frame zero was log_level_compat.dart rather than whoever called it. Frames from core/logger are skipped when picking the origin now, and the '.<anonymous closure>' suffix is dropped — it said nothing the file and line did not already say, and splitting it on whitespace produced 'closure>'. before status <- LogLevelCompat.error (log_level_compat.dart:29) after status <- HiddifyCoreService.startListeningStatus (hiddify_core_service.dart:442) A terminal showed no logs at all. dev.log writes to the VM service, never to stdout — verified with a probe: print and debugPrint appear there, dev.log does not. Printing every record as well would fix the terminal but show everything twice in an IDE console, so stdout gets one notice at startup naming the three places records do appear. CI is unaffected: build failures come from the compiler and the flutter tool, never through the app's logger. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…'s real one Three changes to what lands on disk. The file is no longer emptied at launch. A crash leaves its evidence in the previous run's lines, and wiping on startup threw exactly that away. Growth is bounded instead: past 5 MB the older half is dropped and the newer half kept, checked every 500 lines rather than on every write. Each run writes a marker line, since one file now spans several. Engine lines are filtered to warnings and above. The engine keeps its own complete log, so repeating its routine chatter here only flooded the file and ate the cap — while an engine failure still sits next to the app line that led to it. coreFile() pointed at <workingDir>/box.log, which nothing writes. The engine logs into <workingDir>/data/box.log — so "share core logs" has been handing over an empty file. Adds coreHangFile() for the goroutine dump the engine writes beside it, which is what a hang needs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
enableFlutterDriverExtension registers a TestTextInput, which takes over the text input channel. Every debug build called it, so no text field in the app could accept a real keyboard — the search box on the logs page would focus and show a caret, and then swallow every keystroke. It now runs only when asked for, so a normal debug build types again and the driver is still one flag away. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds the strings the redesigned page needs: the three filter chips, the share sheet's sources, the export action and its match count, and the buffer chip in the app bar. Drops shareCoreLogs and shareAppLogs, since sharing is no longer two buttons, and the Settings strings for debug mode and log level, since neither setting lives there any more. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The same 17 strings in the other ten locales, written from the English rather than from each other, and anchored on the words each language already uses for core, app and log so the page reads like the rest of the app. Drops the two old share buttons and the Settings strings for debug mode and log level, which no longer exist in the base locale. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
package:logging spells its levels FINEST, FINE, INFO, WARNING, SEVERE. The engine says trace, debug, info, warn, error. Both names appeared on the same page, next to each other, for the same idea. The engine's win, since those are the ones the user picks from, and the log file now writes them too. uiLevels is the subset a filter can offer: FINER and CONFIG are never produced, and SHOUT never needs its own entry because the filter is a floor and error already covers it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two controls produced one outcome. The app had a debug mode switch it
passed to the core at startup, and the core turns the same flag on by
itself whenever the level is trace or debug:
static.debug = static.debug || static.logLevel <= LogLevel_DEBUG
So the two could disagree — switch off, level debug, core in debug, app
believing otherwise. The switch is gone and the app reads the level with
the same rule the core uses, which is what isVerbose says.
The buffer size preference lands here too. It answers the same question
as the level does: how much gets recorded.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The page filtered by five prefixes: core, app, ui, infra, boot. Two of them told the reader nothing — a logger named HomePage already says it is a widget — so ui is gone and PresLogger writes app like AppLogger. The remaining three become a type rather than a prefix string, and app is defined as whatever boot and core do not claim, so loggers from packages that never heard of our naming still land somewhere. The ring also returns oldest first now, which is how a log reads on the page, in the clipboard and in a file, and drops the per-category counts nothing was reading. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The file exists so a crash leaves something behind. Everything quieter than an error is already somewhere better: the page reads the in-memory ring, and the core keeps its own complete log in data/box.log. Narrowing it to errors removed three mechanisms that only existed to make a busy file affordable — the write batching, its timer, and the size cap with the trim that enforced it. Errors are rare, so each one is written straight through and is on disk before the crash that follows it. The file grows by the handful, so it no longer needs bounding. Four tests cover what the sink now promises. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The file used to hold everything the app recorded. It now holds errors from the app and the core alike, so "App log file" named the wrong two things: the wrong source, and the wrong amount. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Four pieces did one job. The engine kept its own buffer of the last 300
messages and re-emitted the whole list on every line, a parser turned
those messages into an entity, a repository wrapped the parser, and the
page watched the repository.
The engine already writes each message straight into the shared ring:
coreLogFor(event.type).log(getLogLevel(event.level), event.message)
so the buffer, the parser, the entity and its failure type are gone. The
repository's last job — making sure the file exists — moved into the
sink that opens it, which leaves nothing for the repository to hold.
The path resolver now points at the engine's real crash file rather than
the goroutine dump it writes at every start. That dump is not a crash
report: it exists on a healthy machine and says nothing about a failure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The page used to show only what the engine said, from a buffer that started empty every time it opened. It now reads the shared ring, so it holds every category and already has history the moment it opens. The list reads like a terminal: oldest at the top, newest at the bottom, and the view follows the newest line until you scroll away from it. A long press or a right click copies one line. Three chips and a search field sit under the list, next to the newest line rather than the oldest. Two of them hide rows; the third is the core's log level, which changes what gets recorded, so a toast says it lands on the next connection. Below 700px the search takes its own row. Sharing became a choice rather than two buttons: the visible history, which can be filtered again before it leaves, or one of the files, each listed only when it exists and has something in it. A history too long for the clipboard falls back to a file. Both sheets go through BottomSheetsNotifier, so they get the app's own width, corners and route rather than a bare showModalBottomSheet. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Every status went through one info call, success and failure alike. A failure carries the core's own panic text, which is the last thing that should need hunting for among the ordinary comings and goings — and the app log file now keeps errors only, so at info it was not kept at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The confirmation parsed the deep link itself, whose host is always the literal "import", so every link claimed to come from the same place. It now unwraps the link to the subscription URL first and reports that host. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
veto9292
force-pushed
the
refactor/logging-system
branch
from
September 19, 2026 16:26
ebfe779 to
038bde8
Compare
The picker allowed .txt and .json only, so a Clash .yaml, a WireGuard .conf or an .ovpn had to be opened and pasted by hand. It now takes .yaml, .yml, .conf, .ovpn and .mobileconfig as well. Widening it exposed two ways the read could fail. utf8.decode is strict, so one byte that is not valid UTF-8 threw and took the whole import with it; it now runs with allowMalformed, and a UTF-8 byte order mark is skipped rather than left in front of the first character, where it breaks JSON parsing. The path was also dereferenced with a bang, which the web build never has. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The PopScope that sent back to home lived inside the settings branch. Branches stay mounted after the user leaves them, so it kept answering presses meant for other branches and the app could no longer be closed with back at all. Reported upstream as flutter/flutter#193098. Move the handling to the shell, where exactly one instance exists and it always belongs to the branch on screen. The NotificationListener beside it keeps the platform told that Flutter handles back; without it Android stops believing that once a branch pops a page, and closes the app without calling Flutter. Verified on an Android 16 emulator with both the back button and the edge swipe. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A .mobileconfig holds an Apple IKEv2 profile. Hiddify is a sing-box client and cannot read one, so offering it in the picker only ever led to a failed import, with a message that reads like a broken file rather than an unsupported format. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The portable package is repacked with tar -a, which picks the archive format from the file extension. The recipe runs in Git's sh, where tar is GNU tar. GNU tar does not know .zip, so it quietly wrote an uncompressed tar under a .zip name; v4.0.4 and v4.1.1 both shipped that way. Windows' own zip support shows the file as empty and Expand-Archive rejects it, so only tools that read the content, such as 7-Zip, could open it. Call Windows' own tar.exe, which is bsdtar and writes a real, compressed zip for -a. Checked by running the recipe with a stand-in fastforge: the result opens in Explorer and in Expand-Archive, with the Hiddify folder intact. Reported in hiddify#2348. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
veto9292
force-pushed
the
refactor/logging-system
branch
from
October 1, 2026 09:12
6515524 to
8aaeb36
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Supersedes #2325 — this branch contains all of its commits plus the logging
system rebuild, so the two are reviewed and merged together.
Logging system rebuild
Logging kept three separate records of what the app did: the engine held one in
memory, the logs page held another, and a file held a third, each with different
contents and different rules. None held the whole picture, and the page could
only ever show the engine's half.
It ran on loggy, which was suspected of freezing the app during heavy
logging. Measurement cleared loggy — the real cost was
print()at 82-133ms per2000 lines against 11-20ms for everything else — but it was replaced with
package:logging regardless: maintained by the Dart team, a Flutter Favorite,
and it separates producing a record from deciding where it goes.
Bugs fixed
Cost us crash evidence:
FlutterError.onErrorreplaced
presentError, discarding Flutter's own reportif (!isSingleChannel()), which is false on desktop, andlistenSingledrops asubscription for good when its stream errors
IOSink.close()isasynchronous and the trim did not await it
info, so they never reached the fileWrong or misleading:
FilePicker.saveFileonly returns a path, and itsbytesargument throws on macOSis what the Go runtime writes via
debug.SetCrashOutputdata/box.logother in
buildconfighelper.goTRACEfell through to theINFOdefaultenableFlutterDriverExtensioninstalls a
TestTextInputthat takes over the platform text channel. It is nowbehind
--dart-define=driver=trueNotes
log_parser,log_entity,log_repository,log_failure,logger_controller,analytics_logger, the engine's private 300-message buffer,and the Debug mode switch
Loggerdirectly; the mixins and factories incustom_loggers.dartare the only way to get onehiddify-coreis rebuilt: thecrash-report mechanism is newer than the currently released binary
Also included, from #2325
Profile management, sorting and update-status improvements, localization,
connection haptics, native iOS swipe-back, and the
hiddify-corebump todb74dfc.CI
The three failing jobs fail identically on #2325 and are unrelated to this work:
windows/sign.pfx, written fromsecrets.WINDOWS_SIGNING_KEY, which GitHub does not expose to fork pullrequests. The
.exeitself buildsmake android-libsdownloads from the hiddify-core releases. The Kotlin glueexpects
androidPackageName,closeNeighborMonitoranddnsServerAddress.value,which that published build predates
Linux, macOS and the test job pass.