* [Client] Active pill for alts table, dismissed pill for report queue
* Apply suggestion from @ZeldaZach
Co-authored-by: Zach H <zahalpern+github@gmail.com>
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: Zach H <zahalpern+github@gmail.com>
* [Alts] Server-side search controls, IP counts, adjustable window
Plumbs the match-type chips through to the server: include_email/include_clientid/include_ip skip disabled identifier types in both matching and frontier expansion, so excluded types (e.g. throwaway domains) never enter the graph at any depth. Session lookback becomes adjustable (session_months, clamped 1-24, default 6). Each alt row carries ip_count, the distinct recent session IP count shown in a new IPs column. All proto additions are optional with old defaults, so old clients/servers interoperate.
* Fix remaining review threads on the alts search controls
- Disable keyboard tracking on the depth and window spin boxes so typing a
two-digit value sends one search, not one per digit.
- Reload the user under investigation on a filter change instead of reading
the search box, which could switch to a name never submitted.
- Keep a chip's last match count while it is unchecked; the server omits
unchecked match types, so the label used to drop to "(0)".
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Client] Alt badges, breadcrumb trail, investigate button
AltTableDelegate paints palette-tinted match badges with paint-through selection, style warning/critical icons on nonzero counts, and font/elision handling. TabAlts gains a clickable breadcrumb trail, an Investigate-this-user button, and bold usernames. Async alts responses are guarded against stale investigations.
* [Client] Fix alt badge and trail review findings
- Drop the dead VPN warning branch: rowVisible() already hides IP rows
above the threshold, so setVpnThreshold(), its member and the spin-box
connect never took effect.
- Dim IP rows at shared_count > 2 instead of > 1: the count already
includes the investigated account, so every IP row was dimmed.
- Truncate backStack when a breadcrumb is clicked instead of pushing the
current user again, so the trail keeps a single path and Back works.
- Word-wrap the trail label so a long chain cannot widen the tab.
- Extract the status/match pill painting into widgets/utility/pill_painter
and use it from both delegates instead of duplicating it.
* [Client] Route investigate() through the trail helper
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Client] Moderation and alts dashboards
Turn the two staff tables into dashboards without behavior changes: TabModeration gets an identity header (display-size name, status, registered/last-login subline), Reports/Warnings/Bans/Alts count cards with an Open-graph deep link, full-width admin notes, and Alts/Sessions/Staff tabbed sections with tab memory. TabAlts gets toggle chips with match counts, bold usernames, palette-token dimming of shared-IP rows, and a resizable side card for the selected-user timeline with selection restore. Row activation works via Enter as well as double-click; async responses are guarded against stale investigations and retranslation re-applies cached dashboard data.
* [Client] Report queue dashboard and status pills
TabReport becomes a dashboard: selected-report identity header (display-size user, bold status, category/time/room subline), Description/Chat/Comments/Reported-user tabbed sections with tab memory, always-visible user context with empty cues, and bold reported users. ReportStatusDelegate paints palette-tinted status pills with paint-through selection, translated status strings, and font/elision handling. report_utils gains formatReportStatus plus an opt-out status-color flag so the delegate owns color in the mod queue while the user dialog keeps its colors. Async responses stay guarded; retranslation re-applies header and comment states.
* Fix remaining review threads on the report queue dashboard
- Read the raw report status from Qt::UserRole in DlgMyReports, so regular
users can comment on their own open reports again.
- Exclude the server's depth-0 seed row from the related-alt count.
- Dim only IP matches shared by more than the pair, not every IP match.
- Activate alts rows on double-click or Enter instead of `activated`, which
fires on a single click on single-click platforms.
- Fold the reported-user bolding pass into the fill loop.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
TabAlts gains client-side match filters (Email / Client ID / IP toggles plus a hide-IPs-shared-by-more-than-N-users threshold for VPN noise) with a shown/hidden status count, and a Selected user panel that loads the row account's counts, admin notes, warning history, and ban history in one place. Detail requests are guarded against stale selections.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
TabModeration gains an Open alt graph button plus alts-table double-click, both routing to the User Alts tab. UserContextMenu gains an Investigate alts action. TabAlts gains row double-click navigation with a Back history stack so moderators can walk the alt graph without retyping names.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Client] Transient User Alts investigation tab
New TabAlts tab with user search, depth selector (1-3), and an alt table showing per-row match evidence (Via, Depth, Shared fan-out) alongside the account data. Sends Command_GetUserAlts with the requested depth and degrades gracefully against old servers (missing evidence fields render as dashes). Wired as a transient single-reuse tab via TabSupervisor::openTabAlts with no menu action or persisted setting; closed on disconnect like the other staff tabs.
* Fix remaining review threads on the user alts tab
- Switch to the User Alts tab when it is created, so the first investigate
click does not open it in the background.
- Treat an empty RespOk as "user not found" and use a generic message for
error codes, matching what the server actually returns.
- Exclude the depth-0 seed row from the related-account count.
- Guard the reply with the requested user name so a stale response cannot
overwrite a newer one.
- Move the duplicated formatEpoch body into widgets/utility/epoch_format.h.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Server] Recursive alt search with match evidence
getUserAlts() now walks a breadth-first graph to the requested depth (clamped 1-3): each level expands frontier accounts over registration email, current client id, recent session IPs (6-month window), and historic session client ids, so identifier changes no longer hide older alts. Every row carries match_reason (email / clientid:<id> / ip:<addr>), depth, and shared_count fan-out so moderators can distinguish real alts from VPN/shared-network overlap. Command_GetUserAlts depth is plumbed through; unset depth defaults to 1, preserving previous single-hop behavior for old clients.
* [Server] Fix alt search review findings
- Seed the BFS with the stored user name so the searched account is not
re-listed as its own alt at depth 1.
- Normalise emails to lower case for the membership and fan-out checks;
SQL compares them case-insensitively.
- Widen the per-level row limit by the visited count so level 2+ cannot
return only already-known accounts.
- Prepare dynamic IN-list queries locally instead of through the shared
prepared-statement cache, whose keys are query texts.
- Count historic client-id fan-out from _sessions, not from the current
_users ids.
- Order capped identifiers by relevance (current email/client id first,
then most recent session IPs) instead of alphabetically.
* Apply suggestion from @ZeldaZach
Co-authored-by: Zach H <zahalpern+github@gmail.com>
* Apply suggestion from @ZeldaZach
Co-authored-by: Zach H <zahalpern+github@gmail.com>
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: Zach H <zahalpern+github@gmail.com>
ServerInfo_UserAlt gains match_reason, depth, and shared_count so moderators can see why an account was flagged and how far it sits from the investigated user. Command_GetUserAlts gains a depth parameter (default 1) for transitive alt-of-alt searches. No behavior change: server still answers depth 1 and leaves the new fields empty until the follow-up server/client layers land.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
The macOS packaging leg retries hdiutil for actions/runner-images#7522,
but it gave up ten seconds after the first failure: shorter than an
XProtect scan of the DMG it just wrote. It also re-ran a failed detach
unchanged, so a busy mount kept every attempt busy, and a wedged
DiskImages helper held the step until the job's 100 minute timeout.
- Grow the delay between attempts from 1s to a 30s cap.
- Retry a failed detach with -force, and force-detach a stale volume
before a create or attach.
- Kill a call that exceeds HDIUTIL_TIMEOUT (300s) and give up after two
timeouts instead of spending the job budget on a wedged helper.
The wrapper still exits non-zero when it gives up, so CPack reports the
hdiutil failure where it happens.
Re-enable ccache on the three pull-request macOS legs (14, 15 and 27
Debug), which #7792 dropped because none of them persisted a cache.
Every leg now restores and saves its own entry on master, keyed by
target, and the eviction step keeps each entry inside its bound.
The bounds are sized from the measured working set: one macOS
configuration wrote a 0.5 GB ccache directory at -g1 with the old 700M
bound, and the debug-information change builds the vcpkg legs at level
0, which shrinks it to ~0.4 GB. 700M holds the whole configuration for
each leg, and the three entries add ~1.2 GB.
That fits the quota once master's slimmed Qt archive set and the CodeQL
prune are counted: the live total before the compiler caches is 7.1 GB,
Windows adds 0.95 GB, and the three macOS entries add 1.23 GB, for
~9.3 GB of 10.
* [CI] Build the Windows and macOS legs without debug information
Debug information is most of what a compiler cache stores: one Linux
configuration measures 1.2 GB with full DWARF and 660 MB at -g1. The Windows leg
cannot be lowered that way, because MSVC has no -g level and /Z7 is the only
cacheable form, so it sits at full debug info and fills a 1.2 GB cache.
Teach COCKATRICE_DEBUG_INFO_LEVEL=0 to mean something on MSVC by leaving /Z7
out, and build the vcpkg legs (Windows and macOS) at level 0 for every non-tag
ref. A tag is the build that ships and is symbolicated, so it keeps the full
set. The Linux legs keep -g1: a sanitizer report without file and line is not
worth the quota it would save.
Report the ccache directory and the object total at the end of each leg, so the
working set a bound has to fit is a number in the log rather than an estimate.
* [CI] Size the Windows cache for the no-debug footprint
Measured on run 42377451921, one Windows configuration without /Z7 writes a
890 MB ccache directory, against 1.2 GB with it. A 950M bound holds that with
5.4% headroom, which is the same rule the Linux bounds follow.
The comment block that splits the quota follows: 4.25 GB of bounds, 3.9 GB of
stored compiler cache, 8.1 GB of 10 in total.
ccache drives MSVC since 4.7, but it rejects /Zi and /ZI because the debug
information lives in a shared PDB it cannot restore. /Z7 puts it in the
object file instead, which is what makes the compilation cacheable and
also removes the shared PDB as a serialisation point on a 4-core runner.
Allow ccache on Windows in CMake, install it with Chocolatey, and persist
it under ccache-Windows10 the same way the Linux legs do.
Compiler flags are applied once and for the right compiler:
- Detect the toolchain by CMAKE_CXX_COMPILER_ID (MSVC / GNU / Clang,
warn on an unknown one).
- MSVC: drop /std:c++20 (CMAKE_CXX_STANDARD already adds it) and the
outdated C4251 suppression, add /utf-8, and use /O2 instead of /Ox.
- GCC: drop the no-op -Wno-long-long and the macOS-only -std=gnu++20
override; CMAKE_CXX_EXTENSIONS=OFF now owns the dialect.
- Use add_compile_definitions for SFMT_MEXP instead of add_definitions.
CI build script:
- Only run the schema version check for server builds.
- Accept boolean env flags as 1 and say so in the header.
- Print ccache, ctest and cpack versions next to their log groups.
- Only build the macOS vcpkg triplet when vcpkg is actually used.
- Drop the extra cmake --build verbosity.
- Install NSIS without chocolatey progress noise.
Co-authored-by: toomm <toomm@users.noreply.github.com>
The online installer ships the base package as ~25 archives; aqt installs all
of them unless `--archives` names the ones wanted. Restricting them to what
cmake/FindQtRuntime.cmake requires cuts the Windows Qt cache from 440 MB to
180 MB and the macOS thin Qt download from >200 MB to 60 MB.
The list lives once, in a workflow-level QT_ARCHIVES variable, because both
install steps consume it and the macOS thin-Qt cache key has to change with
it. The addons the build also needs (Multimedia, WebSockets, ShaderTools,
ImageFormats) are not base archives and stay in each leg's qt_modules;
qtdeclarative is required on Windows too, since the onboarding banner links
Qt6::Quick/QuickWidgets.
Rename doc/ to docs/ so GitHub picks up docs/CONTRIBUTING.md and Doxygen keeps including it, and update all references to the old layout:
- Doxygen output now goes to docs-output/, exposed by .gitignore
- docs workflow triggers on docs/** and publishes docs-output/html
- theme submodule moved to docs/doxygen/theme
- schema URLs in both XML parsers point at docs/xsd/
- README, developer docs, desktop workflow filters, .dockerignore and gcovr config updated for the new paths
The v3 card database schema is kept as docs/xsd/cards_v3.xsd.
Player menu shortcuts were bound as soon as a local player was added in the deck-selection lobby. Defer full activation until GameMetaInfo reports the game has started, and bind only Player/aRollDie while in the lobby. Counters follow the deactivation signal, so they cannot re-enable shortcuts before the game starts.
Limited deck views (view top/bottom N cards, scry, surveil) made it
ambiguous which card was the top or bottom of the library. Label the card
itself with a small in-view marker instead of placing text above or below
the whole zone, which became ambiguous once the grid used several columns
or the window was resized.
The marker stays on the part of its card that the column leaves visible:
for a top view, the right end of the exposed strip of the first card; for
a bottom view, the bottom-left corner of the last, fully visible card.
The view logic now answers whether it is a limited library-end view and
which edge it shows. The graphics zone draws and positions a marker on
the first or last displayed card, and re-translates its tooltip with the
rest of the zone view.
Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
Holding or spamming the draw-arrow shortcut fires the action again
before the previous local arrow has been placed. Every new arrow takes
the mouse grab from the previous one, so the previous arrow never
receives the release that deletes it. If the cursor had passed over a
card meanwhile, the orphan stayed visible on the board.
Track the in-progress arrow on the CardItem that started it and cancel
it, together with any child arrows, before starting a new one. The
attach-arrow shortcut (Ctrl+Alt+A) uses the same path, so it no longer
leaks arrows either.
Fixes#4329.
Co-authored-by: mb <mbaecker@unforce.de>
Keep the parser message untranslated so FilterStringLog stays in the source
language, and format the user-facing text in error() instead. That gives both
consumers what they need without localizing the log.
Q_DECLARE_TR_FUNCTIONS provides the tr() context without deriving from QObject,
which would have deleted copy/move assignment and broken the by-value uses in
PlayerActions and ViewZone. Drop the unreachable line-number branch; peglib
reports 1-based lines, so every message keeps master's "position" wording.
Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
The CodeQL job keeps one extracted database per analyzed commit, about 1.07 GB
each, restores the newest by prefix and never deletes any. Four had accumulated
by 2026-11-07: 4.26 GB, 40% of the repository's 10 GB cache quota, of which only
the newest can be restored again.
prune-caches skipped them deliberately, because their key shape belongs to
codeql-action rather than to a workflow of ours. Add that shape and keep exactly
the entry with the latest access date on master; a miss costs one re-extraction
in a job that reports rather than gates.
This is the room the Windows compiler cache needs: with the superseded entries
retired the quota holds 7.1 GB instead of 10.3 GB.
Every macOS leg restores nothing (persist_ccache is false), so ccache only
paid hashing on ~1160 compiles for the 3% of calls that repeat within one
build: macOS 15 measured 37 hits out of 1163. The restored cache this
replaces served 42% and saved about 32s on a 20 min leg.
Drop the three matrix keys, the brew install, and the restore/delete/save
steps, and leave the Linux docker path untouched.
* [CI] Spend the ccache quota on the legs that still thrash
Arch and Debian 13 were the two bounds that still evicted entries they
needed: on master 2026-11-06 Arch served 75% of calls at 96% full and
Debian 13 82% at 96%, while Fedora 45 debug/release served 99.5% at 80%.
Their working sets measure 820 MB and 770 MB, so 850M and 800M could not
hold one configuration.
Raise Arch to 1200M and Debian 13 to 1050M, and drop the macOS 14 cache:
its 800M bound served 42% and saved about 32s on a 20 min leg that is
already shorter than the Windows leg. That frees 800M and leaves the
bounds at 3.9 GB, with the measured quota at about 7.8 GB of 10.
Also document why pull requests, tags and nightlies never write: a PR
save would evict the warm set, tags build full DWARF, and a schedule
shares refs/heads/master with the push. No behavior change there.
* [CI] Correct the quota estimate in the ccache sizing comment
The 7.8 GB figure subtracted the dropped macOS bound without adding the
600 MB Arch/Debian increase. The fixed entries are about 4.4 GB, so
3.9 GB of bounds puts the estimate at about 8.3 GB of 10.
* [CI] Size the Arch and Debian bounds to the measured working set
* [CI] Keep the measured ccache bounds instead of raising them
Steady-state master shows Arch at 90.1% and Debian 13 at 96.75% per-run
hits on the existing 850M/800M bounds. The 75%/82% that motivated the
increase were warming runs right after the -g1 switch invalidated the
cache, not thrashing. Keep the bounds, keep the macOS 14 cache drop.
The Ubuntu 26.04 sanitize leg was the largest single cache entry in the 10 GB
quota (1.2G) and is not on the pull-request critical path: Windows sets that
wall clock. Moving it to build-linux-nightly drops the cache, since nightly
legs persist none, and frees the quota for the legs whose bounds still miss.
Measured on master 2026-11-06, two runs: Fedora 45 debug/release served 99.5%
of cacheable calls at 80% full, while Arch served 75% at 96% full, Debian 13
82% at 96% and macOS 14 42% at 99.96%. The sanitize cache was the largest of
the six and is now nightly-only.
Pull requests keep the uninstrumented suite on Debian 13 (Release) and Fedora
45 (Debug), so the only thing a PR loses is the instrumented run, which now
happens nightly and on tags. The leg keeps a 1.2G scratch ccache so the larger
instrumented objects do not thrash mid-build, and it is allow-failure so a
sanitizer finding cannot stop vcpkg-cache-import from importing the nightly
run's archives.
prune-caches reclaims ccache-Ubuntu26.04sanitize on the next master push.
The Accept header added in ef87b54b was missing asterisks in two
MIME type wildcards: "image/" and "/*" instead of "image/*" and
"*/*". Servers may reject or mishandle requests with invalid MIME
types, preventing card images from loading.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
The "from …" fragments built by getFromStr() carried a leading space so
the sentence templates could concatenate them directly (%2%3). That blank
is invisible on Transifex and most translations dropped it, merging the
card name and the zone phrase in the log (#4943).
Drop the blank from the tr() strings and add the separator in code where
the fragment is built. The space is only prepended when the fragment is
non-empty, so messages that put the zone in the card name instead — "the
top card of their library." — keep their punctuation tight.
The templates stay at %2%3, since %3 is empty on those paths.
- Span chat and alert-word checkboxes across the full grid width
- Add right-aligned Color labels and HEX placeholders to the mention,
highlight and channel color fields
- Reword the horizontal hand option to "requires more space"
Build the card menu's related card actions from complete strings with %1
placeholders instead of appending translated fragments. This also fixes
tr(cond ? "Transform into " : "Attach to "), which lupdate cannot extract:
neither string appears in cockatrice_en@source.ts, so neither was ever
translated.
Give the EDHRec tab title a %1 placeholder so French can render
"EDHRec : %1" instead of concatenating into "EDHRec :CardName".
Keep the colon in tr("Token:") and the leading space in tr(" seconds"),
since that separator is language-specific, and attach //: translator
comments so they are no longer shown out of context on Transifex.
* Parent card menu submenus to the menu that owns them
QMenu::addMenu(QMenu *) does not take ownership of the submenu, and these
submenus were created without a parent. Since CardMenu is rebuilt from
scratch on every right-click, each rebuild leaked the MoveMenu, PtMenu,
card counters and related-cards submenus.
Pass the owning CardMenu as the QWidget parent so Qt's parent-child
mechanism frees them with it.
* Delete the replaced card menu in TabGame to fix menu rebuild leak
PlayerMenu creates a new CardMenu on every right-click and TabGame installs
it with QAction::setMenu(), which does not take ownership, so the previous
menu was never freed. TabGame now deletes the menu it replaces and reuses a
single empty placeholder menu instead of allocating one per update. The
installed menu is also released when the tab is destroyed.
The downloader wrote to a predictable /tmp/<url-basename> path, ignored the
result of write(), and leaked the previous reply when a check was retried. It
also connected no error handler to the reply and had no transfer timeout.
Create the file inside a private QTemporaryDir, write it with QSaveFile and
commit it only after every byte was accepted, report reply errors, give the
request a 30 s transfer timeout, and delete the superseded reply. Ignore a
queued finished()/errorOccurred() from a reply that has already been replaced,
and drop the manual redirect branch because Qt follows redirects by default.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
The bounds in #7667 were set below what one build configuration writes, so
ccache filled to its limit and evicted the entries the next build was about to
ask for. The probe measured what that costs: a 6G cache on one leg served 1059
of 1059 calls and the 700M bound on the same leg served 14%. The quota also
went to legs that finish under the Windows leg, which has no compiler cache.
Debug information is most of an object's size. A measured Arch Debug
configuration is 1.2 GB with the built-in flags and 660 MB at -g1; Debian 13
Release is 1.1 GB and 614 MB. CI therefore builds every non-tag leg at level 1
through a new COCKATRICE_DEBUG_INFO_LEVEL option, which stays empty by default
so local, distro and release builds are unchanged. Level 1 keeps file and line,
which is what a failing test or a sanitizer backtrace reports. Tags and the
nightly legs keep full DWARF for the Sentry symbols.
With one configuration at half the size, the quota goes to fewer legs at a size
that holds one whole configuration, and the legs that duplicate another leg's
coverage give their cache up: Servatrice, Ubuntu 26.04 behind the sanitize leg
of the same distro, macOS 15 and macOS 27 Debug behind macOS 14. Their keys
leave LIVE_CCACHE_KEYS, so prune-caches reclaims the existing entries.
The single Fedora leg built Debug for the suite and Release for the package in
one job (measured on Fedora 44: 30.8 min, the only leg above Windows), so the
package compile queued behind the debug one while one 1.6 GB cache held two
configurations. Fedora 45 now runs as two legs with one cache each, in
parallel.
The option reaches compile.sh inside the container through docker.sh, and
compile.sh turns it into the CMake flag.
Bounds: Arch 850M, Debian 13 800M, Fedora 45 debug 850M and release 800M,
Ubuntu 26.04 sanitize 1200M, macOS 14 800M. That is 5.3 GB of compiler cache
on top of roughly 4.5 GB of buildkit, vcpkg, Qt and CodeQL entries.
Windows still sets the pull-request wall clock: 29.5 min, 23.7 of it compiling
with no compiler cache, because ccache and sccache cannot store the 711 of 1054
translation units that use PCH under MSVC (#7381). That is a separate trial.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Client] Add exclude and color identity filters to the visual database display
* [Filters] Extract the color filter mode derivation into libcockatrice_filters
The mode table and the 'which mode produced this filter set' derivation are
filter semantics, not widget code, so they belong next to CardFilter rather
than in an anonymous namespace in the client. Moving them also makes them
testable: filter_mode_test pins every mode, the Custom fallback and the
'keep the live mode when it still fits' rule.
* [Tests] Cover the negated color identity query
ci! read the camelCase colorIdentity property instead of the lowercase
coloridentity key, so it matched the empty string and rejected every card.
The new case uses a card whose colors and color identity differ, so it fails
on the old lookup and passes on the fixed one.
---------
Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
* [DlgCreateGame] Heap-allocate create game dialog to prevent crash on parent deletion
* [Tests] Run the short-write cases without forking a death test
The three cases that force a write failure - CardDatabaseTest.Xml4ShortWriteKeepsInstalledFile,
OracleDatabaseWriter.ShortWriteKeepsPreviouslyInstalledDatabase and
SpoilerFileTest.ShortWriteLeavesExistingFileIntact - all forked a child, lowered
RLIMIT_FSIZE and ignored SIGXFSZ. That child is killed by SIGSEGV on the macOS
runners, so all three have been red on every macOS leg of master since they landed,
while the same cases pass on Linux.
Cap the soft limit in the test process instead: ctest already runs one case per
process, and the guard restores the limit and the SIGXFSZ handler, so a whole binary
keeps working. Same real EFBIG failure, no fork, and macOS keeps its coverage of the
writers.
* [CI] Derive DEBUG_TEST and RUN_TESTS in the nightly job
The shared steps gate on both, but build-linux-nightly never set them, so its
coverage leg skipped "Build debug and test" and then failed collecting a report
from an empty build/ directory on every nightly and tag run. continue-on-error
kept the run green, so the coverage artifact silently never existed.
Derive both exactly as the pull request job does.
---------
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
The badge rendered `theme:icons/pin` through the pixmap generator, which
resolved `currentColor` once and cached the result, so the glyph stayed at its
first palette and was rasterized at DPR 1.
- Render the glyph through `loadThemedIcon`, at the label's device pixel ratio,
and re-render it on palette and theme changes.
- Compose it onto a rounded backdrop filled with `Window` and outlined with
`WindowText`. The badge floats on card art rather than on a palette surface,
so a bare outline vanishes into art of the same lightness; the backdrop keeps
it readable on every printing, and both roles still follow the palette.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Settings] Delete update channels at shutdown
SettingsCache owns the stable and beta update channels as raw pointers, and
each owns a QNetworkAccessManager, so the process leaves QtNetwork objects
alive past QApplication teardown.
Delete them from a QCoreApplication::aboutToQuit handler instead of the
destructor: SettingsCache is a Q_GLOBAL_STATIC, so ~SettingsCache() runs
during static teardown, after QApplication is gone, and destroying the
network managers at that point is the post-application window that ASan
reports. The hook is installed on the first instance() call that has an
application object, since the first access may predate it.
* [Game] Follow the palette in judge message tags
Judge tags hardcoded a black prefix and a `theme:icons/scales` image inside rich
text, where `currentColor` cannot resolve and a palette change leaves the tag
unreadable. Render the scales glyph through the themed icon engine, register it
as a document image resource, and repaint the log when the palette or theme
changes.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Icons] Add an icon asset ratchet test
The icon rules were enforced by review only, so the next Inkscape export could
reintroduce a baked fill or a physical size without anyone noticing.
Add a test that rejects non-24 viewBoxes, physical sizes, editor metadata,
hardcoded paints, missing tokens, color-coded filenames, and rasters. Keep the
legacy baseline empty so the ratchet can only tighten.
* [Tests] Drop the empty icon baseline
The legacy icon set is fully paid down, so the ratchet had nothing left to
ratchet: BaselineIsWellFormed required the baseline to be empty, which made
the baseline file, the stale-entry diff and the entry-format regex
unreachable. The rules are enforced directly now.
- AssetsFollowTheRules fails on any violation of collectViolations().
- tests/icon_assets/icon-baseline.txt is gone with its reader.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Icons] Migrate UI glyphs to palette-token SVGs
Runtime inversion is not a theming system: it rotates hue, breaks duotone art
and mishandles alpha, and a QPixmap snapshot cannot follow a palette change.
Redraw the top-level UI glyph set on a 24x24 grid with `currentColor` as the
paint token, drop Inkscape/Sodipodi cruft, and delete the icons whose metaphor
was duplicated (cogwheel/gear -> settings, scale_balanced -> scales,
update.png -> reload).
Load the glyphs through `loadThemedIcon` so the engine resolves the token from
the palette on every request, and let `themePixmap` use the same resolution for
one-shot pixmaps. `loadColorAdjustedPixmap` is gone; every caller now keeps a
QIcon, which `SettingsButtonWidget::setButtonIcon` accepts instead of a QPixmap.
Content art (mana, counters, countries) keeps its authored colors.
Add a render test covering aspect ratio, palette flips, icon modes and
high-DPI pixmaps.
* [Tests] Keep the icon render test QApplication alive until exit
Creating it as a function-local static tears it down before Qt's library statics
at exit, and libQt6Network then runs QObject destructors against freed memory —
a SEGV the sanitizer leg reports after the tests have already passed. Leak the
application instead, the usual pattern for a test binary.
* [Icons] Render lock and dropdown glyphs through the icon engine
Both generators rendered the sentinel SVG to a fixed height x height pixmap
at DPR 1. The pre-migration path rendered larger and let QIcon scale it, so
on a 2x display the games-list lock (13px) and the banner dropdown arrow
(24px) came back crisp; the new path hands the widget a DPR-1 bitmap that
gets upscaled and blurred.
- The games list asks the themed engine for `icons/lock` directly, so the
item view paints it at the device pixel ratio and the lock also follows
HighlightedText on a selected row.
- The dropdown goes through `loadThemedIcon(...).pixmap(size, dpr)`, which
keeps the glyph's aspect ratio and renders at the requested ratio.
- LockPixmapGenerator has no consumer left, so it and its cache are gone.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
A QPixmap snapshot cannot follow palette changes, so expose a QIcon whose
engine resolves the `currentColor` sentinel from the live palette on every
pixmap request. Icons stay legible across light/dark switches without
recreating actions or buttons.
- Resolve the asset, its bytes and its intrinsic size once, and drop them on
ThemeManager::themeChanged/paletteChanged. A repaint does no file I/O and
no theme.cfg parse; the palette color stays part of the render cache key.
- Centre the glyph on QRectF(rect).center(): QRect::center() floors and uses
right = left + width - 1, which shifted every 16x16 item-view glyph up and
left by a logical pixel and clipped its last row and column.
- Take a raster override's intrinsic size from QImageReader instead of the
24x24 SVG default, so a non-square override reports the box it draws.
- Set the device pixel ratio before inserting into the render cache, so a
cache hit no longer detaches the pixmap on every paint.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Qt's SVG renderer has no CSS cascade, so a `currentColor` fill has to be
substituted before the bytes reach QSvgRenderer.
- Match and replace on bytes: both the sentinel and the replacement are
ASCII, so the document never goes through a UTF-16 round trip, and files
that are not valid UTF-8 survive unchanged.
- Document that the match is a plain case-insensitive substring, so an
identifier that merely starts with the sentinel gets rewritten too.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Add the Game History dock to the left of replay tabs, wire it to the replay timeline, and register it with the View menu and layout reset.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Render the event index as a scrollable list of event boxes. Selecting an entry seeks the replay to that event and keeps the current event highlighted while playback advances.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Turn replay protocol events into short localized summaries with an actor, badge, and time so the history UI can render them without retaining the replay or duplicating the chat log.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* Preserve spoiler files and serialize update reloads
* Surface spoiler removal reload failures and allow disabled-state retry
* Address review feedback on spoiler update lifecycle
Only remove the spoiler file when the user unchecks the setting; opening
Settings no longer queues an unrequested reload for a leftover file.
Remove the spoilersUpdatedSuccessfully signal, which had no receiver.
* Fix spoiler settings state after reopen and while disabled
Reopen Settings while an application-owned update is running: the new page
now observes that updater, so the button reads Updating... and re-enables
on completion instead of offering a second update that cancels the first.
Observation does not take ownership: only updaters the page creates are
retired with deleteLater, because the launch check is stack-allocated.
When spoilers are off, the hint now describes what Reload Card Database
actually does, stays readable, and wraps instead of clipping.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* Preserve installed card databases when Oracle refresh fails
* Fix the Oracle refresh verdict and cover the parser write path
A background refresh that already installed cards.xml exited 1 when the
token save afterwards failed, so the client kept its old in-memory
database and reported the update as failed. Return the same verdict
main() already computes: a reject after the database was written is a
successful update.
Cover the writer Oracle actually uses: add a short-write death test for
CockatriceXml4Parser::saveToFile, which reverted to QFile would truncate
an installed database without any test noticing.
Route a failed background token download through reject() instead of a
modal box on a hidden wizard, which left the client waiting with no
timeout and blocked later updates. Test the first non-space byte in
place rather than copying the payload, and report an import refusal with
the importer's own message.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>