704 Commits
Author SHA1 Message Date
spfenwickandGitHub bc9651b664 fix: use same file name as KOReader for OPDS downloads (#1286)
This allows KOSync to work for files downloaded from OPDS when set to
use filename matching

## Summary

* **What is the goal of this PR?** Brings the names of files downloaded
via OPDS into line with the KOReader OPDS plugin so the files sync when
KOSync is set to filename matching
* **What changes are included?** Change the filename generated by the
OPDS browser from "\<title\> - \<author\>" to "\<author\> - \<title\>"
so it matches KOReader.

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

Both KOReader and crosspoint sanitise the filename by removing special
characters. This step seems to already use the same rules.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? NO
2026-04-28 19:29:05 -05:00
741dd89ac1 fix: cap per-side horizontal CSS inset at 2em (#1694)
## Summary

- **What is the goal of this PR?** Fix chapter-opener text collapsing to
1-2 words per line in EPUBs that apply large em-based horizontal CSS
insets.
- **What changes are included?** Caps `marginLeft`, `marginRight`,
`paddingLeft`, and `paddingRight` at 2em in `BlockStyle::fromCssStyle`
(`lib/Epub/Epub/blocks/BlockStyle.h`). Vertical margins/padding are
unchanged - the bug is horizontal-only.

## Additional Context

**Repro:** *Mother Night* by Kurt Vonnegut, Chapter 21 ("My best
friend..."). The chapter-opening element's embedded CSS sets a large
horizontal inset.

- Settings: Embedded Style = On, Justify alignment (default).
- Before: 1-2 words per line with a visible river between them.
- After: body text fills the usable page width like every other
paragraph.

**Why the clamp lives in `fromCssStyle`:** This is the single point
where CSS lengths resolve to pixels, so clamping here keeps both
`effectiveWidth` call sites in `ChapterHtmlSlimParser` (`:1131-1133`
makePages, `:841-844` long-block split) consistent with the
`leftInset()` xOffset. A width-only clamp at either call site would
leave text pushed right by a large xOffset.

**Test plan:**
- [x] Flashed to Xteink X4. Chapter 21 of *Mother Night* renders
normally with Embedded Style on.
- Users with cached layouts of affected books need to delete
`.crosspoint/epub_<hash>/sections/` (or the whole `.crosspoint/`) on the
SD card to pick up the fix.

**Before / After:**
<p float="left">
<img height="400" alt="IMG_0320"
src="https://github.com/user-attachments/assets/de8815ae-f2f4-4845-be1f-449c5a25d191"
/>
<img height="400" alt="IMG_0324"
src="https://github.com/user-attachments/assets/95f5f4cb-8aa1-418e-b611-18e0c342cbef"
/>
</p>

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it helps set the right
context for reviewers.

Did you use AI tools to help write this code? _**< YES >**_

I used Claude Code (Opus 4.7) to evaluate the codebase and find the
relevant code related to this rendering. From there, it was human
designed, reviewed and tested.

Co-authored-by: rhoopr <>
2026-04-27 19:37:41 +03:00
Stefan Blixten KarlssonandGitHub ef98d44ef3 fix: python requirements files (#1768)
## Summary
The Python scripts in the current repo have many requirements that are
not mentioned in any requirements.txt files, so I have therefore added
them to the directories that need them.

Question: Should these changes maybe moved into a "root"
requirements.txt file?

---
### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-04-27 19:28:53 +03:00
b8a51522ca feat(theme): add roundedraff theme and fix sleep cover crop grid artifacts (#918)
## Summary
- Add new `roundedraff` theme.
- Fix sleep screen artifact where book cover showed grid lines in
`Cover` mode with `Crop`.

## Additional Context
## Key changes
- Added `src/components/themes/roundedraff/` theme implementation.
- Updated sleep cover rendering logic in:
  - `src/activities/boot_sleep/SleepActivity.cpp`
  - `src/activities/boot_sleep/SleepActivity.h`
- Improved cover generation/regeneration paths in:
  - `lib/Epub/Epub.cpp`
  - `lib/Epub/Epub.h`
  - `lib/Txt/Txt.cpp`
  - `lib/Txt/Txt.h`

## Verification
- Sleep screen set to Book cover
- Sleep screen cover mode set to Crop
- Confirmed grid artifacts no longer appear on cover sleep screen.

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES >**_

---------

Co-authored-by: CaptainFrito <yzq6x5zypy@privaterelay.appleid.com>
Co-authored-by: Zach Nelson <zach@zdnelson.com>
Co-authored-by: Uri Tauber <142022451+Uri-Tauber@users.noreply.github.com>
2026-04-27 19:01:22 +03:00
Uri TauberandGitHub 33386953d9 feat: enable pio build cache (#1769)
## Summary

* **What is the goal of this PR?** 
Enables the PlatformIO build cache to speed up compilation.
 
## Additional Context

Significant improvement when switching branches, as unchanged files
won't need re-compilation.
See
[Docs](https://docs.platformio.org/en/latest/projectconf/sections/platformio/options/directory/build_cache_dir.html).

Note: May need to manually delete .cache folder if updating toolchain or
framework.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< NO >**_
2026-04-27 16:45:38 +03:00
Arthur TazhitdinovandGitHub 02822e01b3 feat: include short SHA in CROSSPOINT_VERSION (#1728)
## Summary

* Includes short SHA in version string for better version tracking, so
it looks like `1.1.0-dev-feat-kosync-xpath-05c6cf8`
* Closes
https://github.com/crosspoint-reader/crosspoint-reader/issues/1247

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES >**_
2026-04-27 15:52:09 +03:00
Stefan Blixten KarlssonandGitHub 198b9d7786 fix: swedish translations (#1762)
## Summary

* Add swedish translation of strings added by "feat: Support for
multiple OPDS servers (#1209)"

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-04-27 00:01:36 +03:00
Zach NelsonandGitHub 15e0d39d2c Revert "feat: Add royalty.dev funding integration" (#1745)
Reverts crosspoint-reader/crosspoint-reader#1741

I don't entirely understand Royalty.dev and want to vet this further.
2026-04-22 16:29:09 -05:00
6c4e946e27 chore: git pre-commit hook for format fix (#1730)
## Summary

* Adds pre-commit git hook that automatically runs
./bin/clang-format-fix

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES >**_

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-22 23:20:03 +03:00
pablohcandGitHub 907e14da28 fix: pressing space barely moves input cursor (#1729) (#1733)
## Summary

* **What is the goal of this PR?** 
Fix the visual keyboard cursor not advancing when typing a space. Typing
one space leaves the cursor at position 0; typing two spaces advances it
only by one space width. This affects all input types except URL.
* **What changes are included?** 
Replace `getTextWidth()` with `getTextAdvanceX()` in four locations
within `KeyboardEntryActivity::render()` for cursor positioning and
line-wrapping calculations.

## Additional Context

* **Root cause**: `getTextWidth()` returns the bounding-box width of the
drawn glyphs. The space glyph has `width=0` and `height=0` (it's
invisible), so `getTextWidth(" ") == 0`. The trailing `advanceX` of the
last character is only flushed when the *next* character is processed,
so a string ending in space reports zero width. `getTextAdvanceX()`
correctly includes the final glyph's advance, matching how `drawText()`
actually positions characters.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**YES**_
2026-04-22 22:18:26 +03:00
Justin MitchellandGitHub b9b795bf45 feat: Add royalty.dev funding integration (#1741)
Adds royalty.dev as a funding platform for contributors by adding custom
funding link and badge to README

A maintainer would need to click the "Are you the maintainer?" button on
https://app.royalty.dev/crosspoint-reader/crosspoint-reader to install
the github app which pulls the PRs in and ranks them.

(Disclosure, Royalty is a product I've recently built and am using for
my own OSS and currently gating custom font builds and nightlys on
crosspointreader.com)

This page currently can collect donations but without the integration it
wont notify contributors and it wont rescore fresh PRs.
2026-04-22 13:32:36 -05:00
Stefan Blixten KarlssonandGitHub b21b10f4b9 fix: Add swedish keyboard translations (#1726)
## Summary

* Add the swedish translations of the text strings added by #1697 

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-04-22 11:25:39 -05:00
1cf2239742 feat: Support for multiple OPDS servers (#1209)
## Summary

* Add support for configuring and using multiple OPDS servers, replacing
the previous single-server limitation. Closes
https://github.com/crosspoint-reader/crosspoint-reader/issues/1178
* New OpdsServerStore singleton (modeled after WifiCredentialStore) that
persists up to 8 OPDS servers to /.crosspoint/opds.json with MAC-based
password obfuscation.
* One-time migration from legacy single-server fields in
CrossPointSettings to the new store on first boot.
* New OpdsServerListActivity for the device UI — works in two modes: a
settings list (add/edit/delete servers) and a picker (select which
server to browse). When only one server is configured, the picker is
skipped automatically.
* Renamed CalibreSettingsActivity → OpdsSettingsActivity for clarity. It
now edits individual OpdsServer entries (name, URL, username, password,
delete).
* OpdsBookBrowserActivity now receives an OpdsServer at construction and
uses its credentials for all fetches/downloads, and shows the server
name in the header.
* HttpDownloader::fetchUrl and downloadToFile accept optional per-call
username/password parameters instead of reading from global settings.
* REST API endpoints on CrossPointWebServer: GET /api/opds, POST
/api/opds, POST /api/opds/delete — passwords are never exposed over the
API (only a hasPassword flag), and omitting the password field on update
preserves the existing one.
* Web UI (SettingsPage.html) with dynamic OPDS server management cards —
add, edit, save, and delete servers from the browser.
<img width="932" height="906" alt="SCR-20260416-stvu"
src="https://github.com/user-attachments/assets/a8f18d84-4204-46a0-bb31-b73d24b3255f"
/>


## Additional Context

* The OpdsServerStore JSON format and obfuscation scheme are identical
to WifiCredentialStore, so the same JsonSettingsIO infrastructure
handles both.
* The web API uses POST /api/opds/delete instead of DELETE /api/opds
because the ESP32 WebServer doesn't support the DELETE method with a
request body.
* Existing single-server configurations are migrated automatically — no
user action required. After migration the legacy CrossPointSettings
fields are cleared so it only runs once.
* The HttpDownloader changes are backward-compatible: the credential
parameters default to empty strings, so existing callers are unaffected.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES >**_

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-04-21 18:35:41 -05:00
Zach NelsonandGitHub c5f82709c0 fix: Replaced Bookerly with Noto Serif for licensing reasons (#1736)
## Summary

Fixes #258. Bookerly is not licensed for use in CrossPoint. Switched to
Google's open Noto Serif font.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-04-21 17:51:07 -05:00
867fb7cbe2 style: unify page headers across web UI (#1702)
## Summary

* **Goal**: Align `FilesPage` and `SettingsPage` headers with the
existing `HomePage` pattern.
* **Changes**:
- Added `<h1>📚 CrossPoint Reader</h1>` at the top of `<body>` on both
pages.
- Demoted the page-specific `📁 File Manager` heading on `FilesPage` from
`<h1>` to `<h2>` (inside `.page-header`), so every page has a single
`<h1>` — the app name.
- Moved the accent-colored `border-bottom` from `.page-header` to `h1`,
matching the HomePage CSS.

All three pages now share the same top structure:
`<h1>CrossPoint Reader</h1>` → nav links → page content.

## Screenshots

### Home

**Before and after is the same**
<img width="1153" height="388" alt="home before"
src="https://github.com/user-attachments/assets/4eb1c969-89fd-4965-ba17-5ff956576a8b"
/>

### File Manager

**Before**
<img width="1153" height="388" alt="files before"
src="https://github.com/user-attachments/assets/ba689d6b-56b1-44d1-a151-3f67b0fcdb16"
/>

**After**
<img width="1153" height="388" alt="files after"
src="https://github.com/user-attachments/assets/437db5e4-9102-4a19-b049-7a93e4e403b2"
/>

### Settings

**Before**
<img width="1153" height="388" alt="settings before"
src="https://github.com/user-attachments/assets/39a7e489-f0d3-4ac1-a2d7-98028aae65c0"
/>

**After**
<img width="1153" height="388" alt="settings_after"
src="https://github.com/user-attachments/assets/1c479618-ec8b-4dee-b704-3da07e6c279e"
/>



## Additional Context

* Pure markup + CSS, no JavaScript or behavioral changes.
* Improves HTML semantics (single `<h1>` per page, referring to the
app).
* No new CSS duplication introduced — borrows existing styling from
HomePage.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it helps set the right
context for reviewers.

Did you use AI tools to help write this code? _**YES**_

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:32:13 -05:00
Zach NelsonandGitHub 5e26baef63 chore: One Italian translation tweak (#1718)
## Summary

Following up on #1685. One tweaked string confirmed by @alan0ford.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-04-21 15:20:41 -05:00
CSCMeandGitHub 8154f88dbe fix: Erroneous navigation with long filenames in footnote links (#1723)
## Summary

* **What is the goal of this PR?** 
* Increase the allowed link length for footnotes
* **What changes are included?** 
* Un-magic-numbers parts of the footnote parser
* Increases max href length

## Additional Context

* Additional RAM usage was not noticable to me, worst case 16 * 128 * 2
= 4KB instead of previously 16 * 88 * 2 ~ 2.8KB, see #1031
* Motivation: My book navigated me to the start of the footnote chapter,
which required manual navigating for every footnote due to ungodly file
name choices which pushed the required href length to 72, which caused
truncating, which caused Crosspoint to not find the anchor (Sacred and
Terrible Air, available via reddit)

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-04-21 18:34:17 +03:00
Stefan Blixten KarlssonandGitHub 56d3ab929c feat: show long branch names (#1727)
## Summary

* This change will make better use of the space available for Title and
Subtitle in the Settings (Theme: Lyra).
Instead of having a fixed maxwidth in pixels for subtitle, it will use
the whole line and shorten the longest one if needed.

So instead of: <img width="480" height="140" alt="image"
src="https://github.com/user-attachments/assets/67e4777b-10d0-4a05-b5c7-fb775776c186"
/>

It will now show: <img width="480" height="142" alt="image"
src="https://github.com/user-attachments/assets/2f143f4e-75d7-45c4-b61a-aad56bdf39fb"
/>


---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-04-21 18:33:33 +03:00
Pietro CampagnanoandGitHub 9dab5f471b style: put page name first in browser titles (#1703)
## Summary

* **Goal**: Make browser tab titles, bookmarks, and history entries
distinguishable across pages (including individual folders in the File
Manager).
* **Changes**:
- Reordered `<title>` so the page name comes first, followed by
`CrossPoint Reader`. Browser tabs usually truncate titles from the
right, and bookmark/history lists are sorted alphabetically by title —
putting the differentiating part first keeps it visible in both cases,
instead of every entry starting with `CrossPoint Reader - …`.
- On `FilesPage`, added a small JS hook that updates `document.title`
with the current subfolder leaf when a `path` query parameter is present
— previously all folders shared the same title.

| URL | Title before | Title after |
|-----|--------------|-------------|
| `/` | `CrossPoint Reader` | `CrossPoint Reader` _(unchanged)_ |
| `/settings` | `CrossPoint Reader - Settings` | `Settings - CrossPoint
Reader` |
| `/files` | `CrossPoint Reader - Files` | `Files - CrossPoint Reader` |
| `/files?path=/Books` | `CrossPoint Reader - Files` | `Books - Files -
CrossPoint Reader` |
| `/files?path=/Books/Fantasy` | `CrossPoint Reader - Files` | `Fantasy
- Files - CrossPoint Reader` |

## Additional Context

* Pure client-side change: `<title>` tags plus ~3 lines of JavaScript on
FilesPage. No network, behavior, or memory impact.
* The static `<title>` tag remains as a fallback if JS fails.
* `HomePage` title left as-is since the app name alone is the standard
convention for a root/home page.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it helps set the right
context for reviewers.

Did you use AI tools to help write this code? _**YES**_
2026-04-21 16:38:20 +03:00
mvidelatraducandGitHub ce22deab7c chore: Update spanish.yaml (#1717)
## Summary

Make this more transparent in Spanish:

`STR_AUTO_TURN_PAGES_PER_MIN: "Auto Turn (Pages Per Minute)"`

A weakness of the current translation is that the verb used is "to
read", but it's not clear that it is the user who will be reading a
certain amount of pages per minute. I've replaced it with the verb for
"to turn (pages)" which is the (analogous) physical action happening
regardless of who executes it, and the abbreviated adverb to clarify
that this is an automatic process. Lastly, a clarification of what the
number means in a simple proportion: "X pages in a minute". The Spanish
is very good overall, this one string is very hard to do in such a short
space. Even more so given that the number cannot be accompanied by any
units, so it must be clear from the text alone what it means.

## Additional Context

N/A

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**NO**_
2026-04-20 19:29:13 -05:00
e28918b24d fix: pluralize folder/file counts correctly in file list summary (#1701)
## Summary

* **Goal**: Fix incorrect pluralization in the file manager web UI
summary line.
* **Changes**: `FilesPage.html` always rendered the plural forms
("folders"/"files") regardless of count. The summary now selects
singular or plural based on each count.

Example:
- Before: `1 folders, 1 files, 12 KB`
- After: `1 folder, 1 file, 12 KB`

## Additional Context

* Cosmetic-only fix — no behavior, performance, or memory impact.
* Change is fully client-side (JavaScript inside a single HTML
template).
* English-only; web UI localization is out of scope here.

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it helps set the right
context for reviewers.

Did you use AI tools to help write this code? _**YES**_

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 15:36:19 -05:00
c0ee096841 fix: relative opds paths and query param with copyparty (#1535)
## Summary

* **What is the goal of this PR?**

This PR fixes bugs when using Copyparty as an OPDS server. 

https://github.com/9001/copyparty?tab=readme-ov-file#opds-feeds

OPDS uses a query parameter `?opds` to differentiate between HTML
requests and OPDS requests for the same path. It also uses relative
paths in the responses instead of full paths. Crosspoint didn't handle
these two cases.

* **What changes are included?**

Fixes to the two issues above.

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

Here is some example XML from my Copyparty instance:
https://gist.github.com/philips/9ecec29dfb69ed0591b032f16e799675

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _**< YES | PARTIALLY | NO
>**_

Partially. I used Claude code to write the fix. I am not a strong C++
programmer. But, I manually compiled and tested.

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-20 15:35:17 -05:00
pablohcandGitHub 1a145fe085 fix: keyboard feedback #1644 (#1697)
Addresses reviewer feedback from #1644:
- **Localize keyboard hint strings** — 13 hardcoded English strings
replaced with \`tr()\` macro (\`STR_KB_HINT_*\`), making them
translatable across all 22 languages (fallback to English when not yet
translated)
- **Deduplicate \`Lyra3CoversMetrics\`** — now derives from
\`LyraMetrics\` via lambda copy, overriding only \`homeCoverTileHeight\`
and \`homeRecentBooksCount\` (eliminates ~30 duplicated metric fields)
- **Unify keyboard drawing in \`BaseTheme\`** — \`drawTextField\` and
\`drawKeyboardKey\` overrides removed from \`LyraTheme\`; variability
controlled via \`keyboardKeyCornerRadius\` metric (0=Base, 6=Lyra).
Unified text field padding to 6, adopted Lyra's secondary label draw
order (main first, then secondary)
- **Add URL-optimized keyboard layout** — \`urlLayout\` with \`:\` and
\`/\` replacing \`=\` and \`,\` for easier URL input without switching
to SYM mode
---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it
helps set the right context for reviewers.

Did you use AI tools to help write this code? _** YES **_
2026-04-20 12:47:57 -05:00
302dea1eea fix: Switch to xpath map for paragraph level syncing in KOSync (#1686)
Switch KOReader sync progress mapping from chapter matching to
XPath-based mapping.

- resolves KOReader positions using real XHTML ancestry paths
- supports paragraph-based upload mapping with text offsets
where needed
- passes the current paragraph index into sync so uploads map
back to KOReader more accurately

No HTTP client changes are included. No reader-state or resume-flow
changes are included.

---------

Co-authored-by: jpirnay <jens@pirnay.com>
2026-04-20 12:47:41 -05:00
AngGitHubcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
e8645ed92e docs: fix typos (#1705)
## Summary

Fix typos found via `codespell -S
*.txt,*.yaml,generate_kerning_ligature_epub.py -L
currenty,flate,ser,localy,logicaly,ans,clen,portugues,notin,curren`

## Additional Context

* Add any other information that might be helpful for the reviewer
(e.g., performance implications, potential risks,
  specific areas to focus on).

---

### AI Usage

While CrossPoint doesn't have restrictions on AI tools in contributing,
please be transparent about their usage as it helps set the right
context for reviewers.

Did you use AI tools to help write this code? _**NO**_

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-04-20 09:12:40 +03:00