- Remove the duplicated download loop and the stray '<' plus extra brace
that left the head unbuildable.
- Skip the fallback upload under DownloadMode so a local-only deck is not
pushed when the user asked for downloads only.
- Treat a server row without a hash as unknown rather than changed, so
pre-migration decks are not re-downloaded on every sync.
The unmatched-server-rows pass queued a download for every row with no
local file, including rows whose sidecar record is being deleted in the
same run. The delete ran first, so the download then failed with
NameNotFound and the summary reported a clean delete as removed plus
skipped. Cancel the pending download for the deleted row id instead; a
failed delete keeps it, which preserves the restore-the-file fallback.
- Treat a record whose server row is gone as a remote delete only when the
local file still matches the sidecar's base hash. An edit made after the
remote delete is newer than the delete, so it uploads and revives the row
instead of being removed.
- Revive a tombstone with the same user and guid in cmdDeckUpload instead of
inserting a second row next to it, so the deck keeps its id and version
history and the promise in servatrice.ini.example holds.
- Run tombstone purging on its own clock, driven by the new
[deck_sync] purge_interval option, so disabling share cleanup no longer
disables purging silently.
- Write the base snapshot through QSaveFile and log a failed commit, so a
crash or a full disk cannot leave a truncated base for DeckDiff::merge().
Sync keyed local files to server rows by path, but the server names a row
after the deck, not after the file, so any deck whose file name and deck name
differ uploaded again on every run. A second pass then brought the server copy
back as an extra local file.
- Match by the sidecar's row id, then guid, then content, and only then by the
server path, so renaming either side no longer duplicates a deck.
- Key the server tree by row id: the server allows several rows with the same
name in one folder, and keying by path silently dropped all but one.
- Stop discarding rows whose name contains '/' ("U/W Control"). They stay
matchable, and only the download file name is sanitised.
- Build the download path from the folder plus a sanitised row name, including
every folder segment, so a hostile server name cannot escape the deck folder.
- Download a colliding server row under a unique name and count it, instead of
skipping it without a trace.
- Hash the whole deck instead of the card hash: SHA-1 over the native deck
string covers name, printings, banner, tokens and sideboard plans. The client
and the server hash the exact bytes they exchange, so both agree.
Adds deck_content_hash_test for the client/server hashing invariant and for the
name-only change the card hash cannot see.
- Emit SyncSummary from syncFinished() instead of four positional ints, and
build it from a single currentSummary() helper, so the counts cannot drift
between the status message and the signal.
- Use a designated initializer for LocalFile, like the other records.
- Drop setClient() and setColorIdentityProvider(): both fields are already
constructor parameters and the setters had no callers. setProfile() and
setDeckPath() stay, since the tab re-reads them before every run.
- Fix the include paths in tab_deck_storage.h and the narrowing conversion of
the tree item id, so the branch builds again.
Base deck with 4 Mountain (DOM 250), local adds 2 Shock (M19 156), remote
adds an unrelated card. Asserts the merge keeps one Mountain node with its
printing and adds a single Shock node carrying M19/156.
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.