A mapper that stopped filling its section would leave a hole in the
floating panel with the game's text showing through it, and only its
width was ever checked against the section.
Assisted-by: Claude:claude-opus-5
All three sections of the dock - map, chat and the health bars - are now
adjustable containers with the dragOut constraint, and each keeps a header
row to be taken hold of by rather than the ten-pixel strip the bars had.
The dock shares itself out between whichever sections are still in it, so
one dragged out leaves its space to the others and putting it back gives
that space straight back; with all three docked the layout is the one it
has always been, to the pixel.
Assisted-by: Claude:claude-opus-5
Replaces the package's own drag handler with an Adjustable.Container
using the dragOut constraint from #9850. Geyser then owns the dragging,
the position, and which container the panel is in; what is left here is
fitting the panel to the bars in it, the dock's lifecycle, and taking
away the chrome that could put the bars somewhere with no way back.
Not for merging as it stands: the only thing that can be taken hold of
is the panel's title bar, which measures about ten pixels of grabbable
strip in the dock, and four measured attempts to drag it out under Xvfb
produced one accidental resize and three no-ops. The bars themselves are
no longer draggable, and there is no dragging back in.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
Conflicts were all in src/packages/mudlet-base-ui: the gossip capture
from #9838 and the gauge panel here touch different parts of the script
and both are kept, config.lua takes the gauge paragraph and the "baseui
dock" line on top of #9838's version bump, and the archive was rebuilt
from the resolved sources. The package version goes to 1.4.0 because
both changes claimed 1.3.0.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- new `dragOut` constraint for `Adjustable.Container`: taking the mouse
pointer past the parent container takes the container out of it, into
the window it is in, and the drag carries on from there
- the container keeps the size it had across the move out, and drag out
state is saved with the container, so a container dragged out of its
parent is still out after a restart
- off by default: a nested container without the constraint is held
inside its parent exactly as before
#### Motivation for adding to Mudlet
A container nested in another one can never be pulled out of it, so a
panel a package put in a dock stays in that dock whatever the user
wants.
#### Other info (issues closed, discussion etc)
Opt in with `dragOut = true` at creation. With it off, a drag is bit for
bit what it is today - checked by driving the same real drag against
this branch and against development and comparing the positions.
Dragging back in is deliberately not part of this, `changeContainer`
does that from a script; the asymmetry is documented on the constraint.
The base UI's gauge panel (#9840) could take this up later so its panels
can be pulled out of the dock.
**Test case:** make an `Adjustable.Container` with `dragOut = true`
inside another one, drag it past the parent's edge and it comes out
under the pointer, while the same container without the constraint stays
pinned at the parent's edge.
Assisted-by: Claude:claude-opus-5
https://github.com/user-attachments/assets/0dac73bc-8f34-4a66-be17-6f12e967a7df
#### Brief overview of PR changes/additions
- The base UI's chat capture now recognises `You gossip, 'test!'`-style
lines and copies them into the Tells tab
- New `you gossip` shape under the tells gate (18 of 20 gate substrings
used), mirrored in `chatPatterns` so gossip lines stay out of the vitals
harvester
- Package version 1.3.0, `.mpackage` rebuilt; corpus lines added to
StarterUiTriggerCostTest and UI_spec
#### Motivation for adding to Mudlet
Gossip is a staple channel on Diku-style games, and the starter UI
missed these lines entirely.
#### Other info (issues closed, discussion etc)
Only your own gossips are captured for now - the incoming `Bob gossips,
'...'` counterpart can be added the same way if wanted.
**Test case:** `ctest -R StarterUiTriggerCost`, or gossip on any
Diku-style game with the base UI up and see the line land in the Tells
tab.
Assisted-by: Claude:claude-fable-5
#### Brief overview of PR changes/additions
- `deleteOldProfiles` now checks the target folder exists before listing
it, and says `No modules have been backed up yet, nothing to delete.`
when it does not
- previously `lfs.dir()` raised before the function's first `echo()`, so
the main console showed nothing at all and only the error console had
the reason
- package version bumped to 2 and the `.mpackage` rebuilt, so mpkg
offers the update
#### Motivation for adding to Mudlet
`delete old modules` became a completely silent no-op on any profile
with no module backups, which reads as a broken command.
#### Other info (issues closed, discussion etc)
Fixes#9842. `moduleBackups/` used to be created on every profile save,
which hid this for modules until #9690 made it lazy - but the same
failure has always been there for `delete old maps` on a profile that
never saved a map, and this fixes that too. Behaviour when the folder
does exist is untouched.
**Test case:** On a fresh profile run `delete old modules` and `delete
old maps` - both print the message above instead of nothing; with
backdated files in `moduleBackups/` and `map/` the deletions and the
`2/5`, `2/7` counts are unchanged.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Adds `CMakePresets.json` with configure, build and test presets for
macOS, Linux and Windows, including sanitizer and static-analysis
variants. Each is gated on the host system, so a listing only offers
what the current machine can build.
- Consolidates the AI assistant skills into `.agents/skills/`, which
Claude Code, GitHub Copilot and Cursor all read, replacing two copies
that had drifted into contradicting each other.
- Corrects the build documentation: the Windows toolchain is CLANG64
rather than MinGW64, and the previous "no need to specify number of
jobs" advice holds only for Ninja.
#### Motivation for adding to Mudlet
The instructions presented platform-specific build advice as though it
were universal, so following them on the wrong platform produced either
an unbounded parallel build or a toolchain the setup scripts refuse to
run.
#### Other info (issues closed, discussion etc)
`windows-debug` has been exercised on Windows: `CI/setup-windows-sdk.sh`
in an MSYS2 CLANG64 shell, then configure and build both to completion
against Qt 6.11.1 and Clang 22.1.7. `cmake --list-presets` correctly
offered only `windows-debug` there.
Note that `windows-debug` is a Debug configuration, whereas
`CI/build-mudlet-for-windows.sh` builds Release, so the two are not
equivalent.
`.claude/skills` is a symlink to `.agents/skills`, following the
existing pattern used by `CLAUDE.md`, `AGENTS.md` and `.cursorrules`. On
Windows checkouts without `core.symlinks` it lands as a plain file, in
which case Copilot and Cursor still read `.agents/skills` directly.
**Test case:** `cmake --preset macos-debug && cmake --build --preset
macos-debug`, then `ctest --preset macos-debug`. `cmake --list-presets`
should offer only the current platform's presets, and a variant such as
`macos-debug-nosan` should build into `build-macos-debug-nosan/` while
leaving `build/` untouched.
---------
Signed-off-by: Michael Conley <sousesider@gmail.com>
#### Brief overview of PR changes/additions
- A profile save read a fresh variable tree by walking all of `_G`
first-seen-wins, so a saved table another global name reached first was
filed under that name and written nowhere. Saves now read only the
globals the profile saves, each in its own dedup scope, and a name the
user saves is never deduped away.
- Same change takes the save off the size of `_G`: 0.269 s -> 0.007 s
per save at 20,000 globals, 0.013 s -> 0.001 s on a default profile
(4.22.0 is 0.002 s).
- `iterateTable()` names the table when it stops at 99 levels of nesting
instead of handing back an empty one, and the save tells the user which
saved variables that leaves empty.
#### Motivation for adding to Mudlet
Silent, permanent data loss on every save with no user action: a stock
4.x profile with EMCO/AdjustableContainer packages lost 1416 of its 1444
saved variable entries on the first 5.0 session.
#### Other info (issues closed, discussion etc)
Fixes#9755. Keeps #9704's fix (quitting with the editor on the
Variables tab) intact - the export still builds a throwaway tree, so the
Variables editor's tree items are never stranded.
Measured on a real profile (`Pox`, fresh isolated HOME): 25 variables /
4 groups / 4.3 KB before, 2267 / 535 / 497 KB after, identical on a
second session. 4.22.0 wrote 1163 / 281 / 258 KB. The difference above
4.22.0 is the live EMCO and AdjustableContainer objects the profile
keeps inside its saved `demonnic` table, which the ride-along rule from
#9517 says to save.
**Test case:** `lua qaShared = {a = "alpha"}`, tick `qaShared` in the
editor's Variables view, then `lua aaaAliasOfShared = qaShared`, quit
and reopen - `qaShared.a` is still there. `ctest -R
XMLexportVariablesTest` covers it; 5 of the 8 new cases were verified to
fail against the unfixed source.
#### Brief overview of PR changes/additions
- **Media**: a preload that has to fetch its file now keeps it without
playing it (#9783 - the same guard covers GMCP `Client.Media.Load`,
which preloads the same way), each `load*File()` stamps its own media
type so a load can only reach players of its own kind (#9784), and the
load/play errors name the function that was actually called instead of
always `loadMusicFile`/`playSoundFile` (#9785).
- **IRC**: `setIrcServer()` keeps a stored password it was not given -
an empty string still clears it - and takes an explicit `nil` for any
optional argument the way an omitted one is taken (#9786, #9787);
`getIrcConnectedHost()` returns the documented `true, host` pair instead
of dropping the boolean (#9788); `setIrcChannels()` drops a name
carrying whitespace or a comma rather than storing one channel that
reads back as two (#9789).
- Specs for six of the seven ship with them; the fixture server answers
a GET below `/media` for a `.wav` with generated silence, so a preload
spec has a real file to fetch. `getIrcConnectedHost()`'s success path
needs a client connected far enough for the server's `RPL_YOURHOST`,
which this suite deliberately never opens, so that one was checked with
temporary instrumentation (before: 1 value, the host name; after: `true`
plus the host name) rather than left to a spec that could not reach it.
#### Motivation for adding to Mudlet
All seven were found while writing the IRC and media specs in #9772 and
filed from there. The two with teeth: a preload occupies a player and
reports itself as playing, and a script that adjusts the IRC server
destroys the saved password as a side effect.
#### Other info (issues closed, discussion etc)
Keeping an unmentioned password means it now survives a change of server
too, which the spec pins; `setConfig("ircPassword", "")` and the empty
string both clear it. No `mmcp*` code or Networking_spec MMCP region is
touched, so #9744 stays clear.
**Test case:** `lua loadSoundFile({name = "x.wav", url =
"http://127.0.0.1:PORT"})` - after `sysDownloadDone` the file is in the
profile's media directory and `getPlayingSounds()` is empty, while
`playSoundFile()` with the same url still plays.
Closes#9783Closes#9784Closes#9785Closes#9786Closes#9787Closes#9788Closes#9789
Assisted-by: Claude:claude-opus-5
table.save reports a path it cannot write by returning nil and a message
rather than by raising, so the pcall around it saw nothing and the "could
not save" notice never appeared: a dropped gauge position was lost again
next session with no explanation.
The new test makes the settings path a directory, which is what proved
the old check silent.
Assisted-by: Claude:claude-opus-5
The bars are one group in a container of their own now: pressing any of
them and dragging pulls the whole group out of the dock to sit anywhere
over the game's text, down to the input line. Where they are dropped is
remembered in the package's settings file and restored next session, and
dropping them back on the dock - or "baseui dock" - puts them back.
The panel is only as tall as the bars it holds, so it grows as a game
reveals more stats and a floating one is never part empty backdrop. A
saved position is validated before anything is built from it: a size
Geyser cannot parse would raise inside build() and cost the player the
whole interface, so nonsense means the dock and says so.
Docked, the layout is exactly what it was: the slot percentages moved
from the dock into the panel, and StarterUiGaugePanelTest pins the
resulting pixel positions against the constraints the gauges used to
carry.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- `setSaveCommandHistory()` and `setSaveCommandHistory(name)` turn
saving on instead of raising: the argument count was tested one too
high, which also made the branch reading a boolean after a name
unreachable.
- `setProfileInformation()` / `clearProfileInformation()` refuse a
profile that is not there instead of conjuring one.
`mudlet::writeProfileData()` creates whatever folder it is handed, so an
unknown name used to leave a phantom in `getProfiles()` and the
connection dialog. The check went into the two Lua entry points because
the connection dialog needs that folder creation to save a character
name for a default game (#8101), and a game Mudlet ships with only
counts once it has a folder of its own.
- `verbosePackageInstall()` names the package rather than its whole
path: the profile folder was stripped with `gsub`, which reads a path as
a Lua pattern, and the `-` in "Mudlet self-test" (or in any of the many
real paths holding one) never matches.
#### Motivation for adding to Mudlet
All three were found and filed while writing the core and package specs
merged in #9802, where they were left as `pending()`. Those specs are
flipped to real assertions here, so each fix is pinned by a test that
fails without it.
#### Test case
`lua setSaveCommandHistory()`, `lua setProfileInformation("no such
profile", "x")` then `lua display(getProfiles())`, and `lua
installPackage("https://.../thing.mpackage")` from a profile whose path
holds a `-`.
Closes#9803Closes#9804Closes#9806
Assisted-by: Claude:claude-opus-5
#### Other info
The red ubuntu and windows legs are the known development-side
`ProfileLifecycleTest` failure, not this PR: development @ e42bd1e28
fails the same `initTestCase()` assertion (run 31464046946). A dedicated
PR fixes it.
Stacked on #9799, so the base is `fix-db-index-string` and this
retargets to development once that merges.
- `table.contains()` keeps a set of the tables it has walked, so a
self-referential one (every Geyser object holds its container, which
holds it back) answers instead of overflowing the stack, and
`Geyser.Label:setDoubleClickCallback()` stores the `doubleClickCallback`
key the label's own re-registration reads rather than one nothing reads.
- db: a `UNIQUE` with no `ON CONFLICT` clause is now seen, so a change
in uniqueness rebuilds the sheet; a sheet given as a list of column
names takes the sheet options instead of swallowing `_index` as a
phantom column; and an `_index` naming a column the sheet does not have
is refused rather than quietly dropping the indexes the sheet already
had.
- `saveMap()` resolves a relative location against the profile directory
the way `importMap()` does instead of against the directory Mudlet was
started in, `loadMap()` looks in the same place, and a format version
below the oldest one Mudlet can write is refused the way one that is too
new already was.
Test case: `lua local t = {} t.self = t display(table.contains(t, "x"))`
answers `false` instead of raising, and `lua saveMap(42)` writes into
`getMudletHomeDir()` rather than the directory Mudlet was started in.
Worth knowing: `db:create` now hard-errors on an `_index` naming a
column the sheet does not declare, where it used to load and silently
lose the sheet's indexes.
Closes#9777, Closes#9779, Closes#9780, Closes#9781, Closes#9782,
Closes#9800, Closes#9801
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- `db:create` now normalises a bare-string `_index` into a one-entry
list, in the one place `db.__schema` is written, so all three readers of
the option see the shape they handle. Previously `_index = "city"` died
in `db:_drop_orphaned_indexes` with `bad argument #1 to 'ipairs' (table
expected, got string)`, even though `_unique = "name"` is accepted and
`db:_index_name`, `db:_index_valid` and `db.Database:_drop` all take a
bare string.
- New `db:_validate_index`, a sibling of
`db:_validate_unique_contraints`: `_index = {123}` used to crash
mid-migration with a sqlite cursor still open, which then took the next
`db:close()` down with it. It now reports the bad member the way
`_unique` does.
- **+22 specs**, including the migration paths a string `_index` has to
survive: a plain re-create, and a `_violations` change that drops and
rebuilds the whole table.
#### Motivation for adding to Mudlet
`db:create` is documented as something you run unguarded at the top
level of a script, so this took the whole script down. The string form
reads as supported everywhere else in the module.
#### Other info (issues closed, discussion etc)
Closes#9778. Stacked on #9774, which the specs live alongside;
retargets to `development` when that merges.
Two pre-existing gaps found while writing the specs and left alone: a
sheet declared in the `{"col1", "col2"}` array form sweeps `_index` in
as a column name and never creates an index, and a string `_index`
naming a column the sheet lacks silently drops the indexes that were
there (pinned by a spec rather than changed - `db:_index_valid` refuses
quietly on purpose).
**Test case:** `db:create("x", {sheet = {name = "", _index = "name"}})`
- full suite 2678/0/0 + 140 pending, green on a fresh profile and twice
on a reused one; 20 of the 22 new specs fail with the `DB.lua` hunk
reverted.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- `Geyser.Gauge:setValue` keeps the reading it takes off the back
label's stylesheet instead of re-parsing margin, border and padding on
every call, installs the front label's constraints once per orientation
instead of rebuilding them per value, and lays the front label out once
rather than twice - and not at all when nothing about the fill moved.
- `Geyser.Color.find_color_name` answers from an index instead of
scanning all ~500 `color_table` entries, and `Geyser.Color.parse` calls
it once instead of twice. That is 52 us off every Geyser label echo that
carries a named colour, gauge or not.
- 25 new busted specs, including the staleness cases the two memos
invite: the container moving under a gauge, a format change between two
identical updates, a front label resized from outside, and a colour
added to or taken out of `color_table` at runtime.
#### Motivation for adding to Mudlet
A new profile with the shipped starter UI processed game text 4.5x
slower than 4.22.0 and nearly all of it was `setValue` running on every
prompt line (#9761); this is in Geyser rather than the package, so every
UI with a gauge gets it.
#### Other info (issues closed, discussion etc)
Fixes#9761.
One non-ASan RelWithDebInfo build, Lua swapped between runs,
`test/compare-perf-baseline.py` as the arbiter:
| metric | before | after |
| --- | --- | --- |
| `defaults_text_lines_per_sec` | 16 600 | 27 049 (+62.9%) |
| `defaults_text_best_pass_ms` | 1506 ms | 924 ms |
| `text_lines_per_sec` / `trigger_lines_per_sec` | - | flat, within
noise |
Three A/B pairs: +62.1%, +64.3%, +62.9%. Per call, on a starter-UI
gauge: `setValue(v, max, text)` 115.0 us -> 5.8 us, `Geyser.Label:echo`
with a named colour 55.7 us -> 2.7 us. Stubbing `setValue` out
completely now only buys another 39 ms of the 924, so gauges are no
longer what the starter UI spends its time on; the rest is the chat
dock, which #9761 calls the floor.
Nothing is deferred: `getWindowGeometry` and `getLabelText` still answer
with the new value in the same tick. Coalescing the repaint onto the
next event-loop turn was tried and breaks 8 pre-existing gauge specs
that read both back immediately, so it is left out.
Busted suite: 2437 successes / 0 failures / 0 errors / 129 pending.
ctest: 95/95.
**Test case:** on a fresh profile with the starter UI, paste a few
thousand lines of game text at a game that sends vitals - the gauges
keep up instead of the client falling behind.
`Geyser.Gauge:new{name="g", x=0, y=0, width=300, height=20}` then
`g:setValue(50, 100, "HP 50/100")` behaves exactly as before.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- A DCS/SOS/PM/APC/OSC sequence (`ESC` followed by `P`, `X`, `^`, `_` or
`]`) now ends at the end of the line it started on if the game never
sends a terminator, instead of discarding everything that follows it for
the rest of the session.
- Half of a sequence left over from a dead connection is dropped when
the next one starts, and the 4096-byte cap no longer leaves the parser
stuck with no way out.
- Well-formed sequences are untouched: Sixel images and Kitty graphics
are still swallowed whole, including ones larger than the cap or split
across packets. Their idle-flush carriage returns no longer hide a
String Terminator either.
#### Motivation for adding to Mudlet
One stray escape byte from a game could black out every line it sent
afterwards - across line breaks, `clear()` and a full reconnect - with
only a BEL able to restore output.
#### Other info (issues closed, discussion etc)
Fixes#9757. The `ESC ]` half of this is not a 5.0 regression, it
behaves identically in 4.22.0, so the bound covers the whole
`mGotOSC`/`mGotString` path rather than just the four introducers 5.0
added. Answering the open question in the issue: text lost to the
blackout is missing from the log as well, because it never reaches the
buffer the log is written from -
`logFollowsTheDisplayThroughAnUnterminatedSequence` covers that.
New `TelnetStringSequenceRecoveryTest` - 46 cases over a real TCP socket
via `TelnetServerStub`. With the fix reverted, 24 of them fail.
**Test case:** connect to any game and have it send `PRE<ESC>Ppayload`
followed by a newline and a few more lines. Every line after it must
still be displayed. Repeat with `X`, `^`, `_` and `]`.
#### Brief overview of PR changes/additions
- Stop importing settings from the pre-4.19 NativeFormat store (macOS
plists / Windows registry) into a freshly created Mudlet.ini
- On those platforms the old store ignores HOME/XDG overrides, so any
machine that ever ran Mudlet <= 4.18 made every fresh install inherit
stale keys and classify as an experienced player - skipping the starter
UI, tour and hints, and failing two functional tests
- Add tripwire asserts so any future write into a fresh Mudlet.ini
before init() fails with a self-explaining message
#### Motivation for adding to Mudlet
A fresh install must be recognised as a new player; anyone who ran 4.19+
was already migrated, and a direct <= 4.18 upgrade only loses UI
preferences (window geometry, appearance, storePasswordsSecurely,
deletedDefaultMuds), never profiles.
#### Other info (issues closed, discussion etc)
Fixes#9741
**Test case:** ExperiencedPlayerGateTest (18/18) and DefaultPackagesTest
(28/28) pass; on a Mac that ran Mudlet <= 4.18 (check with `defaults
read com.mudlet.Mudlet pos`), both suites now pass and a fresh profile
gets the starter UI.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- `Geyser.Container:delete()` now defers the layout of the container it
is emptying for the length of its child loop, so tearing down an
`HBox`/`VBox` no longer lays the box out once per child.
- The deferral is restored even if a child's delete raises, so a
container that survives a failed cascade still lays itself out.
- Seven busted specs, counting layout passes rather than timing them.
#### Motivation for adding to Mudlet
`HBox:remove`/`VBox:remove` lay the box out on every removal (#9680,
right on its own) and `Container:delete()` removes children one at a
time, so a cascading delete re-laid-out the whole box once per child;
Mudlet is single-threaded, so a UI package unloading or rebuilding a
large layout froze the main thread for seconds. Every one of those
passes was laying out windows the same loop went on to destroy.
Building a box is unaffected and unchanged: `HBox:add` has always
organized per add, and the documented `begin_update`/`end_update` idiom
already makes a bulk build linear.
#### Other info (issues closed, discussion etc)
Closes#9756.
One RelWithDebInfo build, Lua swapped between runs, `organize` = layout
passes:
| case | before | after |
| --- | --- | --- |
| hbox100 | 0.2228 s, 100 passes | 0.0009 s, 0 |
| hbox200 | 0.9851 s, 200 | 0.0017 s, 0 |
| hbox400 | 4.1680 s, 400 | 0.0076 s, 0 |
| vbox200 | 0.9978 s, 200 | 0.0018 s, 0 |
| plain Container 200 (control) | 0.0035 s, 0 | 0.0017 s, 0 |
Busted suite: 2507 successes / 0 failures / 0 errors / 41 pending.
**Test case:** run `local box = Geyser.HBox:new({name = "b", x = 0, y =
0, width = 600, height = 400}) for i = 1, 400 do Geyser.Label:new({name
= "l" .. i}, box) end box:delete()` - the delete is instant instead of a
multi-second freeze.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- "Copy as image" with nothing selected now copies the visible screen
(timestamps included) instead of doing nothing. With a selection it is
unchanged.
- Copy, Copy HTML and Search on ... genuinely need a selection, so
without one they are greyed out with a tooltip saying so rather than
silently doing nothing.
- Copy as image no longer wipes the clipboard when it runs out of its 3s
budget, crops the drawn lines instead of squashing the whole selection
to fit, and copies a run of blank lines rather than nothing.
#### Motivation for adding to Mudlet
Right-clicking the console and picking "Copy as image" left nothing on
the clipboard, with no hint that a selection was needed.
#### Other info (issues closed, discussion etc)
Fixes#9715
Also fixes two ways a selection could outlive the lines it covers:
clearing a console now clears its selection (`clearWindow()` then "Copy
as image" aborted on Qt's bounds assert), and a selection stranded by
the buffer hitting its size limit is followed down with its lines
instead of copying whatever took their place.
New `CopyAsImageTest` functional test, 15 cases. Verified on X11 with
`xclip -selection clipboard -t image/png -o`.
Assisted-by: Claude:claude-opus-5
**Test case:** Right-click the main console with nothing selected, pick
"Copy as image", and paste - you get a picture of the screen. Select
some text and repeat - you get just the selection.
#### Brief overview of PR changes/additions
- The notepad and IRC client are parentless windows freed only in
`Host::closeChildren()`; a `Host` destroyed without that call orphaned
them. `~Host()` now closes and deletes them, nulling each `QPointer`
first so both teardown paths stay single-delete. Closing (not just
deleting) the notepad also saves the notes and window state.
- The toolbars are not leaked at exit, but survived their profile on
screen holding a freed `TAction`; `~Host()` now deletes them
synchronously.
#### Motivation for adding to Mudlet
Same defect class PR #9700 "fix: trigger editor and deleted item
subtrees leaking memory" fixed for the editor. Two narrow production
paths reach `~Host()` without `closeChildren()` (`requestClose()`
returns early when `mpConsole` is already gone, and `~HostManager` runs
from `~mudlet`), and the test harness takes the second on every run.
#### Other info (issues closed, discussion etc)
Test case: new `HostChildTeardownTest` - three teardown orderings, each
fails without the fix; under ASan+LSan the binary goes from 851,208 to
3,334 leaked bytes (residue is the settings floor fixed in #9694). 79/79
functional tests pass twice; profile-close/quit with the notepad open
are clean under ASan on Xvfb.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- The "Main window size and saved layout" specs read the size to put the
window back to before the first resize of the run. A dock another spec
file leaves open - the map widget - only takes its width out of the
console at the next re-layout, which is that resize, so the baseline is
a size the window can no longer be put back to and
`restoreMainWindowSize()` never converges.
- The baseline is now read after asking for the original size again, so
it is a size the window has actually been.
- This is the failure development's ubuntu Lua leg is red on:
`UI_spec.lua @ 5228 the main window can be put back the size it was`.
**Test case:** running `Mapper_spec` and `UI_spec` together reproduces
the CI failure exactly and this clears it; `UI_spec` on its own and a
second run over the same profile are unaffected.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- `ProfileLifecycleTest` (merged this morning in #9776) seeds
`$XDG_CONFIG_HOME/mudlet` without the `profiles/` subdirectory that
#9712 made the opt-in, so on any machine whose `~/.config/mudlet` holds
profiles it resolves to that instead and fails its own line 331
assertion. Reproduced here before the one-line fix. Its build legs all
finished on 10 Aug 13:52-14:55 UTC and #9712 merged at 22:17 that
evening, so it was merged 16 hours later on green CI that predates the
rule it breaks.
- `XdgRecipeConsistencyTest` stops the next one. It scans `test/*.cpp`
and `test/functional_tests/*.cpp` the way `CMakeListsConsistencyTest`
scans `src/`, and fails on a `mkpath()`/`mkdir()` whose argument spells
a path ending in `/mudlet` unless the file also creates the `profiles/`
opt-in. A test that means it says so with an `xdg-recipe-guard: allow`
comment.
- Comments, strings and raw strings are parsed out first, so a recipe in
prose is not code and an assertion against a `"%1/mudlet"` literal is
not a creation. The sweep reads this file too: its own fixtures spell
the stale recipe out inside string literals.
Test case: the sweep names `ProfileLifecycleTest.cpp:317` before the
fix, and both pre-#9810 files at lines 154 and 173 when those are
checked out of `8901b59d8`; the other 99 test sources are clean, and the
suite is 98/98 locally.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- 139 new busted specs (Miscallaneous_spec +126, Package_spec +13) for
the last core and misc Lua functions with no test presence at all: the
profile description/icon/stats/list accessors, time, process and
encoding info, logging, file watches, the user dictionary and spell
checking, `unzipAsync`, `loadReplay`, `findItems`, `insertHTML`, `send`,
`denyCurrentSend`, `isAncestorsActive`, `raiseGlobalEvent` and the
verbose-install / drag-and-drop package helpers.
- Contract plus effect wherever the effect can be seen offline - the
file that was written, the event that was raised, the line that reached
the console - with no mocking. The functions that reach a browser, the
tray, a modal dialog or the keyboard get their refusals covered instead,
which is all that can be driven headlessly.
- Six bugs turned up while writing them. None is specced: five are
marked `pending` with the reason so they light up when fixed.
#### Motivation for adding to Mudlet
Wave 4 of the Lua API test coverage program. Nothing in the suite
touched these functions, so a regression in any of them was invisible.
#### Other info (issues closed, discussion etc)
Bugs found, all left unspecced:
- `setSaveCommandHistory()` and `setSaveCommandHistory(name)` raise
instead of turning saving on; both count their arguments one too high,
and the branch that would read the boolean after a name is unreachable.
- `setProfileInformation` and `clearProfileInformation` for a profile
that does not exist return true and create the folder, so a phantom
profile appears in the connection dialog and in `getProfiles()`.
- `raiseGlobalEvent` refuses an unsupported argument with `lua_error()`
after building the event, which longjmps past the event's destructor and
leaks it.
- `insertHTML` hands its text straight to `insertText`, so the markup
its name and the wiki promise is printed literally.
- `verbosePackageInstall` strips the profile folder off the announced
name using that folder as a Lua pattern, so a profile path holding a `-`
gets the whole path announced.
**Test case:** full busted suite 2544 passed / 0 failed / 136 pending -
green on a fresh profile, again on that same profile, and once more on
another fresh one (+2.5s of runtime); 97 of the 139 new specs were each
shown to fail against a matching sabotage of the C++ or Lua behaviour,
which was then reverted.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- New `ProfileLifecycleTest` (18 test functions) covering the
profile-lifecycle Lua API the busted suite cannot reach, since busted
runs inside a single profile of an application it must leave standing:
`loadProfile`, `setActiveProfile`, `closeProfile`, `closeMudlet`, and
the cross-profile half of `raiseGlobalEvent`.
- Drives each function from a profile's own Lua state and checks the
application state that follows - host pool, tab, main console, active
profile, socket - not just the return value. Teardown is asserted with
`QPointer`s, so the closed profile's objects have to be genuinely gone.
- Runs against a `QTemporaryDir` config dir and an ephemeral stub port,
so it never touches the developer's own profiles and does not collide
with parallel test runs.
#### Motivation for adding to Mudlet
These five functions had no automated coverage at all. Between them they
disagree on almost every convention - `loadProfile`/`closeProfile`
refuse with `nil`, `setActiveProfile` with `false`; all three resolve
names case-insensitively; `raiseGlobalEvent` serialises its arguments to
strings and appends the sender - and none of that was pinned anywhere.
Bug found while writing it, not fixed here: `raiseGlobalEvent`'s
argument-type rejection is a `lua_error()`, which longjmps out of the C
function so the `TEvent` being filled in on the stack is never
destroyed. `raiseGlobalEvent('name', {})` leaks the arguments collected
before the bad one (170 bytes, confirmed under LeakSanitizer). The test
asserts the refusal with the argument-#1 form, which has collected
nothing yet; a comment marks the realistic form as untested until the
leak is fixed.
#### Other info (issues closed, discussion etc)
Coordinated with #9706 (fix-host-child-teardown): no overlap - that PR
covers notepad/IRC/toolbar teardown, this one the Lua API and the host
pool, and both insert into `test/functional_tests/CMakeLists.txt` at
different points.
**Test case:** `ctest -R ProfileLifecycleTest` - 18 tests, ~8s,
LeakSanitizer-checked; full functional suite 59/59 twice;
sabotage-verified by breaking `setActiveProfile`'s tab switch,
`raiseGlobalEvent`'s sender exclusion, `closeProfile`'s close request
and `loadProfile`'s offline flag - 6 of the 18 fail, and only those.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- 134 new busted specs in `UI_spec.lua` for 24 UI functions that had no
coverage anywhere: label movies, console buffer sizing, main window
size, saved window layout, the application/profile style sheets, toolbar
buttons, command line actions, `setPopup` and `createLabel` into a user
window.
- Effects are read back for real, not just return values: movies through
`getProfileStats().gifs`, buffer limits through observed line trimming,
`setAppStyleSheet` through the `sysAppStyleSheetChange` it raises,
toolbars through `isActive()`, and the user window label by hiding its
parent. No binary fixture is committed - the movie specs assemble a
three-frame GIF89a at run time.
- The button specs install a tiny action package for the block, because
Lua cannot make a push-down button and cannot remove a `tempButton`
again; everything created is taken away in teardown, including the two
layout files that live outside the profile.
#### Motivation for adding to Mudlet
Last of the busted-reachable UI rows in the Lua API test-coverage
program. Nine functions turned out to have no reachable readback at all
and are marked `pending()` with the reason rather than given a spec that
cannot fail.
#### Other info (issues closed, discussion etc)
Bugs found while writing these, all left unspecced and marked
`pending()` instead:
- `Host::setMovie` hands the `QMovie` to the gif tracker before reading
the file, so a refused `setMovie` still counts one in
`getProfileStats()`, and over a working movie it leaves the label
driving a dead one.
- `createLabel` puts the label in the main window and answers `true`
when the parent window name is not a window.
- `showToolBar`/`hideToolBar` only answer to a package's name, never a
packaged toolbar's own name, and move every toolbar in the package at
once; an unmatched name is a silent no-op.
- `setPopup` takes a `luaL_ref` per function command before its size
check and window lookup, so both error paths strand registry references.
- `clearCmdLineSuggestions` gates on `n == 1` where
`addCmdLineSuggestion` gates on `n > 1`, so a second argument silently
retargets the main command line.
- `setConsoleBufferSize` never floors the batch deletion size, so `0`
stops the buffer shrinking at all.
Pre-existing and untouched here: on a reused profile the suite's second
run fails `getMainWindowSize returns a positive width and height` - it
reproduces identically on development.
**Test case:** full busted suite green on a fresh profile (2534
successes, 141 pending, +2.7s in `UI_spec`), and twice on one reused
profile with the same single pre-existing failure development has; 35 of
the 134 new specs (26%) verified to fail when the underlying C++ is
locally broken, and the new `MUDLET_TEST_REQUIRE_WINDOW_RESIZE` gate
proven to fail rather than skip when a resize stops working.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- **+244 busted specs** covering the last uncovered lua-lib rows: the 20
`db:_*` internals (against real sqlite, not strings), IDManager's
private event/timer stores, `saveMap`/`loadMap` round-trips including
the `.xml` import branch, and the Geyser residue (label movies, callback
registration, nested labels, adjustable container mouse handlers,
MiniConsole command line actions, UserWindow constraints).
- **Two new fixtures, both cheap**: a committed 30-line map XML, and a
1x1 two-frame animated GIF written from Lua at run time so no binary is
committed.
- **No runtime cost**: suite goes 2412 -> 2656 specs at the same ~42s,
and passes on a fresh profile and twice on a reused one.
#### Motivation for adding to Mudlet
These are the functions every public `db:`, named-handler and Geyser
call is built out of, and the SQL escaping and quoting rules were
nowhere written down. `saveMap`/`loadMap` had no round-trip at all - the
format that holds a player's whole map was only exercised by hand.
#### Other info (issues closed, discussion etc)
Six bugs found and deliberately *not* specced, filed separately:
`table.contains` stack-overflows on a self-referential table (so on any
Geyser object); `_index = "name"` as a bare string crashes `db:create`
although `_unique` accepts one; `setDoubleClickCallback` stores
`doubleclickCallback` while everything else reads `doubleClickCallback`;
`saveMap` accepts a bare relative path and writes it to Mudlet's working
directory; `saveMap` accepts a format version below `mMinVersion`;
`db:_extract_table_constraints` cannot see a `UNIQUE` with no `ON
CONFLICT`.
Pre-existing and untouched: UI_spec's `getMainWindowSize returns a
positive width and height` already fails on a second run against the
same profile on `development`.
**Test case:** full suite 2656/0/0 + 140 pending, green on a fresh
profile and twice on a reused one; 21 sabotage runs proved 57+ of the
new specs fail when the behaviour under them is broken.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- **49 busted specs** for the last uncovered IRC and media functions:
the IRC configuration round-trips through the profile with no client and
no connection, and both the ordered-argument and table-argument form of
every public media call is now exercised, which is what reaches the ~26
private `*AsOrderedArguments`/`*AsTableArgument` helpers.
- The **video family gets its first coverage at all** - `playVideoFile`,
`getPlayingVideos`, `pauseVideos`, `getPausedVideos`, `stopVideos` and
the widget lookup behind them, both when the request's key names a label
and when it names nothing.
- Two things found on the way and fixed here: busted's `finally()` holds
one function rather than a list, so `Media_spec.lua` specs with two
things to undo kept only the last (a moved-aside media directory stayed
moved, handlers outlived their spec, the speech rate/pitch/volume were
never restored); and handing a player a video widget is the only thing
in the suite that brings a GL context up, whose driver initialisation
leaks unsuppressibly on the leak job's Mesa - so that one spec stands
aside there, the way `Other_spec` already does for `show3dMapView`.
#### Motivation for adding to Mudlet
Part of the Lua API test-coverage programme; this is the residue wave
for `net-media-tts`. Two behaviours these specs pin were previously
unheld anywhere: the numeric-key protection all fourteen media table
parsers carry, and a video request being silently refused when its key
matches no widget.
Bugs found while writing them, none of them specced (filed separately):
a preload that has to download its file then plays it; the load family
never sets a media type; `loadSoundFile`/`loadVideoFile` report a
missing name as `loadMusicFile`; `playMusicFile`'s ordered fade errors
name `playSoundFile`; `setIrcServer` blanks the stored IRC password
whenever it is called without one, and rejects an explicit `nil` where
it accepts an omission.
#### Other info (issues closed, discussion etc)
`Networking_spec.lua` is appended to only, and no `mmcp*` function is
touched, to stay clear of the open #9744 (Fix: Several identified MMCP
issues) which edits the middle of that file. `openIRC` is left pending
with its reason: it creates an IRC dialog nothing in the Lua API closes
again, after which the getters stop reading the profile from disk for
the rest of the run.
**Test case:** full busted suite green twice on a fresh profile and
twice on a reused one (2477 successes, 0 failures, ~+2s), with leak
detection on; 19 sabotage edits were verified to fail 23 of the 49 new
specs.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Renames the six test executables whose filenames trip Windows' UAC
installer detection: `UpdaterChecksumTest` to
`ReleaseChecksumPairingTest`, `UpdaterPlatformAssetTest` to
`ReleasePlatformAssetTest`, `UpdaterTeardownTest` to
`NewReleaseDialogTeardownTest`, `PackageSelfUninstallTest` to
`PackageSelfRemovalTest`, `PackageUninstallSaveTeardownTest` to
`PackageRemovalSaveTeardownTest`, `ActionSelfUninstallTest` to
`ActionSelfRemovalTest`. Assertions are untouched.
- Adds a configure-time gate in `test/CMakeLists.txt` that fails with an
actionable message if any test executable name contains install, setup,
update or patch. It checks both the targets a configuration builds and
the test source filenames, so conditionally registered tests cannot slip
past it.
- Documents the naming rule in `test/README.md`.
#### Motivation for adding to Mudlet
Windows treats an unsigned executable named that way as an installer and
refuses to start it, so those six tests reported `BAD_COMMAND` for
anyone running the suite from an ordinary Windows shell - and because CI
runners are elevated, nothing caught it as more tests were added.
#### Other info (issues closed, discussion etc)
Fixes#9748
The gate was verified to fire on a target named after the guard
statement, on one in a subdirectory, on `EventDispatcherTest` (the
message names the offending substring, since "dispatch" contains
"patch"), on a test registered only under `USE_UPDATER` when configuring
with the updater off, and to fail loudly if the walk ever stops finding
executables.
**Test case:** `cmake --build build && ctest --test-dir build` - 92/92
pass; adding a test named e.g. `FooUpdateTest` fails the configure with
an explanation.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Removed 8 comments that only repeated the statement or assertion
message next to them
- Kept 1 of the 16 identical copies of the `lua_next()` key-copy note in
`TLuaInterpreterMedia.cpp`
- Comment-only: zero code lines changed
#### Motivation for adding to Mudlet
Reading a comment and then the code that says the same thing is wasted
effort; the rationale comments that document real gotchas are all
untouched.
#### Other info (issues closed, discussion etc)
Result of a pass over the last month of commits on `development`. The
vast majority of comments added there explain *why* rather than restate
*what*, so this is deliberately a small diff.
**Test case:** `git diff development...HEAD` shows only comment lines
removed; build and test suites are unaffected.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
#### Brief overview of PR changes/additions
- `61c2afd40` (#9712) made `$XDG_CONFIG_HOME/mudlet/profiles` the opt-in
marker for an isolated config root, and an empty
`$XDG_CONFIG_HOME/mudlet` no longer qualifies. It updated three
functional tests to the new recipe and missed two, so
`ProfileDeletionSafetyTest` and `ConnectionDialogCrashTest` resolved to
the real `~/.config/mudlet` and failed in `initTestCase()` on every leg
(run 31428353403). Every open PR inherits that red, because PR builds
merge the dev tip.
- Both now pre-create `mudlet/profiles`, matching the ten sibling tests
and `src/mudlet-lua/tests/README.md`. Product code is untouched.
- An empty `profiles/` still reads as a fresh install
(`anyProfilesExist()` counts subdirectories), so neither test's
first-launch expectations move.
Test case: `ctest -R
"ProfileDeletionSafetyTest|ConnectionDialogCrashTest"` against a
`~/.config/mudlet` that holds profiles - both fail on `development`,
both pass here; the full functional suite is otherwise unchanged.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Appends `QT_ASSUME_STDERR_HAS_CONSOLE=1` to the `ENVIRONMENT` test
property of every registered test - 95 of 95 confirmed with `ctest
--show-only=json-v1` - leaving each test's existing `ASAN_OPTIONS`,
`QT_QPA_PLATFORM` and `ENVIRONMENT_MODIFICATION` untouched.
- One `cmake_language(DEFER CALL)` per directory that registers tests,
rather than the variable copy-pasted into a dozen strings, so a test
added later cannot miss it wherever in the file it lands.
- Drops the 8 `QT_FORCE_STDERR_LOGGING` entries from the four workflows.
`shouldLogToStderr()` is `forceStderrLogging() ||
stderrHasConsoleAttached()`, so the test property now covers what CI was
setting by hand, and every one of those steps runs nothing but `ctest`.
#### Motivation for adding to Mudlet
Qt on Windows diverts QTest's output to `OutputDebugString` unless it
believes stderr has a console attached, and an MSYS2 shell gives it
none, so a failing test reported an exit code with no `FAIL!` lines, no
compared values and no totals. Setting it as a test property fixes local
runs and CI from one place instead of two.
#### Other info (issues closed, discussion etc)
**Test case:** full Linux `ctest` suite 95/95 pass; `ctest
--show-only=json-v1` shows all 95 tests carrying the variable with no
other property changed; `ctest -V` shows it in the test process
environment. The Windows behaviour itself is not reproducible on Linux -
it rests on the reporter's 219 vs 12233 byte A/B and on
`QPlainTestLogger::outputMessage`, which only calls `OutputDebugStringA`
when `!QtPrivate::shouldLogToStderr()`.
Closes#9747
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- `TMediaPlayer::~TMediaPlayer()` blocks its `QMediaPlayer`'s signals
before the `stop()`/`setSource(QUrl())` that unloads the media, so no
handler is called with a player whose members are going away. The unload
itself is unchanged, and Qt still emits `destroyed()`.
- New `TMediaLoopTest::test_destroyingAPlayerAnnouncesNothing()`, with a
control unload on a player that is not being destroyed so it cannot pass
vacuously.
- `test_continuingToTheNextPassClearsTheEarlierAnnouncement()` declares
its flag ahead of the player, so a lambda connected to that player
always has live stack to write to.
#### Motivation for adding to Mudlet
Emitting signals from a destructor is a landmine for any connected code:
today TMedia's own handlers survive it only because each one locks an
already-expired `weak_ptr`, and the test suite, which does not, aborted.
#### Other info (issues closed, discussion etc)
Closes#9740 "TMediaLoopTest aborts under AddressSanitizer with
stack-use-after-scope".
Reproduced and verified on Linux with a clang ASan Debug build
(`USE_SANITIZER=address`): pre-fix `TMediaLoopTest` aborts with
`stack-use-after-scope` in `~TMediaPlayer` ->
`QMediaPlayer::setSource()`, post-fix the suite is clean. The new test
fails without the destructor change (counts 1 announcement) and passes
with it. GCC does not poison per-variable within a scope, so the abort
only shows up under clang/AppleClang.
**Test case:** `cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug
-DCMAKE_CXX_COMPILER=clang++` then `cmake --build build --target
TMediaLoopTest && ctest --test-dir build -R TMediaLoopTest` - passes
instead of `***Exception`.
Assisted-by: Claude:claude-opus-5
**Not for 5.0 - please hold this for the release after 5.0.**
#### Brief overview of PR changes/additions
- `TTextEdit` lays text out in cells of `QFontMetrics::height()`, a
typographic measure rather than the glyph ink box, so at many font sizes
the ink of `_ g j p q y $ @ ( )` reaches a pixel past the bottom of its
cell. On Bitstream Vera Sans Mono at 14pt the underscore is a 1px bar
sitting entirely below the cell, so it disappears completely.
- The screen is now rendered with each line's cell backgrounds painted
before the previous line's glyphs, the screen pixmap has a spare row for
the bottom line's overflow, and partial repaints and scroll blits put
back the overflow they would otherwise erase.
- With backgrounds no longer able to clobber overflow, the narrowed
background-fill condition from #9288 goes back to what #8887 intended.
Measured with an offscreen A/B of the same scene: 15.0-15.2ms per frame
against 16.5-16.9ms before, on both the full-repaint and the scrolling
path (ASan build, so treat the absolute numbers as relative only).
#### Motivation for adding to Mudlet
#9288 tried to fix this by letting the overflow pixel survive into the
next line's cell, but four separate things still erase it: the next
line's background fill whenever the colour differs (coloured text,
selection, search highlight, caret, background image, alpha), the
partial-repaint clear, the bottom edge of the screen pixmap, and the
scroll blit. That is why the reporter sees different `print`-style
functions behave differently at the same size.
#### Other info (issues closed, discussion etc)
Fixes#9719. Completes #9070, which #9288 only partly addressed.
`GlyphOverflowTest` renders a real console offscreen across two bundled
fonts and 22 font sizes and compares the underscore's ink against the
same glyph drawn on its own. All five of its cases fail on `development`
and pass here: the line below carrying a default, coloured, bright or
selected background; the bottom visible line; a partial repaint; a
scroll-back; and a miniconsole.
Copy-as-image is fixed too: its pixmap is exactly one cell per selected
line, so the bottom line's ink was cut off every time.
Known limitations, both unchanged from before this PR:
- The topmost visible line's ink can overflow above the pixmap and be
clipped. Fixing it would mean shifting the whole screen-pixmap
coordinate system.
- When a pane's height is an exact multiple of the line height there is
no leftover strip below the last line, so the bottom line's overflow has
no pixel to live in. Measured at roughly 1 pane height in every 22 for
both the main console and the split-screen lower pane. The only fix is
to drop a row when there is no slack, and because rows are quantised
that costs a whole line of text plus a blank line-height strip at the
same ~4.5% of heights, which is a worse trade than the pixel it buys.
Resizing the pane by one pixel restores it.
Test case: `ctest -R GlyphOverflowTest`, or set Bitstream Vera Sans Mono
at 14pt and `cecho("<yellow>plain _underscore_\n<white:blue>coloured
line\n")`.
Assisted-by: Claude:claude-opus-5
https://github.com/user-attachments/assets/cbda2288-1e3e-4f24-9763-7275d2f09134
#### Brief overview of PR changes/additions
- An empty `$XDG_CONFIG_HOME/mudlet` silently beat a populated
`~/.config/mudlet`, so a stray `mkdir` hid every profile and Mudlet ran
its first-launch onboarding as though the user were new. It also stuck:
the first such launch wrote `Mudlet.ini` into that directory, which then
kept it winning.
- The two candidate roots are now ranked (`profiles/` > `Mudlet.ini` >
exists > absent) and the stronger claim wins, with
`$XDG_CONFIG_HOME/mudlet` taking ties so a fresh install and a
deliberate opt-in both still land there. A directory that cannot be
listed counts as populated rather than empty, so a permission bit cannot
re-enter the bug.
- Creating `profiles/` is now the opt-in a test harness uses; the
`mudlet` directory alone is not, because other tooling creates that by
accident. Where both roots hold profiles, `setupConfig()` names the one
it is ignoring instead of leaving those profiles apparently gone.
#### Motivation for adding to Mudlet
Data-loss-shaped regression from #9552 "improve: honor XDG_CONFIG_HOME
for Mudlet's config directory" (`e6c268cb0`). The profiles are orphaned
rather than destroyed, but a returning user sees "5.0 wiped my
profiles". `src/mudlet-lua/tests/README.md` itself instructed `mkdir -p
"$CONFIG_DIR/mudlet"`, so following Mudlet's own test docs triggered it.
#### Other info (issues closed, discussion etc)
Test case: create `~/.config/mudlet/profiles/{AlphaGame,BetaGame}`,
`mkdir -p $XDG_CONFIG_HOME/mudlet`, launch. Before: no profiles and the
onboarding dialog. After: both profiles listed.
`ConfigDirOverrideTest` covers the resolution table including the sticky
`Mudlet.ini` state, both-populated, symlinked and unreadable
directories; each new guard was mutation-checked. The busted suite
passes 2422/0 against an isolated `$XDG_CONFIG_HOME/mudlet/profiles`
root.
Not fixed here, and pre-existing rather than 5.0 regressions:
`CredentialManager` stores passwords and the OAuth reconnect token under
`AppConfigLocation` while the config root is `confPath`, so exporting
`XDG_CONFIG_HOME` strands them, and the plaintext-password migration
reads one path, writes the other and deletes the original. Both
reproduce identically on the 4.22.0 binary and need their own migration
path.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Windows CI no longer pipes `luarocks path --lr-path/--lr-cpath`
through `cygpath -u`; the raw Windows-form output is exported instead.
- Same 4-line block fixed in all 5 places:
`CI/build-mudlet-for-windows.sh` and both Windows workflows (QTest + Lua
tests steps in each).
- Added a short note at each site saying why cygpath must not be
reintroduced.
#### Motivation for adding to Mudlet
`cygpath -u` does not know `;` delimits a list, so it rewrites only the
leading entry into a POSIX path the native Windows Lua cannot open -
which is exactly the per-user rock tree, so CI silently ran without
those rocks.
#### Other info (issues closed, discussion etc)
Fixes#9750
Verified functionally in a real MSYS2 CLANG64 shell on a Windows 11 VM,
with a canary module installed into `$HOME/.luarocks-CLANG64`:
Raw luarocks output:
```
C:\msys64\home\Bob\.luarocks-CLANG64\share\lua\5.1\?.lua;C:\msys64\home\Bob\...\?\init.lua;C:\msys64\clang64\share\lua\5.1\?.lua;...
```
After `cygpath -u` (what CI did):
```
/home/Bob/.luarocks-CLANG64/share/lua/5.1/?.lua;C:/msys64/home/Bob/...
```
Only element 1 is genuinely converted, and `C:\home` does not exist on
the guest. Running the native `/clang64/bin/lua5.1`:
| `LUA_PATH` value | `require 'zzcanary'` |
| --- | --- |
| raw luarocks output | `true canary-ok` |
| `cygpath -u` output | fails: `no file
'/home/Bob/.luarocks-CLANG64/share/lua/5.1/zzcanary.lua'` |
| `cygpath -u -p` output | worse: `:`-separated, Lua reads the whole
list as one filename |
`LUA_CPATH` behaves the same (the per-user `.dll` is unreachable after
`cygpath -u`, found before it). Also confirmed MSYS2 does not
auto-convert these variables when spawning a native child, so nothing
was masking the defect. `CI/setup-windows-sdk.sh` already prints the raw
form as the recommended Qt Creator environment, so CI now matches its
own setup advice.
Separately, #9749 (the functional tests never receiving
`LUA_PATH`/`LUA_CPATH` at all) is a different root cause and is not
addressed here.
**Test case:** Windows CI green; the QTest and Lua test steps log
`LUA_PATH`/`LUA_CPATH` in `C:\...;C:\...` form and busted/lfs/rex_pcre2
load from the per-user tree.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
#### Brief overview of PR changes/additions
- MXP `<FRAME>` windows were positioned against the whole main window,
so an edge-aligned frame landed on top of space a package had reserved
with `setBorderRight()` and friends. They now lay out inside the area
the user borders leave.
- Frames are repositioned when those borders or the window size change,
instead of staying where they were first put.
- Frames in a window of their own (`EXTERNAL`) are left alone by that
repositioning.
#### Motivation for adding to Mudlet
With the base UI installed, a game using MXP frames drew its frames on
top of the UI panel instead of beside it.
#### Other info (issues closed, discussion etc)
Fixes#9698
New functional test `MxpFramePlacementTest` (16 cases) covers each edge,
border changes, window resizes, nested frames, external frames, and a
Geyser `Adjustable.Container` attached to the right border, which is how
the base UI reserves its space. 8 of the cases fail on `development`.
**Test case:** With the base UI installed, connect to
`eden-test.rpgframework.de 4000` - the MXP frames sit beside the UI
panel rather than under it.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Hoists the list `slot_deleteProfile()` checks a never-played profile
against out of the function to
`dlgConnectionProfiles::scmConnectionDetailFiles`, beside the file's
other `scm*` constants, and points at it from both `writeProfileData()`
implementations.
- Adds a `ProfileDeletionSafetyTest` case that sets a profile up through
the real dialog (New profile, name it, fill the connection form in,
re-select it) and fails if anything it wrote is missing from the list.
- Pins the deliberate exclusions too: a profile holding a stored
password or a typed-in character name still asks before removal.
#### Motivation for adding to Mudlet
Nothing linked that list to the ~15 places profile data gets written, so
it could silently go stale; because it is an allowlist a stale entry
only ever costs an extra confirmation prompt, but the maintenance trap
was worth closing.
#### Other info (issues closed, discussion etc)
Follows up
https://github.com/Mudlet/Mudlet/pull/9722#discussion_r3740581754 on
#9722 (fix: a profile named "." or ".." deletes every profile when
removed). No behaviour change.
**Test case:** `ctest -R ProfileDeletionSafetyTest` (20 cases). Removing
an entry from the constant makes it fail naming the file; adding `login`
makes the character-name case fail.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Chat capture ships as a permanent trigger tree in the base UI package
instead of triggers created at runtime, so it is visible and copyable in
the editor: one root folder, three cheap substring gates (tells / speech
/ channel tags), each gating its Perl regex shapes as children.
- Lifecycle moves from `tempRegexTrigger`/`killTrigger` to
`enableTrigger`/`disableTrigger` on the three gates, with a
`BaseUI.chatTriggersArmed()` probe replacing the id bookkeeping.
- Vitals capture is unchanged and stays Lua-armed: its prefilter is
machine-built from the label tables, so an XML copy would drift from
what `parseVitalsLine` reads.
#### Motivation for adding to Mudlet
The gate pattern - a cheap substring parent chaining to regex children -
is the thing new scripters most need to learn, and shipping it as a
readable tree teaches it where invisible runtime triggers could not.
#### Other info (issues closed, discussion etc)
A non-matching line now costs ~17 substring scans and zero regex work.
Tightening the speech gate's literals fixed real leakage in the process:
the stems `say`/`ask`/`yell` had been letting "essay", "task", "asked"
and "yellow" through to the regex layer.
New tests: the tree's leaf shapes are asserted equal to
`BaseUI.chatShapeRegexes()`, gates are asserted substring-only, and
every gate literal must have a corpus line - the enforcer for the gate
contract, since a case-mismatched gate would otherwise drop lines
silently. Each was verified to fail by mutating the shipped package.
**Test case:** Start a new profile, connect to a game with tells and
channels, and confirm chat lines still sort into their tabs; open the
Triggers editor and confirm `mudlet-base-ui` -> `Mudlet base UI chat
capture` shows three gates with regex children. `ctest -R
StarterUiTriggerCost` covers both.
Assisted-by: Claude:claude-opus-5
Bumps [lukka/get-cmake](https://github.com/lukka/get-cmake) from 4.4.0
to 4.4.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/lukka/get-cmake/releases">lukka/get-cmake's
releases</a>.</em></p>
<blockquote>
<h2>CMake v4.4.2</h2>
<p>The <code>get-cmake</code> action downloads and caches CMake and
Ninja on your workflows. Versions can be specified using <a
href="https://docs.npmjs.com/about-semantic-versioning">semantic
versioning ranges</a> using <a
href="https://github.com/lukka/get-cmake/blob/latest/action.yml#L13"><code>cmakeVersion</code></a>
and <a
href="https://github.com/lukka/get-cmake/blob/latest/action.yml#L16"><code>ninjaVersion</code></a>
inputs.</p>
<p>Changes:</p>
<ul>
<li><code>latest</code> is now using CMake version <code>v4.4.2</code>,
use this one-liner e.g.:
<code>uses: lukka/get-cmake@latest</code></li>
</ul>
<p>Enjoy!</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="fffaaafeea"><code>fffaaaf</code></a>
New CMake version(s): cmake-v4.4.2</li>
<li><a
href="4a7d025fc6"><code>4a7d025</code></a>
New CMake version(s): cmake-v4.4.1</li>
<li><a
href="06fec8f1da"><code>06fec8f</code></a>
Bump actions/checkout from 5 to 7</li>
<li>See full diff in <a
href="https://github.com/lukka/get-cmake/compare/v4.4.0...v4.4.2">compare
view</a></li>
</ul>
</details>
<br />
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vadim Peretokin <vperetokin@hey.com>
<!-- Keep the title short & concise so anyone non-technical can
understand it,
the title appears in PTB changelogs -->
#### Brief overview of PR changes/additions
Don't offer updates that don't yet have a sha256 sum
#### Motivation for adding to Mudlet
They are incomplete anyhow.
#### Other info (issues closed, discussion etc)
#### Brief overview of PR changes/additions
- lua.org is reachable again, so the mirror-fallback pre-build step from
#9732 is removed.
- In its place, `gh-actions-lua`'s build cache is turned on (it was
explicitly off since 2022 with no recorded reason): every run restores
`.lua/` from the actions cache, so lua.org is only contacted when the
cache is cold.
- The `lua:5.1.5:linux:x64` cache entry already exists in the repo and
is touched daily by other workflows; the macOS entries get created on
this PR's own run and the nightly scheduled builds keep all platforms
warm from then on.
#### Motivation for adding to Mudlet
The 35-line shell workaround duplicated the action's build logic in two
files and leaned on undocumented action behaviour. The cache achieves
nearly the same outage protection using the action as designed - the one
remaining exposure is lua.org being down at the same time as a cache
eviction (7 days unused).
#### Other info (issues closed, discussion etc)
Test case: this PR's own CI runs the modified workflow - macOS legs
build Lua once from lua.org and save the cache, the Linux leg restores
the existing daily-used cache entry.
Assisted-by: Claude:claude-fable-5
#### Brief overview of PR changes/additions
- The starter UI armed **77 always-active PCRE triggers** (12 chat + 65
vitals) at package load, so every line a game sent was matched against
all of them - and every line one matched was then re-walked in Lua with
all 77 patterns **recompiled from source**, because `rex.match` given a
pattern string compiles it afresh on every call. They are now fronted by
4 triggers (3 chat-routing groups + 1 vitals prefilter) and compiled
once. The 65 vitals shapes and 12 chat shapes are byte-identical and
still do all the reading.
- The plain-text vitals layer now retires itself once GMCP or MSDP holds
the source lock, since `applyVitals` discards its readings from that
point anyway, and re-arms on disconnect.
- `PipelineBenchmark` created its profile through the production
new-profile path, so the starter UI was **inside** the
`text_lines_per_sec` baseline backing the "no more than 10% throughput
loss" gate for #9011 - the guard built to catch this class of regression
could not see it. Pipeline metrics now come from a profile with default
packages suppressed; the shipped configuration is reported separately as
`defaults_*` and gated in its own right.
#### Motivation for adding to Mudlet
Every new 5.0 profile was paying roughly half its text throughput to a
default package, and the perf guard had the cost baked into its own
baseline so nothing flagged it.
#### Other info (issues closed, discussion etc)
Findings C17 and C18 of the 5.0 QA sweep. Bisected there to `69cd06b1c`
- "add: starter interface with health bars, map and chat for new
players" (#9454); the benchmark half is the interaction of that with
`7d67d4bfb` - "infrastructure: perf baseline" (#9509).
Measured on a quiet 16-core box, Release, no ASan, alternating paired
runs so drift is shared between arms:
| workload | before | after | |
| --- | --- | --- | --- |
| `TelnetBenchmark` `benchLargeData`, 1000 lines that match nothing |
22.25 ms `[22.1-22.6]` | 12.0 ms `[11.9-12.2]` | **1.85x** |
| `PipelineBenchmark`, 25k lines of realistic game output, new-user
profile | 9,998 lines/s `[9,856-10,072]` | 16,503 lines/s
`[16,257-16,632]` | **1.65x** |
Complete separation in both (21 and 9 pairs; within-arm spread ±1.7% and
±1.5%, so ~3% is the smallest effect distinguishable from noise - the
effect is 85% and 65%). The bare pipeline measures 116,000 lines/s, so
the starter UI's remaining cost on that corpus is 7.0x, down from 11.6x;
the residual is the capture layer doing its designed work on a corpus
where 1 line in 11 is a tell and another 1 in 11 a vitals prompt.
Two notes for reviewers:
- `config.lua` is bumped to 1.1.0, so mpkg offers the update - but
default packages are installed at profile creation, so **profiles
already created on a 5.0 PTB keep the old copy** until they update it.
- Touches `src/mudlet.cpp` / `src/mudlet.h` /
`test/functional_tests/CMakeLists.txt`, which #9695 also touches; the
CMakeLists hunk will likely conflict trivially (both append a test
file).
**Test case:** create a fresh profile against any game without GMCP,
confirm the health/mana gauges and chat tabs still appear from prompt
and chat lines, then `ctest -R StarterUiTriggerCostTest`.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- Hardens how OSC 8 link payloads and link text are handled before they
are run or displayed; link commands are no longer built by
string-formatting remote text into Lua source.
- Adds a per-profile setting (General → Game protocols) to turn OSC 8
hyperlinks off, which also reports `0` for every `OSC_HYPERLINKS*`
NEW-ENVIRON variable and sends an INFO update if toggled mid-session.
- Fixes `selected=` callbacks on `send:` links, which never fired, and
keeps emoji and Persian/Arabic/Indic text intact in tooltips and menu
labels.
#### Motivation for adding to Mudlet
Inspired by
[conversation](https://discord.com/channels/279748146316312576/1416447642472284261/1535109010066251890)
on the MUD Discord and updates to terminal emulators.
OSC 8 sequences arrive from the game server — and often from another
player whose say/tell text the server relays — so they have to be
treated as untrusted input rather than as content the user chose to
load.
#### Other info (issues closed, discussion etc)
New unit tests: `LuaLiteralTest` (28 cases, including an exhaustive
sweep over the bracket alphabet, each evaluated in a real Lua 5.1 state)
and `UntrustedTextTest` (26 cases covering emoji sequences, non-Latin
shaping and the two sanitization policies). There is no automated
NEW-ENVIRON coverage anywhere in the repo, so that path was verified
manually against a live server instead.
**Test case:**
1. `say !osc8-docs` — every documented feature still works.
2. Send a link whose command ends in `]`, e.g. `send:say [OOC]` —
clicking sends the literal text (previously the click silently did
nothing).
3. Settings → General → Game protocols → uncheck "Enable OSC 8
hyperlinks from the server" — links stop rendering and the server is
told without a reconnect; re-check and they return.
4. Send a tooltip or menu label containing a multi-part emoji such as
👨🍳 — it renders normally, not as its component parts.
---------
Signed-off-by: Michael Conley <sousesider@gmail.com>
#### Brief overview of PR changes/additions
- Adds seven contributors to the credits list in Help -> About Mudlet:
Delwing, Zooka, Mike Conley, Edru2, Tim Johnson, Harrison and John
McKisson
- Delwing and Zooka join the highlighted group; the rest slot into the
alphabetical list below it
- Descriptions are sized in line with the existing entries
#### Motivation for adding to Mudlet
Several of Mudlet's most prolific contributors were entirely uncredited
- between them they account for most of today's 2D mapper, the
media/MXP/encoding stack, the Geyser GUI toolkit, the script editor and
preferences, the 3D mapper revival, MMCP, and Mudlet's screen reader
support. Delwing and Zooka also build and maintain Mudlet Web and the
package repository respectively.
#### Other info (issues closed, discussion etc)
- Pronouns are not stated publicly for any of the seven, so the entries
avoid gendered wording.
- Emails are only listed where the contributor commits publicly with a
real address; those using GitHub noreply addresses are credited by
handle alone.
**Test case:** Open Help -> About Mudlet and confirm the seven new
entries render in place, with GitHub handle and email links coloured
like the rest.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
👑 An automated PR to update autocompletion data in Mudlet from
refs/heads/development (930ea5af5c).
#### Motivation for adding to Mudlet
So autocompletion works as expected.
Co-authored-by: mudlet-machine-account <mudlet-machine-account@users.noreply.github.com>
#### Brief overview of PR changes/additions
- Validation: a typed profile name must be a folder of its own - rejects
a lone `.` and anything containing `..`. Folders already on disk stay
exempt
- Containment: `reallyDeleteProfile()` refuses any path that is not a
direct child of `profiles/`, and now checks `removeRecursively()`
instead of failing mute
- Confirmation: the "nothing to delete" shortcut no longer fires when a
map, stored password or dictionary is present
#### Motivation for adding to Mudlet
A profile named `.` or `..` turned **Remove** into a wipe: `.` resolves
to `profiles/`, `..` to the whole `~/.config/mudlet`. The name was
accepted with no error, and the confirmation was skipped because a fresh
profile looks empty - two clicks deep on the first screen every user
sees.
#### Other info (issues closed, discussion etc)
Pre-existing, not a 5.0 regression - shipped 4.22.0 behaves identically.
Dots have been allowed deliberately since 2011 (`ee1fd051c`), and
`Achaea 2.0` keeps working.
**Test case:** name a new profile `.` and press Remove - previously
every profile was deleted with no prompt, now the name is refused.
New `ProfileDeletionSafetyTest` drives the real dialog against a
temporary config dir, plus `profileFolderPath`/`profileNameUsableAsIs`
rows in `ProfileNameValidationTest`. 81/81 ctest pass.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- A stray `ESC` from the game ate the byte after it, including the lead
byte of a multi-byte character, so `café` arrived as `caf<?>`.
- Mudlet now consumes only the escape sequences it recognises: the
CSI/OSC/DCS/SOS/PM/APC introducers as before, the two-byte escapes `ESC
7`, `ESC 8`, `ESC c` and `ESC \`, and ISO 2022 character set designation
(`ESC ( ) * +` plus the byte naming the set, which is what fixes `ESC (
B`).
- Any other byte after an `ESC` is text and is left alone, so a stray
escape now costs zero characters.
#### Motivation for adding to Mudlet
#9433 fixed a real bug - a stray `ESC` staying latched - but
over-applied it by consuming any byte `>= 0x20`. Against shipped 4.22.0,
`AA<ESC>éBBB` renders as `AAéBBB` there, `AA<U+FFFD>BBB` today, and
`AAéBBB` with this PR. Any game that emits a stray escape corrupts
accented text for its players. #9433's other fix, the leaking payloads
of #7446, is done by the `ESC P X ^ _` branch and is untouched.
#### Other info (issues closed, discussion etc)
Test case: `lua setServerEncoding("UTF-8") feedTriggers("Menu:
caf\27\195\169 au lait\n")` - shows `caf<?>` before, `café` after.
20 new busted tests in `TBufferOSC_spec.lua`, 13 of which fail on an
unpatched build; #9433's 8 tests still pass.
Trade-off: an escape sequence Mudlet does not recognise prints its final
byte as text, exactly as 4.22.0 did, rather than risk eating real
output.
Assisted-by: Claude:claude-opus-5
#### Brief overview of PR changes/additions
- One budget of 100 covered every root trigger created while a line was
processed, and tripping it deactivated all of them, so a script arming
101 unrelated triggers lost all 101.
- Triggers created mid-line now carry the creation lineage they belong
to and how many generations deep they sit in it. A batch is one
generation however big it is; only a trigger that re-creates itself
keeps adding generations, so that is the only shape the budget counts.
The limit is 1000 generations, and only the runaway lineage is stopped
and named.
- Generations do not bound a lineage that widens as it deepens, so past
20000 creations on one line new triggers stop being offered that line.
Nothing is disowned there - they are all still armed for the lines that
follow.
#### Motivation for adding to Mudlet
#9697 fixed a real freeze, but its counter had no lineage, so it
destroyed legitimate triggers along with the runaway. Any routine arming
more than 100 triggers from a trigger loses them, permanent ones
included, which is a regression against 4.22.0.
#### Other info (issues closed, discussion etc)
Fixes a regression introduced by merged #9697; release-blocking for 5.0.
Test case: `lua fired=0; tempTrigger("GATE", function() for i=1,200 do
tempTrigger("PAY", function() fired=fired+1 end) end end);
feedTriggers("\nGATE\n"); feedTriggers("\nPAY\n"); print(fired)` -
prints 0 before, 200 after.
A/B against a shipped 4.22.0 binary (4.22.0 / 5.0 RC / this PR), counted
on the line after the one that armed them: 1001 unrelated temp triggers
1001 / 0 / 1001; two scripts of 600 each 600+600 / 0+0 / 600+600; 1001
permanent triggers 1001 / 0 / 1001. #9697's freeze is still stopped and
bounded (601 runaway lines in 8 s, RSS flat at ~820 MB) and its own
tests still pass. Eleven new tests parameterise the creation count, and
cover nested passes and folder/filter-chain children, which none of
#9697's did - that is why this shipped.
Assisted-by: Claude:claude-opus-5
Bumps
[3rdparty/sentry-native](https://github.com/getsentry/sentry-native)
from `a99d64e` to `a185ce8`.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="a185ce80ba"><code>a185ce8</code></a>
release: 0.16.1</li>
<li><a
href="e3c769f0f1"><code>e3c769f</code></a>
feat: added <code>app_hang_pause</code> (<a
href="https://redirect.github.com/getsentry/sentry-native/issues/1928">#1928</a>)</li>
<li><a
href="ee9ac8c856"><code>ee9ac8c</code></a>
fix(docker): expand PIPX_BIN_DIR when adding it to PATH (<a
href="https://redirect.github.com/getsentry/sentry-native/issues/1925">#1925</a>)</li>
<li><a
href="16e7222d8f"><code>16e7222</code></a>
fix(consent): honor checks before launching crash reporter (<a
href="https://redirect.github.com/getsentry/sentry-native/issues/1906">#1906</a>)</li>
<li><a
href="86cb8d3368"><code>86cb8d3</code></a>
Merge branch 'release/0.16.0'</li>
<li>See full diff in <a
href="a99d64efb8...a185ce80ba">compare
view</a></li>
</ul>
</details>
<br />
Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.
[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)
---
<details>
<summary>Dependabot commands and options</summary>
<br />
You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
</details>
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Vadim Peretokin <vperetokin@hey.com>