#### Brief overview of PR changes/additions
- Include `mGotString` (DCS/SOS/PM/APC parser state) in the
server<->local channel swap so string-sequence state no longer leaks
between the game stream and local feeds
- Remove the stray-ESC clear block #9436 added, which #9433 already made
unreachable
- Add the regression test #9436 lacked; `TOscTest` now uses an ephemeral
telnet-stub port so concurrent runs cannot collide
#### Motivation for adding to Mudlet
Without the swap, a local OSC was silently not decoded and a split
game-side DCS payload was wrongly decoded as an OSC, corrupting
colour/control-code handling.
#### Other info (issues closed, discussion etc)
Follow-up to #9436 (found during a post-merge QA review).
**Test case:** Run the functional test suite; `TOscTest` covers the
DCS-leak and stray-ESC scenarios. Manually: send a DCS payload split
across two packets from the game and confirm it is not misdecoded, and
feed a local OSC and confirm it is decoded.
Assisted-by: Claude:claude-fable-5
#### Brief overview of PR changes/additions
Convert raw millisecond integer literals at time-duration call sites to
`std::chrono` literals, and add `#include <chrono>` to each touched
translation unit. Examples:
- `QTimer::singleShot(0, ...)` → `QTimer::singleShot(0ms, ...)`
- `mpTimerReplay->setInterval(1000)` → `setInterval(1s)`
- `mPendingTimer.start(60000)` → `start(1min)`
- `QObject::startTimer(50)` → `startTimer(50ms)`
- `QTest::qWait(100)` → `QTest::qWait(100ms)`
- `QThread::msleep(10)` → `QThread::sleep(10ms)`
This is a semantics-preserving refactor - every duration is kept exactly
equal to before (e.g. `1000` ms becomes `1s`, `60000` ms becomes
`1min`). No behavioural change.
#### Motivation for adding to Mudlet
Chrono literals make time durations self-documenting and type-safe. `1s`
/ `100ms` read unambiguously where a bare `1000` / `100` forces the
reader to remember each API's unit, and the compiler now rejects unit
mismatches. Only genuine duration arguments were converted - loop
counts, scroll-line counts, sizes, ports and the like were deliberately
left as plain integers.
All targeted APIs provide `std::chrono` overloads in the minimum
supported Qt (6.8.2): `QTimer::singleShot`/`start`/`setInterval` (5.8),
`QObject::startTimer` (5.9), `QThread::sleep(std::chrono::nanoseconds)`
(6.6) and `QTest::qWait(std::chrono::milliseconds)` (6.7).
#### Other info (issues closed, discussion etc)
Test case: the full application builds cleanly and the entire functional
`ctest` suite passes. The only failing test is the known, pre-existing
`PasswordMigrationTest` LSan exit-leak (GTK3/fontconfig noise), which is
unrelated to this change.
Assisted-by: Claude:claude-opus-4-8
#### Brief overview of PR changes/additions
- TelnetServerStub now binds an OS-assigned (ephemeral) port; its log
reports the actual bound port
- All 11 functional tests that hardcoded listen ports (three shared port
4000, two pairs shared 4003/4004) now read the real port back via
serverPort()
#### Motivation for adding to Mudlet
Concurrent test runs (parallel CI jobs, multiple checkouts on one
machine) collided on the fixed ports, causing flaky bind failures and
tests connecting to the wrong run's server.
#### Other info (issues closed, discussion etc)
Follows the pattern GMCPCharLoginTest already used. Verified by running
two copies of TelnetTextDisplayedTest simultaneously - both passed on
distinct ports (37503/42303), impossible before.
**Test case:** Run the functional suite twice in parallel (two build
dirs or ctest -j2 repeated); no "address already in use" failures.
Assisted-by: Claude:claude-opus-4-8
#### Brief overview of PR changes/additions
An `ESC` not followed by `[` or `]` (for example `ESC c`, `ESC 7`, or a
charset designator like `ESC(B`) left the internal "saw ESC" flag stuck
on. The next literal `[` or `]` anywhere in later text was then
misparsed as an escape-sequence introducer and swallowed the text after
it. The flag is now cleared in that case.
#### Motivation for adding to Mudlet
Fixes display corruption / silently lost text when a server emits such
sequences.
#### Other info (issues closed, discussion etc)
Found via a source-code audit; no linked issue.
---------
Signed-off-by: Vadim Peretokin <vadim.peretokin@mudlet.org>
#### Brief overview of PR changes/additions
The Qt documentation for `QtConcurrent` points out:
> If you include the `<QtConcurrent>` header, the entire Qt Concurrent
module with the entire Qt Core module will be included, which may
increase compilation times and binary sizes. To use individual functions
from the QtConcurrent namespace, you can include more specific headers.
>
> The table below lists the functions in the QtConcurrent namespace and
their corresponding headers:
|Function|Header|
|--------|------|
|`QtConcurrent::run()`|`<QtConcurrentRun>`|
|`QtConcurrent::task()`| `<QtConcurrentTask>`|
|`QtConcurrent::filter()`,<br>`QtConcurrent::filtered()`,<br>`QtConcurrent::filteredReduced()`|`<QtConcurrentFilter>`|
|`QtConcurrent::map()`,<br>`QtConcurrent::mapped()`,<br>`QtConcurrent::mappedReduced()`|`<QtConcurrentMap>`|
#### Motivation for adding to Mudlet
To speed up the build a little by removing stuff that isn't needed.
#### Other info (issues closed, discussion etc)
In doing this I happened to start cleaning up a couple of header files
`T2DMap.h` and then `mudlet.h`, I then got into converting some
`#include`s into forward declarations in a "include-what-you-use" move.
This then rippled through into a (more than 10!) number of files but
should "improve" things.
Note that the ordering of `#include` in many files seems to be rather
haphazard and is due for some serious overhaul - I suggest that we
should actually declare an "official" style for this project so that
everyone knows what it is.
**During the CI/CB process I discovered that Linux and then MacOS builds
were failing because the file referred to by the `#include
<QtConcurrentTask>` header file was missing, yet was present on my local
PC when I was using the Qt framework from the On-line installer.
Initially I suspected a Debian (and then Devuan - as the packaged
version on my own machine also had this defect AND Ubuntu) package
problem; however it now seems to be an upstream Qt issue as the various
Qt versions & OS combinations suggest that Qt themselves fixed it for Qt
6.10:**
| OS | QtVersion | Missing header |
|--------|-----------------------|----------------|
| Windows| 6.11.0 package | No |
| Devuan | 6.8.2 package | Yes |
| Devuan | 6.10.0 online install | No |
| Ubuntu | 6.9.0 package | Yes |
| Debian | 6.8.2 package | Yes |
| MacOS | 6.9.0 package | Yes |
**To fix this I reverted to an `#include <qtconcurrenttask.h>` for Linux
and MacOS builds - although it would probably have been better to make
it conditional on the Qt Version instead...**
*I have reported this upstream to Debian - see:
https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1135197*
---------
Signed-off-by: Stephen Lyons <slysven@virginmedia.com>
#### Brief overview of PR changes/additions
Adds screen reader accessibility support for OSC 8 hyperlinks when using
caret mode. Users can now navigate between links with Tab/Shift+Tab,
activate links with Enter/Space, and open link context menus with the
Menu key or Shift+F10. Links announce their tooltip,
visited/disabled/selected state, and menu availability to assistive
technology. IAccessible2 text attributes now expose link URLs, visited
state, disabled state, selection state, and group membership. Hidden
links are announced when the visibility manager conceals them. The
Accessibility settings combobox that picks the caret-mode pane-switching
key now lists these link shortcuts in its tooltip and accessible
description, announces them when the key is changed, and warns if Tab is
selected (since Tab is then shared with link navigation). Ctrl+End /
Ctrl+Home jump to the latest content / start of the buffer with audible
announcements so screen-reader users can opt in to following new output
without losing their current link focus.
**Scope note:** While this PR was motivated by OSC 8 hyperlinks, the
keyboard navigation and screen reader features work with **all link
types** (MXP, `echoLink`, `cechoLink`, etc.). The link system uses a
unified `linkIndex` with no type field, so Tab/Shift+Tab, Enter/Space
activation, `announceLinkFocus`, and IAccessible2 text attributes apply
to every link in the buffer. The only OSC 8-specific behaviors are
additive: spoiler reveal, selection group toggling, disabled state, and
custom styling—these gracefully no-op for links that lack styling data.
#### Motivation for adding to Mudlet
MUD games rely heavily on clickable hyperlinks for interaction, and
blind or low-vision players using screen readers had no way to discover,
navigate, or activate these links from the keyboard. This brings
Mudlet's hyperlink system in line with web accessibility standards.
#### Other info (issues closed, discussion etc)
[Wiki
Update](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Accessibility)
- Tests are included.
- `say !osc8-docs` will work from anywhere, or type `osc8` on StickMUD
and `look` to try out the test room.
- For accessibility, you can try out caret mode (go to Accessibility tab
on Mudlet settings and pick a shortcut like F6).
- Navigate links with Tab/Shift+Tab, activate with Enter or Space, open
the link context menu with Menu or Shift+F10, and use Ctrl+End /
Ctrl+Home to jump to the latest content or the start of the buffer.
- Use arrow keys on a menu, space or enter to select a menu item or
follow the hyperlinks.
- Output-window navigation in caret mode has been tuned for snappier
response.
#### Brief overview of PR changes/additions
- Convert 30 individual TOscTest functions into 4 data-driven tests + 1
standalone, using Qt's `_data()` pattern
- Start mudlet once in `initTestCase` instead of per-test, and use
`cTelnet::loopbackTest()` to inject data directly into the telnet
processing pipeline - eliminating 29 redundant startup/teardown cycles
and TCP connections
- `buffer.clear()` in `init()` provides test isolation between runs
- Net reduction of 336 lines of duplicated test boilerplate
#### Motivation for adding to Mudlet
TOscTest runtime dropped from ~55s to ~3-5s across all CI platforms
(TOscTest only, not including other test executables):
| Platform | Before | After | Speedup |
|---|---|---|---|
| macOS arm64 | 54s | 5.5s | 10x |
| macOS x86_64 | 56s | 5.7s | 10x |
| Ubuntu gcc | 58s | 3.6s | 16x |
| Ubuntu x86_64 | 44s | 3.1s | 14x |
| Ubuntu clang | 44s | 3.0s | 14x |
#### Other info (issues closed, discussion etc)
No issues closed. Pure test refactor, no behavioral changes. The
`loopbackTest()` approach was already used by TelnetBenchmark - this
extends it to functional tests.
Loopback injection means `getCurrentLine()` can't be used (cursor
position varies by OSC terminator type), so text display tests join all
buffer lines instead. 5 of 6 rows use exact match (`QCOMPARE`); only the
BEL-terminated OSC 8 row uses `contains` because its link text splits
across buffer lines in loopback mode.
**Test case:** `ctest -R TOscTest -V` - all 37 data rows should pass
with the same coverage as before.
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## Summary
Adds a `"title"` property to OSC 8 hyperlink configuration that renders
a non-clickable section header at the top of right-click context menus.
This gives players immediate context about what a menu relates to (e.g.,
an item name, NPC name, or action category).
Context menus now also use the main console display font set in
Settings, matching the rest of the game output.
## Configuration
**Simple string** (renders with default teal color):
```json
{"title": "Lamb and Barley Stew", "menu": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]}
```
**With Tier 2 style properties:**
```json
{
"title": {
"text": "Lamb and Barley Stew",
"style": {"color": "#5fbdaf", "bold": true}
},
"menu": [
{"View Details": "send:look stew"},
{"Buy": "send:buy stew"}
]
}
```
**Compact syntax** (shorthand `ti` for `title`):
```json
{"ti": "Lamb and Barley Stew", "m": [{"View Details": "send:look stew"}, {"Buy": "send:buy stew"}]}
```
## Title style
The title's `style` object uses the same [Tier 2 style
properties](https://wiki.mudlet.org/w/Manual:Supported_Protocols#Style_Properties)
as OSC 8 hyperlink styling, including all supported color formats (named
colors, hex, rgb). Currently supported properties for the title:
| Property | Type | Description |
|----------|------|-------------|
| `color` | String | Title text color (default: `#5fbdaf`) |
| `bg` | String | Title background color |
| `bold` | Boolean | Bold text |
| `italic` | Boolean | Italic text |
## Behaviour notes
- Title renders as a non-clickable label with a separator line below,
appearing above all menu items
- If `title` is set but no `menu` is configured, the title has no effect
(no popup is shown for single-action links)
- Left-click behaviour is unaffected
- When no style is specified, the title defaults to teal (`#5fbdaf`) to
visually distinguish it from clickable menu items
- Title without a `style` object is backward-compatible - older clients
that don't recognize the `title` key will silently ignore it
## Files changed
- **`src/TBuffer.h`** - Added `menuTitle` and `menuTitleStyle` fields to
`HyperlinkStyling`
- **`src/TBuffer.cpp`** - JSON config parsing for `title` (string and
object formats), query param extraction and URL stripping
- **`src/TTextEdit.cpp`** - Renders the title label in the popup menu
with style properties; sets console font on context menus
- **`src/TConsole.h/cpp`** - Registers `ti` shorthand and `title` preset
property
## Screenshots
<img width="500" height="329" alt="Screenshot 2026-02-06 at 14 21 56"
src="https://github.com/user-attachments/assets/6d7d047d-2654-4aec-8241-e1ea1614a6f0"
/>
## Test plan
- [x] Send an OSC 8 link with `"title"` as a simple string - verify teal
header appears above menu items
- [x] Send an OSC 8 link with `"title"` as an object with custom style -
verify color/bold/italic apply
- [x] Send an OSC 8 link with menu but no title - verify no header
appears
- [x] Send an OSC 8 link with title but no menu - verify no popup on
right-click
- [x] Verify left-click still executes the primary action
- [x] Change the console font in Settings - verify context menu font
updates to match
- [x] Verify compact syntax `"ti"` works as shorthand for `"title"`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Zooka <136661366+ZookaOnGit@users.noreply.github.com>
#### Brief overview of PR changes/additions
OSC 8 hyperlinks now only strip `config` and `preset` query parameters
from web URLs when the corresponding features are actually advertised
via NEW-ENVIRON. Also fixes stripping when `=` is percent-encoded as
`%3D`.
#### Motivation for adding to Mudlet
Improves protocol correctness for the MUD server community. Servers
using only basic `send:` and `prompt:` schemes no longer have their web
URLs unexpectedly modified.
#### Other info (issues closed, discussion etc)
- Regression fix from #9073 for encoded `=` handling
- Added tests for encoded `=` variants
- `config` stripped when any config-using feature enabled (STYLE_BASIC,
STYLE_STATES, TOOLTIP, MENU, COMPACT, VISIBILITY, SELECTION, SPOILER,
DISABLED)
- `preset` stripped only when PRESETS feature is enabled
---
**Before**
<img width="467" height="119" alt="Screenshot 2026-03-22 at 2 07 41 AM"
src="https://github.com/user-attachments/assets/5662a8c5-2c9f-415c-9a49-31b653c9ed7e"
/>
**After**
<img width="497" height="126" alt="Screenshot 2026-03-22 at 2 42 42 AM"
src="https://github.com/user-attachments/assets/15d6afd5-a9e0-4faf-ab2f-31251ea3e633"
/>
#### Brief overview of PR changes/additions
Clickable links (OSC 8 hyperlinks) that contain query parameters in the
URL now keep them intact. Previously, all query parameters were
stripped, breaking the link.
#### Motivation for adding to Mudlet
Links from games that include query parameters (e.g. `?id=2896`)
resolved to the wrong page because the parameters were lost.
#### Other info (issues closed, discussion etc)
Closes#9071