MainWindow::closeEvent has a static re-entrancy guard that returns early on a
second close event, leaving it in its default accepted state. DlgUpdate reached
from a nested event loop while a shutdown prompt was up could then get close()
== true with no shutdown work done (no settings flush, no tab shutdown) and tear
down the process behind an unanswered prompt.
Add MainWindow::closeForUpdate(), which reports false when a close is already in
progress or was vetoed, and gate the update-exit on it.
Wait for MainWindow::close() to be accepted before quitting after the
update installer is launched. When the close is vetoed (a running card DB
update, open games, or an unsaved deck), keep running and tell the user
the installer is already waiting, instead of exiting over their answer.
Also fix the comment so it does not claim settings are saved on the vetoed
path.
- Drop !include nsExec.nsh: nsExec is a plugin DLL, not a header, so
makensis aborts before NSIS can build the installer.
- Match processes by image name and executable path under $INSTDIR via
PowerShell, then close (WM_CLOSE) and force-stop only those PIDs, so an
unrelated oracle.exe (Oracle DB) is never killed on a silent /R update.
- Gate the stale-runtime-DLL purge on $INSTDIR\cockatrice.exe existing, so
a first-time install can't recursively delete an unrelated Plugins
directory.
Ctrl+scroll over a card display now resizes the cards everywhere a card
size slider is shown, matching the standard way of resizing content.
CardSizeWidget learns to forward Ctrl+wheel events onto its slider via an
event filter that is installed on the display container and, when present,
on the scroll area's content widget so the resize intercepts the wheel
event before the view scrolls. The existing slider valueChanged wiring
then rescales the displayed cards.
Applied to the visual deck editor (per card group, covering flat and
overlapped layouts), visual database display, printing selector, sample
hand, visual and public deck storage, Archidekt previews and EDHRec card
displays.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
CardPictureLoaderLocal compared the full file name (including extension)
of the custom-folder candidate against the extension-stripped base name of
each directory entry, so a CUSTOM subfolder image like
pics/CUSTOM/poker/1 of Hearts.png never matched and the card fell through
to the network. Compare the complete base names instead.
Add matcher tests covering CUSTOM subfolder resolution.
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Theme] Revert default light palette and only include AppColors
* [Style] Also remove dark mode palette
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* tests: add manual picture loader benchmark against the real card hosts
(cherry picked from commit 588d71d3b9a8278ab98b847802908f322c1035b7)
* tests: address review on picture loader benchmark
- Sandbox via unique app/org names plus Linux-only XDG redirection, derive
the warm-cache probe and data path from SettingsCache, and bail out when the
temporary sandbox cannot be created.
- Run each pass with a fresh worker and shut workers down before reading the
429 counters, so the redirect cache is persisted and no worker thread can
outlive the stack-local counters or the installed message handler.
- Make s_activeCounters atomic and always forward log output when the previous
handler is the built-in (nullptr) one.
- Validate --timeout-min, scale the cached-pass budget with --count, honour the
first --url as the stress template, and add the missing trailing newlines.
- Zero-initialise SettingsCache members so the benchmark mock cannot
dereference an indeterminate pointer.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [PictureLoader] Fix worker thread shutdown and cross-thread cache clearing
clearNetworkCache() ran directly on the UI thread while the worker thread
owned the disk cache and redirect cache, racing cache reads/writes. Make it
a worker-thread slot invoked via a blocking queued call when the thread is
running, so the 'Cached card pictures have been reset.' message is truthful.
The worker thread was also never quit()/wait()ed: both destructors only
deleteLater'd their objects, so Qt warned 'QThread: Destroyed while thread
is still running' and leaked a running loop at exit. Wire the worker's
finished() signal to its own deleteLater() (canonical worker-object
pattern), add shutdownThread() to stop the loop, and let CardPictureLoader
destroy the QThread only after wait() has returned.
* [PictureLoader] Guard cache teardown and stop blocking the UI thread
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [PictureLoader] Add user-configurable per-host request caps
Picture downloads were throttled to a uniform 10 requests/second per host
with no way to tune a specific server. A rate-limited API host (Scryfall
caps at 10 req/s) can trip 429s during bursts, and CDN hosts with no rate
limit were throttled needlessly.
Introduce developer-owned per-host caps that users can only ever lower,
never raise, exposed in the download settings page:
- DownloadSettings::DEVELOPER_HOST_CAPS sets the ceiling per host
(api.scryfall.com 9, cards.scryfall.io unlimited, others 10).
- A new hostRequestLimits setting stores user overrides in downloads.ini;
clampHostRequestLimit() bounds them to [1, devCap] so a user can reduce
api.scryfall.com to 5 but never raise it above 9.
- The picture worker seeds, halves on 429, and recovers its sustained
per-host allowance against the effective ceiling instead of the global
maximum, and skips per-host accounting entirely for unlocked hosts
(cards.scryfall.io) while global pacing and 429 backoff still apply.
- The deck editor settings page gains one spinbox per known host, each
clamped to its developer cap.
* [PictureLoader] Let unlocked hosts skip dispatch pacing; adjust limits per URL
Two refinements to the per-host request caps:
- Unlocked hosts (UNLIMITED_HOST_QUOTA, e.g. cards.scryfall.io) no longer
wait on the 100ms dispatch pacing or consume the global per-second quota.
dispatchQueuedRequest fires their queued requests back-to-back, bounded
only by their 429 backoff window and Qt's per-host connection pool, so
an unthrottled CDN is not artificially slowed.
- The deck editor download settings page replaces the static grid of one
spinbox per known host with an "Adjust Rate Limit" toolbar action on the
URL list. It picks the host out of the selected URL and clamps the entry
against the developer cap table (including for user-added URLs).
Also fixes a review finding: resetRequestQuota could write the
UNLIMITED_HOST_QUOTA sentinel (-1) into the sustained per-host quota when a
host became unlocked mid-run, permanently poisoning its allowance. Stale
entries for unlocked hosts are now dropped, and the per-second seed is
clamped against the effective ceiling so a lowered limit applies immediately.
* [PictureLoader] Cap unlocked host bursts and adapt them to 429s
* [PictureLoader] Store per-host limits readably and show them per URL
* [PictureLoader] Make dispatch and rate-limit bookkeeping key on the real host
Addresses ZeldaZach's round-4 review nits:
- Dispatch now resolves the cached-redirect chain before the in-flight gate,
so a redirect learned after a URL was queued can no longer bypass the
MAX_IN_FLIGHT_PER_HOST cap and drain the whole queue onto the redirect
target, which may carry its own developer cap. processSingleRequest does
the same so the allowance math keys on the host that is actually hit.
- The per-host in-flight slot is released when the reply is destroyed (with
the worker as the connection context) rather than on a 'finished'
connection bound to the work object, so an aborted reply or a work object
deleted while a reply is pending can never permanently shrink the fast
path's concurrency.
- storeSettings only prunes limits for hosts with neither a URL nor a
developer cap, so throttles on redirect targets (api.scryfall.com ->
cards.scryfall.io) survive URL removal.
- Unlocked hosts are offered 0..UNLOCKED_HOST_LIMIT_MAX (50) in the rate
limit dialog, matching clampHostRequestLimit() and the documented
hand-editable range, so values written into downloads.ini are no longer
silently rewritten on the next edit.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [PictureLoader] Seed per-host allowances on demand and skip hosts in 429 backoff
The quota reset re-filled every host's remaining allowance to a full
MAX_REQUESTS_PER_SEC as soon as the queue had a request for it, so a server
that was just rate limited could be hammered again at full speed immediately
after (or even during) recovery.
Only seed a host's allowance the first time it is dispatched in the current
second, seeded from its reduced sustained quota, and skip hosts still inside
their 429 backoff window entirely, handing the entry back to its worker so it
can wait the backoff out or fall through to another source instead of parking
in the queue with no reply pending. Deferrals wait on the host that is
actually blocking the request (cached-redirect targets and the reply host of
a 429) rather than the current card URL's host.
Rebased onto network-requests/request-pacing, which absorbed the earlier
pacing and dispatch-guard commits, and reuses its updateTimerState idle-429
recovery plumbing.
* [PictureLoader] Hand backed-off entries back across the whole dispatch tick
Review fixes on the 429-backoff skip:
- Hand a backed-off queue entry back to its worker via
scheduleDeferredRetry(host) instead of startNextPicDownload(), which was
looping on the pre-redirect host every 100ms tick whenever the queued URL
was a cached-redirect target whose redirect host was the one in backoff.
- Keep scanning the queue after a hand-back (--i; continue) so a backed-off
host at the head no longer consumes the entire dispatch tick, stalling
every healthy host further down.
- Emit imageRequestSucceeded(url) before makeRequest()'s cached-redirect
backoff return so the status bar reclaims the deferred URL's widget instead
of inflating the progress bar forever.
- Only spend a per-second allowance when makeRequest() actually issues a
request: it returns nullptr when the redirect target is backed off and the
work is handed back, so the slot would otherwise be wasted on a no-op.
- Gate the dispatch-time backoff skip on requestTouchesNetwork() so entries
served straight from the disk cache (e.g. with downloads disabled) are not
bounced into a 30-60s deferred retry for a host that 429'd earlier.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [PictureLoader] Pace requests and run the throttle timers on the worker thread
Previously the whole backed-up queue was drained in a burst as soon as a
request was enqueued, sending up to 10 requests back-to-back and then
immediately re-filling the quota one second later. That hard-bursts a
rate-limited API like Scryfall's (10 requests/second) into a 30 second
lockout.
Introduce a pacing timer that dispatches a single queue entry every
100 ms, so the per-second allowance is used smoothly instead of in spikes,
and keep the quota timer at 1 second. Also fix both timers' thread
affinity: they are QTimer value members and so are not QObject children,
meaning moveToThread() on the worker left them on the main thread while
the slot code started them from the picture thread, which was a no-op that
also warned. They are moved to the worker thread explicitly and started
lazily from there.
* [PictureLoader] Guard dispatch timer restarts and drop dead request quota
* [PictureLoader] Reconcile quota-timer lifecycle with idle 429 recovery
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Filter] Restore exact, case-insensitive set code search (#7332, #7333)
* [Filter] Group set query suffix modes into a subrule
Address review feedback: keep the 'e'/'set' prefix in one place and move
the three suffix modes (exact, negated, release-date comparison) into a
dedicated choice-like rule so sv.choice() still dispatches on them.
Add coverage for the release-date comparison mode.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* Render room chat history usernames as live user tags (#1595)
Room chat history carries no user-level data, so history usernames were
rendered as fixed, zero-level tags: the moderation context menu was missing the
buddy/ignore and promote/demote entries and the stored name casing was never
corrected.
Resolve each history author against the online user list and, when found, build
the user tag with the real user level and name so the entry behaves exactly like
a live chat tag. Offline users keep the plain fallback.
- chat_view: look up history authors via getOnlineUser for the real level/name
* Fix offline history usernames getting a leading underscore
The offline fallback used "_" as the level placeholder, producing an
href of user://__NAME. The hover handler splits at the first underscore,
so interactions targeted a nonexistent "_NAME" user. Use level 0 so
offline history entries render as zero-level tags like before.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Card] Add a setting for the language used in card search
Localized card names and texts can now be searched too, controlled by a
'Language used in card search' toggle (English, selected card language, or
both) on the general settings page. Untranslated cards always keep matching
in English.
Removed the redundant local copy of the URL templates list in the localized
picture loader while here.
* [Card] Bind search language per FilterString instance
The peg parser rules are set up once per process, so the GenericQuery and
OracleQuery rule actions could not capture per-instance state. Instead of
storing the search language in a process-global that FilterString instance
methods mutate, hand it to the rule actions through a thread-local parse
context and copy it into the filter closures they produce. Card evaluation
in FilterString::check no longer reads any process-global state, and each
instance keeps the language it was built with; constructing one instance no
longer changes what unrelated instances (deck filter, drop-to-hand, zone
views) match against.
The card database display model stores the raw query and rebuilds the
FilterString when the search language changes, since the language is now
bound at parse time.
Add tests for the English/Selected/Both search modes, the English fallback
for untranslated cards, and per-instance language independence.
* [Card] Pass the card search language to deck and zone card searches
Wire the two remaining FilterString consumers to the configured card search
language so card-name matches respect it everywhere:
- DeckFilterString now takes the search language and mode, exposes them to its
[[card name]] rule action via a thread-local parse context (same pattern as
FilterString), and the engine's card database uses them for content search.
- ZoneViewZone reads the card language from CardsDisplaySettings when applying
its search filter, and the reveal-zone widget re-applies the active search
when the language setting changes.
- The deck-storage search re-runs its filter against the current card language
setting, including live re-application when the setting changes.
Game-action targeting (DlgMoveTopCardsUntil) intentionally keeps evaluating
against English card names.
* [Card] Rename CardSearchLanguage to SearchLanguageMode
* [Card] Restore displaced namespace doc in card_localization.h
* [Filters] Pass CardSearchLanguage as a single struct
* [CardSearchModel] Match English and localized names in Both mode
Card names are stored in both English and localized forms, so search for
matches in both during the 'Both' search mode instead of checking only
the localized name.
* [CreateTokenDialog] Fetch cardsDisplay settings inside the apply lambda
Avoid capturing the raw settings pointer in the lambda: resolve the card
language and card search language from the settings cache at call time so
the values are always current when the search language is re-applied.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Doxygen] More picture docs
Took 12 minutes
Took 8 minutes
* [Doxygen] Move custom card pictures page into card_pictures subfolder
The actual cyclic dependency fix is converting the mutual @subpage
reference from custom_card_pictures to fixing_card_pictures into a
plain @ref, so the page hierarchy no longer loops back on itself.
* [Doxygen] Correct card-picture docs per review
* fix table layout
* [Doxygen] Deduplicate placeholder table and document image overrides
- Make custom_card_pictures.md the canonical home of the URL reference-point
table; loading_card_pictures.md cross-references it through @ref instead of
maintaining a second copy (unaddressed review comment).
- Switch the remaining @subpage custom_card_pictures to @ref in
fixing_card_pictures.md so the page keeps its single parent under
user_reference.
- Document the Image Overrides feature added in #7311/#7312 on the user page,
loading_card_pictures.md and fixing_card_pictures.md: local override storage,
the downloadedPics root lookup, exact file-name matching, and the set-folder
vs flat export naming schemes.
* Update doc/doxygen/extra-pages/user_documentation/card_pictures/custom_card_pictures.md
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
* [DeckShare] Browse and open public decks with loading, error and accessibility states
Add a public-decks tab that lists decks published by other users using the
server's deck visibility feature, previewing each deck's banner card, color
identity, tags and upload time without downloading the deck list until the
user opens it.
- Add a public-decks tab with a shared-settings widget and a remote model
that fetches the target user's decks and refreshes both automatically and
on user request, with a loading indicator and a server-error message
instead of a blank tab when the fetch fails or the connection drops
- Render each deck as a focusable preview tile whose banner, color identity,
tags and upload time follow the existing Preview settings, with the deck
name announced as the tile's accessible name and Space/Enter opening the
deck, mirroring the shared-deck preview tile
- Show a message box when opening a public deck fails or arrives corrupted
- Publish and unpublish decks from the server storage toolbar and context
menu, toggling the deck's own visibility bit (what the server persists)
rather than the inherited effective state, and batch the visibility
refresh until the last in-flight change is acknowledged
- Add the Show Upload Time setting so the tile's upload stamp can be hidden
like the other preview details
- Update the retranslateUi wiring for the new public-decks tab and rename
the share action tooltip from "Deck share" to "Share link"
* [DeckShare] Adapt deck upload to the server-derived banner and tag protocol
The server now derives the banner card and tags from the uploaded deck
list itself, so Command_DeckUpload only carries the client-computed color
identity. Drop the reserved banner/tag setters from the editor and storage
uploads, send the color identity on remote saves, and read tags from the
now-repeated ServerInfo_DeckStorage_TreeItem field.
* [DeckStorage] Refresh the visibility column with a guarded timer instead of a latch counter
A dropped visibility reply used to leave the pendingVisibilityChanges
counter permanently positive, so the Public/Private column never refreshed
again and nothing reset it on disconnect. A restartable single-shot timer
with a boolean guard re-reads the tree whenever publishes quiet down and is
stopped on disconnect, so a lost reply costs one stale refresh instead of
killing the column for the session.
* [DeckStorage] Summarize batch publish failures when the batch drains
Each rejected node stacked its own modal dialog, so publishing a ten-deck
selection against a rejecting server made the user dismiss ten dialogs one
at a time. Failures are now collected while the batch is in flight and shown
as a single summary when the visibility refresh timer fires; a reply that
lands outside an active batch still reports right away.
* [PublicDecks] Time out the loading state so a dropped reply cannot wedge the tab
loading only cleared in decksReceived, but the ping sweep can drop a pending
command without ever emitting finished, leaving the tab stuck on 'Loading
public decks...' and the refresh button permanently inert. A single-shot
timer started per refresh clears the latch and reports a timeout; the latch
also clears when the client disconnects.
* [PublicDecks] Escape remote-crafted text in tooltips and the tab title
Deck names and usernames come from other users' records and Qt renders
QLabel tooltips as AutoText, so a name like '<h1><table>...' parsed as
markup. Escape and bound the deck-name tooltip and escape the username
interpolated into the title label.
* [DeckStorage] Distinguish an inherited public state in the visibility column
The column reported the effective state while publishing toggles the node's
own bit, so a private deck inside a public folder already read 'Public' and
toggling appeared to do nothing (and toggling again silently unpublished
it). The cell now shows 'Public (inherited)' for that case and the tooltip
explains why.
* [PublicDecks] Run retranslateUi at construction and name the refresh button
retranslateUi was never called from the constructor, so the tooltips set
there were absent until a language change. Call it before the first refresh,
and give the icon-only refresh button an accessible name for screen readers.
* [PublicDecks] Keep the empty and status variants correct across language changes
retranslateUi unconditionally rewrote the empty label to the 'nothing
published' variant, stomping the 'no decks match your filters' choice
rebuildGrid had made, and a visible loading message stayed in the old
language. Let retranslateUi pick the same variant rebuildGrid does and
re-show the status so it retranslates.
* [VDS] Share one color-identity match rule between the two deck grids
The remote public decks model verbatim-copied updateColorMatches' switch,
down to the ExactMatch normalization and the fact that Includes/Excludes do
not normalize case. Extract colorIdentityMatches() next to the FilterMode
enum and call it from both so the subtle rule cannot drift.
* [DeckStorage] Drop the unused tree widget model accessor
The accessor handed the model out past the wrapper methods that exist to
keep it encapsulated, and nothing in the stack called it.
* [DeckShare] End the public-decks files with a trailing newline
keeps the final line's diff clean and stops clang-format CI from flagging
the files.
* [VDS] Reuse the shared quick settings widget for the public decks tab
PublicDecksQuickSettingsWidget was VisualDeckStorageQuickSettingsWidget
minus the folders, banner and tooltip controls, with identical wiring for
the shared keys and a version of the near-identical file to keep in sync by
hand. Fold the Show Upload Time checkbox into the shared widget, give it a
setPublicDecksMode() that hides the controls that do not apply, and delete
the duplicate.
* [PublicDecks] Drop stale deck-list replies after the loading timeout
A reply that lands after its own loading timeout (the reverse of the ping
sweep dropping the command) could stop the newer request's timeout timer
and repaint the grid with out-of-date data. Each refresh now captures a
monotonically increasing request id, and only the newest request's reply
updates the grid.
* [PublicDecks] Re-show a displayed failure message on language changes
The status label carries both the loading and the failure message, and
retranslateUi hid it whenever the model was not loading, so a language
change while a server-error or timeout message was on screen swapped it
for the (empty) grid. The tab now keeps the last failure text and
re-shows it when not loading, clearing it once a new refresh starts.
* [DeckStorage] Keep the visibility refresh armed until replies land
The single-shot drain was armed with the 500 ms delay at send time, so a
round trip slower than that drained before the server applied the change,
re-read the old state and never re-armed, leaving the column stale until
a manual refresh. The timer is now armed with the full network timeout at
send time (a lost reply still costs one stale refresh) and re-armed with
the short delay every time a reply lands.
* [DeckShare] Close public decks tabs when the client disconnects
TabSupervisor::stop() built tabsToDelete from the room and game tabs
only, so a public decks tab survived a disconnect, sitting with stale
contents and a refresh button that kept hitting the dead client. Its
values are now folded into the same cleanup.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [DeckShare] Open shared decks via links with a gated preview flow
- Serialized url-chain dispatcher in IntentUrlParser; queue-drained
urlChainFinished(bool) drives the startup auto-connect fallback
- Open-shared-deck intent with sequential download state machine,
15s per-item timeout, partial-success offer, livable Cancel via
ApplicationModal dlg_login_prompt interactive fallback
- Preview dialog: download progress label, share vocab sweep,
palette-highlight selection frame, Space/Enter keyboard toggle,
NoFocus checkbox, double-click tile opens immediately
- Confirm-before-server-migration with one-shot restore to the
previous server on failed/cancelled chains (statusChanged settle
deferral), hostname-only identity comparisons
- Skip credential link when already connected; arrow-key navigation
in FlowWidget; card glows use palette highlight
- Address code-review M1-M4 and UI/UX QA blockers 1-2
* [DeckShare] End the open-shared-deck files with a trailing newline
* [DeckShare] Forward a dependency's cancellation as the owner's own
* [DeckShare] Let intent chains opt into the link sign-in dialog
* [DeckShare] Track link-intent chains per-run so each can restore its own session
* [Settings] Match a server on the exact host and port when adding it
* [DeckShare] Confirm the share link's target server before opening a deck
* [DeckShare] Reformat the link sign-in intent constructor
* [DeckShare] Time the share-list round trip and backstop silently-destroyed intent chains
* [Client] Drain a single-instance payload before its handlers read the socket again
* [Client] Treat a busy single-instance primary as alive instead of stealing its socket
* [DeckShare] Keep arrow-key navigation between flow items inside a scroll area
* [Client] Skip the startup connection when a macOS URL launch owns the connection
* [Client] Redact share secrets from activation URL logs
* [Client] Make the link-connection gates port-aware and keyboard-safe
Second-pass review notes for the shared-deck link flow (Cockatrice#7244):
- FlowWidget arrow-key navigation is opt-in via addNavigableWidget, so
combo/spin controls on the analytics flows keep their own arrow keys
- isConnectedTo and the open-deck/join-game preconditions compare the
configured server port alongside the host, so a same-host/different-port
link cannot resolve its share token or game id on the wrong instance
- the link sign-in dialog reuses an existing server entry's saved name
instead of renaming it to the raw hostname
- skipStartupAutoConnect is cleared once the launch chain connects, so a
later mid-session declined link cannot fire the startup fallback
- the plain-launch path of SingleInstanceManager no longer blocks on the
primary's ACK
- link- and server-supplied text is html-escaped in the confirm prompts and
shared-deck preview so markup cannot spoof the shown messages
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [DeckShare] Create temporary share links for local and server decks
* [DeckShare] Address review findings and harden the share flows
Gate every share entry point on login, de-duplicate the share-link and
color-identity logic behind DeckShareUtils and an injected querier, and
replace the silent tray/status-bar notices with always-visible dialogs.
- abstract_tab_deck_editor: explain that sharing requires a connection
instead of silently doing nothing when logged out
- tab_deck_storage: disable the share action on disconnect, reject
folder/deck mixes and the root folder with clear warnings, re-enable
Create on every entry/response so a dropped connection cannot leave
the button disabled
- tab_deck_storage_visual: same login gate for the context-menu entry,
visible success/error dialogs, and a symmetric in-flight guard
- getDeckColorIdentity now takes a CardDatabaseQuerier, dropping the
CardDatabaseManager singleton access and enabling unit tests
* [DeckShare] Fix share-link expiry build on the minimum-supported Qt
QTimeZone::UTC (the Initialization enum) only exists since Qt 6.7, so
Debian 12 and Ubuntu 24.04 (Qt 6.4) fail to compile the share-link expiry
handling in the share dialog and the two deck-storage tabs. Mirror the
existing games_model guard and fall back to Qt::UTC on older Qt.
* [DeckShare] Use the stable server client for the visual deck storage tab
* [DeckShare] Extract the share-creation response handling into DeckShareUtils
* [DeckShare] Drop includes left unused by the share-response extraction
* [DeckShare] Format share expiry with the locale-aware short format
* [DeckShare] Build share links with QUrl and QUrlQuery for percent-encoding
* [DeckShare] Replace the duplicate computeColorIdentity with the shared getDeckColorIdentity
* [DeckShare] Recover the share controls when the server never answers
* [DeckShare] Provide the full share hint in each plural form
* [DeckShare] Join the selected-count label with a non-translatable separator
* [DeckShare] Retranslate the share button tooltip with the storage widget
* [DeckShare] Forward retranslateUi to the visual deck storage widget
* [DeckShare] Let the share bar owners supply the hint text
* [DeckShare] End the share-related headers and sources with a trailing newline
* [DeckShare] Keep the settings include in the project include block
* [DeckShare] Include the network settings header used by the share timeout
* [DeckShare] Resolve the share theme icon through themePixmap
QPixmap("theme:icons/share") has no file extension, so ThemeManager::assetPath()
is bypassed and the pixmap is always null. Use themePixmap(QStringLiteral("icons/share"))
like every other toolbar action, so the .svg (and dark/light variants) resolves.
* [DeckShare] Keep the share selection consistent with the visible decks
Filtered-out previews are hidden but kept alive, so selectedFilePaths() counted
them in the share and the selection highlight. Only decks the user can see are
now shared, and a deck that stops matching the filters is deselectd as the deck
pass runs, keeping the %n count and the highlight in sync with the screen.
* [DeckShare] Abandon an in-flight tree share on cancel
Leaving share mode never stopped the timeout timer, and a late response still
ran shareFromTreeFinished, copying the link and announcing success for a share
the user backed out of. Stopping the timer and tracking the outstanding request
by sequence number means a stale reply (or a timed-out one) after cancel is
ignored, and cancelling + re-entering share mode can no longer confuse the two
requests.
* [DeckShare] Abandon an in-flight tile share on cancel
exitShareMode() left shareTimeoutTimer running and did not abandon the pending
Command_DeckShareCreate, so a timer pop or a late success still reported the
share after the user cancelled. Stop the timer and ignore stale responses via a
sequence number, mirroring the tree tab.
* [DeckShare] Wire the status-changed handler after shareBar exists
handleConnectionChanged() dereferences shareBar->isVisible(), but the connection
was set up before shareBar was constructed and shareBar had no in-class
initializer. On any status change delivered before construction the slot read an
indeterminate pointer. Seed the connection (and the initial share availability)
after shareBar exists and give shareBar a = nullptr initializer.
* [DeckShare] Explain why a blank deck cannot be shared
A blank deck exited the share flow silently. The menu only disables the entry
via setSaveStatus(), a different predicate, so the path is reachable (e.g. add a
card and remove it again). Mirror the not-logged-in branch with a short
information dialog.
* [DeckShare] Restore the banner-text doc comment
Re-add the doc block above refreshBannerCardText() that was removed as part of
the share-selection work; it documents the coupling to refreshBannerCardToolTip.
* [DeckShare] Resolve the stable server client in the deck editor gate
actShareDeck went through tabSupervisor->getClient(), which hands back a
LocalClient while an offline game is running. LocalClient never sets its status,
so a logged-in user could not share from the deck editor during a local game,
and got a misleading "You must be connected" message. Expose the supervisor's
stable remote client and use it for the gate and the dialog, matching the other
share tabs.
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [VDS] Decouple tag filter and fix reordered-chips crash
* [VDS] Address review: dead code, chip reparenting, filter signal and sort fast-path
* [VDS] Address second round of review nits
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
* [Build] Keep Windows installs free of build-tree artifacts
Several Windows packaging gaps could leak Visual Studio CMake build
output into the installed application or the NSIS installer:
- The per-app DLL sweep used
${CMAKE_BINARY_DIR}/${PROJECT_NAME}/${CMAKE_BUILD_TYPE}, which is
empty on multi-config generators, collapsing the recursive
DIRECTORY install into the whole build tree (containing *.dir,
*_autogen, .qt, .qsb, x64, ...). Point it at the real per-config
output with $<TARGET_FILE_DIR:...> and exclude build artifacts.
- install(FILES ${OPENSSL_INCLUDE_DIRS} ...) tried to install OpenSSL
include directories as files. CMake refuses this
("install FILES given directory"); it only slipped through CI
because the vcpkg OpenSSL config leaves the variable empty. Remove it;
fixup_bundle already ships the OpenSSL runtime DLLs.
- The NSIS uninstaller only deleted *.exe/*.dll and a few known files,
so build-tree leftovers survived an uninstall/reinstall cycle. Wipe
the whole directory tree instead.
- Add a Windows CI gate that lists the packaged installer with 7-Zip
and fails the build if any build-tree artifact path is found.
* Update .ci/compile.sh
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
* [Build] Rework Windows installer artifact exclusions per review
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
Co-authored-by: tooomm <tooomm@users.noreply.github.com>
* Direct wildcard resolution in action + cache with version key
* add back space
* Delete .ci/resolve_latest_aqt_qt_version.sh
* Disable Qt slimming and manual caching (use build-in fat caching)
* cleanup
* Re-add resolve_latest_aqt_qt_version.sh
* [Client] Fix spurious server room join error
The server replies RespContextError when a join command is received for a
room that connection is already registered in. The client was sending such
duplicate joins in benign situations - double-clicking to join a room, or
clicking a room the selector was already auto-joining - and answered them
with a modal telling users to restart the client.
Joins for the same room are now deduplicated while one is in flight, and a
remaining RespContextError is healed by leaving and rejoining the room so
the tab appears without a client restart. Error dialogs are only shown for
user-initiated joins, so failed auto-joins no longer spam critical popups.
* [Client] Bound stale-membership room join heal to one attempt
The RespContextError heal (leave + rejoin) previously recurred
unconditionally, so a server that kept returning RespContextError for a
reason other than stale membership would loop forever. Track room ids
that already received a heal and surface the error dialog after one
attempt instead of retrying indefinitely.
* [Client] Scope room-join heal guard to one join attempt
* [Client] Hoist room-join heal guard lookup out of response switch
---------
Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>