Commit Graph
5156 Commits
Author SHA1 Message Date
Anthony Stirling 007c8e17de Exclude JPDFium native cache from temp-file regression check 2026-05-21 17:46:46 +01:00
Anthony Stirling 81dbb29be3 Iterative addBookmarkFlat with depth + cycle + node-count guards 2026-05-21 17:17:06 +01:00
Anthony Stirling caef0477a9 Merge remote-tracking branch 'origin/main' into feat/jpdfium-integration
# Conflicts:
#	app/common/build.gradle
2026-05-21 16:55:18 +01:00
Anthony Stirling 7682a0dd54 sign-jpdfium-dylibs: sign both Gradle and Tauri bootJar copies 2026-05-21 16:26:52 +01:00
9d081d1792 SaaS Consolidation (#6384)
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2026-05-21 16:05:35 +01:00
Anthony Stirling f90ed4657a Apply spotless; bash 3.2-compatible array handling in sign-jpdfium-dylibs 2026-05-21 15:53:28 +01:00
Anthony Stirling a6c7a68242 Move sign-jpdfium-dylibs-in-bootjar.sh into frontend/scripts/ 2026-05-21 15:21:25 +01:00
Anthony Stirling a184b394d6 Strip rationale-history comments from JDK 25 references 2026-05-21 15:20:11 +01:00
Anthony Stirling 302d04c201 Merge remote-tracking branch 'origin/main' into feat/jpdfium-integration
# Conflicts:
#	build.gradle
2026-05-21 15:05:51 +01:00
Anthony Stirling 68ae9d52fb Bump JPDFium dep to 1.0.0; drop snapshot repo 2026-05-21 14:46:10 +01:00
089de247b4 Updated Spanish translation (#6309)
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-05-21 13:16:07 +01:00
Anthony Stirling 6552ba905c Cleanup: drop local mavenLocal hack; gate MergeBenchmark with -Dmerge.bench=true 2026-05-21 11:58:43 +01:00
Anthony Stirling ee36b6f616 MergeController: preserve source bookmarks through merge (PDFium drops them) 2026-05-21 11:39:59 +01:00
Anthony Stirling 627091f8df MergeController: use JPDFium native merge (94% less heap on 5-doc merge) 2026-05-21 10:37:01 +01:00
Anthony Stirling 67a37d3291 Filter JPDFium natives per Tauri target OS (saves ~45 MB per bundle) 2026-05-21 00:28:31 +01:00
dependabot[bot]andGitHub 0c3a512b21 build(deps): bump softprops/action-gh-release from 2.6.1 to 3.0.0 (#6346)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:34:16 +01:00
dependabot[bot]andGitHub 707cfa3140 build(deps): bump devalue from 5.6.4 to 5.8.1 in /frontend (#6367)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:18:43 +01:00
dependabot[bot]andGitHub ea91a35518 build(deps): bump org.postgresql:postgresql from 42.7.10 to 42.7.11 (#6375)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:17:36 +01:00
dependabot[bot]andGitHub 5a5aada2d8 build(deps): bump pypdf from 6.10.2 to 6.11.0 in /testing/cucumber (#6376)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:17:18 +01:00
dependabot[bot]andGitHub a4ef70eaf8 build(deps): bump sigstore/cosign-installer from 4.1.1 to 4.1.2 (#6377)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:17:04 +01:00
dependabot[bot]andGitHub d76d25654a build(deps): bump actions/dependency-review-action from 4.9.0 to 5.0.0 (#6378)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:16:48 +01:00
dependabot[bot]andGitHub 31b00b7d06 build(deps): bump github/codeql-action from 4.35.3 to 4.35.4 (#6379)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:16:42 +01:00
dependabot[bot]andGitHub f00c12ad8a build(deps): bump step-security/harden-runner from 2.19.0 to 2.19.1 (#6380)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:16:38 +01:00
dependabot[bot]GitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
1f5c046362 build(deps): bump tokio from 1.49.0 to 1.50.0 in /frontend/src-tauri in the tokio group across 1 directory (#6296)
Bumps the tokio group with 1 update in the /frontend/src-tauri
directory: [tokio](https://github.com/tokio-rs/tokio).

Updates `tokio` from 1.49.0 to 1.50.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/tokio-rs/tokio/releases">tokio's
releases</a>.</em></p>
<blockquote>
<h2>Tokio v1.50.0</h2>
<h1>1.50.0 (Mar 3rd, 2026)</h1>
<h3>Added</h3>
<ul>
<li>net: add <code>TcpStream::set_zero_linger</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7837">#7837</a>)</li>
<li>rt: add <code>is_rt_shutdown_err</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7771">#7771</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>io: add optimizer hint that <code>memchr</code> returns in-bounds
pointer (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7792">#7792</a>)</li>
<li>io: implement vectored writes for <code>write_buf</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7871">#7871</a>)</li>
<li>runtime: panic when <code>event_interval</code> is set to 0 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7838">#7838</a>)</li>
<li>runtime: shorten default thread name to fit in Linux limit (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7880">#7880</a>)</li>
<li>signal: remember the result of <code>SetConsoleCtrlHandler</code>
(<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7833">#7833</a>)</li>
<li>signal: specialize windows <code>Registry</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7885">#7885</a>)</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>io: always cleanup <code>AsyncFd</code> registration list on
deregister (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7773">#7773</a>)</li>
<li>macros: remove (most) local <code>use</code> declarations in
<code>tokio::select!</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7929">#7929</a>)</li>
<li>net: fix <code>GET_BUF_SIZE</code> constant for <code>target_os =
&quot;android&quot;</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7889">#7889</a>)</li>
<li>runtime: avoid redundant unpark in current_thread scheduler (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7834">#7834</a>)</li>
<li>runtime: don't park in <code>current_thread</code> if
<code>before_park</code> defers waker (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7835">#7835</a>)</li>
<li>io: fix write readiness on ESP32 on short writes (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7872">#7872</a>)</li>
<li>runtime: wake deferred tasks before entering
<code>block_in_place</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7879">#7879</a>)</li>
<li>sync: drop rx waker when oneshot receiver is dropped (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7886">#7886</a>)</li>
<li>runtime: fix double increment of <code>num_idle_threads</code> on
shutdown (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7910">#7910</a>,
<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7918">#7918</a>,
<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7922">#7922</a>)</li>
</ul>
<h3>Unstable</h3>
<ul>
<li>fs: check for io-uring opcode support (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7815">#7815</a>)</li>
<li>runtime: avoid lock acquisition after uring init (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7850">#7850</a>)</li>
</ul>
<h3>Documented</h3>
<ul>
<li>docs: update outdated unstable features section (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7839">#7839</a>)</li>
<li>io: clarify the behavior of <code>AsyncWriteExt::shutdown()</code>
(<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7908">#7908</a>)</li>
<li>io: explain how to flush stdout/stderr (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7904">#7904</a>)</li>
<li>io: fix incorrect and confusing <code>AsyncWrite</code>
documentation (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7875">#7875</a>)</li>
<li>rt: clarify the documentation of <code>Runtime::spawn</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7803">#7803</a>)</li>
<li>rt: fix missing quotation in docs (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7925">#7925</a>)</li>
<li>runtime: correct the default thread name in docs (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7896">#7896</a>)</li>
<li>runtime: fix <code>event_interval</code> doc (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7932">#7932</a>)</li>
<li>sync: clarify RwLock fairness documentation (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7919">#7919</a>)</li>
<li>sync: clarify that <code>recv</code> returns <code>None</code> once
closed and no more messages (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7920">#7920</a>)</li>
<li>task: clarify when to use <code>spawn_blocking</code> vs dedicated
threads (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7923">#7923</a>)</li>
<li>task: doc that task drops before <code>JoinHandle</code> completion
(<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7825">#7825</a>)</li>
<li>signal: guarantee that listeners never return <code>None</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7869">#7869</a>)</li>
<li>task: fix task module feature flags in docs (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7891">#7891</a>)</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/tokio-rs/tokio/commit/0273e45ead199dac7725faee1e3dc35a9c8753ab"><code>0273e45</code></a>
chore: prepare Tokio v1.50.0 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7934">#7934</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/e3ee4e58dc9bb7accf26dfd51b0a2146922b5269"><code>e3ee4e5</code></a>
chore: prepare tokio-macros v2.6.1 (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7943">#7943</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/8c980ea75a0f8dd2799403777db700c2e8f4cda4"><code>8c980ea</code></a>
io: add <code>write_all_vectored</code> to <code>tokio-util</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7768">#7768</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/e35fd6d6b7d9a8ba37ee621835ef91372c2565cb"><code>e35fd6d</code></a>
ci: fix patch during clippy step (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7935">#7935</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/03fe44c10302fdb55c29dbe5b08d4f8769c80272"><code>03fe44c</code></a>
runtime: fix <code>event_interval</code> doc (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7932">#7932</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/d18e5dfbb0cdc28725bebb28cde80a6c11ee32bc"><code>d18e5df</code></a>
io: fix race in <code>Mock::poll_write</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7882">#7882</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/f21f2693f02aec9a876ac2bd21566c85e15b682e"><code>f21f269</code></a>
runtime: fix race condition during the blocking pool shutdown (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7922">#7922</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/d81e8f0acbdd7d866bce4f733b3545fd834c7840"><code>d81e8f0</code></a>
macros: remove (most) local <code>use</code> declarations in
<code>tokio::select!</code> (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7929">#7929</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/25e7f2641ef2555d688c267059431a2802805f1d"><code>25e7f26</code></a>
rt: fix missing quotation in docs (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7925">#7925</a>)</li>
<li><a
href="https://github.com/tokio-rs/tokio/commit/e1a91ef114a301b542d810abab9956f2868861b9"><code>e1a91ef</code></a>
util: fix typo in docs (<a
href="https://redirect.github.com/tokio-rs/tokio/issues/7926">#7926</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/tokio-rs/tokio/compare/tokio-1.49.0...tokio-1.50.0">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>
2026-05-20 14:16:18 +01:00
dependabot[bot]andGitHub 1787443102 build(deps): bump fast-uri from 3.0.6 to 3.1.2 in /devTools (#6332)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:15:08 +01:00
dependabot[bot]andGitHub 8fb281525b build(deps): bump quinn-proto from 0.11.13 to 0.11.14 in /frontend/src-tauri (#6322)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:13:31 +01:00
LudyandGitHub 4c3be4992f deps: align Bouncy Castle modules to shared version property (#6362) 2026-05-20 14:11:43 +01:00
dependabot[bot]andGitHub 2790467241 build(deps): bump urllib3 from 2.6.3 to 2.7.0 in /testing/cucumber in the pip group across 1 directory (#6352)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:11:11 +01:00
dependabot[bot]andGitHub dd770dfa76 build(deps): bump rustls-webpki from 0.103.9 to 0.103.13 in /frontend/src-tauri (#6323)
Signed-off-by: dependabot[bot] <support@github.com>
2026-05-20 14:10:56 +01:00
ConnorYohandGitHub b146d9994d fix(task): make task dev / task dev:all work on Windows (#6392)
## Summary

`#6145` (port picker) and `#6244` (gradle unification) combined to break
`task dev` / `task dev:all` on Windows. Two independent regressions,
both addressed here.

### 1. `find-free-port.ps1` panic (`#6145`)

```
$ task --dry dev
The argument 'scriptsfind-free-port.ps1' to the -File parameter does not exist.
panic: ended up with a non-nil exitStatus.err but a zero exitStatus.code
```

- **Backslash stripped.** `Taskfile.yml` had
`scripts\find-free-port.ps1`. go-task pipes the `sh:` block through
mvdan/sh, which treats `\` as a POSIX escape and silently drops it,
leaving `scriptsfind-free-port.ps1`. PowerShell can't find the file,
exits non-zero, mvdan/sh panics on the inconsistent exit status.
Switched to a forward slash.
- **`-Preferred 8080,5173` is brittle.** Relies on PowerShell parsing
the comma-list into `[int[]]`. Dropped the named flag; switched the
script's `param` to `[Parameter(ValueFromRemainingArguments =
$true)][int[]]$Preferred`; pass each port as its own positional token
(matching how the bash variant is called).

### 2. `bash gradlew` can't find Java on Windows (`#6244`)

```
[backend:dev] ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
```

`#6244` unified all gradle invocations under `bash gradlew` on the
assumption that Git-Bash inherits Windows-side env. It doesn't (and
neither does WSL bash, which can also shadow `bash` on a developer's
PATH). The standard Adoptium/Temurin installer sets `JAVA_HOME` in the
Windows env only, so `bash gradlew` fails before Spring Boot even loads.

Restored the per-platform branch for every backend task: `cmd /c
".\gradlew.bat ..."` on Windows, `./gradlew ...` on Linux/macOS. Two
Windows-specific gotchas to be aware of for future edits:

- mvdan/sh strips `\` outside of double quotes, so `cmd /c
.\gradlew.bat` ends up as `.gradlew.bat`. The entire payload must be
wrapped in double quotes (`cmd /c "..."`).
- Modern Windows excludes cwd from cmd.exe's search path, so the leading
`.\` is required — bare `cmd /c gradlew.bat` errors with "is not
recognized" even from the repo root.

## Test plan

- [x] `task --dry dev` / `task --dry dev:all` on Windows resolve ports
cleanly and dispatch the inner tasks
- [x] `powershell -NoProfile -File scripts/find-free-port.ps1 8080 5173
5001` prints three ports
- [x] `task backend:dev PORT=8081` on Windows starts Spring Boot in ~9s;
`/api/v1/info/status` returns `{"status":"UP"}`
- [x] `task dev:all` on Windows brings up all three services healthy:
backend `/api/v1/info/status` 200, engine `/health` 200, frontend `/`
200
- [ ] Linux / macOS path unaffected (only the Windows branches changed
in behaviour)
2026-05-20 12:57:41 +00:00
Anthony Stirling 4b6d4885f4 sign-jpdfium-dylibs: resolve native-jar paths via jar tf first
`jar xf <jar> <path>` doesn't expand glob patterns in <path> the way
`unzip` does — paths must be exact. My initial script passed
`BOOT-INF/lib/jpdfium-natives-darwin-x64-*.jar` as a literal path,
which matched nothing, so the script always reported
"No JPDFium darwin natives in bootJar; nothing to sign" and exited
without actually signing anything.

Fix: `jar tf` to LIST the bootJar's contents, grep with a regex for
the snapshot-versioned native-jar paths, then `jar xf` those exact
paths.

The natives jars are normally:
  BOOT-INF/lib/jpdfium-natives-darwin-x64-1.0.0-SNAPSHOT.jar
  BOOT-INF/lib/jpdfium-natives-darwin-arm64-1.0.0-SNAPSHOT.jar
…but the version number floats with the snapshot timestamp, hence
the regex match instead of a fixed path.
2026-05-20 09:46:07 +01:00
Anthony Stirling 2a151b65f7 Move JPDFium dylib signing step AFTER cert import
My first placement put the sign-jpdfium-dylibs-in-bootjar step at
the wrong point in the workflow: BEFORE the "Verify Certificate"
step that sets APPLE_SIGNING_IDENTITY in GITHUB_ENV. So the gate
`if: ... && env.APPLE_SIGNING_IDENTITY != ''` always evaluated to
false and the step silently skipped, leaving the dylibs unsigned
and notarytool still rejecting the .app.

Move it to right after Verify Certificate (which sets the env var
from the keychain identity). Also switch the gate to checking
env.APPLE_CERTIFICATE (the secret that's set at job level and
available from step 1) rather than env.APPLE_SIGNING_IDENTITY (set
mid-workflow via GITHUB_ENV) — the latter is fine in `run:` blocks
but flaky in `if:` evaluation depending on GH Actions evaluation
timing.
2026-05-20 09:08:37 +01:00
Anthony Stirling 9458fcd0e2 Re-run CI against JPDFium with Windows ICU trim + harfbuzz-no-glib 2026-05-20 00:45:22 +01:00
Anthony Stirling 1e8c41425b Sign JPDFium dylibs in bootJar before Tauri notarization (macOS)
Tauri-build macos-universal has been failing notarytool because the
.dylib files inside the JPDFium native jars (jpdfium-natives-darwin-
x64-*.jar / -arm64-*.jar) ship unsigned — JPDFium's publish workflow
has no Apple Developer credentials, so it can't sign during publish.
Apple's notarytool walks INTO nested .jars in the .app and reports:

  "The binary is not signed."
  path: Stirling-PDF.zip/Stirling-PDF.app/Contents/Resources/libs/
   stirling-pdf-*.jar/BOOT-INF/lib/
   jpdfium-natives-darwin-x64-1.0.0-SNAPSHOT.jar/natives/darwin-x64/
   libjpdfium.dylib

Tauri's own codesign walk doesn't open .jars, so the fix has to
happen here before tauri-action runs. New step between
`task desktop:prepare` (builds bootJar) and `tauri-action` (builds
.app + notarizes):

  scripts/sign-jpdfium-dylibs-in-bootjar.sh
    1. jar xf bootJar BOOT-INF/lib/jpdfium-natives-darwin-*.jar
    2. for each native jar, explode it, codesign every .dylib with
       APPLE_SIGNING_IDENTITY + --options runtime + --timestamp
    3. jar cfM0 to repack the natives jar (stored, no deflate —
       matches Spring Boot's preferred layout)
    4. jar uf bootJar to replace the natives jars in the outer
       bootJar with the freshly-signed versions

Gated on macOS-15 + APPLE_SIGNING_IDENTITY being set, so PR builds
from forks (no secret) fall through and the existing
"binary not signed" failure persists — no regression vs current.
2026-05-19 23:32:09 +01:00
2399da6893 migrate exportUpdatedPages from bytes to stream (#6201)
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2026-05-19 23:03:44 +01:00
EthanHealy01andGitHub 2c15044dfa Snag/disable logo drag (#6393) 2026-05-19 18:13:58 +00:00
Anthony Stirling b3277a18c8 Re-run CI: pick up Rust-LTO-trimmed JPDFium snapshot 2026-05-19 18:56:29 +01:00
Anthony Stirling 5ca1586976 Re-run CI: pick up smaller (ICU+qpdf trim) JPDFium snapshot 2026-05-19 18:18:21 +01:00
Anthony Stirling ce47a4e3af Re-run CI: pick up ICU-trimmed JPDFium snapshot 2026-05-19 17:08:17 +01:00
ConnorYohandGitHub 31f6ea4b25 perf(frontend): stabilize hot-path context subscriptions to fix excessive rerenders (#6373)
## Summary

The frontend was rerendering excessively across many interactions —
typing, clicking tools, opening modals, toggling the sidebar — because
**multiple compounding ref-instability cascades defeated `memo()` checks
in hot paths**. This PR fixes the cascades structurally.

Seven focused commits, low-to-high blast radius:

1. `perf(contexts): memoize BannerContext provider value`
2. `perf(contexts): memoize CommentAuthor and ActiveDocument provider
values`
3. `perf(contexts): memoize AppConfigContext provider value`
4. `perf(useToolManagement): stop spreading tool entries to keep refs
stable` — **root-cause fix**
5. `refactor(ToolPicker): hoist module-scope styles and helpers`
6. `feat(ToolWorkflowContext): add ref-stable Actions and Data subset
contexts` — additive
7. `perf(tools): migrate hot consumers to slim contexts and wrap in
memo()`

(Plus `style: apply prettier formatting` for CI.)

## What was wrong

Whenever something high-up in the tree caused a render, a chain of
unstable references propagated downward and forced every `ToolButton` to
re-execute its full body (hooks, derived computations, hook
subscriptions to other contexts). The chain:

- **4 unstable Context providers** (`Banner`, `CommentAuthor`,
`ActiveDocument`, `AppConfig`) were passing fresh `value={{ … }}`
objects on every render. Every consumer rerendered on every ancestor
render.
- **`useToolManagement.toolRegistry`** spread `{...baseTool, name,
description}` — a no-op spread that manufactured a new tool object
identity on every memo recompute.
- **The big `ToolWorkflowContext`** (25+ fields including
`state.searchQuery`) rebuilt its entire value on every
keystroke/click/toggle, forcing every `useToolWorkflow()` consumer (~36
files) to rerender.
- **`useToolNavigation`** transitively subscribed every `ToolButton` to
the full workflow context.
- **`ToolButton` & `ToolPicker`** weren't `memo()`-wrapped, so nothing
checked.
- **`ToolPanel`** passed inline `onSelect={(id) =>
handleToolSelect(...)}` — fresh ref every render, defeats child
memoization.
- **`ToolPicker`** allocated inline styles / `[]` / `toTitleCase` inside
the function body — churned `useToolSections`'s internal memo.

## Interaction matrix — what improves

The PR fixes the underlying ref-stability problem; the same fix benefits
*every* interaction that previously triggered the cascade:

| Interaction | Before | After |
|---|---|---|
| **Typing in tool search** | All visible buttons rerender per keystroke
| Only buttons whose matched-text changes rerender |
| **Clicking a tool** | All 36 `useToolWorkflow()` consumers rerender |
Only previously-selected and newly-selected buttons rerender (via
`isSelected` prop) |
| **Toggling sidebar / panel mode / reader mode** | Every tool button
rerenders | Tool components stay still (slim context doesn't see UI
state) |
| **Switching workbench / navigation** | `handleToolSelect` identity
changes → cascades through `onSelect` props | Ref-stabilized in Actions
context. Identity stable. Children's memo bails |
| **Modal/dialog open/close** | AppConfig churns → every `useAppConfig`
consumer rerenders (ToolButton reads `premiumEnabled`) | AppConfig
memoized; consumers rerender only when config changes |
| **Banner show/hide** | BannerProvider value churns → every consumer
rerenders on any ancestor render | Memoized; AppLayout rerenders only
when banner content changes |
| **Any state update high in the tree** | Compounding cascade defeats
memo everywhere | Stable subscriptions; memo bails out |

## Evidence

Per-keystroke prop instability on `ToolButton` (cleanest measurable
signal, captured via custom memo comparators logging which prop refs
differ):

| | `tool` ref diffs | `onSelect` ref diffs | `matchedSynonym` value
diffs | Total |
|---|---|---|---|---|
| Before | 18 | 18 | 6 | **42** |
| After  | 0  | 0  | 6 | **6 (all legitimate)** |

→ **86% reduction** in spurious per-keystroke prop instability. The 6
remaining matched-synonym diffs are correct (different substring
highlighted per keystroke).

Context value rebuild counts during a keystroke (verified with
instrumented `useMemo` factories): `useToolWorkflowData=0`,
`useToolWorkflowActions=0`, `AppConfigContext=0`.

The same stabilization applies to click/toggle/modal interactions — they
were all driven by the same cascading invalidations.

## Honest caveat on render-count metrics

`React.Profiler` counts and function-body execution counts in **dev
mode** came back identical before vs after (StrictMode + concurrent
rendering + Mantine internal commits dominate the numbers). The PR's
value is measured against the **prop-stability signal** above, not
Profiler counts. Production builds — where StrictMode doesn't
double-render and Mantine internals aren't constantly committing — will
show memo bail out properly.

## Risk × benefit

| # | Commit | Risk | Benefit |
|---|--------|------|---------|
| 1 | BannerContext memo | ⬛ Trivial | 🟦 Small |
| 2 | CommentAuthor + ActiveDocument memo | ⬛ Trivial | 🟦 Small |
| 3 | AppConfig memo | ⬛ Trivial | 🟦 Moderate (wide consumer base) |
| 4 | useToolManagement spread removal | ⬛ Trivial | 🟥 **High (root
cause)** |
| 5 | ToolPicker hoist | ⬛ Trivial | 🟦 Small |
| 6 | ToolWorkflowContext split | 🟧 Low-Med | 🟥 **High (foundation)** |
| 7 | Hot consumer migration + memo | 🟧 Low-Med | 🟥 **High
(actualization)** |

Commit 6 introduces an invariant: ref-stabilized callbacks in the
Actions context must only be invoked from event handlers (post-commit),
never during render. All current call sites comply.

## Test plan

- [x] `npx playwright test --project=stubbed` — 145 / 6 skipped / 0
failed before and after.
- [x] Targeted regression: `main-dashboard`, `tool-search`, `navigation`
— 11/11 passing.
- [x] CI passing on commits (one infrastructure flake on
`docker-compose-tests` — "No space left on device" — unrelated;
rerunning).
- [ ] Manual sanity check in a dev build after merge.

## What this enables

The same Actions + Data subset-context pattern can be applied to
`FileContext`, `NavigationContext`, and other big contexts. The
foundation is in place.
2026-05-19 15:56:50 +00:00
Anthony Stirling 859a2d97c2 Always re-resolve JPDFium snapshots in CI
PR rerun against new JPDFium snapshots kept using Gradle's cached
versions from earlier runs. Gradle caches 'changing modules' (anything
ending in -SNAPSHOT) for 24h by default, and gh run rerun --failed
reuses the existing run's Gradle home — so even after publishing a
fresh com.stirling:jpdfium-natives-*:1.0.0-SNAPSHOT, the bootJar kept
embedding the old natives.

cacheChangingModulesFor 0 means every Gradle invocation re-checks the
snapshot's maven-metadata.xml and pulls a newer timestamped build if
present. cacheDynamicVersionsFor 0 does the same for non-snapshot
dynamic versions (e.g. '1.+'). Both are scoped to the
subprojects.configurations.all block alongside the existing security
force-versions.

Trade-off: every Gradle invocation in CI does a HEAD against Maven
Central per snapshot dep (4 natives + 1 main jpdfium = 5 small
requests). Negligible compared to a download. Reverts naturally when
JPDFium ships a tagged release.
2026-05-19 15:00:02 +01:00
Anthony StirlingandGitHub f9fbc37800 add translations (#6390) v2.11.0 2026-05-19 11:25:13 +01:00
Anthony Stirling a422deecdb Re-add jpdfium-natives-darwin-x64 (Intel Mac) runtime dep
The publish-github-packages workflow's restored darwin-x64 build now
cross-compiles from macos-14 under Rosetta + a second Homebrew, so the
natives jar is available again. Adding it back to the bootJar classpath
lets the tauri-build macos-15 universal binary cover Intel Macs too.
2026-05-19 10:15:16 +01:00
Anthony Stirling 7f8f09c899 Native-access via bootJar manifest, not CLI flags everywhere
JDK 22+ honors an 'Enable-Native-Access' attribute on the executable
jar's manifest (JEP 472). Setting it once on the Spring Boot bootJar
removes the need to remember --enable-native-access=ALL-UNNAMED in:
  - Docker init script's JAVA_BASE_OPTS injection
  - Tauri Rust launcher's java_options vector
  - Anywhere else somebody runs the bootJar
…and also future-proofs against JDK 26's hard-fail behavior without
each launch point having to track the flag.

app/core/build.gradle: add 'Enable-Native-Access': 'ALL-UNNAMED' to the
bootJar manifest attributes block.

build.gradle (bootRun jvmArgs): keep --enable-native-access=ALL-UNNAMED
because bootRun launches from classfiles, not the bootJar — the
manifest mechanism doesn't apply to that codepath, only to 'java -jar'.

scripts/init-without-ocr.sh: drop the FFM injection.
frontend/src-tauri/src/commands/backend.rs: drop the CLI arg.
2026-05-18 21:35:06 +01:00
Anthony Stirling 96920b1186 build.gradle: modernJavaVersion 21 -> 25
This was the actual culprit behind 'JVM runtime version 21 vs 25' Gradle
variant-attribute mismatch errors during PR CI. modernJavaVersion drives:
- options.release on JavaCompile.configureEach across all subprojects
- restart-helper compile target

Even though sourceCompatibility / targetCompatibility were already bumped
to VERSION_25 in an earlier commit, --release 21 on javac overrode them
and made Gradle's compileClasspath declare org.gradle.jvm.version=21,
which then refused to resolve com.stirling:jpdfium:1.0.0-SNAPSHOT
(published as JVM 25).

Bumping modernJavaVersion to 25 aligns everything.
2026-05-18 19:09:28 +01:00
Anthony Stirling 3626319685 docs+ci: scrub remaining JDK 21 references
JPDFium's published artifacts target JVM 25 and Stirling-PDF's main
sources are now compiled with sourceCompatibility = VERSION_25, so JDK
21 is no longer supported. Update header comments and developer-facing
docs to reflect the hard floor.

- backend-build.yml header: 'JDK 21/25 \xc3\x97 spring-security' -> 'JDK 25 \xc3\x97 spring-security'
- AGENTS.md Important Notes: minimum 21 -> requires 25
- DeveloperGuide.md prerequisites + setup steps: drop 'or later, 25 recommended'
  language, say 'JDK 25' outright.
2026-05-18 19:07:11 +01:00
Anthony Stirling 755f270a31 ci: drop JDK 21 from backend build matrix
JPDFium's published artifacts carry org.gradle.jvm.version=25 (FFM is
finalized at JDK 22+; JPDFium picks 25 as the source target). Gradle's
variant attribute matching refuses to resolve the dep on JDK 21
targets, so the 21 cells in this matrix were guaranteed to fail.

Now that targetCompatibility is bumped to 25 across the project, JDK 25
is the only supported toolchain.
2026-05-18 19:04:59 +01:00
Anthony Stirling 6e1a7454ca Integrate Stirling-Tools JPDFium for PDF operations
This is a bring-up branch to validate JPDFium consumption across all
Stirling-PDF release targets (server jar, Docker, Tauri desktop). Open
as PR for cross-platform CI validation.

Source dep
- com.stirling:jpdfium:1.0.0-SNAPSHOT  (pulled from Maven Central snapshots,
  fully anonymous — no PAT or GitHub Packages auth needed)
- Per-platform natives: linux-x64, linux-arm64, darwin-arm64, windows-x64
  All four are added as runtimeOnly so the bootJar carries every platform
  and NativeLoader picks the right one at startup. The natives jars are
  hermetic — each bundles its bridge, PDFium component libs, and the
  third-party deps (pcre2/freetype/harfbuzz/icu/qpdf/pugixml/libunibreak),
  so no system package install is required at runtime.
- TODO follow-up: jpdfium-natives-darwin-x64 (Intel Mac) — dropped from
  the upstream publish matrix while cross-compile-on-macos-14 setup lands.

JDK 25 enforcement
- Bump root + subprojects from sourceCompatibility / targetCompatibility
  21 -> 25. JPDFium publishes with org.gradle.jvm.version=25 (FFM is final
  in JDK 22+; JPDFium picks 25 as the floor), and a 21-targeted Stirling-PDF
  jar would fail Gradle's variant attribute match at compile time.
- bootRun jvmArgs: add --enable-native-access=ALL-UNNAMED. JDK 25 warns
  on restricted FFM methods without it; JDK 26 will hard-fail.

Docker
- scripts/init-without-ocr.sh: prepend --enable-native-access=ALL-UNNAMED
  to JAVA_BASE_OPTS so the JAVA_TOOL_OPTIONS env var that the container
  sets includes it for the running JVM. Idempotent — won't duplicate if
  already present.

Tauri
- frontend/src-tauri/src/commands/backend.rs: the sidecar Java spawn now
  passes --enable-native-access=ALL-UNNAMED. The bundled jlink JRE's
  module list (desktop.yml JLINK_MODULES) already includes java.desktop
  and java.net.http which is everything JPDFium's module-info requires.

Smoke test
- app/common/src/test/java/stirling/software/common/jpdfium/JPDFiumSmokeTest.java
  Opens the existing common test resource example.pdf via PdfDocument.open
  and asserts a positive page count. Confirms the dep resolves, the right
  natives jar lands on the classpath, the bridge + PDFium component libs
  + third-party deps all load, and the FFM bindings parse a real PDF
  end-to-end. Runs as part of :common:test on each platform's CI runner.
2026-05-18 18:31:37 +01:00
6b9567cf38 Split and delete forms (#6277)
Delete orphaned forms when removing pages and maintain forms correctly
when splitting

---------

Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
2026-05-18 11:14:55 +00:00
c731d5fd5d UI redesign staging (#6149)
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2026-05-15 15:36:50 +01:00