Implements search functionality for plugin catalogs with configurable search URL and body templates. Adds search icon to browsing header, keyboard entry for queries, and URL encoding for query parameters. Search results use the same item format as browse results. Includes navigation improvements to prevent button press fall-through and proper authentication gating for search requests.
Separate job lease expiration from slot reuse logic. Jobs that exceed the lease timeout are now requeued as PENDING instead of being treated as stale slots. Add retry logic for job completion requests - if completion fails, it will be retried on the next poll. Also allow completion endpoint to be idempotent by accepting already-completed jobs.
Remove the check for sink.total > 0 before calling progress callback. This allows progress to be reported for downloads where the total size is not known in advance (e.g., chunked transfer encoding).
Check that the full token was written to storage. If a short write occurs (e.g., SD card full or I/O fault), remove the truncated token file and return false to prevent pollAuth from reporting success while later loadToken() calls would fail.
Standardize the footer hint behavior across OPDS browser and plugin catalog. Pager rows (prev/next) and book/item entries now consistently show 'Fetch' for server operations and 'Open' for local navigation. Track pager row presence with boolean flags to determine correct label.
Remove the « and » arrow symbols from STR_NEXT_PAGE and STR_PREV_PAGE translation strings across all languages. This simplifies the pagination UI text to just the page navigation labels without directional indicators.
Introduces reusable FreeInkUI screen components for OPDS book browser and SD-plugin catalogs. Provides functions for rendering themed headers with optional action buttons, centered message blocks, download progress screens, and list bodies with viewport syncing.
Consolidates repeated IO, HTTP and error-handling logic across plugin/catalog and webserver code. Key changes:
- lib/Epub/Epub.cpp: use extractItemToFile for cover extraction instead of manual write/read.
- src/activities/plugins/PluginCatalogActivity.*: add FsHelpers, pick(), use decodeUriEscapes, introduce fail(), beginLoading(), substitutedHeaders(), openApiClient(), and centralize apiRequest; replace many duplicated header builds and error-state code.
- src/activities/plugins/PluginInfoActivity.cpp: reserve() paragraphs/lines to reduce reallocations.
- src/network/CrossPointWebServer.*: add streamFileToClient() and readJsonBody() to centralize file streaming and JSON body parsing; include ArduinoJson in header.
Reduces duplication, improves robustness and minor perf gains.
Prevent runaway allocations and improve plugin FS handling.
- src/network/CrossPointWebServer.cpp: In handleCrypto() clamp requested random byte length to a new kMaxRandomBytes (4096) and allocate exactly n bytes to avoid large/unbounded allocations. Also, in handlePluginFs() ensure the parent directory for the requested path is created by calling Storage.ensureDirectoryExists(path.substr(0, lastSlash)) instead of only /.crosspoint, covering nested plugin paths.
- src/network/html/shared/PluginHost.js.inc: Replace innerHTML error string with safe DOM construction and container.replaceChildren(...) to avoid HTML injection and ensure consistent DOM updates.
Replace hardcoded '> ' prefix with direction markers from translated strings. Change 'Download' button to 'Fetch' for consistency. Add up/down navigation labels when multiple items exist. This allows better localization of navigation UI elements.
Browse responses (JSON/XML) now stream to an SD temp file instead of being buffered in memory, allowing large catalog pages (60+ KB) to work on low-heap devices. XML parsing reads the file in 2KB chunks. Also adds optional browse.lists feature for named sub-catalogs with a picker UI, and keeps TLS session alive across requests to avoid heap fragmentation.
## Summary
* **What is the goal of this PR?**
* Prevent reset-only watchdog events from opening the crash-report flow
with an empty panic reason.
* **What changes are included?**
* Record a marker in RTC memory only when panic diagnostics are
captured.
* Continue reporting explicit panics and CPU lockups.
* Require that marker before treating watchdog resets as crashes, and
clear it with the existing panic state.
## 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
* This is a focused port of CrossInk commit `c96d4275`.
* The change adds 4 bytes of RTC no-init state; it does not affect
storage formats, normal boot routing, or real panic reporting.
* Hardware follow-up: on an X3/X4, confirm a reset-only watchdog boots
normally without an empty crash report, while a genuine panic still
reaches the crash-report 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>**_
Replace inline findPluginDir with scanPlugins() that returns structured Entry objects containing plugin metadata (plugin.js, device.json, manifest.json markers). This centralizes plugin discovery logic and provides a single source of truth for what constitutes a valid plugin across all roots.
Enables downloading multiple files from a catalog item instead of just one. Each item can specify a base URL and an array of relative file paths. All files are fetched into a subdirectory with path traversal protection. This supports use cases like plugin installers and theme packs that consist of multiple files.