Finish the upstream sync: adopt upstream's margin API, merge the SDK fork with Free-Ink main - #32
Merged
Conversation
## Summary * **What is the goal of this PR?** Fix the sleep-screen image remaining visible underneath the Home screen after a splashless wake. * **What changes are included?** * Pass a one-shot clean-initial-refresh option to HomeActivity for splashless wakes without a retained Quick Resume frame. * Use `HALF_REFRESH` for that first Home render instead of issuing a second post-render refresh. * Preserve Quick Resume and normal Home navigation behavior. Fix crosspoint-reader#3002. ## Scope Check - [x] I have read SCOPE.md and ROADMAP.md. - [x] This PR is **not** a new built-in theme. - [x] This PR is **not** a new external network connector. - [x] This PR is **not** an interactive app, writing tool, RSS/news/browser, media playback, or PDF feature. - [x] The stock firmware does not already handle this well, and no other popular CrossPoint fork already does. - [x] This PR does not touch `freeink-sdk/`, `lib/hal/`, the bootloader, OTA, or recovery code. ## Additional Context Tested in Xteink X4: - Custom sleep image + waking up from main menu + Double checked that we are only refreshing once (the first fix I did the screen flashed twice) - Custom sleep image + waking up from book (already working, just checking for regressions) - Quick sleep + waking up from book (not really related to the changes here but just to make sure there were no regressions) --- ### AI Usage Did you use AI tools to help write this code? **YES**
Also carries a fix for lists and x4pro display variants
## Summary * **What is the goal of this PR?** * Make the estimated chapter-page marker (`~`) larger in the reader status bar so it's not confused with a dash (`-`), especially on lower PPI devices. * **What changes are included?** * Render the estimate marker separately with the 10pt UI font. * Preserve its placement in the right-side status-bar layout, including when a clock is shown. * Keep the marker limited to estimated chapter counts; it is not shown for book-progress-only status bars. * Adds a 2px right margin since it was colliding with the chapter page number without it. ## Additional Context * This is a visual-only status-bar change in `BaseTheme::drawStatusBar`; it adds no heap allocation or persistent-state changes. **X4 - Before** <img width="493" height="640" alt="X4 Before" src="https://github.com/user-attachments/assets/b9f471ee-71c2-4aea-9d92-b868dbb4f995" /> **X4 - After** <img width="525" height="640" alt="X4 After" src="https://github.com/user-attachments/assets/4b7a167f-2217-4219-a1b4-ca49f91998fd" /> **X3 - After** <img width="598" height="640" alt="X3 After" src="https://github.com/user-attachments/assets/f5b59c8c-66a3-4d9d-a716-8f2ce24a9678" />
…-reader#3186) ## Summary * **What is the goal of this PR?** Fix Cyrillic glyphs that render fused together in the UI, reported in crosspoint-reader#3099. * **What changes are included?** Ubuntu Medium is converted through the FreeType auto-hinter instead of its own bytecode; the two Medium UI headers and the font IDs are regenerated. At UI ppem Ubuntu Medium's bytecode grid-fits `U+0442` (т) to 11px inside a 10px advance and lands its left bearing at **-1**, so the glyph hangs into the one before it. In Russian "скрытые" the `ы` and `т` merge into a single shape and the word reads as something else. <img width="2469" height="580" alt="fix_before_after" src="https://github.com/user-attachments/assets/bcc3ee9f-3ce6-44a4-9e6d-756f6f3695c7" /> The outline is not at fault — its left bearing is positive in both weights — and Ubuntu Bold grid-fits cleanly, so only the Medium face changes. It joins the Hebrew fallback already on the auto-hinter in crosspoint-reader#3176. ``` U+0442 before: { 11, 11, 161, -1, ... } after: { 10, 11, 161, +1, ... } gap after ы: -0.5px (overlap) +2.5px ``` **Stacked on crosspoint-reader#3176** — it needs the per-face `--autohint-font` flag introduced there, so the diff shows that PR's commit too. Merge after it. ## Scope Check - [x] I have read SCOPE.md and ROADMAP.md. - [x] This PR is **not** a new built-in theme. - [x] This PR is **not** a new external network connector. - [x] This PR is **not** an interactive app, writing tool, RSS/news/browser, media playback, or PDF feature. - [x] The stock firmware does not already handle this well. - [x] This PR does not touch `freeink-sdk/`, `lib/hal/`, the bootloader, OTA, or recovery code. ## Additional Context Layout is untouched. The converter stores `linearHoriAdvance`, which is unhinted, so hinting cannot move anything: **zero** of the 1031 glyph advances change, and Latin, Cyrillic and Vietnamese strings measure identical widths before and after. Only bitmaps and bearings move — 320 glyphs at 10pt, 269 at 12pt. No stem drops to a 1px hairline in Latin, Greek or Cyrillic at either size, so nothing gets thinner on e-ink. `verify-ui-noto-fonts.py` passes (Hebrew 101.9%/95.3%, Arabic 95.3%/102.6%). Flash 5,706,019 bytes (87.1%), RAM 16.6% — 46 bytes over crosspoint-reader#3176 alone. Worth noting for reviewers: Ubuntu Sans 1.006 fixes this same defect upstream (it is hinted with ttfautohint), but even its Bold renders 2px stems at UI ppem, which would give back the weight crosspoint-reader#3096 was after. That is why this fixes the hinting rather than bumping the font. --- ### AI Usage Did you use AI tools to help write this code? _**YES**_
…ouch boards) (crosspoint-reader#3156) Co-authored-by: Justin Mitchell <justin@jmitch.com>
…t-reader#3095) ## Symptom A text-field value long enough to wrap loses a character at every wrap point, if that point falls inside a multi-byte character. A Cyrillic OPDS server name shows it plainly — `литературы` renders as `литера` / `уры`, the `т` is simply gone:  ## Steps to reproduce Reproducible on `develop` in the simulator, no special hardware or data: 1. Open a text field, e.g. `Settings → System → OPDS Servers → <server> → Server Name`. 2. Enter a non-ASCII value long enough to wrap onto a second line. 3. A character disappears wherever the wrap lands mid-sequence. ## Cause `lineBreakEnd()` (`src/activities/util/KeyboardEntryActivity.cpp`) binary-searches over **byte** indices for the widest prefix that fits, so the returned wrap point can land inside a multi-byte character. `render()` then slices the line at that byte and hands both halves to the renderer as incomplete sequences. Each decodes to `U+FFFD`, and since no built-in font carries that glyph, the character is not drawn at all. ## Fix Snap the wrap point back to a code point boundary. Because widths measured mid-sequence are unreliable, the snapped line is re-measured and stepped back whole characters until it fits, and a line always keeps at least one whole character so the wrap loop still advances. ASCII is unaffected — the snap is a no-op there. ## Testing - Verified in the simulator on `develop` (character lost) and on this branch (intact). - Long Latin name still fills the line to the same edge as before. - `clang-format` 21 clean. - Not tested on hardware. Found by @coderabbitai's review on crosspoint-reader#2840, which carried this fix together with the cursor-block one. That PR is now split: the cursor half is a separate PR, this is the wrapping half.
…oint-reader#3094) ## Symptom In cursor mode, the block cursor over any non-ASCII character is drawn one glyph too narrow and the character underneath is not rendered inside the block. This affects every layout that has accented letters — `é` on AzertyFr, `ü`/`ß` on QwertzDe, `ñ` on SpanishEs — and any text loaded from outside (OPDS server names, download folder names). **Before** — French UI, server name `Café`, cursor on `é`:  **After** — same screen, same keypresses:  The block should be as wide as the character and show it reversed out, which is exactly what it already does for ASCII. ## Steps to reproduce No special hardware or data needed — this is reproducible on `develop` in the simulator: 1. Set the UI language to French (`Settings → System → Language`). 2. Open any text field, e.g. `Settings → System → OPDS Servers → <server> → Server Name`. 3. Type `é` (bottom letter row on the AZERTY layout). 4. Hold **Up** to enter cursor mode, then press **Left** until the cursor sits on the `é`. ## Cause `cursorPos` is a byte offset that is always kept on a code point boundary, but four places in `render()` sliced exactly **one byte** at that offset: | Code | Purpose | |---|---| | `text.substr(cursorPos, 1)` | block width | | `displayText.substr(cursorPos, 1)` (×2) | kerning offset | | `cursorPos + 1` | start of the trailing run | | `char buf[2] = {text[cursorPos], '\0'}` | drawing the character in the block | For a two-byte character that single byte is a lone lead byte. `utf8NextCodepoint()` (`lib/Utf8/Utf8.cpp`) validates continuation bytes, finds none, and returns `REPLACEMENT_GLYPH` (`0xFFFD`) — so the width is measured for the replacement glyph and the draw call paints that instead of the letter. Byte-identical to the old behaviour for ASCII, where one byte *is* one code point. ## Fix Uses the existing `utf8Next()` helper to take the whole code point in all four places. No new helper, no behaviour change for ASCII. The masked-password path is unaffected: `displayText` is all `*` there, so the span is still one byte — that is why the fix computes the span separately for `text` and `displayText`. ## Testing - Verified in the simulator on `develop` (bug) and on this branch (fixed) — the screenshots above are those two runs, same input script, only the branch differs. - ASCII behaviour unchanged (checked with a Latin server name in the same screen). - `clang-format` 21 clean. - Not tested on hardware. <details> <summary>Full screenshots</summary> Before:  After:  </details> Split out of crosspoint-reader#2828 at @Uri-Tauber's request — this fix is independent of where the keyboard layout tables live. Replaces crosspoint-reader#2840, which carried this fix and the line-wrapping one together; the wrapping half is now a separate PR.
…nt-reader#3153) ## Summary * **What is the goal of this PR?** Fix `htmlToPlainText()` dropping the line breaks around headings, so a dictionary definition's `<h1>`–`<h6>` no longer run into the text that follows. * **What changes are included?** A one-word fix in `tagBreak()`, plus a host test for the function. `tagBreak()` lists `h1`–`h6` among the names that produce a paragraph break, but the tag-name scan reads *alphabetic* characters only: ```cpp while (start < end && std::isalpha(static_cast<unsigned char>(input[start]))) start++; ``` So it sees `h1` as `h`, `equals("h1")` can never be true, and the heading break never happens: ``` htmlToPlainText("<h1>title</h1>body") -> "titlebody" (expected "title\n\nbody") ``` Scanning alphanumerics makes the listed names reachable. No other name in that table contains a digit, so nothing else changes — `p`, `br`, `div`, `li`, `tr` and `hr` behave exactly as before. ## Scope Check - [x] I have read SCOPE.md and ROADMAP.md. - [x] This PR is **not** a new built-in theme. - [x] This PR is **not** a new external network connector. - [x] This PR is **not** an interactive app, writing tool, RSS/news/browser, media playback, or PDF feature. - [x] The stock firmware does not already handle this well — this is a defect fix in existing dictionary code. - [x] This PR does not touch `freeink-sdk/`, `lib/hal/`, the bootloader, OTA, or recovery code. ## Additional Context Found while porting the dictionary feature into a fork, where the same bug showed up in a definition whose sense headings all collided with their text. New test `test/html_to_plain_text` covers tag stripping, block breaks, entity decoding, whitespace trimming and malformed markup. The heading case fails on current `develop` with exactly the `"titlebody"` output above, and passes after the fix — I verified both directions rather than only the green one. Memory / flash impact: none (`isalpha` → `isalnum`). --- ### AI Usage Did you use AI tools to help write this code? _**YES**_ — written with Claude Code; the diagnosis, the test cases and the before/after verification were reviewed and run by me.
## Summary Fixes an incorrect navigation path in the _Installing Fonts_ section of `docs/sd-card-fonts.md`. **Before:** `Settings > System > Manage Fonts` **After:** `Settings > Reader > Manage Fonts`
…er#3191) Co-authored-by: Uri Tauber <uritaube@gmail.com>
## Summary
* **What is the goal of this PR?** Ensure Title Check runs for the
current PR head after commits are pushed.
* **What changes are included?** Add the `synchronize` activity type to
the existing `pull_request_target` workflow.
## Scope Check
CrossPoint is intentionally narrow. See
[SCOPE.md](../blob/master/SCOPE.md) and
[ROADMAP.md](../blob/master/ROADMAP.md).
Please confirm:
- [x] I have read SCOPE.md and ROADMAP.md.
- [x] This PR is **not** a new built-in theme (themes are temporarily
closed pending the move to SD-loaded themes).
- [x] This PR is **not** a new external network connector (sync engine,
cloud storage, remote file access, etc.).
- [x] This PR is **not** an interactive app, writing tool,
RSS/news/browser, media playback, or PDF feature.
- [x] The stock firmware does not already handle this well, **and** no
other popular CrossPoint fork already does
(or, if one does, I explain why CrossPoint still needs it below).
- [x] If this PR touches `freeink-sdk/`, `lib/hal/`, the bootloader,
OTA, or recovery code, I have coordinated with
the relevant maintainer.
**If this PR was opened against the previous (broader) scope and was
already in flight under Phase 0, link the
relevant Discussion or issue so reviewers can see the history.**
## Additional Context
Without `synchronize`, pushing commits does not trigger this workflow,
leaving Title Check without a result for the new head. GitHub documents
`synchronize` as the pull-request activity emitted when the head branch
is updated:
https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target
Validated with `git diff --check` and a local YAML syntax parse.
Firmware memory and flash impact: none.
---
### 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: Julia Nguyen <julia@uxj.io> Co-authored-by: Uri Tauber <uritaube@gmail.com>
…ss maps, Zopfli) (crosspoint-reader#3144) Co-authored-by: Uri Tauber <uritaube@gmail.com>
…-reader#3215) ## Summary * **What is the goal of this PR?** Fixes the battery-only deep-sleep wake bug on the Xteink X4 Pro (crosspoint-reader#2863). After a battery-only sleep, the next power-button press cold-booted (~8–15 s to content) instead of fast-waking (~1–2 s). With the serial/pogo adapter connected, external power masked the problem. * **What changes are included?** One change in `HalPowerManager::startDeepSleep()` (+21 lines, `lib/hal/HalPowerManager.cpp` only): every configured power-latch pin (`BoardConfig::ACTIVE.power.latch0/latch1`) is driven HIGH and armed with `gpio_hold_en()` before entering deep sleep. Root cause: `freeink::PowerManager::deepSleep()` calls `esp_sleep_config_gpio_isolate()`, which strips every pad without an armed hold. The X4 Pro's master peripheral-rail enable is `power.latch0` = GPIO1 — `holdPowerRails()` drives it HIGH at boot but arms no sleep hold, so once isolated the latch floats LOW when external power is removed. The fix keeps all configured power-latch pins driven and held HIGH through sleep; peripheral rails (SD / touch / EPD reset) keep their existing OFF-level holds from `powerDownRailsForSleep()`, so standby current doesn't regress. The loop deliberately skips GPIO13 on the C3 Xteink boards: it *is* `power.latch0` there and is driven LOW on purpose as the battery power-off. Generic over the profile's latch pins — also covers the Sticky's PWR_HOLD/PWR_LOCK (GPIO45/46). ## Scope Check CrossPoint is intentionally narrow. See [SCOPE.md](../blob/master/SCOPE.md) and [ROADMAP.md](../blob/master/ROADMAP.md). Please confirm: - [x] I have read SCOPE.md and ROADMAP.md. - [x] This PR is **not** a new built-in theme (themes are temporarily closed pending the move to SD-loaded themes). - [x] This PR is **not** a new external network connector (sync engine, cloud storage, remote file access, etc.). - [x] This PR is **not** an interactive app, writing tool, RSS/news/browser, media playback, or PDF feature. - [x] The stock firmware does not already handle this well, **and** no other popular CrossPoint fork already does (or, if one does, I explain why CrossPoint still needs it below). - [x] If this PR touches `freeink-sdk/`, `lib/hal/`, the bootloader, OTA, or recovery code, I have coordinated with the relevant maintainer. → Touches `lib/hal/` only (`HalPowerManager.cpp`); filed against crosspoint-reader#2863 where maintainer triage is already happening. No SDK changes required (SDK pointer unchanged at `e4d3cc3`). Stock firmware note: this restores behavior stock users get implicitly (adapter plugged in or self-latching hardware revisions); it fixes wake for battery-only use, which stock firmware does not handle well. ## Additional Context * Memory / flash impact: none measurable — ~21 lines of flash, no new RAM, no heap allocation. Runs once per sleep entry. * Risk area to focus on: the C3 GPIO13 exclusion (battery power-off must still drop the pin LOW); covered by keeping that block ahead of the new loop and skipping the pin in the loop. * Verification evidence: - `pio run -e x4pro` SUCCESS locally on this exact commit. - Identical patch fully green on CI twice: upstream run 32916960939 (Build default/sticky/papermono/x4pro, unit tests, cppcheck, clang-format) and Belphemur#3 (all checks pass incl. CodeRabbit). - Hardware test procedure for reviewers: flash, sleep on battery, unplug adapter 60 s, short-press power → expect instant wake landing back on the page (`ESP_RST_DEEPSLEEP` + `ESP_SLEEP_WAKEUP_GPIO`), not splash/cold boot (`ESP_RST_POWERON`). --- ### 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**_ — root-cause analysis and patch drafted with an AI coding agent (Hermes), reviewed and tested by hand before submission.
## Summary Should help with crosspoint-reader#3019. --- ### 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 >**_
…point-reader#2858) Co-authored-by: Uri Tauber <uritaube@gmail.com>
# Summary Mixed Hebrew and English passages now keep the book's intended reading direction, including paragraphs that begin with an English term. suppress crosspoint-reader#3143, which was a downstream change, rather than taking care of the root. --- ### 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? _**< PARTIALLY >**_
## Summary I implemented four requests from crosspoint-reader#2964 - Hold Left/Right 500ms to move repeatedly through dictionary words - Wider, taller, evenly spaced Lyra tabs - Rotated controls automatically follow orientation on touch devices - “Add Server” directly from the OPDS server picker All tested on x4 and sticky apart from the last one - I dont use OPDS server... --- ### 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? _**< PARTIALLY >**_
… on (crosspoint-reader#3341) Co-authored-by: Jim Myers <jim.myers@jfmyers.dev> Co-authored-by: Uri-Tauber <uritaube@gmail.com>
…#3355) ## Summary * **What is the goal of this PR?** * Restore CSS superscript and subscript styling on internal EPUB links, including the footnote markers commonly written as `<a style="vertical-align: super">1</a>`. * **What changes are included?** * Apply resolved `vertical-align: super/sub` styles when parsing internal link elements, matching the existing generic inline-style behavior. * Bump `section.bin` to version 45 so cached layouts created before the fix are automatically rebuilt. * Add a generated EPUB fixture plus focused host tests covering both superscript and subscript internal links. The tests verify the text style and retained link target/ID. ## Additional Context * The regression was limited to the parser’s internal-link branch: it preserved the link and underline but returned before applying `vertical-align`.
Adopts upstream's setContentMarginFromScreen everywhere and drops the fork's setContentMarginAbsolute from every shared activity. Mario's call: a permanent divergence on a call every screen makes is paid at every future sync. No unfolding was needed. marginBeyondSafeArea(m,s) = m>s ? m-s : 0, so setContentMarginFromScreen(m) insets the screen by s+max(0,m-s) = max(m,s): it is idempotent w.r.t. the safe area. The SDK comment that warns about applying the safe area twice is about plain setContentMargin(), not the FromScreen form. 18 of the 19 conflicting call sites passed arguments already identical to upstream's, so those resolved to a rename. The submodule now points at the SDK fork merged with Free-Ink main (16 upstream commits, clean merge, 2832 SDK host checks pass). Conflicts where BOTH sides' work was kept, not one side taken: - MappedInputManager: upstream's isNavDirectionSwapped() routed through the fork's readButton(), which is the single place a button index is read and therefore the only thing the release gate can cover. Upstream's update() (long-press bookkeeping) landed by auto-merge in the .cpp while the fork defined update() inline in the .h; taking either side alone would have silently dropped settleReleaseGate() or duplicated the definition. Both behaviours are now in the one definition. - ActivityManager: settleReleaseGate() kept ahead of upstream's new exclusive-storage early return; night mode keeps appliesNightMode(); the frontlight panel keeps its Frontlight.present() guard on top of upstream's status-bar tap entry. - GfxRenderer: upstream's applyPromotedRefresh() plus the fork's PanelSpan panel-time billing and paintclock::notePainted(). - main.cpp: the fork's dev-serial-bridge wake escape wraps upstream's wakeHoldVerified gate; heartbeat::begin() moved to follow upstream's deliberate relocation of SETTINGS.loadFromFile(). - FirmwareBoardTag: the fork defines the board macro in the header, so upstream's new .cpp block would have redefined it; upstream's x4c board was ported into the fork's header instead. - listIcons: 18 fork icons + upstream's usb = 19, disjoint sets. - BaseTheme: upstream's Usb keeps its enum number, Games/Apps stay last. ROADMAP.md and the two GitHub issue templates stay deleted (3b76299). NOT adopted: upstream's env:x4c / env:x4c-gh_release (X4 Classic). That is new device support, a product call, not a merge decision. Docs: bezel-insets.md updated in a follow-up commit. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ists
The two remaining shared-activity callers (OpdsFilter, OpdsDetail) move to
setContentMarginFromScreen. Both derive their margins from
getScreenSafeArea(), so every side is >= the safe area and max(m, safe)
returns them unchanged: no geometry moves.
setContentMarginAbsolute now has exactly two callers, both fork-only:
- toybox::absoluteChrome() passes Insets{}. FromScreen({}) = the safe rect,
which pushes every game's chrome down by the top inset -- the regression
docs/bezel-insets.md records finding on the device twice.
- XkcdScreens passes a band that can sit on the true panel edge. X4 Pro's
safe.bottom is 0 so they agree there, but a board on the inherited
{9,3,3,3} default would have a deliberate full-bleed clamped by 3px.
Upstream cannot express "content rect = full frame" at all: Screen seeds
content_ from safeRect() and both upstream calls inset from safeRect().
.gitmodules named branch x4pro-measured-insets, which has been DELETED on
the fork; the pinned commit lives on fix/sd-free-space, now fast-forwarded
to the SDK merge (c444616..29221d2) so the pointer resolves.
Docs: docs/bezel-insets.md carries the arithmetic and both exceptions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three things upstream changed that the merge could not resolve by itself. 1. EpdFontData gained 7 pointer fields (split kern class maps, sparse CSR kerning) BETWEEN kernRightClasses and kernLeftEntryCount. The fork's 14 generated cuts in src/apps_local/ui/fonts/ use positional initializers, so every field after that point was reading the wrong slot -- the build named it as "cannot initialize const uint16_t* with const int8_t[3551]". Migrated all 14 from 20 to 27 fields, NOT regenerated: regenerating a Toybox cut moves glyph metrics and shifts text in every app. The fork's cuts keep the packed class maps and the dense matrix, so the new pointers are null and getKerning() takes the same branch it always did. Field order taken from upstream's own regenerated notosans_12_bold.h. 2. The simulator ships its own HalStorage that shadows lib/hal, and upstream's USB Drive work added prepareForDeepSleep(), beginUsbDrive(), disconnectUsbDriveHost(), endUsbDrive() and usbDriveState() plus the UsbDriveState enum. Added sim_catchup patches for all six; parity check now reports 26 methods, both sides agree. The stubs answer Unsupported, which is the truth for a host-side simulator with no USB host attached. 3. Night mode: upstream removed Activity::appliesNightMode() in 62e84a1 and made the polarity global, with SleepActivity forcing normal itself. That method came from upstream (d720891), not from this fork, so by the same rule as the margin API the fork takes upstream's version. 4. prewarmscope pinned the OLD prewarm shape. Upstream now buckets the scan by RESOLVED style and hands prewarm a deduplicated character set, so a style a face does not have folds into one it does instead of costing a second pass. The suite's own property -- one face is one prewarm however many styles the page draws -- still holds and still passes. Three value assertions were updated to the new shape after watching each go red, and the SdCardFont fake now folds every style to regular, which is what a single-face fake actually does. Audited every identifier present in the fork and absent from the merge (103 after filtering font tables): 102 originated upstream and were superseded by upstream's own rewrites; the one fork-authored name, rowSubtitle, was a parameter of the legacy BaseTheme::drawList upstream deleted, and its capability survives through the fui list path with the fork's tuned listWithSubtitleRowHeight values intact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The simulator ships its own HalGPIO.h and BoardConfig.h that shadow lib/hal and the SDK, and both had drifted from what src/ now calls: - verifyPowerButtonWakeup() lost its two arguments upstream (it reads the settings itself now) and main.cpp calls the new form; the simulator package still declared the old one. Its body was already a constant true. - BoardConfig::isX4Classic() is called by the reader; the simulator's Board enum has no such board, so the honest stub is a constant false. Three fork blocks were orphaned by upstream rewrites and could not compile: - EpubReaderActivity: resolving that conflict as "keep both sides" put upstream's idle prewarm back in, whose members the fork deleted on purpose in 0ccbfa3 after measuring that PrewarmScope threw the warmed glyphs away before the turn meant to use them. The fork's rationale comment stays, the code does not; upstream's orientation reflow is kept. - LyraTheme::iconForName had grown a size argument for a 24px list path upstream has since deleted, and the per-icon headers it read (folder24.h and friends) are gone with it. Back to upstream's one-argument form, with the fork's Games/Apps shelf icons kept in the switch. - UITheme::getNumberOfItemsPerPage lost its declaration, the BaseTheme helper it called, and every caller. Removed rather than propped up. Also closed a brace: taking the fork's side of the OpdsServerList picker-mode conflict left its `if (!pickerMode)` open, because upstream's side had no such block and the shared trailing brace went with it. Verified by rendering, not by exit code: Home, Settings and the Games shelf all draw correctly in the simulator. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI fails on formatting drift since 9f24eb5. bin/clang-format-fix --check is clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dy has
The gate caught what my tree could not: the impl patch anchored on
"bool HalStorage::freeBytes(uint64_t &out) {", a line that a LATER patch in
the same file inserts. In a warm tree freeBytes had already been applied by
an earlier build, so the anchor was there and the patch worked. In the gate's
fresh worktree mine runs first, the anchor does not exist yet, and the patch
silently does nothing -- "no longer applies" in the log, then five undefined
HalStorage symbols at link time, naming UsbDriveActivity and main rather than
anything to do with the patch.
Both patches now anchor on pristine simulator text (removeDir in the header,
HalStorage::begin in the impl) and keep that anchor in their replacement, so
the order they run in cannot decide whether they apply.
Verified the way the gate does it: wiped .pio/libdeps/simulator_x4_pro
entirely and rebuilt. All three patches report "applied", the five
definitions are in the simulator's HalStorage.cpp, and the simulator boots
to Home.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The sync runbook described the stale branch as a live condition. It named x4pro-measured-insets, which had since been deleted on the fork, so a fetch --recurse-submodules failed outright rather than merely pointing somewhere unhelpful. Records what it names now and what is still owed. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
# Conflicts: # src/main.cpp
Upstream's new screen hands margins that already fold the X4 Pro safe area to plain setContentMargin(), which insets from the safe rect again: the list started 10px lower and 1px narrower than every other settings screen. setContentMarginFromScreen() is the call docs/bezel-insets.md prescribes for folded margins. Inherited from upstream, found by the cold review of the sync; worth an upstream issue. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The gate on the sync branch flagged two things of ours: the one-line Keyboard Layouts fix needed clang-format, and site/study/tools.zip was stale against tools_local/study after the upstream merge; regenerated with tools_local/study/sync_site.py. The emulator staleness is CI's to rebuild after the merge, as always. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Finishes the upstream sync that stopped on 2026-09-03 (card crosspoint-reader#133). Do not
compare this with
sync/upstream-20260903-- that branch holds the half-donemerge with conflict markers still in it and is evidence, not a candidate.
The margin decision
Adopted upstream's
setContentMarginFromScreeneverywhere. Mario's call:a permanent divergence on a call every screen makes is paid at every future
sync.
The premise the card was written on turned out to be wrong, and it made the
work far smaller. The SDK comment warning that the safe area gets applied
twice is about plain
setContentMargin(), not aboutsetContentMarginFromScreen(). FromFreeInkApp.h:max(m, s)returns an already-folded margin unchanged, and every one of thesecall sites folds the safe area in already (they derive from
getScreenSafeArea()). So no unfolding was needed, and 18 of the 19conflicting sites passed arguments already identical to upstream's -- those
resolved to a rename.
What actually moves: 9 screens that passed
0for the side margins now insetby the safe area (1px on the X4 Pro, 3px in the simulator's X4 profile). That
is upstream's behaviour and it makes content clear the side glass instead of
running under it. The top does not move on any screen, because
topPadding + headerHeightis far larger than the 10px top inset, somax()returns it unchanged.setContentMarginAbsolutesurvives with two callers, both fork-only:toybox::absoluteChrome()passesInsets{}.FromScreen({})ismax(0, safe)= the safe rect, which pushes every game's chrome down by thetop inset -- the regression
docs/bezel-insets.mdrecords finding on thedevice twice.
XkcdScreens.cpppasses a band that can sit on the true panel edge. On theX4 Pro
safe.bottomis 0 so they agree, but a board on the inherited{9,3,3,3}default would have a deliberate full-bleed clamped by 3px.Upstream cannot express "content rect = the full frame" at all:
Screenseedscontent_fromsafeRect()and both upstream calls inset fromsafeRect().So this is a capability the fork needs, not a style preference -- and no screen
shared with upstream diverges any more.
The SDK
Merged
Free-Ink/freeink-sdkmain into the fork (16 commits, clean, noconflicts); the SDK's own host suite passes 2832 checks.
.gitmodulesnamedbranch
x4pro-measured-insets, which has been deleted on the fork; thepinned commit lives on
fix/sd-free-space, now fast-forwardedc444616..29221d2, and.gitmodulesnames it. That also fixes theupload-pack: not our reffailure a plaingit fetch --recurse-submoduleshit.Verification
Both sides of every conflicted file were checked by name, and then every
identifier present in the fork and absent from the merge was audited (103 after
filtering font tables): 102 originated upstream and were superseded by
upstream's own rewrites; the single fork-authored name was a parameter of a
legacy helper upstream deleted, whose capability survives.
Rendered and looked at in the simulator: Home, Settings, and the Games shelf.
Reviewer notes
uihost suite cannot validate the margin change: it constructsDeviceContextwithsafeArea = {}, which pins the same geometry eitherway by construction. The renders are the evidence, not that suite.
env:x4c/env:x4c-gh_release(X4 Classic) are deliberatelynot adopted -- that is new device support, a product call, not a merge
decision. The board-name mapping for it was ported so adopting it later is
a one-line change instead of an
#error.🤖 Generated with Claude Code