Commit graph

6663 commits

Author SHA1 Message Date
Bryce!
16455c9fd5
Honor finite deck copy limits across printings and deck zones [skip ci] (#7719) 2026-11-09 13:57:39 +01:00
RickyRister
18388d9994
[Game] Make scale on mouseover instant and independent of hover zoom (#7817) 2026-11-09 05:52:19 -07:00
SwitchOCE
7709184ecc
[Server] Let users fetch their own private chat history (#7506)
* [Server] Let users fetch their own private chat history

Players lose their private conversations on reconnect or on another
device. Servatrice can already log private messages, but only
moderators can read the logs.

Add Command_GetPrivateChatHistory / Response_PrivateChatHistory so a
registered user can page through their own logged conversation with one
other user. Operators opt in with logging/allow_user_chat_history
(default off, and log_user_msg_chat must be on). The caller is matched
by account id so reused guest or deleted-account names can't read older
messages; only time, sender name and text are returned. Pages hold
1..200 messages and never split a second, since log_time has 1-second
resolution.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* [Server] Harden private chat history identity matching

Match both participants by account id and name so a federated user's remote id cannot collide with a local account, and stop storing foreign ids in the local chat log. Extract the paging assembly into a pure helper and cover the whole-second boundary rules with gtests.

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
2026-11-09 13:21:43 +01:00
Bryce!
3d8c729517
[Client] Preview token edits live and preserve custom drafts (#7737)
* Preview token edits live and preserve custom drafts

* Restore the upstream reveal-random-hand-cards action

The rebase onto upstream/master took the PR side of player_actions.cpp and
dropped PlayerActions::actRevealRandomHandCards, which upstream added after
the PR merge base. Re-apply the face-down P/T fix on the current file so the
branch keeps both changes.

* Draw the token preview P/T as the hover-panel pill

The preview painted the name and P/T unconditionally: the name duplicated the
artwork, and the raw P/T text was drawn even when it matched the printed value,
which looked wrong at non-default dialog sizes.

- Suppress the card name over a token picture via a small AbstractCardItem hook;
  cards without art still show it.
- Draw only a P/T that differs from the printed getPowTough() as the #7700
  hover-panel pill: dark rounded scrim, bold white text, anchored on the printed
  P/T box and clamped inside the card.
- Use designated CardRef initializers in the new code.

The face-up draft still previews while face-down creation is selected.

---------

Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
2026-11-09 12:59:53 +01:00
RickyRister
3f3aff9408
[Game] Refactor: rename hovered to restore previous semantics (#7816) 2026-11-09 12:54:10 +01:00
sentry[bot]
9793a0df4a
[Crash] Add lifecycle breadcrumbs for unattributable native crashes (#7813)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 45 release (push) Blocked by required conditions
Build Desktop / Fedora 45 debug (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Crash] Add lifecycle breadcrumbs for unattributable native crashes

Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>

* Restore the native split host button on non-macOS styles

The merged quick-host control composed a QPushButton and a separate autoRaise arrow QToolButton, which Fusion draws as two adjacent controls. Version one of the quick-host branch used a single QToolButton with MenuButtonPopup, which every style draws as one native split button.

Use that split button on every style that can draw one, and keep the plain primary + settings button pair as the native macOS fallback.

* Enable hover zoom and the hover panel by default

Both hover triggers now default to Hover, with the existing 400 ms zoom delay and 200 ms panel delay. The Off trigger remains available for users who want the old behavior.

---------

Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
2026-11-08 23:06:21 +01:00
BruebachL
40ebd22549
[Lobby] Add instant quick host to the game room (#7802)
* Add instant quick host to the game lobby

* Use a native pull-down on macOS and a split button elsewhere

- Gate on the active application style instead of the platform, so
  choosing Fusion on macOS still gets the split button: QMacStyle ignores
  autoRaise/flat and draws a QToolButton menu as a separate square.
- Rebuild the host control on QEvent::StyleChange, because the appearance
  settings switch styles while the room is open.
- Keep the review action, its arrow and the host button disabled while a
  host or join command is still in flight.
- Announce inline quick-host failures to screen readers.

* Address review findings

* Replace the macOS pull-down with a sibling settings button

QMacStyle has no split-button rendering: a menu-bearing QToolButton is drawn as a second square bezel. Keep the primary Host game button as the one-click quick host on every platform, and show a plain Game settings button as the secondary action on macOS instead; other styles keep the split arrow.

Rename the secondary action from "Review settings first..." to "Game settings..." so the label says what it opens, and update the shortcuts page label to match.
2026-11-08 22:12:29 +01:00
BruebachL
b1c8f156dc
[Game] Add hover zoom and a tabbed card panel to the board (#7700)
* [Settings] Add hover zoom/panel triggers, delays, and double-confirm

* [Game] Smooth board hover-zoom with fade helpers

* [Game] Add hover card panel with tabbed actions

* [Game] Wire hover zoom/panel triggers through the scene

* [Game] Repaint cached zones when the hover zoom moves

`TableZone` cuts the hovered card out of its own paint so zone washes,
indicators, dimming and the land divider never draw over the zoom. It is a
`DeviceCoordinateCache` item, so the card changing geometry never dirtied it
and the stale hole stayed transparent, uncovering the black viewport as soon
as the zoom crossed a zone edge.

- Emit `hoverFootprintChanged(old, new)` from `setCardScale()` and once from
  `setHovered(false)`.
- Repaint every overlapping `TableZone` on that signal.
- Clip with the card's painted outline instead of its bounding rectangle, and
  treat a zoom covering the whole zone as "paint nothing".

* [Interface] Keep the hover panel P/T controls out of the way

The P/T set/inc/dec rows were always visible and sat under the cursor, so
clicking a card to select it could mutate its P/T instead.

- Collapse the controls to a pill on the printed P/T box, anchored on the
  drawn artwork so letterboxed and sideways cards stay aligned.
- Open the rows only from the pill and grow them upward, and re-measure the
  pill on every value so three-digit P/T does not clip.
- Close the panel when another card is hovered, and repaint the area it
  covered so no patch is left behind.

* [Interface] Address hover panel review findings

* [Game] Fix include order flagged by the format gate

The card hover widget header was included before theme_manager.h, which
clang-format rejects; the branch's format CI job failed on it.

* [Interface] Load the hover panel icons through the themed icon engine

The panel still named pre-#7595 assets (arrow_*_green, arrow_right_blue,
cogwheel) and built QIcon snapshots from themePixmap(), so the deleted names
rendered blank and every other glyph froze one palette.

Load every glyph through loadThemedIcon() with the emphasis axis the adoption
plan calls for: the category tabs take the primary level as panel wayfinding,
the action grid the quiet level because its glyphs label the button text.
Semantic tints keep their family and follow the same quiet level; Clone keeps
its sticker treatment.

* [Icons] Add an Informational semantic color family

Face-down play and the exile zone are states that are neither a gain nor a
loss, and the pre-#7595 art painted them blue (arrow_right_blue). #7595 dropped
that hue, and the icon engine has had only Positive and Negative since, so both
states fall back to neutral ink.

Add the family with the same three-level ladder the other semantic roles use:
authored roles per theme, scheme defaults otherwise, an emphasis mapping, the
palette-editor rows and the generator seed. The defaults clear the 4.5:1 label
floor on both schemes and the ladder's saturation ordering; the existing
emphasis, hue and preview tests now cover the new family. Preview sheets that
name roles resolve an unauthored one through the same default the client
paints.

* [Interface] Split the hover panel icons by emphasis and semantics

Category tabs only navigate between pages, so they take the quiet level; the
action grid is the surface the player works in and takes the primary level. Both
stay line glyphs: a filled treatment reads as a second state on every cell.

Hue marks an outcome where the glyph cannot. Play, clone and to hand are
Positive; reduce life and graveyard Negative; play face down and exile
Informational. Direction-only moves (top and bottom of library) stay neutral
because the arrow already carries the meaning.

* [Icons] Add Hidden and Marked semantic color families

Two game concepts had no hue of their own. Hidden information (a face-down
play, peeking at a card face) now has violet, and the marks a player leaves on a
card (annotation, draw arrow) now share amber instead of splitting blue and red
for the same job.

Both families follow the existing three-level ladder: authored roles per theme,
scheme defaults otherwise, an emphasis mapping, palette-editor rows and generator
seeding. The defaults clear the 4.5:1 floor on the representative platform
surfaces and keep the saturation ordering the emphasis tests assert; the hue
tests pin violet's blue-red-green dominance and amber's red-green-blue one, and
every preview sheet now renders the two families.

The hover panel grid preview is added alongside: it renders the real pages
(section headers, two-column cells, glyph over label) so the whole map can be
judged at once instead of glyph by glyph.

* [Icons] Add a Movement semantic color family

A card changing position inside the library is neither a gain, a loss nor
information, so it had no hue. Movement names it with teal and covers the three
library placements (top, bottom, X from top).

It follows the existing ladder: authored roles per theme, scheme defaults
otherwise, an emphasis mapping, palette-editor rows and generator seeding. The
defaults clear the 4.5:1 floor on the representative platform surfaces, keep
strong > base > soft saturation, and keep green > blue > red so teal cannot be
mistaken for the green gain family.

* [Interface] Give every hover panel action a semantic color

The quick actions are the panel's whole point, so a neutral glyph made the
most-used buttons the hardest to scan. Every action now names a game fact with
its hue: gain, loss, information, hidden information, a mark on the card or a
move. The line glyph stays identical, so the colour is the only difference
between neighbours.

The grid is also ordered by family so a row reads as one idea: the two state
toggles, the two gains, the two marks, the two hidden-information actions and
the two losses each share a row. Conditional actions (Peek, Unattach) sit at the
end of their pair, so a missing one leaves a hole at the section edge instead of
splitting a row. In a zone, Play and Play Face Down stay adjacent on purpose:
the same action, one of them hiding information. The move tab leads with the
three library placements (Movement), then the destinations.

actionGridIcon is removed; semanticActionIcon is now the only grid ink. The
preview renders the real pages so the whole map can be judged at once.

* [Icons] Use quiet emphasis for glyphs that label rather than act

The search and info glyphs inside the search fields, and the combo-box
chevron, label or afford an action rather than performing one. At the
default secondary weight they compete with the actual controls next to
them. Paint them at quiet emphasis so they stay legible without claiming
attention.

Sites: deck editor, visual database display, visual deck storage, set
manager, settings search, in-game zone view, and the dropdown chevron.

* [Icons] Restore sticker treatment to the add, remove and delete actions

The pre-#7595 add and delete glyphs were filled shapes with an outline.
After the line migration they read as ordinary toolbar glyphs, losing the
weight their verbs had. Give the add and delete family the sticker
treatment again.

- Deck editor Add card: primary positive sticker. It is the one action
  the database panel exists for.
- Visual database Clear filters: negative sticker, matching the delete
  family.
- Filter dock Clear all: negative sticker, matching Clear one.

* [Icons] Give sharing and pinning their semantic families

Sharing is an informational act: the app already tints reveal and
make-public with the informational family, so the deck storage share
action, the replay share-code action and the visual deck storage share
button follow it.

The printing-selector pin badge marks a card as pinned. Paint it with the
marked family, the same one used for a mark left on a card, instead of
leaving it neutral.

* [Game] Dismiss the hover panel before opening a modal dialog

The hover panel is a Qt::ToolTip window, i.e. a popup: it is always on
top and holds the input grab. Actions on the panel that open a modal
dialog (Set power/toughness, Set annotation, Set counter value, token
creation) therefore opened the dialog behind the panel, where it could
not be seen or activated.

Hide the panel synchronously and defer the action to the next event-loop
turn, so the popup grab is released before the dialog's event loop
starts. The panel asks the scene to dismiss it through a new
dismissRequested signal, keeping hover timers and the covered-card fade
on the scene's side.

Reported from the P/T overlay SET button; the same path covers the
counter tab and the token tab.

* [Icons] Refine VDE and VDD emphasis and semantics

The visual database filter section and the syntax help are information
aids, so paint them with the informational family at the default
emphasis: the filter section glyph and the search field's help glyph.
The help glyph leaves quiet emphasis because it is a clickable action,
not a field label.

The visual deck editor's display-type toggle is the one control that
changes the whole view, so it takes primary emphasis.

The search glyphs stay quiet; only the help action changes.

* [Icons] Raise the visual database filter controls to primary

The six quick-filter buttons are the controls the filter section exists
for, so paint them at primary emphasis instead of the default weight.

The sort glyph carries the same informational family as the filter
glyph, in both the visual database toolbar label and the visual deck
editor's sort-criteria button.

* [Icons] Keep the display options sort glyph neutral

The sort button beside the display-mode toggle is not part of the
informational family. Only the visual database toolbar's sort glyph
carries informational; the display options button returns to the
default neutral weight.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-08 21:51:31 +01:00
BruebachL
6138fbd0a0
[Card] Add Scryfall Tagger tag search and filtering (#7337)
* [Card] Import Scryfall Tagger tags from MTGJson

* [Card] Search cards by Scryfall Tagger tag

* [Card] Add a Tag attribute to the card filter tree

* [Card] Add a Tags filter to the visual card database

* [Oracle] Add a script to inject Scryfall tags into AllPrintings

* [Card] Elide long tag lists in the card info and drop the Scryfall label from the tag filter tooltip

* [Oracle] Load card tags from a tag database during import

Add a TagDatabase loader that understands Scryfall's Oracle Tags bulk data and user-supplied tag files (tag objects, flat oracle-id or name maps, optional manifest, JSON or JSONL, gzip/xz/zip). The importer joins the slugs on identifiers.scryfallOracleId while the identifiers are still available, falls back to the card name, and unions them with any tags the source already provides. Split-card faces merge through the existing tag union path.

This replaces oracle/scripts/inject_scryfall_tags.py, which patched AllPrintings.json before import. Loading during import keeps tags on by default for every source and lets users pair Oracle with any tag database.

* [Oracle] Add a tag data step to the import wizard

Insert a Tags page between set selection and the import. It downloads Scryfall's Oracle Tags by default (or a user-supplied file/URL), can include parent tags, and persists tagsenabled, tagssource and tagsparents in oracle.ini. The page loads asynchronously and soft-fails in background mode, so a tag download or parse error never aborts the card database import.

* [Help] Describe tag search without naming Scryfall

Tags can come from Scryfall's Oracle Tags or any user-supplied tag database, so the search help should not claim a single source.

* [Oracle] Fix Scryfall tag requests and the retry loop

api.scryfall.com rejects requests that lack an Accept header with HTTP 400, and QNetworkAccessManager does not add one. Send application/json for the bulk metadata request and */* for the tag file, and surface the URL plus Scryfall's JSON error body when a request fails.

A failed tag load used to call next(), which re-ran validatePage() and started the same failing download again, trapping the user in an error loop. Stay on the page instead, drop the unusable database, and tell the user they can retry, change the source, or untick the option to import without tags. Also explain parent tags inline with a real Scryfall hierarchy example.

* [Oracle] Give the parent-tag explainer room to breathe

The wrapped explainer sat flush under the checkbox with no separation from the status label, which made the paragraph look cramped. Indent it under the checkbox and add a little vertical margin.

* [Oracle] Stop the parent-tag explainer from being clipped

A word-wrapped QLabel reports a one-line sizeHint, so the wizard page reserved too little height and the explanation lost its top and bottom lines. Pin the vertical size policy to Minimum on the explainer and on the status label, so the wrapped height becomes part of the page minimum. Verified with a layout probe: the label went from 28 px tall (needed 56) to its full height.

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-08 20:57:45 +01:00
BruebachL
c7e8422808
[Icons] Author icon emphasis colors as palette roles (#7789)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 45 release (push) Blocked by required conditions
Build Desktop / Fedora 45 debug (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* [Icons] Author icon emphasis colors as palette roles

The emphasis levels were computed from the accent at paint time, so a
theme could not vary them: every theme got the same derived strong and
soft tones, and a semantic hue was pushed toward the accent whether the
author wanted that or not.

Give each family three authored roles instead. Icon/IconStrong/IconSoft
carry the neutral glyph ink and Positive/Negative gain the same Strong
and Soft companions. Emphasis becomes a lookup inside the family, and a
caller that names a variant pins that level at every emphasis. The paint
path returns the authored color verbatim; only the filled treatments keep
their saturation floor, because carrying a disc or a fill is a treatment
decision.

Shipped palettes author the full ladder for both schemes; the palette
editor exposes the new rows and the generator writes them. System keeps
deriving its neutral ink from the platform palette, since it ships no
palette of its own.

* [Icons] Keep the authored emphasis in filled treatments

The saturation floor was added while the emphasis levels were still
computed: it rescued the pale dark-scheme tones once a whole shape
carried them. With the levels authored, the floor overrides the author
instead: a quiet level jumped to the secondary saturation as soon as a
sticker or a badge painted it, so the ladder only existed for line
glyphs.

The authored tone is now the top of the fill gradient and the treatment
only darkens the bottom for depth, so every treatment renders exactly the
level the palette stores. The badge glyph ink no longer guesses the
scheme from the tone's value: an authored quiet level is a dark color on
the dark scheme too, so the scheme is passed in from the surface palette.
The preview sheet renders every shipped palette theme, three glyphs per
family, and each row prints the authored tone and the darker bottom.

* [Icons] Order the emphasis ladder by chroma, not contrast

Contrast cannot order the ladder. On a dark surface the highest-contrast
tone is the palest one, so a 7:1 primary came out near-white while the
secondary kept the family's color; the ladder read inverted, and for the
shipped semantic greens and reds the primary level was already satisfied
by the secondary, so the two levels resolved to the same hex.

Emphasis is chromatic intensity now. Quiet recedes toward the surface at
the 3:1 non-text floor, secondary keeps the family color at the 4.5:1
text floor, and primary is the most chromatic tone the same floor allows.
Every level stays legible on the window and the button surface, and the
primary is a strictly more saturated member of its family on both
schemes. The reformatted palettes move the two upper levels onto the
shared floor and leave the quiet level alone.

* [Icons] Make the emphasis ladder structural on every theme

The chroma ladder landed with four P1s from review: reachContrast picked
its pole by background lightness instead of by what each pole can reach,
the generated neutral ink was solved on the window surface only, Fusion's
neutral family was still the green accent, and quiet sat below the text
floor while disabled reused the palette gray.

Pick the pole by achievable contrast, solve fallback and preview inks on
the harder of the window and button surfaces, re-author Fusion's neutral
trio in slate blue, and keep quiet on the shared text floor by muting its
saturation to 45% instead of blending it into the surface. Disabled is now
derived from the quiet ink, so it is structurally dimmer than every
enabled level on every theme.

Re-author all soft roles at a 4.60 target with a rounding margin, rebuild
the per-theme preview generator the rebase dropped, and add a Disabled row
to every treatment sheet.

* [Icons] Render preview glyphs at the sheet's device pixel ratio

The preview sheets paint on a 2x painter but rasterized each glyph at its
logical size, so every icon was upscaled on the way into the file and the
sheets read as blurry. Render each glyph at device size and tag the pixmap
with the ratio, so one source pixel lands on one file pixel.

* [Icons] Keep the System semantic ladder on the text floor

System authors no palette of its own, so its semantic family is fixed data
measured against whatever surfaces the platform provides. The ladder that
landed with the authored roles reused the old contrast-ordered values:
PositiveSoft and NegativeSoft sat at 2.76–3.90:1 on common light and dark
platform surfaces, and the strong level was less saturated than the
secondary, which is the inversion the chroma rule removes.

Re-author the six default semantic colors per scheme at the same 4.60
target the shipped themes use, against representative platform surfaces,
and mirror them in System's palette files. The new
DefaultSemanticFamilyMeetsTheLadderOnPlatformSurfaces test holds the floor
and the saturation order there, and shippedPalette's early return now says
why System is outside the authored-palette assertions.

* [Icons] Apply the CI clang-format output to the new tests

* [Icons] Adapt the treatment preview to the authored roles

The preview file is inherited from the treatment PR. Here the palette already
authors the fill tone and the badge takes an explicit scheme, so the generator
stops re-saturating the role and passes the scheme the sheet renders.

* [Icons] Author neutral System icon ink

The System theme derived icon ink from the platform palette, which rendered blue under the default palette. Author Icon/IconStrong/IconSoft explicitly: a black ladder in light mode, a white ladder in dark mode. Pin authorship, achromaticity and ordering in the render test.

* Address review findings
2026-11-08 19:09:02 +01:00
BruebachL
3e2884e6e1
[Icons] Paint neutral glyphs with icon ink and add an emphasis axis (#7788)
* [Icons] Paint neutral glyphs with icon ink instead of body-text ink

Neutral icons resolved `currentColor` straight to `QPalette::WindowText`. A
glyph is not a word: it is a 16-24px mark drawn with a 2-unit stroke, so
antialiasing thins it until it reads lighter than the text beside it, in the
one color the palette reserves for reading. The result is that icons recede
into the chrome exactly where they should anchor it.

Add `theme_icon_ink`, one rule with one job: mix a share of the theme accent
into the neutral text color, then hold the result to a contrast floor. The
accent share gives the glyph chroma the text does not have, so it reads as an
icon rather than as leftover text, and it is derived from `QPalette::Highlight`
rather than a new token. The floor keeps a theme with a weak text color from
producing an illegible glyph.

Only the neutral label roles (`WindowText`, `Text`) are inked. Semantic tints
are hues the call site chose on purpose, disabled uses the disabled group, and
the selected-row swap to `HighlightedText` is unchanged.

The WCAG contrast math moves from `theme_icon_badge` to the new unit, so badge
ink decisions and the floor share one implementation.

Tests: the ink rule against every shipped palette, the weak-text floor, and the
palette-following render tests now ask the rule instead of restating its
output. A preview sheet sweeps the accent share at 0-35% over window and button
surfaces for both schemes.

* [Icons] Add an emphasis axis for icon ink

Tint says which hue, style says how much shape, and nothing said how loud.
Call sites that have a hierarchy (a card hover panel's nav strip, the visual
database filter toolbar) could only pick between "the theme color" and
"another theme color", so "this is the important one" had no way to be
expressed and every glyph in a surface arrived with the same weight.

Add `IconEmphasis` as the third axis: Quiet, Secondary, Primary. It travels
inside `IconTint` instead of becoming a fourth `loadThemedIcon` parameter, so
every existing call site keeps working and its look, and emphasis reads as
part of the same "how should this glyph be inked" decision as the tint.

- Primary lifts a neutral glyph to `QPalette::Highlight`, so the surface's own
  action color names the primary action instead of a second gray token, and
  pushes both neutral and tinted glyphs to a 7:1 floor.
- Quiet blends toward the surface and stops at the 3:1 WCAG non-text floor,
  for glyphs that label a control rather than act (the magnifier in a search
  field, a section marker).
- Secondary is the previous color verbatim.

The default ink borrows enough accent to sit off the palette's pole (5.4:1 on
light, 6.4:1 on dark), so the 7:1 primary floor is measurably above it in both
schemes instead of being lost against a near-black or near-white default. The
tests pin that order where it exists, and the hue shift still separates the
levels at a glance.

Disabled glyphs and the Selected -> HighlightedText swap are unchanged at
every emphasis level.

* [Icons] Render the emphasis sheet for every shipped theme

The preview only rendered one theme, so a reviewer could not see that the
emphasis levels behave the same way across Fabric, Fusion, Leather,
Plasma and VelvetMarble. It now writes one sheet per theme and scheme:
the neutral, positive and negative families at all three levels, three
glyphs per family, as line, sticker and badge at 16 and 24 px, on the
window and button surfaces.

* Address review findings
2026-11-08 19:09:01 +01:00
BruebachL
e81ee02c24
[Icons] Tint semantic glyphs and add badge and sticker treatments (#7775)
* [Icons] Keep semantic red and green on themed glyphs

The one-glyph cutover recolored every icon from a single palette role, so a
disabled deck, a not-ready player and a remove button all lost the red or green
that carried their meaning.

Add Positive and Negative application colors beside the accents, ship
scheme-appropriate defaults for every theme, and resolve them through
ThemeManager. Icons now take an IconTint: a palette role for structural glyphs,
or a semantic role for states and add/remove actions. A disabled semantic icon
falls back to the disabled foreground; on a selected row a semantic tint keeps
its color only while it stays legible on the highlight, and a palette tint keeps
the existing WindowText -> HighlightedText swap.

* [Icons] Render a semantic-tint sheet for every shipped theme

The PR showed two sheets for one theme, which cannot show that the shipped
reds and greens stay legible on every palette. Each shipped theme and scheme
now gets its own sheet: both families, three glyphs, at 16 and 24 px, on the
window and button surfaces the contrast is measured against, with the
disabled tone beside each family.

The generator lives in its own test file so the preview work does not have to
move the branches above it.

* [Icons] Add a badge icon treatment

Restore the filled-disc look the ready/not-ready icons had before the
one-glyph cutover, without bringing back palette-locked art.

IconStyle::Badge composes the existing sentinel SVG at paint time: a
gradient disc, a dark ring, and the glyph re-weighted and inked for
contrast against the tone. Line stays the default and is unchanged.

Only the two state icons opt in. The add/remove icons keep the line
treatment because their old art was a filled glyph with an outline and
no disc, so badging them would invent a look the UI never had. Disabled
badges fall back to the line glyph so an unavailable control still reads
as unavailable.

* [Icons] Add a sticker icon treatment

The pre-#7595 add, remove and delete icons were filled shapes with a black
outline and no disc. A tint cannot bring that back, and the badge disc is a
different icon language, so add a third value to the existing style axis.

IconStyle::Sticker draws the sentinel glyph twice: a near-black rim at the
band width plus one rim on each side, then the tone pass clipped to the glyph
and filled with a vertical gradient. Both treatments keep the disabled
fallback to the line glyph.

The fill rule moves into theme_icon_fill: the semantic tones are tuned for
text, so a filled shape gives its tone a saturation floor, and a dark-scheme
tone is pulled down rather than up. IconTint::resolve() applies the same floor
to line glyphs, and the badge disc uses the same fill, so a line, a badge and
a sticker next to each other carry the same green or red instead of a pale
line beside a saturated glyph.

The badge's glyph ink follows the scheme: white on light-scheme tones, as in
the pre-#7595 art, and the saturated tone darkened to an outline-dark ink on
dark-scheme tones, where the shared saturated disc is light enough that white
would sit near 2.7:1. The new test pins both cases so the glyph cannot
silently flip.

resolveSvgStrokeWidth() moves the stroke-width rewrite into
libcockatrice_utility so the badge and the sticker share it; the badge's local
copy is gone.

The sticker is opted into at 17 call sites: increment, decrement and
remove_row in the deck editor, settings pages, token dialog, server dialogs
and replay/deck storage tabs. The visual database's clear-filter button keeps
its trash can (icons/delete) in the line treatment: that glyph is new in
#7595, but it is the clearer metaphor, and a sticker rim closes its lid gap
at toolbar sizes.

The preview generators also gain a semantic-tint sheet for #7775 and a
line/badge/sticker family strip, so the three PR descriptions can show the
same rendered colors.

* [Icons] Render authored theme SVG art without the SVG image plugin

`QPixmap("theme:file.svg")` needs Qt's SVG *image-format* plugin. Debian
builds that as the separate `qt6-svg-plugins` package, which is only a
Recommends of `qt6-svg-dev`, so a `--no-install-recommends` install (CI,
minimal user installs) does not have it. Authored theme art then loaded as
a null pixmap: the two authored-art render tests failed on Debian 13 and on
the Ubuntu sanitize leg while passing on a developer machine whose Qt
bundles the format.

Render authored SVG assets through QSvgRenderer instead, which is already
linked for the sentinel path and needs no plugin. Raster assets keep the
QPixmap loader. The test suite now hides the plugin directories, so the
dependency cannot come back unnoticed on platforms that ship the plugin.

* [Icons] Render a badge/sticker sheet for every shipped theme

The PR showed one theme's badge and sticker sheets, which cannot show that the
filled treatments read on every palette. Each shipped theme and scheme now
gets its own sheet: both semantic families, three glyphs, as line glyphs,
stickers and badges, at 16 and 24 px, on the window and button surfaces the
contrast is measured against, with the disabled fallback under each family.

The generator lives in its own test file so the preview work does not have to
move the branches above it.

* [Icons] Show the trash can with the sticker treatment again

The sticker sheet kept the delete glyph out of the sticker rows and explained
why: at toolbar sizes the rim closed the lid gap. The icon is used with the
sticker treatment again for destructive actions, so the sheet now shows it and
the exception note is gone.

* [Icons] Apply the CI clang-format output to the treatment preview

clang-format 18 wraps the family label and the glyph x offset differently from
the local clang-format 23. The preview test takes the CI output so the bottom
of the stack passes the style job on its own.

* [Icons] Rewrite the icon role docs in plain language

Address review: drop the AI-cadence prose from the IconStyle/IconTint and treatment headers, use ///< for the enum member docs, and move theme_icon.cpp's anonymous namespace above the IconTint definitions. No behavior change.

* Address review findings

---------

Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
2026-11-08 19:09:01 +01:00
Bryce!
1ed7e45357
[PictureLoader] Improve image negotiation, cache recovery and redirects (#7703)
* Fix picture-loader cache formats, redirects and offline queue handling

* Format picture-loader changes using project style

* Address review: name decoded image fields, test cancelled requests

Use designated initializers for DecodedImage so a field reorder cannot
silently swap the image and its error string.

Extract the cancelled-request sweep shared by the dispatcher and the
single-request path into takeCancelledRequests(), and cover it: one status
retirement per vanished work item, in queue order, with live entries kept.

---------

Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
2026-11-08 19:02:07 +01:00
Bryce!
078d0858a3
[Game] Reveal multiple distinct random hand cards to a recipient (#7753)
* Add distinct random hand reveals with private recipient delivery

* Route random hand reveals through PlayerActions

Keep command creation, dispatch and response handling in the game action
layer. HandMenu requests the typed operation and presents the count and
failure dialogs. Show the chosen recipient, distinguish the count action
from the immediate single-card reveal, and use the game window as parent.

* Apply project formatting to random-reveal warning construction

* Simplify random reveal to follow existing command patterns

Forward server dispatch through cmdRevealRandomCards and use ordinary PlayerActions command sending. Remove the bespoke server-version error UI and long-lived-object guards.

Reduce the test from 246 to 150 lines with two focused cases using the current RoleInfo player API, preserving distinct-card, private-event and rejected-count coverage.

* Extract the shared reveal helper and name the test's CardRef fields

RickyRister asked for cmdRevealCards and cmdRevealRandomCards to share a
helper instead of threading a randomCount parameter through the reveal
virtual. Restore the three-argument cmdRevealCards signature, resolve the
reveal preconditions and zone in resolveRevealZone, and let both commands
build their card list and hand it to revealCards, which owns the private and
public Event_RevealCards construction and the enqueue. The random command no
longer fabricates a Command_RevealCards with the -2 sentinel to re-enter the
legacy path; its behaviour is unchanged.

Name the CardRef fields in random_reveal_test instead of relying on
declaration order, and state in the user documentation that a rejected
reveal is not reported in the client, matching what the client does.

---------

Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
2026-11-08 16:41:14 +01:00
BruebachL
39220a2ea9
[Refactor] Split CreateGameFormWidget out of DlgCreateGame (#7801)
* Extract CreateGameFormWidget from DlgCreateGame

* Address review findings
2026-11-08 16:22:31 +01:00
BruebachL
554368967d
[Refactor] Extract GameCreationOptions from DlgCreateGame (#7800)
* Extract GameCreationOptions from DlgCreateGame

* Address review findings
2026-11-08 16:22:30 +01:00
dependabot[bot]
870e4be310
Bump docker/login-action from 4.7.0 to 4.8.0 (#7812)
Bumps [docker/login-action](https://github.com/docker/login-action) from 4.7.0 to 4.8.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](81c6f93d4b...8aa5831fb8)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-version: 4.8.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-11-08 16:21:03 +01:00
tooomm
aa0c8dc7cb
[CI] Pin used GH Actions (#7716)
* pin used actions

plus some reordering and harmonization across files

* [CI] Link the version string and RNG stub into the test targets

The distribution libgtest.a now used by the Linux images makes the linker
pull more translation units out of libcockatrice_network than the fetched
googletest did. Those units reference VERSION_STRING and the global rng,
which several test targets never defined, so the instrumented Ubuntu 26.04
legs failed at link:

  undefined reference to `VERSION_STRING'
  undefined reference to `rng'

Add the generated version string source wherever a test links the network
umbrella, and define the rng stub in the tests that pull the server game
classes without declaring it. This matches what the neighbouring server
tests already do.

A full Debug build and ctest suite in the ci-ubuntu2604 image (system
libgtest.a) is green: 841 tests pass.

Fixes the nightly failures introduced by #6894.

---------

Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
2026-11-08 15:45:54 +01:00
tooomm
1ca67ebb0f
[CI] Fill in empty draft releases, never rewrite published ones (#7360)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 45 release (push) Blocked by required conditions
Build Desktop / Fedora 45 debug (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
* Check if release tag exists before blindly creating/overriding

* cleanup diff

* [CI] Fill in empty draft releases without touching published ones

The existence check skipped release creation for any existing release, which left a draft saved from the GitHub UI empty and silently reused a published release. Inspect the release instead: create it when missing, fill in the title, notes and prerelease flag of an empty draft, and leave a draft that already has a body or a published release untouched.

---------

Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
2026-11-08 09:05:51 +01:00
tooomm
fb3f895cfd
[CI] Update Linux Dockerfiles (#6894)
- Install the distribution's gtest (libgtest-dev / gtest-devel) in the
  Debian, Ubuntu and Fedora images, so configuring the test suite no
  longer downloads googletest; Arch already had it.
- Drop the toolchain meta packages. g++, Ninja and cmake are all that is
  used, and make is not: Ninja is the generator on every Linux leg. The
  DEB legs keep dpkg-dev, which CPack needs for dpkg-shlibdeps when
  CPACK_DEBIAN_PACKAGE_SHLIBDEPS is on.
- Use the slim Debian bases and install Fedora packages with --nodocs.
- Define the global rng in the tests that pull the server game classes
  out of server_remote, as the other server tests already do. Debian's
  libgtest.a leaves a std::string reference undefined, so the linker
  drags server_protocolhandler.cpp and the classes behind it into any
  test that links the library, and those need the symbol.

git, ca-certificates, libcurl and OpenSSL stay: sentry-native is cloned
over git at configure time on every packaging leg, and OpenSSL also backs
CryptoUtil. mariadb-devel stays on Fedora for the same reason it is
installed elsewhere: without it Servatrice configures successfully but
silently loses its MySQL connector.

Co-authored-by: Lukas Brübach <44814898+BruebachL@users.noreply.github.com>
2026-11-08 09:01:56 +01:00
BruebachL
2b4019ccaf
[Client] Active pill for alts table, dismissed pill for report queue (#7519)
* [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>
2026-11-08 09:01:11 +01:00
BruebachL
53f69c9216
[Alts] Server-side search controls, IP counts, adjustable window (#7517)
* [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>
2026-11-08 08:59:51 +01:00
BruebachL
2fba13753b
[Client] Alt badges, breadcrumb trail, investigate button (#7516)
* [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>
2026-11-08 08:59:50 +01:00
BruebachL
18f9b26a3d
[Client] Report queue dashboard and status pills (#7515)
* [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>
2026-11-08 08:59:50 +01:00
BruebachL
e603257e08
[Client] Alt tab filters and unified user timeline (#7513)
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>
2026-11-08 08:18:01 +01:00
BruebachL
9fda5c3c3f
[Client] Alt investigation entry points and in-tab navigation (#7512)
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>
2026-11-08 08:15:38 +01:00
BruebachL
88de1ff39b
[Client] Transient User Alts investigation tab (#7511)
* [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>
2026-11-08 08:15:12 +01:00
BruebachL
933db4edd2
[Server] Recursive alt search with match evidence (#7510)
* [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>
2026-11-08 08:14:03 +01:00
BruebachL
a1ff047a43
[Protocol] Add alt match evidence and search depth (#7509)
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>
2026-11-08 08:05:29 +01:00
BruebachL
6e8c7c806d
[CI] Harden the hdiutil retry wrapper (#7806)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 45 release (push) Blocked by required conditions
Build Desktop / Fedora 45 debug (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
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.
2026-11-07 22:12:58 +01:00
Bryce!
4e05aa0c54
Refresh the Printing Selector after hidden card and deck changes (#7748) 2026-11-07 22:08:45 +01:00
BruebachL
bf2529d768
[CI] Cache the macOS legs again (#7805)
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.
2026-11-07 20:58:29 +01:00
BruebachL
1df8c8efcb
[CI] Build the Windows and macOS legs without debug information (#7798)
* [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.
2026-11-07 20:58:28 +01:00
BruebachL
ee66e73b2c
[CI] Give the Windows leg a compiler cache (#7797)
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.
2026-11-07 20:58:28 +01:00
tooomm
3931447729
[Build] Clean up CMake compiler flags and CI build script (#7219)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 45 release (push) Blocked by required conditions
Build Desktop / Fedora 45 debug (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
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>
2026-11-07 18:37:09 +01:00
tooomm
d7d1872638
[CI] Install only the Qt archives the desktop builds use (#7024)
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.
2026-11-07 18:34:33 +01:00
tooomm
0e9ba72924
[Docs] Move documentation sources to docs/ and include CONTRIBUTING (#7584)
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.
2026-11-07 17:47:10 +01:00
Mmonge123
257a584971
fix: keep only the roll-die shortcut active in the lobby (#6754)
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.
2026-11-07 17:38:33 +01:00
scotland0208
d9cd308a2e
Show the library edge on the card in limited deck views (#6741) (#6741)
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>
2026-11-07 17:24:40 +01:00
unf0rce
80986266b9
Fix orphaned arrows when the draw shortcut repeats (#6789)
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>
2026-11-07 16:53:53 +01:00
sentry[bot]
01572bfce2
[VisualDeckStorage] Replace QFutureWatcher tracking with QFuture continuations (#7691)
* [VisualDeckStorage] Cancel in-flight deck loads and enforce teardown order on destruction

* [VisualDeckStorage] Reformat activeWatchers comment

* [VisualDeckStorage] Use QFuture continuations instead of QFutureWatcher tracking

Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>

---------

Co-authored-by: sentry[bot] <39604003+sentry[bot]@users.noreply.github.com>
Co-authored-by: rickyrister <42636155+rickyrister@users.noreply.github.com>
2026-11-07 16:51:27 +01:00
ebbit1q
4b224f7fe5
Translate the filter string error at display time (#5283)
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>
2026-11-07 16:26:31 +01:00
BruebachL
7487a383a3
[CI] Prune all but the newest CodeQL trap cache (#7796)
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.
2026-11-07 13:56:38 +01:00
BruebachL
c6fadef000
[CI] Stop running ccache on macOS (#7792)
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.
2026-11-07 13:54:10 +01:00
RickyRister
eab217d1bf
[DlgCreateGame] Revert dlg to use exec instead of open (#7795) 2026-11-07 13:44:40 +01:00
BruebachL
d7dac8395b
[CI] Spend the ccache quota on the legs that still thrash (#7781)
* [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.
2026-11-07 13:02:27 +01:00
BruebachL
818c0161ed
[CI] Run the sanitizer leg nightly and stop caching it (#7780)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 45 release (push) Blocked by required conditions
Build Desktop / Fedora 45 debug (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
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.
2026-11-07 09:31:27 +01:00
tuckerandrew21
9fdbe45ffb
[PictureLoader] Fix malformed MIME wildcards in Accept header (#6609)
Some checks are pending
CodeQL / Analyze (cpp) (push) Waiting to run
CodeQL / Analyze (actions) (push) Waiting to run
Build Desktop / Configure (push) Waiting to run
Build Desktop / vcpkg baseline (push) Waiting to run
Build Desktop / Ubuntu 26.04 sanitize (push) Blocked by required conditions
Build Desktop / Debian 13 (push) Blocked by required conditions
Build Desktop / Fedora 45 release (push) Blocked by required conditions
Build Desktop / Fedora 45 debug (push) Blocked by required conditions
Build Desktop / Servatrice_Debian 12 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 (push) Blocked by required conditions
Build Desktop / Arch (push) Blocked by required conditions
Build Desktop / Debian 12 (push) Blocked by required conditions
Build Desktop / Fedora 44 (push) Blocked by required conditions
Build Desktop / Ubuntu 24.04 (push) Blocked by required conditions
Build Desktop / Ubuntu 26.04 cover (push) Blocked by required conditions
Build Desktop / macOS 14 (push) Blocked by required conditions
Build Desktop / macOS 15 (push) Blocked by required conditions
Build Desktop / macOS 27 Debug (push) Blocked by required conditions
Build Desktop / Windows 10 (push) Blocked by required conditions
Build Desktop / macOS 13 Intel (push) Blocked by required conditions
Build Desktop / Prune caches (push) Waiting to run
Build Docker / Servatrice (arm) (push) Waiting to run
Build Docker / Servatrice (x86) (push) Waiting to run
Build Docker / Publish multi-platform Servatrice image (push) Blocked by required conditions
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>
2026-11-06 23:19:02 +01:00
tooomm
dec5e217a2
Remove leading spaces from message log zone strings (#4945)
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.
2026-11-06 21:43:14 +01:00
tooomm
0798d28dc9
UI: Settings dialog layout fixes (#4531)
- 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"
2026-11-06 21:38:47 +01:00