Compare commits

...
Author SHA1 Message Date
ConnorYoh cbc8af1951 feat(desktop): custom in-app window title bar on Windows (#7781)
<img width="1750" height="1223" alt="image"
src="https://github.com/user-attachments/assets/c115a30c-be62-4eaa-8de3-26a93a605919"
/>

## Custom windows top bar
* Doesn't work on mac
* doesn't effect web 
* little effort been put into mobile view
2026-09-02 21:31:45 +00:00
James BruntonandEthanHealy01 aca0e40c37 Combine Policies and Pipelines pages (#7681)
# Description of Changes
Combine the Policies and Pipelines pages into one, so we have the new
concept of Policies as Pipelines that always run which the user cannot
disable. What used to be Policies are now referred to as Templates, and
they allow you to create a new Pipeline more easily with the simple UI.

There's followup work to be done here to improve the template UIs
because they've not been touched in a long time, but I've considered
that beyond the scope of this merge. The only real changes I've made to
them in this PR is that they have a toggle for whether they're policies,
they now have a "Customise" button to kick you into the full Pipeline
editor, and I've removed the source selection. Previously, they
supported selecting as many sources as you liked, but that feature never
worked and is incompatible with the backend as it stands now, which only
allows for one source. Because of that, I've made it so that they can
only run in editor unless you open them in the custom pipeline editor,
where you can switch out which source it will use.

There's also another bit of followup to rename and remove all the
previous Policies code. Now that they've been combined into one, we
don't need a lot of the Policies code anymore, but also there's about
300 files in the frontend referencing policies in text/comments which
need to be updated to say pipelines. This is way more work than is
reasonable to do in this PR so I'll just do it in a new PR.

## Limitations
This PR is about the merging of the old Policies and Pipelines and I'm
considering enforcing the new definition of a Policy where it's only
modifiable by admins beyond the scope of this PR.

<img width="756" height="395" alt="image"
src="https://github.com/user-attachments/assets/d31be5ce-f1c9-46b3-8e8d-866e63f89a81"
/>

<img width="1507" height="793" alt="image"
src="https://github.com/user-attachments/assets/9ba8875f-8be5-4881-91cf-40e0bc1076dc"
/>

<img width="1508" height="787" alt="image"
src="https://github.com/user-attachments/assets/3e1da77b-a0c0-4262-aad3-16650098db81"
/>

---------

Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
2026-09-02 16:08:15 +00:00
Reece Browne 1b2a3118a6 Disk-mounted folders on desktop and improved folder management (#7502)
Description of Changes

Adds folder kinds so the file manager can work with real directories on
disk.

Desktop
- New folder is now a menu with two options: "Add local folder" and "New
folder on the server".
- Add local folder opens the native picker and mounts a directory. Files
are listed straight from disk, nothing is copied in.
- Subfolders show inside a mount and open like any folder. New folder
inside a mount creates a real directory on disk.
- Moving, dropping or uploading files into a mount writes them to the
directory. The app copy is only removed after the write succeeds. Name
clashes get a " (2)" suffix.
- Mounted files get thumbnails.
- Adding the same directory twice just returns the existing mount.
- Removing a mount never touches the disk.
- The server option is disabled in local mode with a sign in message.

Web + desktop
- Uploading or dropping files while inside a folder puts them in that
folder instead of Local.
- Files can be dragged onto folders in the grid and the tree to move
them.
- Folders show an origin badge (cloud or local).
- The Local view now means files that are not in any folder.

Follow ups for a future pr
- Mount listing cap: large directories currently show the 500 most
recent files with no notice. Will be removed as part of the
virtualisation/performance PR.
- Folders within folders need to be supported
- Symlinks in mounts: currently not listed. Behaviour to be decided
alongside the wider folder work.
2026-09-02 14:18:57 +00:00
ConnorYoh 3056e5ff44 Reset the PAYG free grant each billing period (#7709)
Needs the schema half: Stirling-Tools/Stirling-PDF-SaaS#327

## Current state

The PAYG free allowance is a one-time lifetime pool.
`pricing_policy.free_tier_units` is copied into
`payg_team_extensions.free_units_remaining` once, at team creation (V14
trigger, updated in V19), and the charge pipeline decrements it until it
reaches zero. Nothing ever puts it back.

## Problem

The product promises a monthly allowance the billing model does not
grant.

- The account-link connect dialog advertises "500 free per month". That
has **merged to main** (#7415), so the claim is live and unhonoured
until this lands.
- The wallet meter already read "Process 500 PDFs free, then $X/PDF",
which reads as an allowance-then-meter model.
- `SignupRequiredBootstrap`'s own doc comment described a "free
500-op/month allowance" while its copy said only "500 free operations".

Three separate comments asserted the opposite in code
(`billing/types.ts`, `WalletSnapshotResponse`, `TeamBillingContext`), so
the two halves of the repo disagreed about what a customer is owed.

## Solution

The grant now recurs each billing period, **for every team**. Paying
does not cost you the allowance: a subscribed team draws its grant first
each period and meters only the excess, which is what the meter's copy
always described. That also matches how the grant already worked at
charge time, where it reduced metered units regardless of subscription.

### The reset is lazy, with no scheduler

`payg_team_extensions` gains `free_units_period_start`: the period
`free_units_remaining` was last written for.

- A stamp older than the current period start, or absent as on every
existing row, means the reset is owed.
`TeamBillingService.remainingForPeriod` projects it to a full grant, so
the entitlement gate and the wallet both show it the instant the period
turns.
- `JobChargeService.consumeFreeGrant` persists it on the next charge,
under the pessimistic row lock that already makes the per-job free/paid
split exact.

One rule, both callers, so display and enforcement cannot drift onto
separate schedules. A team that runs nothing for a month has nothing to
write, and no job is needed to hand out the grant.

### One period definition

"Per period" is `TeamBillingContext.periodStart`: the Stripe
subscription's current period when subscribed, the calendar month
otherwise. It was already the only period notion in the system, so the
grant joined it rather than inventing its own:

- `InstanceEntitlement.periodCapUnits` is enforced over the same window.
- `localUsageService.currentPeriodUnsynced` already buckets a linked
instance's local usage by the `periodStart` it reads from the same
snapshot, and resets its counters on that boundary.

For an un-subscribed team, the only kind the grant gates, that window is
the calendar month, which is what the copy promises.

The period rule stays in Java by choice, not necessity: SQL could reach
the Stripe period through the sync engine, but restating the rule there
would give it a second home to drift from. Hence a nullable column and
no backfill in the migration — NULL already means "stale", so every
existing team reads as owed the current period's grant.

### Refunds

A refund landing after the period turned would have stacked last
period's units on top of the fresh grant.
`JobChargeService.restoreFreeGrant` now clamps the restore to one
period's grant, taking the same row lock, and the bulk-increment
`restoreFreeUnits` query is gone. Removing it also removed a `@Query`
string that no test would have parsed before application startup.

### Copy and comments

Every comment and user-facing string that asserted the lifetime model is
corrected. The strings that changed (code defaults and `en-US` TOML
updated together):

| Key | Now reads |
| --- | --- |
| `portal.billing.walletMeter.title` / `titleWithRate` | "500 free
credits every month, then $X per PDF" |
| `portal.billing.walletMeter.capSuffix` / `barAria` | "of 500 free
credits left this month" / "Free credits remaining" |
| `payg.free.hero.capSuffix` | "of 500 free PDFs left this month" |
| `plan.freeLimit.message` | "...this month. ... It resets next month,
or keep the momentum going now..." |
| `payg.signupRequired.body` | "500 free operations a month" |

Main rewrote these keys to "500 free credits to start" while this branch
was open. The merge keeps main's credits vocabulary and drops "to
start", which asserts the one-time grant this branch removes and which
main's own connect dialog already contradicts.

Also fixed in passing: `testing/compose/payg/saas-seed.sql` still
inserted `free_tier_units_per_cycle`, the pre-V19 column name, so that
INSERT had been failing since the rename.

## How to test

Backend:

```bash
STIRLING_FLAVOR=saas ./gradlew :saas:test spotlessCheck
```

Frontend:

```bash
task frontend:typecheck && task frontend:lint && task frontend:format:check
```

New coverage, 10 tests:

- `TeamBillingServiceMoreTest` — a past-period stamp reads as a fresh
grant, a current stamp reads the stored balance, an unstamped row reads
as a fresh grant, the grant follows the Stripe window rather than the
calendar month, plus the `remainingForPeriod` rule itself including a
future stamp and null/negative balances.
- `JobChargeServiceTest` — the first charge of a new period resets and
re-stamps, an unstamped row resets, a zero-grant policy still advances
the stamp, and a refund crossing a period boundary does not exceed the
grant.

Manually, against a team whose grant is spent: set
`free_units_period_start` back a month (or leave it NULL) and the
wallet, the sidebar meter and the entitlement gate should all show a
full grant before any job runs. The first billable job should then draw
from it and write the reset.

Three tests fail on a local Windows run and pass in CI, on files this
branch does not touch: `workbenchSession.test.ts`,
`notificationActions.test.tsx`, and `:proprietary`
`FolderIdentitiesTest.identityAgreesAcrossASymlinkedAliasOfTheDirectory`.
Nothing to do here — noted so a local run does not look like a
regression.

## Merge order

The migration is additive, and Hibernate `ddl-auto=update` will add the
column in a dev environment, so either order works locally. Beyond that
the schema goes first: Stirling-Tools/Stirling-PDF-SaaS#327 targets `v3`
(staging), so it needs to reach an environment before this lands there.
2026-09-02 13:57:56 +00:00
Anthony Stirling 798ba57f0b Reply to chat in the user's UI language (#7766)
# Description of Changes

Pass a user browser lang ID to engine

<img width="1400" height="900" alt="image"
src="https://github.com/user-attachments/assets/7e8fc5c2-8881-4a74-b718-7f5cd350d457"
/>



---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] Every comment I added says something the code does not
([guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/CODE_COMMENTS.md))
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-09-02 12:35:22 +00:00
Anthony Stirling 42bdce155c Fix mobile scanner upload flow and fit it to one screen (#7684)
file mobile phone scanner UI issues when on http and scaling UI issues
Ensuring that smaller screens dont cut off UI elements 
better handling of batch photos

<img width="2104" height="8800" alt="montage_mobile-scanner"
src="https://github.com/user-attachments/assets/b4dd114b-c54d-4101-8700-7307dbb0eee9"
/>


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-09-02 09:13:31 +00:00
ConnorYoh 2cf355c5cd feat(editor): move admin settings onto TanStack Query (#7437)
# Description of Changes

## The problem

`useAdminSettings` backs all 18 admin config sections. Each section
fetched its own copy of its settings block, held it in hand-rolled
loading/saving state, and refetched manually after every save.

Three consequences:

- **Duplicate fetching.** Four AI tabs all read the `aiEngine` block.
Nothing was shared, so each open refetched it.
- **Duplicated wiring.** All 18 sections carried the same effect to
trigger the fetch, each one depending on a `fetchSettings` callback that
would have refetched on every render had it ever become unstable.
- **Console noise.** The hook made 11 `console.*` calls, four of them
`JSON.stringify(settings, null, 2)` on **every fetch and every save** —
admin configuration serialised into the console of every admin session.

Every save also ended with a hand-written `await fetchSettings()`.
Forget it in a new section and its pending badges silently go stale.

## The fix

The hook uses TanStack Query, keyed on `sectionName`, so sections
reading the same block share one fetch and one cache entry.

The fetch gate moved into the hook. Sections used to write:

```ts
const { settings, fetchSettings } = useAdminSettings({ sectionName: "legal" });

useEffect(() => {
  if (loginEnabled) fetchSettings();
}, [loginEnabled, fetchSettings]);
```

and now write:

```ts
const { settings } = useAdminSettings({
  sectionName: "legal",
  enabled: loginEnabled,
});
```

Saving is a mutation that invalidates the section on success, so the
refetch is structural rather than something each section remembers.

The delta computation and the save transformer are unchanged — that is
domain logic, not fetching. `settings` is still an editable draft seeded
from the server response, so forms behave exactly as before.

## Why it is better

Measured against the previous implementation across identical scenarios.
`commits` counts committed renders.

| Scenario | Before | After |
|---|---|---|
| Open one section | 2 commits, 1 request | 2 commits, 1 request |
| Browse the four AI tabs | 8 commits, 4 requests | **5 commits, 1
request** |
| Edit and save | 4 commits, 2 requests | 4 commits, 2 requests |

Committed renders are equal or better everywhere; browsing the AI tabs
costs a quarter of the requests.

The diff reads +449 / −303, but that includes a test file for a hook
that had no tests:

| | Added | Removed | Net |
|---|---|---|---|
| Production code (21 files) | 154 | 303 | **−149** |
| Tests (1 file) | 295 | 0 | +295 |

The 18 section files account for −133 of that: each drops an effect, a
destructure and usually an import, and gains one `enabled:` line. The
hook itself goes from 234 to 180 lines. `console.*` calls go from 11 to
0.

## Caching

Settings inherit the client's 30s stale window rather than refetching on
every mount, which is where the request saving comes from.

Nothing inside a cached block is server-observed — the only live reads
in these sections, `/api/v1/ai/health` and the tessdata language list,
are separate calls outside this query. A block therefore only changes
when another admin writes it.

Two things bound the staleness:

- Sections already held a single snapshot for as long as the modal
stayed open, with no refetch on focus. 30s is shorter than that window,
not longer.
- `computeDelta` only emits fields whose draft differs from the baseline
it was seeded from, so a stale baseline cannot produce a collateral
write. The only race is two admins editing the same field, which is
unchanged. Saving invalidates, so acting refreshes to current values.

The blocks where a stale read would matter most — `security`, `premium`,
`database` — are set once at deployment and effectively never edited
concurrently. The block with the most cache reuse, `aiEngine`, is the
least consequential.

**Convention:** config blocks cache; observed state does not. A section
that displays live server state inside its settings block should
override `staleTime` locally.

## Testing

14 tests, covering the shared fetch, cache reuse across tab reopens, key
separation between blocks, the `enabled` gate, delta-only saves, the
empty-delta short circuit, post-save invalidation, pending-value
display, and draft reseeding.

Each was checked by breaking the implementation and confirming the suite
fails: per-consumer query keys, sending the whole draft instead of the
delta, dropping the post-save invalidate, reporting loaded while
disabled, skipping the empty-delta short circuit, and reverting the
stale window to zero.

`task frontend:check` green. Two unrelated tests fail on this branch —
`workbenchSession.test.ts` and `notificationActions.test.tsx` — and fail
identically on `main`.

## Follow-ups

The sections that fetch through services rather than this hook — Teams,
TeamDetails, People, roughly 2,600 lines — are unchanged. Between them
they share two reads (`getTeams` and `getUsers`, both used by all three)
and carry ten distinct write operations, with no test coverage today.

---

## Primer: mutations

`useQuery` is for reads. It caches, dedupes, and re-renders when data
arrives. `useMutation` is for writes, where none of that applies — a
write happens once, when the user asks.

```ts
const save = useMutation({
  mutationFn: (body) => putAdminSection("legal", body),
  onSuccess: () => queryClient.invalidateQueries({ queryKey }),
});

save.mutate(body);            // fire and forget
await save.mutateAsync(body); // or await it
save.isPending;               // disable the button
save.error;                   // show the failure
```

`isPending` and `error` replace the `useState` flag and
`try/catch/finally` you would otherwise write around every save.

After a write the cache holds stale data. Two ways to fix it:

| | What it does | Use when |
|---|---|---|
| `invalidateQueries` | Marks the data stale so it refetches | The
server may transform, queue or reject part of what you sent |
| `setQueryData` | Writes your value into the cache, no request | The
response tells you exactly what the server now holds |

**Invalidate by default. Use `setQueryData` only when the response is
authoritative.**

This hook has to invalidate: the server can queue a settings change
rather than applying it, returning it in a `_pending` block that the
form renders as a badge. Writing the local draft into the cache would
show a queued change as applied.

Most mutations are not like that. A "rename a team" write, where the
response is the new team, is a `setQueryData` case.

One gotcha: `mutate` does not throw, `mutateAsync` does. An awaited
`mutateAsync` without a `try/catch` is an unhandled rejection.
2026-09-01 21:58:50 +00:00
Anthony Stirling c57a2a45de Add v2 client-side PDF text editor (#6500)
# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-09-01 20:55:59 +01:00
ConnorYoh d30faf246b fix(billing): the paid tier is Team, and it is not unlimited users (#7730)
Copy only. No behaviour, no lookup keys, no licence semantics, no
backend.

## Current state

Every surface that sells the paid self-hosted tier offers **"unlimited
seats"** for **"$99/server/mo"**, and the portal's free plan badges
**"Unlimited users"** and **"SSO included"** as free-tier facts.

## Problem

Both claims are now enforceably false.
[#7492](https://github.com/Stirling-Tools/Stirling-PDF/pull/7492) makes
the licence carry a real user cap, and
[Stirling-PDF-SaaS#325](https://github.com/Stirling-Tools/Stirling-PDF-SaaS/pull/325)
sells capacity in blocks of 100 users. An admin reading "unlimited
seats" and then hitting a 409 at the invite screen is the worst version
of this.

The demo has already dropped both claims; ours were the last ones
standing.

## Solution

| Surface | Was | Now |
|---|---|---|
| Onboarding licence slide | "Stirling Server plan, **unlimited seats**
… $99/server/mo" | "Stirling Team plan, **100 users** … $99/mo" |
| Plan comparison table | `unlimitedUsers` = "Unlimited users" |
`usersIncluded` = "100 users included" |
| Plan card highlights | "Unlimited users" | "100 users included" |
| Static plan section | `name: "Server"`, `maxUsers: "Unlimited users"`
| `plan.team.name`, `plan.team.maxUsers` |
| Upgrade banner | "Upgrade to Server Plan" / "unlimited users" |
"Upgrade to the Team plan" / "100 users, SSO" |
| Portal free plan | "Editor" + "SSO included" + "Unlimited users" |
"Editor" + "Every PDF tool" + "Web, desktop & self-hosted" |

The i18n keys are **renamed** (`unlimitedUsers` to `usersIncluded`)
rather than just revalued, so the key name cannot outlive the claim.

Also drops "per server" from `plan.licenseWarning` — we price a block of
100 users and count the provisioned roster, never nodes. And deletes the
orphaned `[settings.planBilling.tier]` block: zero source references,
and it described a retired model (50 credits/mo free, 500 included plus
overage billing).

## Deliberately unchanged

**"Processor" stays the name of the product surface.** The demo names
each plan for its price tier (Editor = $0, Team = $99/mo, Credits = 1¢
each) while keeping Processor as the surface a plan unlocks. Renaming
the surface here would conflate the two, so the plan-name split is left
for the explicit plan catalogue. The free plan also gains no "500 free
credits monthly" badge yet: that is true in the demo but not in our
backend, which still grants a one-time lifetime pool.

## How to test

Self-hosted, as an admin over the free user limit: Settings → Plan
should offer the Team plan at "100 users included", and the onboarding
licence slide should no longer promise unlimited seats. On the portal
billing page, the free plan should read "Free" with no SSO or
unlimited-users badge.

Green locally: 4/4 i18n audits (missing, unused, structure,
translation), 876 tests across 110 files, oxlint, prettier, and all four
typecheck variants (core, proprietary, saas, portal).
2026-09-01 19:09:57 +00:00
EthanHealy01 f6661a8f87 Failure action slots, resolve transition, and the bell that renders them (Review Flow PR 5a) (#7761)
Review Flow PR 5a — the first half of #7479, which stays open for
reference until both halves land. This PR is the ranking and the
bookkeeping; #7762 adds the retry handlers. Merging both reproduces
#7479's diff byte-for-byte.

## What's added

**The action slot model (backend).** `FailureActionSlot` ranks each of a
kind's offers as its `RESOLUTION`, `SECONDARY` or `OVERFLOW`.
`FailureKind` now declares placement per offer — the password-protected
kind names `DECRYPT_AND_RETRY` as its resolution, `UNKNOWN` leads with a
plain `RETRY` — and `FailureActionId` gains those two ids. The
declarations are data; their client handlers arrive in the follow-up, so
this build withholds them with a reason rather than rendering unwired
buttons (the same forward-compatibility #7478 relied on).

**A resolve transition.** `POST /api/v1/notifications/{id}/resolved`
lets a client report a failure fixed. `NotificationSource.parse` turns a
qualified notification id back into the source that owns it, and
`FileRunEventService` folds the resolution into the incident rather than
deleting it.

**`viewerReviewsTeam` on the list response.** A member sees only rows
whose document this browser holds — they can neither open nor fix
anything else — while a team reviewer keeps every row.

**The bell renders the ranking** (`promoteActions`): one primary button,
at most one secondary, the rest in an overflow menu beside **Copy log**.
The row's body is the kind's own sentence; the raw failure message moves
into the menu.

**Read state is a timestamp, not a row id.** `readThroughAt` replaces
`lastSeenId`: when a resolved or dismissed row leaves the list, the rows
below it stay read instead of re-lighting the badge.

## How to test

Needs a proprietary or SaaS build with login enabled (`task dev:all`,
sign in).

1. **Create a failure.** Add a password-protected PDF to the editor and
choose **Skip for now**; the upload's policy run fails on it.
2. **Open the bell.** The row reads the kind's sentence, not a stack
trace. Its primary button is **View file** — the server offers Decrypt
and retry as the resolution, but this build withholds it (handler lands
in the follow-up), so the best renderable offer is promoted instead.
3. **Open the row's ⋯ menu.** View in processor and Dismiss sit there,
along with **Copy log**, which copies the raw message.
4. **Check the read marker survives a departure.** With two failures,
open the bell (badge clears), dismiss the newer row, and refresh: the
badge stays dark. On main, the marker held the departed row's id and the
older row re-read as unread.
5. **Member visibility.** As a plain member, a failure recorded from
another browser does not appear in the bell; as a team reviewer it does.
6. **Resolve endpoint.** `POST
/api/v1/notifications/failure-{eventId}/resolved` as the owner removes
the row on the next poll; `NotificationResolveTest` pins refusal for a
non-owner, an unknown id, and a foreign prefix.

## Migration

None.
2026-09-01 13:25:19 +00:00
Anthony StirlingandJames Brunton ceeec53df4 Let a pipeline run on the editor, on upload or export (#7581)
Redesigns the policies system so that the backend has an understanding
of policies running over the Editor. The Editor is not set up as a
source for the backend because the backend can't actively get files from
it, they come in via the frontend sending them to the backend, so
instead pipelines have a specific editor key in them to encode whether
the pipeline is triggered on file upload/export in the editor.

Also make a big effort in the frontend code towards genericising policy
running. Previously, there was specific support in the main policy
executor for each policy that it had to run, which was not going to be
appropriate long-term, especially when users can run any pipeline in the
editor. There's more work needed here for me to really be happy with it
but this PR is plenty large on its own and moves it in the right
direction.

All of the above was required to allow arbitrary user pipelines to run
in the editor. This PR makes it so that the user can select Editor as a
source in the pipeline creator, along with whether it should run on
upload or export.

<img width="1437" height="506" alt="image"
src="https://github.com/user-attachments/assets/b2d176a1-185c-480b-9916-abdd1447d8e1"
/>

---------

Co-authored-by: James Brunton <james@stirlingpdf.com>
2026-09-01 13:12:06 +00:00
ConnorYoh 4ef2e3811c ci(preview): give PR previews the Stirling account config they need to link (#7728)
Add CI steps to enable PR deploy servers to link to prod saas. This will
allow pr testing of payment flows, usage of real credits etc
2026-09-01 12:35:57 +00:00
ConnorYoh 31d52d4c32 Connect flow for self-hosted account linking, and the triggers that drive it (#7415)
Replaces the bare account-link login box with a guided Connect flow, and
wires up the triggers that actually put it in front of someone.
## Top bar 
<img width="1580" height="422" alt="image"
src="https://github.com/user-attachments/assets/719e12fc-121a-4caa-bc72-124c5167b011"
/>

## The modal

Three steps on the portal's own `FlowModal` + `StepModalHeader`, the
shells procurement and prepay already wear:

1. **What you unlock** — six benefits as a plain list.
<img width="817" height="503" alt="image"
src="https://github.com/user-attachments/assets/4644ddd2-6181-44e1-9be9-7a961972195d"
/>

2. **Sign in** — the existing `SupabaseLoginForm`, reseated.
<img width="880" height="930" alt="image"
src="https://github.com/user-attachments/assets/fc66cbbb-9f98-40a4-9daa-4f2447713f39"
/>

3. **Connected** — confirms, then deep links into Users, Pipelines and
Policies.
<img width="876" height="752" alt="image"
src="https://github.com/user-attachments/assets/28358e4d-a44f-4118-a8ae-8275984ebd00"
/>


Re-auth stays a single step with no pitch and no success screen.

## The triggers

**`LinkGate` stops being dead code.** It was built as the drop-anywhere
"link to unlock" wrapper and was imported by nothing. It is now a
blocking empty state that replaces the feature it guards, wired into
Pipelines, Policies, Users, Sources and Integrations.

**Scoped to creating and editing, never viewing.** Existing pipelines,
policies, sources and connections keep listing and running, so upgrading
an unlinked instance cannot take away something that already works. The
clicks that would open a builder or a create modal ask for the
connection first, which is the moment an admin has already declared
intent.

## Capability signal

`accountLinkAvailable` on `/api/v1/config/app-config`. Gating needs two
facts: whether the instance is linked (`LinkContext`) and whether it
*could* be (this flag). The account-link endpoints 404 when the feature
flag is off, which the client cannot distinguish from "not linked yet" —
so gating on link state alone would lock all five views on every default
install with no way out. `useConnectGate` holds that decision in one
place and shares the app-config query key, so it costs no extra request.

Read from the environment rather than `AccountLinkProperties` because
`:core` cannot depend on `:proprietary`.
2026-09-01 10:39:57 +00:00
ConnorYoh d55d8acbfa fix(portal): keep the processor's cache across a trip to the editor (#7729)
# Description of Changes

## The problem

The portal's query client was created per mount:

```ts
const [queryClient] = useState(createPortalQueryClient);
```

The portal is a route (`/processor/*`, a lazy element), and the switch
to the editor is a client-side `navigate()`. So leaving the processor
unmounts `PortalApp`, the client goes with the component, and the cache
goes with the client. Coming back refetches everything, whether or not
anything changed: four requests for the Users page alone (roster,
grants, teams, auth config), and 21 `useQuery` sites across the portal.

The editor's client sits above the router in `AppProviders` and survives
the same trip. The round trip only ever cost in one direction.

## The fix

The module already kept the instance in a module-level slot so
`tryGetPortalQueryClient()` could find it. It just replaced it on every
mount instead of reusing it, so the change is to create it lazily and
hand out the same one:

```ts
export function getPortalQueryClient(): QueryClient {
  current ??= new QueryClient({ defaultOptions: { queries: baseQueryOptions } });
  return current;
}
```

Still a separate instance from the editor's. The two namespace their
keys apart (`["portal", ...]` against `["editor", ...]`) and invalidate
independently, which this does not change.

## What this does not do

`gcTime` is 5 minutes, from the shared `baseQueryOptions`. An entry with
no observer is still collected on that timer, so this warms a quick trip
to the editor and back, not a return after a long editing session.
Raising the portal's `gcTime` is a separate decision and is not made
here.

## Why it is safe

**Signing out.** A cache that outlives a mount must not outlive a
session, because the portal's holds the admin roster, emails and roles.
Logout goes through `window.location.assign`, a full page load, so the
whole JS context is discarded and no cache can survive it. Nothing in
the codebase calls `queryClient.clear()` on sign-out, and nothing needs
to. If logout ever becomes a client-side navigation, this needs an
explicit reset, and `resetPortalQueryClient()` is the hook for it.

**The one caller of the null check.** `resolveTeam` in
`saas/portal/usersBackend.ts` uses `tryGetPortalQueryClient()` and falls
back to a direct fetch when there is no client, which its comment
describes as the unit-test path; the cache path is preferred because it
honours both `staleTime` and invalidation. A longer-lived client means
the preferred path is taken more often, not less.

## Testing

Three tests in `queryClient.test.tsx`, and the first two fail if the
client goes back to being created per call:

| | |
|---|---|
| A remount is served from cache rather than refetching | the behaviour
this changes |
| Every caller gets the same instance | the mechanism |
| No client is reported until the portal first mounts | the contract
`resolveTeam` reads |

The three existing portal caching suites called the factory expecting a
fresh client per case. They now call `resetPortalQueryClient()` in a
`beforeEach`, which is what keeps `sharing.test.tsx`'s "a later screen
refetches nothing" case honest rather than passing on a leaked cache.

`task frontend:check` passes typecheck, lint and oxfmt, and 2402 of 2404
editor tests. The two failures, `workbenchSession.test.ts` and
`notificationActions.test.tsx`, are untouched here and fail the same way
on `main`.
2026-09-01 08:56:35 +00:00
stirlingbot[bot] af97e1b27b Update Backend 3rd Party Licenses (#7713)
Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
2026-08-31 12:45:42 +01:00
dependabot[bot] 01c908e95d build(deps-dev): bump openai from 2.53.0 to 3.3.1 in /engine (#7700)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 11:37:12 +01:00
dependabot[bot] 0920ea9493 build(deps): bump go-task/setup-task from 2.1.0 to 2.2.0 (#7532)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 10:17:05 +01:00
dependabot[bot] 96207a7304 build(deps): bump @tanstack/react-query from 5.101.4 to 5.102.0 in /frontend in the tanstack group across 1 directory (#7749)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 10:15:11 +01:00
dependabot[bot] d93049db9f build(deps): bump log from 0.4.33 to 0.4.34 in /frontend/editor/src-tauri (#7747)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 10:14:56 +01:00
dependabot[bot] 54e839ae65 build(deps-dev): bump reportlab from 5.0.0 to 5.0.1 in /engine (#7699)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 10:14:31 +01:00
dependabot[bot] 3aca7a26f6 build(deps-dev): bump python-dotenv from 1.2.2 to 1.2.3 in /engine (#7702)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 10:14:19 +01:00
dependabot[bot] 5fe7df3933 build(deps): bump jackson2Version from 2.22.1 to 2.22.2 (#7703)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 10:14:04 +01:00
dependabot[bot] b1e857fd01 build(deps): bump com.tngtech.archunit:archunit-junit5 from 1.4.2 to 1.5.0 (#7704)
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-31 10:13:47 +01:00
dependabot[bot] b92f88361e build(deps): bump docker/setup-buildx-action from 4.2.0 to 4.3.0 (#7711)
Bumps
[docker/setup-buildx-action](https://github.com/docker/setup-buildx-action)
from 4.2.0 to 4.3.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/docker/setup-buildx-action/releases">docker/setup-buildx-action's
releases</a>.</em></p>
<blockquote>
<h2>v4.3.0</h2>
<ul>
<li>Bump <code>@​docker/actions-toolkit</code> from 0.92.0 to 0.95.0 in
<a
href="https://redirect.github.com/docker/setup-buildx-action/pull/595">docker/setup-buildx-action#595</a></li>
<li>Bump brace-expansion from 1.1.13 to 1.1.18 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/600">docker/setup-buildx-action#600</a></li>
<li>Bump js-yaml from 5.2.0 to 5.3.0 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/585">docker/setup-buildx-action#585</a></li>
<li>Bump postcss from 8.5.10 to 8.5.25 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/598">docker/setup-buildx-action#598</a></li>
<li>Bump undici from 6.27.0 to 6.28.0 in <a
href="https://redirect.github.com/docker/setup-buildx-action/pull/601">docker/setup-buildx-action#601</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/docker/setup-buildx-action/compare/v4.2.0...v4.3.0">https://github.com/docker/setup-buildx-action/compare/v4.2.0...v4.3.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/37fe631027851001ddb9b187196cc803df7f5f0e"><code>37fe631</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/595">#595</a>
from docker/dependabot/npm_and_yarn/docker/actions-to...</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/b5c4f91922681cc7c58d15ab7838986951f09d19"><code>b5c4f91</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/3e93b637c6430ba8fa896fad44d3aa6821899d63"><code>3e93b63</code></a>
build(deps): bump <code>@​docker/actions-toolkit</code> from 0.92.0 to
0.95.0</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/e527031b32c86649307d5d492506855f90470604"><code>e527031</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/600">#600</a>
from docker/dependabot/npm_and_yarn/brace-expansion-1...</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/c68814b33cb66f1f7538e546190d410ae557a640"><code>c68814b</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/3f891b01bd5012a434f582800366972569aa1886"><code>3f891b0</code></a>
build(deps): bump brace-expansion from 1.1.13 to 1.1.18</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/787db26fcde8ddcabd49a81472318028f7113962"><code>787db26</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/585">#585</a>
from docker/dependabot/npm_and_yarn/js-yaml-5.2.1</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/f7793687c711790ca336bd4934f1b1bf5f778e17"><code>f779368</code></a>
[dependabot skip] chore: update generated content</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/7d5e60413489a33d28077e11d71c668580cfaf8d"><code>7d5e604</code></a>
build(deps): bump js-yaml from 5.2.0 to 5.3.0</li>
<li><a
href="https://github.com/docker/setup-buildx-action/commit/292c2fb3837a12d3ac2d1e47bbc5c00712bad939"><code>292c2fb</code></a>
Merge pull request <a
href="https://redirect.github.com/docker/setup-buildx-action/issues/590">#590</a>
from docker/dependabot/github_actions/actions/setup-n...</li>
<li>Additional commits viewable in <a
href="https://github.com/docker/setup-buildx-action/compare/bb05f3f5519dd87d3ba754cc423b652a5edd6d2c...37fe631027851001ddb9b187196cc803df7f5f0e">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-08-31 07:14:30 +00:00
dependabot[bot] f6124223e4 build(deps): bump github/codeql-action/upload-sarif from 4.37.7 to 4.37.8 (#7750)
Bumps
[github/codeql-action/upload-sarif](https://github.com/github/codeql-action)
from 4.37.7 to 4.37.8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/upload-sarif's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.8</h2>
<p>No user facing changes.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/upload-sarif's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.9 - 26 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.4">2.26.4</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4106">#4106</a></li>
</ul>
<h2>4.37.8 - 21 Aug 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28"><code>db488dd</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4102">#4102</a>
from github/update-v4.37.8-9ee088e13</li>
<li><a
href="https://github.com/github/codeql-action/commit/1845f5ba8b4057590f49ee8e246c95ef2ba4b53f"><code>1845f5b</code></a>
Update changelog for v4.37.8</li>
<li><a
href="https://github.com/github/codeql-action/commit/9ee088e13615f8d1eaef4766f9dde95d3356a8f6"><code>9ee088e</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4080">#4080</a>
from github/henrymercer/studious-giggle</li>
<li><a
href="https://github.com/github/codeql-action/commit/1aef003397c876c0ab5bd118e1b1f34c175622e9"><code>1aef003</code></a>
Address review feedback on overlay disk flags</li>
<li><a
href="https://github.com/github/codeql-action/commit/508b83bc415e8df76ce8ea08c0cf42c2529ebc63"><code>508b83b</code></a>
Merge main into overlay minimum disk feature branch</li>
<li><a
href="https://github.com/github/codeql-action/commit/d97b3428e8eebbb1810cf454d6397886d136b4ba"><code>d97b342</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4098">#4098</a>
from github/mbg/permission-error-as-configuration-error</li>
<li><a
href="https://github.com/github/codeql-action/commit/47fa6222231b12097f83215dd7a6b4a0915841fd"><code>47fa622</code></a>
Make <code>EACCES</code> a <code>ConfigurationError</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/45693cc6882bb175b58a06818c91876e201037c7"><code>45693cc</code></a>
Refactor <code>ENOSPC</code> check into
<code>isDiskConfigurationError</code> function</li>
<li><a
href="https://github.com/github/codeql-action/commit/c2fd8f54d19fa46c94ed79cb92e6dd6606d61762"><code>c2fd8f5</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4081">#4081</a>
from github/mario-campos/version-cache-to-disk</li>
<li><a
href="https://github.com/github/codeql-action/commit/c56f48e9bd458a387eb68a68534459e503e56b17"><code>c56f48e</code></a>
Log unexpected conditions during caching CLI output</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd...db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/upload-sarif&package-manager=github_actions&previous-version=4.37.7&new-version=4.37.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-31 07:14:20 +00:00
dependabot[bot] 3235645203 build(deps): bump step-security/harden-runner from 2.20.0 to 2.21.0 (#7746)
Bumps
[step-security/harden-runner](https://github.com/step-security/harden-runner)
from 2.20.0 to 2.21.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/step-security/harden-runner/releases">step-security/harden-runner's
releases</a>.</em></p>
<blockquote>
<h2>v2.21.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Support for denied endpoints in block mode. This is included in the
enterprise tier. Customers can deny outbound calls, for example, to
public package registries.</li>
<li>Improved Support for AWS CodeBuild GitHub Actions Runners.</li>
<li>Bug fixes.</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/step-security/harden-runner/compare/v2.20.1...v2.21.0">https://github.com/step-security/harden-runner/compare/v2.20.1...v2.21.0</a></p>
<h2>v2.20.1</h2>
<h2>What's Changed</h2>
<ul>
<li>AWS CodeBuild-hosted runner support</li>
<li>Implicitly allow single-labeled (internal) domains in
block-mode</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/step-security/harden-runner/compare/v2.20.0...v2.20.1">https://github.com/step-security/harden-runner/compare/v2.20.0...v2.20.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/step-security/harden-runner/commit/05e31511f85b41b11d1cf0ef85d0992719546e2c"><code>05e3151</code></a>
Merge pull request <a
href="https://redirect.github.com/step-security/harden-runner/issues/684">#684</a>
from step-security/rc-42</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/0f37afa338f57c61ee3dfc274daca8834963d83e"><code>0f37afa</code></a>
fix: ignore denied-endpoints on non-enterprise tier</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/93b58ee491c5b6cf3a5324966fca2908f8d447f3"><code>93b58ee</code></a>
fix: resolve cache host read-first and never downgrade egress
policy</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/e7399dd3e93d6c159d314af54b4704bc48abf6bc"><code>e7399dd</code></a>
fix: align deny-list mode detection with agent and log when both
endpoint inp...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/c16689f716a10cdfd9cfe22e63938b8c6c0657de"><code>c16689f</code></a>
test: add denied_endpoints to Configuration fixtures and cover deny-list
merge</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/40b99cf0c7161e4dcdc6c5508927188b65028df9"><code>40b99cf</code></a>
Merge pull request <a
href="https://redirect.github.com/step-security/harden-runner/issues/682">#682</a>
from rohan-stepsecurity/rp/feat/codebuild-self-v2</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/fedec027a205365a7d64001a81931e4c36a1af6e"><code>fedec02</code></a>
Merge branch 'rc-42' into rp/feat/codebuild-self-v2</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/5361fb178b926b2be6df52e11ee257823821567b"><code>5361fb1</code></a>
feat: add build artifacts</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/286474fffe0b8fe7c9db855f132d04a9b48ab564"><code>286474f</code></a>
feat: Support Bravo agent install on CodeBuild runners</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/051ec05283d064bd82f41279db4f70f0717bf778"><code>051ec05</code></a>
Merge pull request <a
href="https://redirect.github.com/step-security/harden-runner/issues/683">#683</a>
from h0x0er/jatin/deny-list</li>
<li>Additional commits viewable in <a
href="https://github.com/step-security/harden-runner/compare/v2.20.0...05e31511f85b41b11d1cf0ef85d0992719546e2c">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=step-security/harden-runner&package-manager=github_actions&previous-version=2.20.0&new-version=2.21.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-31 07:13:30 +00:00
dependabot[bot] 1f4cc2612d build(deps): bump the eclipse-temurin group across 3 directories with 1 update (#7740)
> [!WARNING]
> Cooldown could not be applied because no publication date was
available from the registry.
>

Bumps the eclipse-temurin group with 1 update in the /docker/backend
directory: eclipse-temurin.
Bumps the eclipse-temurin group with 1 update in the /docker/base
directory: eclipse-temurin.
Bumps the eclipse-temurin group with 1 update in the /docker/embedded
directory: eclipse-temurin.

Updates `eclipse-temurin` from `fbcf915` to `b4c93a5`

Updates `eclipse-temurin` from `fbcf915` to `b4c93a5`

Updates `eclipse-temurin` from `fbcf915` to `b4c93a5`


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-31 07:12:18 +00:00
dependabot[bot] e539eb1ab1 build(deps): bump the ubuntu group across 2 directories with 1 update (#7698)
> [!WARNING]
> Cooldown could not be applied because no publication date was
available from the registry.
>

Bumps the ubuntu group with 1 update in the /docker/base directory:
ubuntu.
Bumps the ubuntu group with 1 update in the /docker/unoserver directory:
ubuntu.

Updates `ubuntu` from `561618e` to `33ceb71`

Updates `ubuntu` from `561618e` to `33ceb71`

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-30 10:13:31 +00:00
stirlingbot[bot] 1bb6961414 Update Frontend 3rd Party Licenses (#7738)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-30 10:12:56 +00:00
briosandAnthony Stirling 34694c6f5e refactor(api): standardize syntax and simplify type declarations across security, workflow, and controller modules (#7127)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-29 23:19:26 +01:00
briosandAnthony Stirling 0b7b4e02c2 chore(crop): Remove invalid crop area message and related validation logic (#7160)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-29 23:11:41 +01:00
briosandAnthony Stirling 74be5bf0ad fix(forms): Fix checkbox export values and wide dropdown options (#7288)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-29 23:04:35 +01:00
briosandAnthony Stirling 8c00fffe18 refactor(api): replace com.fasterxml.jackson with tools.jackson (Jackson 2 to Jackson 3 namespace.) (#7444)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-29 23:04:06 +01:00
briosandAnthony Stirling c5da4177c4 refactor(ui): improve button layouts and modal sizing of formFill (#7509)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-29 22:22:34 +01:00
brios ddc0ac0ced fix(api): fix endpoint set concurrency and in-memory leaks (#7505) 2026-08-29 22:19:29 +01:00
dependabot[bot] 8bdd00b2fa build(deps): bump @tanstack/react-virtual from 3.13.23 to 3.14.10 in /frontend in the tanstack group across 1 directory (#7605)
Bumps the tanstack group with 1 update in the /frontend directory:
[@tanstack/react-virtual](https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual).

Updates `@tanstack/react-virtual` from 3.13.23 to 3.14.10
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/TanStack/virtual/releases">@​tanstack/react-virtual's
releases</a>.</em></p>
<blockquote>
<h2><code>@​tanstack/react-virtual</code><a
href="https://github.com/3"><code>@​3</code></a>.14.10</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/a0a411e06f7334a063422de35d59b12b264b3573"><code>a0a411e</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/d2cf98beea1696c7187c06b57c9e724d1957963c"><code>d2cf98b</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.8</li>
</ul>
</li>
</ul>
<h2><code>@​tanstack/react-virtual</code><a
href="https://github.com/3"><code>@​3</code></a>.14.9</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/a5417b4b0d3c82876747bb9635db7239c28d3e44"><code>a5417b4</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.7</li>
</ul>
</li>
</ul>
<h2><code>@​tanstack/react-virtual</code><a
href="https://github.com/3"><code>@​3</code></a>.14.8</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/TanStack/virtual/pull/1237">#1237</a>
<a
href="https://github.com/TanStack/virtual/commit/aa536e7746a88d9f55ca8a4b50d2f548a888fea6"><code>aa536e7</code></a>
- Fix a gap at the top of the list after an end-anchored prepend in
<code>directDomUpdates</code> mode. The prepend grows the total size and
bumps <code>scrollOffset</code> to the new bottom in the same pass, but
the size container's height was written <em>after</em>
<code>_willUpdate</code> synced the scroll position — so the browser
clamped the <code>scrollTop</code> write to the stale (shorter)
<code>scrollHeight</code>, leaving whitespace at the top until the next
scroll. The container is now grown before the scroll sync. Only affected
<code>directDomUpdates</code> mode (React-rendered sizers receive their
height during render).</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/7ae32b55887fd044a48c788546cd940279b338e0"><code>7ae32b5</code></a>]:</p>
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.6</li>
</ul>
</li>
</ul>
<h2><code>@​tanstack/react-virtual</code><a
href="https://github.com/3"><code>@​3</code></a>.14.7</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/1e3b908705e04e45be2615f2277580cb09f5cdef"><code>1e3b908</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/7dcfc07b877479697124157d3124c09537b87a75"><code>7dcfc07</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.5</li>
</ul>
</li>
</ul>
<h2><code>@​tanstack/react-virtual</code><a
href="https://github.com/3"><code>@​3</code></a>.14.6</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/6cbecd887df56faaee3b6a81a1aae8049de0671e"><code>6cbecd8</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/d49cc526fe248be7b5ad97ec6ac814db8271b0d0"><code>d49cc52</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/cf7834daade953fea5dfd2ab5685c15771ca300a"><code>cf7834d</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.4</li>
</ul>
</li>
</ul>
<h2><code>@​tanstack/react-virtual</code><a
href="https://github.com/3"><code>@​3</code></a>.14.5</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/767ead46e4fab761fd6e15bcf281486042723152"><code>767ead4</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/bc8643b7579e10e512654f58269de13d98b48781"><code>bc8643b</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.3</li>
</ul>
</li>
</ul>
<h2><code>@​tanstack/react-virtual</code><a
href="https://github.com/3"><code>@​3</code></a>.14.4</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/b04f9ee48f0812e89156c1dac1fa58277cc32464"><code>b04f9ee</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/37be28427ba52399ce8884e0006933e83f2645e9"><code>37be284</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.2</li>
</ul>
</li>
</ul>
<h2><code>@​tanstack/react-virtual</code><a
href="https://github.com/3"><code>@​3</code></a>.14.3</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/TanStack/virtual/pull/1201">#1201</a>
<a
href="https://github.com/TanStack/virtual/commit/2ba5eb60f108f4ba9b2bd9570bbd41f9ce618438"><code>2ba5eb6</code></a>
- Make <code>directDomUpdates</code> a no-op for direct DOM writes when
<code>containerRef</code> is omitted. Previously the virtualizer still
wrote item positions while never sizing the container (a broken
half-state). Now omitting <code>containerRef</code> skips all direct
writes while still skipping re-renders, letting consumers own the DOM
updates themselves (e.g. in <code>onChange</code>).</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/ef69ea31738caa2819142e922efa03d3c408e25c"><code>ef69ea3</code></a>]:</p>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/TanStack/virtual/blob/main/packages/react-virtual/CHANGELOG.md">@​tanstack/react-virtual's
changelog</a>.</em></p>
<blockquote>
<h2>3.14.10</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/a0a411e06f7334a063422de35d59b12b264b3573"><code>a0a411e</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/d2cf98beea1696c7187c06b57c9e724d1957963c"><code>d2cf98b</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.8</li>
</ul>
</li>
</ul>
<h2>3.14.9</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/a5417b4b0d3c82876747bb9635db7239c28d3e44"><code>a5417b4</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.7</li>
</ul>
</li>
</ul>
<h2>3.14.8</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/TanStack/virtual/pull/1237">#1237</a>
<a
href="https://github.com/TanStack/virtual/commit/aa536e7746a88d9f55ca8a4b50d2f548a888fea6"><code>aa536e7</code></a>
- Fix a gap at the top of the list after an end-anchored prepend in
<code>directDomUpdates</code> mode. The prepend grows the total size and
bumps <code>scrollOffset</code> to the new bottom in the same pass, but
the size container's height was written <em>after</em>
<code>_willUpdate</code> synced the scroll position — so the browser
clamped the <code>scrollTop</code> write to the stale (shorter)
<code>scrollHeight</code>, leaving whitespace at the top until the next
scroll. The container is now grown before the scroll sync. Only affected
<code>directDomUpdates</code> mode (React-rendered sizers receive their
height during render).</p>
</li>
<li>
<p>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/7ae32b55887fd044a48c788546cd940279b338e0"><code>7ae32b5</code></a>]:</p>
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.6</li>
</ul>
</li>
</ul>
<h2>3.14.7</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/1e3b908705e04e45be2615f2277580cb09f5cdef"><code>1e3b908</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/7dcfc07b877479697124157d3124c09537b87a75"><code>7dcfc07</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.5</li>
</ul>
</li>
</ul>
<h2>3.14.6</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/6cbecd887df56faaee3b6a81a1aae8049de0671e"><code>6cbecd8</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/d49cc526fe248be7b5ad97ec6ac814db8271b0d0"><code>d49cc52</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/cf7834daade953fea5dfd2ab5685c15771ca300a"><code>cf7834d</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.4</li>
</ul>
</li>
</ul>
<h2>3.14.5</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/767ead46e4fab761fd6e15bcf281486042723152"><code>767ead4</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/bc8643b7579e10e512654f58269de13d98b48781"><code>bc8643b</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.3</li>
</ul>
</li>
</ul>
<h2>3.14.4</h2>
<h3>Patch Changes</h3>
<ul>
<li>Updated dependencies [<a
href="https://github.com/TanStack/virtual/commit/b04f9ee48f0812e89156c1dac1fa58277cc32464"><code>b04f9ee</code></a>,
<a
href="https://github.com/TanStack/virtual/commit/37be28427ba52399ce8884e0006933e83f2645e9"><code>37be284</code></a>]:
<ul>
<li><code>@​tanstack/virtual-core</code><a
href="https://github.com/3"><code>@​3</code></a>.17.2</li>
</ul>
</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/TanStack/virtual/commit/e9874f033c74afd3251eeb9f3e60b2530cc7ae88"><code>e9874f0</code></a>
ci: Version Packages (<a
href="https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual/issues/1247">#1247</a>)</li>
<li><a
href="https://github.com/TanStack/virtual/commit/b4a76cac25ef7e334c180ceb8c0d859b7c91ab09"><code>b4a76ca</code></a>
fix(marko-virtual): consolidate Marko e2e into one in-package app, fix
test (...</li>
<li><a
href="https://github.com/TanStack/virtual/commit/deca524a9b2ed29a8a23001389580de10f8002db"><code>deca524</code></a>
ci: Version Packages (<a
href="https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual/issues/1240">#1240</a>)</li>
<li><a
href="https://github.com/TanStack/virtual/commit/32b2f2b412739015a47da1463fe2749456cdc4e9"><code>32b2f2b</code></a>
ci: Version Packages (<a
href="https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual/issues/1238">#1238</a>)</li>
<li><a
href="https://github.com/TanStack/virtual/commit/aa536e7746a88d9f55ca8a4b50d2f548a888fea6"><code>aa536e7</code></a>
fix(react-virtual): grow size container before scroll sync on
end-anchored pr...</li>
<li><a
href="https://github.com/TanStack/virtual/commit/87f689a5c67ee1ed8db1e6754021a6b6b41c8550"><code>87f689a</code></a>
ci: Version Packages (<a
href="https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual/issues/1231">#1231</a>)</li>
<li><a
href="https://github.com/TanStack/virtual/commit/ba5c47a93f597f8370bc9e0119d505551c962a09"><code>ba5c47a</code></a>
feat(angular-virtual): add chat example and require Angular 20 (<a
href="https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual/issues/1228">#1228</a>)</li>
<li><a
href="https://github.com/TanStack/virtual/commit/e2cb096862f5b74aa586957eae207b39999cb654"><code>e2cb096</code></a>
ci: Version Packages (<a
href="https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual/issues/1225">#1225</a>)</li>
<li><a
href="https://github.com/TanStack/virtual/commit/d49cc526fe248be7b5ad97ec6ac814db8271b0d0"><code>d49cc52</code></a>
fix(virtual-core): invalidate measurements when gap option changes (<a
href="https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual/issues/1223">#1223</a>)</li>
<li><a
href="https://github.com/TanStack/virtual/commit/151e9f47abd4ef2d3b11936c04be8908e6bd0607"><code>151e9f4</code></a>
ci: Version Packages (<a
href="https://github.com/TanStack/virtual/tree/HEAD/packages/react-virtual/issues/1213">#1213</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/TanStack/virtual/commits/@tanstack/react-virtual@3.14.10/packages/react-virtual">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-08-29 14:50:18 +00:00
dependabot[bot] 3718af45ff build(deps): bump the mui group across 1 directory with 2 updates (#7602)
Bumps the mui group with 1 update in the /frontend directory:
[@mui/icons-material](https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material).

Updates `@mui/icons-material` from 9.2.0 to 9.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mui/material-ui/releases">@​mui/icons-material's
releases</a>.</em></p>
<blockquote>
<h2>v9.3.1</h2>
<p>A big thanks to the 4 contributors who made this release
possible.</p>
<h3><code>@mui/material@9.3.1</code></h3>
<ul>
<li>[transitions] Prevent exit transitions from getting stuck (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48881">#48881</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
</ul>
<h3><code>@mui/codemod@9.3.1</code></h3>
<ul>
<li>Include transforms in published package (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48934">#48934</a>)
<a
href="https://github.com/brijeshb42"><code>@​brijeshb42</code></a></li>
</ul>
<h3>Core</h3>
<ul>
<li>[blog] Clarify early bird renewal discount scope (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48906">#48906</a>)
<a href="https://github.com/DanailH"><code>@​DanailH</code></a></li>
<li>[test][pagination] Add more unit tests (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48927">#48927</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<p>All contributors of this release in alphabetical order: <a
href="https://github.com/brijeshb42"><code>@​brijeshb42</code></a>, <a
href="https://github.com/DanailH"><code>@​DanailH</code></a>, <a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a>,
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></p>
<h2>v9.3.0</h2>
<p>A big thanks to the 18 contributors who made this release possible.
Here are some highlights :</p>
<ul>
<li>️ Keyboard navigation in the <a
href="https://mui.com/material-ui/react-toggle-button/">Toggle Button
Group</a> now follows the roving tabindex pattern.</li>
<li>️ The <a
href="https://mui.com/material-ui/react-autocomplete/">Autocomplete</a>
announces its loading and no options messages through a new
<code>status</code> slot.</li>
</ul>
<h3><code>@mui/material@9.3.0</code></h3>
<ul>
<li>[autocomplete] Wrap the no results and loading messages in an aria
live region (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48690">#48690</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[buttongroup] Respect global disableRipple / disableFocusRipple in
grouped buttons (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48762">#48762</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[checkbox][radio] Respect global disableRipple from MuiButtonBase
defaultProps (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48795">#48795</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[formcontrollabel] Add missing <code>labelPlacementEnd</code> class
(<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48843">#48843</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[listitembutton] Fix typos in component code (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48868">#48868</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[menuitem] Add <code>aria-checked</code> for checkbox and radio menu
items (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48651">#48651</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[modal] Replace custom findIndexOf with findIndex (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48827">#48827</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[modal][dialog] Fix scrollbar compensation in Shadow DOM (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48826">#48826</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[select] Fix endAdornment overlapping the open indicator (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48723">#48723</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[tablepagination] Add focus style to default InputBase used in
Select (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48871">#48871</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[togglebuttongroup] Add roving tabindex keyboard navigation (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48849">#48849</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<h3><code>@mui/system@9.3.0</code></h3>
<ul>
<li>Prevent prototype pollution in cssVarsParser (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48822">#48822</a>)
<a href="https://github.com/Janpot"><code>@​Janpot</code></a></li>
</ul>
<h3><code>@mui/codemod@9.3.0</code></h3>
<ul>
<li>Don't leak state between files in v5.0.0/path-imports (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48797">#48797</a>)
<a
href="https://github.com/manbearwiz"><code>@​manbearwiz</code></a></li>
<li>Remove use of eval() (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48701">#48701</a>)
<a
href="https://github.com/oliviertassinari"><code>@​oliviertassinari</code></a></li>
<li>Transform all style exports in <code>v5.0.0/path-imports</code>
codemod (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48800">#48800</a>)
<a
href="https://github.com/manbearwiz"><code>@​manbearwiz</code></a></li>
</ul>
<h3>Docs</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/mui/material-ui/blob/master/CHANGELOG.md">@​mui/icons-material's
changelog</a>.</em></p>
<blockquote>
<h2>9.3.1</h2>
<!-- raw HTML omitted -->
<p><em>Aug 6, 2026</em></p>
<p>A big thanks to the 4 contributors who made this release
possible.</p>
<h3><code>@mui/material@9.3.1</code></h3>
<ul>
<li>[transitions] Prevent exit transitions from getting stuck (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48881">#48881</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
</ul>
<h3><code>@mui/codemod@9.3.1</code></h3>
<ul>
<li>Include transforms in published package (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48934">#48934</a>)
<a
href="https://github.com/brijeshb42"><code>@​brijeshb42</code></a></li>
</ul>
<h3>Core</h3>
<ul>
<li>[blog] Clarify early bird renewal discount scope (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48906">#48906</a>)
<a href="https://github.com/DanailH"><code>@​DanailH</code></a></li>
<li>[test][pagination] Add more unit tests (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48927">#48927</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<p>All contributors of this release in alphabetical order: <a
href="https://github.com/brijeshb42"><code>@​brijeshb42</code></a>, <a
href="https://github.com/DanailH"><code>@​DanailH</code></a>, <a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a>,
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></p>
<h2>9.3.0</h2>
<!-- raw HTML omitted -->
<p><em>Aug 4, 2026</em></p>
<p>A big thanks to the 18 contributors who made this release possible.
Here are some highlights :</p>
<ul>
<li>️ Keyboard navigation in the <a
href="https://mui.com/material-ui/react-toggle-button/">Toggle Button
Group</a> now follows the roving tabindex pattern.</li>
<li>️ The <a
href="https://mui.com/material-ui/react-autocomplete/">Autocomplete</a>
announces its loading and no options messages through a new
<code>status</code> slot.</li>
</ul>
<h3><code>@mui/material@9.3.0</code></h3>
<ul>
<li>[autocomplete] Wrap the no results and loading messages in an aria
live region (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48690">#48690</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[buttongroup] Respect global disableRipple / disableFocusRipple in
grouped buttons (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48762">#48762</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[checkbox][radio] Respect global disableRipple from MuiButtonBase
defaultProps (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48795">#48795</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[formcontrollabel] Add missing <code>labelPlacementEnd</code> class
(<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48843">#48843</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[listitembutton] Fix typos in component code (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48868">#48868</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[menuitem] Add <code>aria-checked</code> for checkbox and radio menu
items (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48651">#48651</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[modal] Replace custom findIndexOf with findIndex (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48827">#48827</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[modal][dialog] Fix scrollbar compensation in Shadow DOM (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48826">#48826</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[select] Fix endAdornment overlapping the open indicator (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48723">#48723</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[tablepagination] Add focus style to default InputBase used in
Select (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48871">#48871</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[togglebuttongroup] Add roving tabindex keyboard navigation (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48849">#48849</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<h3><code>@mui/system@9.3.0</code></h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/mui/material-ui/commit/5b91ac75008dbd43286a20ef87847042cc7a44ca"><code>5b91ac7</code></a>
[release] v9.3.1 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48935">#48935</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/da37c088786eead9c7ddaffe0798ec692ece0a11"><code>da37c08</code></a>
v9.3.0 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48909">#48909</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/2e38eb7f8f77152f4bb4047169cce332da420cb9"><code>2e38eb7</code></a>
Bump chalk to 6.0.0 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48902">#48902</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/20fe2b6aa86965e3f1e2e5b0a82e2ed38f753ffd"><code>20fe2b6</code></a>
Bump code-infra:devDependencies (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48891">#48891</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/a900cd7d7e66e37248ec0ae8da44d588d0577aa3"><code>a900cd7</code></a>
Bump react monorepo to 19.2.8 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48858">#48858</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/8cbc3ce36fb59cd6f4e3a3e925fb247b6c4b971b"><code>8cbc3ce</code></a>
Bump code-infra:devDependencies (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48830">#48830</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/a5faab53e647b92b5efb8ea26ce1ae758778736e"><code>a5faab5</code></a>
Bump react monorepo (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48770">#48770</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/ca10194ad116e97fa4ecfc95ca09421bbbb6e2a7"><code>ca10194</code></a>
Bump code-infra:devDependencies (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48767">#48767</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/620c9e95e8e57d99d91524f6f60de00d194184f1"><code>620c9e9</code></a>
Bump babel monorepo to ^7.29.7 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-icons-material/issues/48766">#48766</a>)</li>
<li>See full diff in <a
href="https://github.com/mui/material-ui/commits/v9.3.1/packages/mui-icons-material">compare
view</a></li>
</ul>
</details>
<br />

Updates `@mui/material` from 9.2.0 to 9.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/mui/material-ui/releases">@​mui/material's
releases</a>.</em></p>
<blockquote>
<h2>v9.3.1</h2>
<p>A big thanks to the 4 contributors who made this release
possible.</p>
<h3><code>@mui/material@9.3.1</code></h3>
<ul>
<li>[transitions] Prevent exit transitions from getting stuck (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48881">#48881</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
</ul>
<h3><code>@mui/codemod@9.3.1</code></h3>
<ul>
<li>Include transforms in published package (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48934">#48934</a>)
<a
href="https://github.com/brijeshb42"><code>@​brijeshb42</code></a></li>
</ul>
<h3>Core</h3>
<ul>
<li>[blog] Clarify early bird renewal discount scope (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48906">#48906</a>)
<a href="https://github.com/DanailH"><code>@​DanailH</code></a></li>
<li>[test][pagination] Add more unit tests (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48927">#48927</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<p>All contributors of this release in alphabetical order: <a
href="https://github.com/brijeshb42"><code>@​brijeshb42</code></a>, <a
href="https://github.com/DanailH"><code>@​DanailH</code></a>, <a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a>,
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></p>
<h2>v9.3.0</h2>
<p>A big thanks to the 18 contributors who made this release possible.
Here are some highlights :</p>
<ul>
<li>️ Keyboard navigation in the <a
href="https://mui.com/material-ui/react-toggle-button/">Toggle Button
Group</a> now follows the roving tabindex pattern.</li>
<li>️ The <a
href="https://mui.com/material-ui/react-autocomplete/">Autocomplete</a>
announces its loading and no options messages through a new
<code>status</code> slot.</li>
</ul>
<h3><code>@mui/material@9.3.0</code></h3>
<ul>
<li>[autocomplete] Wrap the no results and loading messages in an aria
live region (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48690">#48690</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[buttongroup] Respect global disableRipple / disableFocusRipple in
grouped buttons (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48762">#48762</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[checkbox][radio] Respect global disableRipple from MuiButtonBase
defaultProps (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48795">#48795</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[formcontrollabel] Add missing <code>labelPlacementEnd</code> class
(<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48843">#48843</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[listitembutton] Fix typos in component code (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48868">#48868</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[menuitem] Add <code>aria-checked</code> for checkbox and radio menu
items (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48651">#48651</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[modal] Replace custom findIndexOf with findIndex (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48827">#48827</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[modal][dialog] Fix scrollbar compensation in Shadow DOM (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48826">#48826</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[select] Fix endAdornment overlapping the open indicator (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48723">#48723</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[tablepagination] Add focus style to default InputBase used in
Select (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48871">#48871</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[togglebuttongroup] Add roving tabindex keyboard navigation (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48849">#48849</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<h3><code>@mui/system@9.3.0</code></h3>
<ul>
<li>Prevent prototype pollution in cssVarsParser (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48822">#48822</a>)
<a href="https://github.com/Janpot"><code>@​Janpot</code></a></li>
</ul>
<h3><code>@mui/codemod@9.3.0</code></h3>
<ul>
<li>Don't leak state between files in v5.0.0/path-imports (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48797">#48797</a>)
<a
href="https://github.com/manbearwiz"><code>@​manbearwiz</code></a></li>
<li>Remove use of eval() (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48701">#48701</a>)
<a
href="https://github.com/oliviertassinari"><code>@​oliviertassinari</code></a></li>
<li>Transform all style exports in <code>v5.0.0/path-imports</code>
codemod (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48800">#48800</a>)
<a
href="https://github.com/manbearwiz"><code>@​manbearwiz</code></a></li>
</ul>
<h3>Docs</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/mui/material-ui/blob/master/CHANGELOG.md">@​mui/material's
changelog</a>.</em></p>
<blockquote>
<h2>9.3.1</h2>
<!-- raw HTML omitted -->
<p><em>Aug 6, 2026</em></p>
<p>A big thanks to the 4 contributors who made this release
possible.</p>
<h3><code>@mui/material@9.3.1</code></h3>
<ul>
<li>[transitions] Prevent exit transitions from getting stuck (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48881">#48881</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
</ul>
<h3><code>@mui/codemod@9.3.1</code></h3>
<ul>
<li>Include transforms in published package (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48934">#48934</a>)
<a
href="https://github.com/brijeshb42"><code>@​brijeshb42</code></a></li>
</ul>
<h3>Core</h3>
<ul>
<li>[blog] Clarify early bird renewal discount scope (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48906">#48906</a>)
<a href="https://github.com/DanailH"><code>@​DanailH</code></a></li>
<li>[test][pagination] Add more unit tests (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48927">#48927</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<p>All contributors of this release in alphabetical order: <a
href="https://github.com/brijeshb42"><code>@​brijeshb42</code></a>, <a
href="https://github.com/DanailH"><code>@​DanailH</code></a>, <a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a>,
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></p>
<h2>9.3.0</h2>
<!-- raw HTML omitted -->
<p><em>Aug 4, 2026</em></p>
<p>A big thanks to the 18 contributors who made this release possible.
Here are some highlights :</p>
<ul>
<li>️ Keyboard navigation in the <a
href="https://mui.com/material-ui/react-toggle-button/">Toggle Button
Group</a> now follows the roving tabindex pattern.</li>
<li>️ The <a
href="https://mui.com/material-ui/react-autocomplete/">Autocomplete</a>
announces its loading and no options messages through a new
<code>status</code> slot.</li>
</ul>
<h3><code>@mui/material@9.3.0</code></h3>
<ul>
<li>[autocomplete] Wrap the no results and loading messages in an aria
live region (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48690">#48690</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[buttongroup] Respect global disableRipple / disableFocusRipple in
grouped buttons (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48762">#48762</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[checkbox][radio] Respect global disableRipple from MuiButtonBase
defaultProps (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48795">#48795</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[formcontrollabel] Add missing <code>labelPlacementEnd</code> class
(<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48843">#48843</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[listitembutton] Fix typos in component code (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48868">#48868</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[menuitem] Add <code>aria-checked</code> for checkbox and radio menu
items (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48651">#48651</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[modal] Replace custom findIndexOf with findIndex (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48827">#48827</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[modal][dialog] Fix scrollbar compensation in Shadow DOM (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48826">#48826</a>)
<a
href="https://github.com/ZeeshanTamboli"><code>@​ZeeshanTamboli</code></a></li>
<li>[select] Fix endAdornment overlapping the open indicator (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48723">#48723</a>)
<a
href="https://github.com/siriwatknp"><code>@​siriwatknp</code></a></li>
<li>[tablepagination] Add focus style to default InputBase used in
Select (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48871">#48871</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
<li>[togglebuttongroup] Add roving tabindex keyboard navigation (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48849">#48849</a>)
<a
href="https://github.com/silviuaavram"><code>@​silviuaavram</code></a></li>
</ul>
<h3><code>@mui/system@9.3.0</code></h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/mui/material-ui/commit/5b91ac75008dbd43286a20ef87847042cc7a44ca"><code>5b91ac7</code></a>
[release] v9.3.1 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48935">#48935</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/a13824f0bae9214534d2a35740802d15d711a373"><code>a13824f</code></a>
[transitions] Prevent exit transitions from getting stuck (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48881">#48881</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/54e1993311bbc3e61fe1684dfcf3fed784bfdcd8"><code>54e1993</code></a>
[test][pagination] Add more unit tests (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48927">#48927</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/da37c088786eead9c7ddaffe0798ec692ece0a11"><code>da37c08</code></a>
v9.3.0 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48909">#48909</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/0bb025974d5eca56f626121cd14a21b77e71e982"><code>0bb0259</code></a>
[tablepagination] Add focus style to default InputBase used in Select
(<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48871">#48871</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/20fe2b6aa86965e3f1e2e5b0a82e2ed38f753ffd"><code>20fe2b6</code></a>
Bump code-infra:devDependencies (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48891">#48891</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/7fb01101f45fb72fdbeb3d826984030583e71ea9"><code>7fb0110</code></a>
[togglebuttongroup] Add roving tabindex keyboard navigation (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48849">#48849</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/3dfeb20bb65e598f90200ef1fc1429d02fa8c4b7"><code>3dfeb20</code></a>
[internal] Fix typos in ListItemButton component code (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48868">#48868</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/27f46fa1acabd6d70898b40544f20000bea0149d"><code>27f46fa</code></a>
Bump <code>@​types/sinon</code> to 22.0.0 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48865">#48865</a>)</li>
<li><a
href="https://github.com/mui/material-ui/commit/a900cd7d7e66e37248ec0ae8da44d588d0577aa3"><code>a900cd7</code></a>
Bump react monorepo to 19.2.8 (<a
href="https://github.com/mui/material-ui/tree/HEAD/packages/mui-material/issues/48858">#48858</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/mui/material-ui/commits/v9.3.1/packages/mui-material">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-08-29 14:50:15 +00:00
LudyandCopilot 41cbd97b48 ci: reuse shared Python dependency cache across workflows (#7693)
# Description of Changes

This PR removes workflow-specific cache suffixes from Python dependency
caching in several CI workflows.

Previously, the following workflows appended their own `cache-suffix`
even though they use the same Python dependency files:

- `ai-engine.yml`
- `check-generated-models.yml`
- `pre_commit.yml`
- `sync_files_v2.yml`

All of these workflows use the same cache dependency inputs:

- `engine/pyproject.toml`
- `engine/uv.lock`

The workflow-specific suffixes caused separate cache entries to be
created for effectively identical dependency sets. This resulted in
unnecessary cache duplication and reduced cache reuse between workflows.

By removing the suffixes, these workflows can now share the same cache
when their dependency inputs and other cache key components match.

This change reduces redundant cache storage, improves cache hit
potential across CI workflows, and avoids repeatedly creating equivalent
caches under different names.

No functional application behavior is changed. The modification only
affects CI cache key generation and reuse.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-08-29 14:50:11 +00:00
dependabot[bot] 993adaa3cd build(deps-dev): bump @iconify-json/material-symbols from 1.2.83 to 1.2.89 in /frontend in the iconify group across 1 directory (#7641)
Bumps the iconify group with 1 update in the /frontend directory:
[@iconify-json/material-symbols](https://github.com/iconify/icon-sets).

Updates `@iconify-json/material-symbols` from 1.2.83 to 1.2.89
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/iconify/icon-sets/commits">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-08-29 14:50:09 +00:00
ConnorYohandAnthony Stirling ead8a536d2 feat(editor): move signing sessions onto TanStack Query (#7436)
# Description of Changes

Step 4 of the TanStack Query rollout, and the first of the polling
hooks. Follows #7264, #7283, #7285.

## The problem

`useSigningSessions` hand-rolled its own fetch, loading state and
`setInterval`. Two consequences:

- **A raw `setInterval` keeps polling a hidden tab.** Browsers throttle
background timers, they do not stop them, so a backgrounded editor with
Shared Sign open keeps hitting both endpoints for as long as it is open.
- **No tests.** The hook had none, and its quietest behaviour (below) is
the easiest thing to break without noticing.

## End state

One query behind `qk.signingSessions()`, with the polling lifecycle
handed to the library:

- Polling stops while the tab is hidden, and refetches on return rather
than leaving data up to a full interval stale.
- Mounts render from cache while they revalidate, so moving between the
tool picker and the signing tool no longer flashes an empty list.
- 12 tests where there were none.

Same return shape, so no consumer files change.

### What this is not

This is not a deduplication win. The three consumers are never mounted
at the same time: `ToolPanel` renders the tool picker or the active tool
and never both, so the badge cannot be on screen with either of the
others, and `SharedSigningLauncher` and `useSigningSessionController`
sit inside two different tools. The shared key earns its keep on cache
reuse across those transitions, not on concurrent fetches.

## The bit worth reviewing

The hand-rolled `{ silent: true }` flag encoded three states, and no
single Query flag reproduces them:

| | Spinner | Toast on failure |
|---|---|---|
| First load | yes | yes |
| Background poll | no | no |
| Explicit refetch | **yes** | **yes** |

`isLoading` is false during an explicit refetch when data is already on
screen; `isFetching` is true during a background poll. Neither matches,
so the user-initiated case is tracked with a small flag and the failure
toast is gated on `isLoadingError` plus the explicit path.

## Testing

Twelve tests. Rather than trust them, each claim was checked by breaking
the implementation and confirming the relevant test fails:

| Mutation | Caught by |
|---|---|
| `refetchIntervalInBackground: true` | hidden-tab test |
| Drop `refetchOnWindowFocus` | returns-to-view test |
| Drop the user-initiated spinner flag | manual-refresh test |
| Toast on every error | background-failure-is-silent test |
| Give each observer its own key | dedupe test |

Three things worth knowing for the next conversion:

- **`waitFor` flushes renders.** Recording an index *after*
`waitFor(callCount === 2)` skips past the in-flight render, so a "did
the spinner flip on" assertion passes vacuously. The marker has to go
before the poll.
- **Fake timers hide in-flight state.** The fetch settles inside the
same `act()`, so the intermediate render never happens. That test uses
real timers and a held-open promise.
- **`visibilitychange` has to bubble.** query-core listens for it on
`window`, and the real event bubbles from `document`. A test helper
dispatching a non-bubbling event never reaches the focus manager, and
the pause behaviour still appears to work because `refetchInterval`
reads `document.visibilityState` directly at tick time rather than
through the event.

**One claim is deliberately unguarded.** `isLoading` vs `isFetching` for
a background poll produces no re-render at all, so there is nothing
observable for a test to assert and no user-visible difference to
protect.

## Pre-existing failures

`task frontend:check` passes typecheck, lint and oxfmt, and 2363 of 2365
editor tests. The two failures, `workbenchSession.test.ts` and
`notificationActions.test.tsx`, fail identically with this branch's
changes reverted and are untouched by it.

## Scope

This is one of five pollers. The remaining four, `useLocalFolderPoller`,
`WatchedFolderWorkbenchView`, `SessionDetailPanel` and cloud
`TeamSection`, are separate files with their own consumers and follow
separately, now that the silent-refresh pattern has a worked example.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-29 10:35:40 +00:00
ConnorYoh c22d9ecf58 feat(editor): move the admin directory onto TanStack Query (#7726)
# Description of Changes

Step 5 of the TanStack Query rollout, covering the admin People, Teams
and Team details screens. Follows #7264, #7283, #7285.

## The problem

Two separate ones, in the same three files.

**Reads.** Each section fetched and held its own copy of the same
resources: People read the roster and the team list, Teams read the team
list plus the roster again when its add-member modal opened, Team
details read all three. Cost scaled with how many screens you visited
rather than with how much data exists.

**Writes.** Thirteen handlers each did the same five things by hand: set
a processing flag, call the service, toast the outcome, dig a message
out of an axios error, and reload their own slice. Refreshing was a
convention, not a mechanism, and one handler had already forgotten it.

## The fix

Three shared query keys (`adminUsers`, `teams`, `teamDetails`), and one
`useAdminMutation` helper that every write is declared against:

```ts
const createTeam = useAdminMutation({
  write: (name: string) => teamService.createTeam(name),
  invalidates: ["teams"],
  success: t("workspace.teams.createTeam.success"),
  errorFallback: t("workspace.teams.createTeam.error"),
  onDone: () => { setNewTeamName(""); setCreateModalOpened(false); },
});
```

Each write names the slices it disturbs, which is the part that only
works when reads and writes are designed together: `createTeam`
invalidates the team list, while a membership move invalidates the list,
both teams' detail rows and the roster, because it genuinely changes all
three. Invalidation refetches only mounted queries, so this costs
nothing extra.

The blanket "invalidate everything" helper survives in exactly one role:
child components (invite, password change, seat update) that write
through their own services, where the affected scopes are not visible
from the call site.

## Why it is better, measured

Request counts come from one harness driving `teams -> team details ->
back -> people`, run against the branch point and against this branch.
The assertion is committed, so it cannot silently regress.

| | Before | After |
|---|---|---|
| Requests | 7 | **3** |
| `getTeams` | 4 | **1** |
| `getUsers` | 2 | **1** |
| `getTeamDetails` | 1 | 1 |
| Committed renders | 17 | **15** |

Three is one per distinct resource, the floor for that sequence. The
four `getTeams` were the Teams table, Team details fetching the same
list for its "move to team" dropdown, the explicit refresh on the back
button, and People.

Renders barely move, which is expected: this changes where data lives,
not how often React draws. It is reported because a caching change can
quietly cost renders, and this one does not.

On the code itself, across the three sections:

| | |
|---|---|
| Net lines | **-216** |
| `useState`/`useEffect` removed | **11**, none added |
| Duplicated `isAxiosError` blocks | 13 to **1** |
| `setProcessing` calls | 19 to **0** |

`isAxiosError` is no longer imported by any of the three files.

## Bug fixed

`disableMfaByAdmin` showed a success toast and never refreshed. The menu
item renders only when `user.mfaEnabled` is true, so an admin disabled
MFA, was told it worked, and watched the option stay on screen until a
manual reload. It is covered by a test that fails if the invalidation is
removed.

## Behaviour worth checking in review

- A write no longer blocks its handler before closing the modal. The
dialog closes when the write succeeds and the table updates when the
refetch lands, rather than the button spinning through both.
- Modal submit buttons now track their own mutation rather than one
shared flag. Team details still derives a single busy flag, now from its
five mutations rather than a `useState`, so its row actions disable
together as before.
- The per-handler `console.error` is kept, once, in the shared error
path.

## Testing

Five tests, each verified by breaking the implementation and confirming
that one test, and only that one, fails:

| Mutation | Caught by |
|---|---|
| Drop the shared stale window (`staleTime: 0`) | request-count test |
| Make invalidation a no-op | write-visibility test |
| Ignore the login-enabled gate | login-disabled test |
| Stop invalidating after the MFA write | MFA-refresh test |
| Fall back to the generic error message | server-message test |

The write tests drive the real flows through their modals and menus
rather than calling hooks directly.

`task frontend:check` passes typecheck, lint and oxfmt, and 2383 of 2385
editor tests. The two failures, `workbenchSession.test.ts` and
`notificationActions.test.tsx`, are untouched here and fail identically
with this branch's changes reverted.

## Scope

The three services keep their current shape; nothing outside these three
sections and the new hook module changes. Child modals that write
through their own services still refresh via the blanket helper, and
converting those is separate work.
2026-08-29 00:22:05 +00:00
Reece Browne d3708c1e63 Highlight the rail entry whose tool is open (#7723) 2026-08-28 13:02:47 +00:00
EthanHealy01 1c055f3d18 Centre modals in the viewport instead of pinning them near the top (#7715)
## What

Every dialog in the processor is the shared `.sui-modal` shell, and its
backdrop was top-aligning the panel:

```css
align-items: flex-start;
padding: 5rem 1.5rem 1.5rem;   /* 80px above, 24px below */
```

On a 900px-tall viewport that started every dialog at `y=80` with ~350px
of dead space beneath it. Phones already had an `align-items: center`
override; desktop never got one.

## Change

`frontend/editor/src/core/ui/Modal.css` only:

- Symmetric block inset, `align-items: center`.
- The inset is published as `--modal-inset-block`, and `.sui-modal`'s
`max-height` derives from it. That coupling is the point: if the two
drift apart, a tall modal overflows a centre-aligned backdrop and loses
its header off the top of the screen, unreachable.
- The phone breakpoint now only moves the variable. Measured at 375x812
it resolves to exactly the previous values (`16px 12px`, `max-height:
780px`), so mobile behaviour is unchanged.

One shared file, so this covers flow modals, source / user / pipeline /
API-key modals, billing and procurement.

## Before / After


<img width="2104" height="2284" alt="image"
src="https://github.com/user-attachments/assets/bcb50145-f75e-449e-92c6-a0b085cc091c"
/>


## Testing

- `task frontend:check` passes (lint + typecheck + 2356 tests).
- Phone breakpoint measured directly in the browser, values match the
previous behaviour.
2026-08-28 12:27:05 +00:00
ConnorYoh 4ab2505a6c Comment-quality standard, and the gate that enforces it (#7663)
## The problem

AI PRs write comments that restate the line below them, mark sections
with box drawing, and narrate the diff. Nothing in the repo said not to,
and nothing checked. `AGENTS.md` had one line about comments and it was
buried in the Python section.

Banners and `Step N:` narration have zero occurrences in the 15 months
before Aug 2025, so this is new.

## The fix

A written standard, plus a linter that enforces the mechanical part of
it on added lines only.

-
[devGuide/CODE_COMMENTS.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/devGuide/CODE_COMMENTS.md)
holds the reasoning and worked examples; a section in
[AGENTS.md](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/AGENTS.md)
holds the operative rules, kept short so they stay in an agent's
context. The two are split by kind rather than duplicated, because the
same prose in two places drifts.
- Rules in
[comment-rules.mjs](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs),
shared by both engines.
- Two engines. `.ts` / `.tsx` / `.mjs` go to an [oxlint JS
plugin](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-lint-oxlint-plugin.mjs)
so comments come from the parser rather than a line scan; `.java` /
`.py` go to a [line
scanner](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-lint.mjs).
Neither reads the other's files, so they cannot disagree about one file.
- Between them they read every comment form the repo writes: `//` and
`/* */`, Javadoc and JSDoc, JSX comments, `#`, and Python docstrings.
- Runs in `task pre-commit`, so the git hook and the `pre_commit.yml` CI
job both get it, and as a Claude Code [`Stop`
hook](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-lint-hook.mjs)
so an agent fixes the comment inside the turn that wrote it.

## The rules

The part worth arguing about. **Every rule blocks.** A rule that only
warns is a rule nobody acts on, so a finding you believe is wrong is a
bug in the rule: narrow it, or mark the line and say why.

| | Fires on |
| --- | --- |
|
[CMT001](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L71)
| Every word in the comment already appears in the code below it. Max 6
words, skipped for prose punctuation and for a bare Arrange/Act/Assert
marker |
|
[CMT002](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L92)
| 4+ rule or box-drawing characters, or a bare section label from [a
fixed
list](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L84)
(`Types`, `Helpers`, `State`, `Handlers`, ...) |
|
[CMT003](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L110)
| `Step N:` with a separator, or `Then,` / `Next,` / `Finally,`.
Suppressed in test files |
|
[CMT004](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L129)
| A comment about the code's own past: `this used to`, `renamed from`,
`was previously called`. Suppressed in test files |
|
[CMT005](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L154)
| 3+ consecutive comment lines where 2/3 [parse as
code](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L143)
|
|
[CMT006](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L31)
| A run of implementation comment over 12 lines, outside the first 5
lines of a file. Doc blocks are exempt, because the standard asks for
thorough contracts |
|
[CMT007](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L180)
| A parameter or return description that adds no word its name lacks.
Reads Javadoc/JSDoc `@param`, Sphinx `:param name:` and Google `name:
description` |
|
[CMT008](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L239)
| An allow directive naming a rule that does not exist, or one that
silenced nothing |
|
[CMT009](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L219)
| A `TODO` / `FIXME` / `HACK` naming no issue or link. An owner is not
accepted: a username goes stale, an issue outlives it |

Each rule carries the readings it deliberately excludes, next to the
rule. Those exclusions came from running the rules over this repo, not
from taste: `CMT004` does not match a bare "no longer needed" because
that is as often about runtime lifecycle as about history, and `CMT003`
needs a separator after the number so a wrapped line beginning "step 2
unmounts + remounts the panel" reads as the prose it is.

A comment sharing a line with code is judged by the rules that do not
depend on the code below it, so a trailing `// TODO fix this` or `/*
this used to run before the flush */` still reports, while `50L * 1024 *
1024 // 50 MB` does not. `CMT001` would have been wrong about six in
seven trailing comments here, so it stays out of them.

If a finding is wrong, `// comment-lint-allow: CMT002` on the line
above. Rule-specific, [no blanket
disable](https://github.com/Stirling-Tools/Stirling-PDF/blob/claude/ai-pr-comment-quality-dd970e/scripts/lint/comment-rules.mjs#L229).
A directive naming a rule that does not exist, or silencing nothing, is
itself a `CMT008` failure, so a typo cannot quietly disable a rule and a
stale one gets deleted rather than accumulating.

No native linter covers `CMT007`. `eslint-plugin-jsdoc`'s
`require-param-description`, Checkstyle's `NonEmptyAtclauseDescription`
and ruff's D-rules all check that a description exists, not whether it
says anything.

## Scoping

Added comment **text** only, not lines git calls new. Reindenting a file
or moving a block makes git mark untouched comments as added; findings
are matched against the comment text at the base, so only genuinely new
content reports.

The whole file is read and every comment in it evaluated. Only the
*reporting* is filtered, so a rule still sees the code a comment
introduces, the full run it belongs to, and the base version of the
file.

Existing tree is untouched. `task pre-commit:comment-lint:all` reports
it and always exits 0:

| | java | ts/js | py |
| --- | --- | --- | --- |
| findings | 1,218 | 741 | 204 |

2,163 across 542 files, mostly `CMT002` banners (1,482) and `CMT001`
restatements (456). Clearing it is separate work, by directory.

Not in this PR: an advisory LLM review layer for the things no pattern
can judge.

## Verification

Run against
[#7494](https://github.com/Stirling-Tools/Stirling-PDF/pull/7494) as CI
would, in a throwaway worktree: **two findings on a 78 file, +4,512 line
change, both genuine banners, in 952ms**. A whole-file scan of those
same files gives 11; the other 9 were withheld because that PR's author
did not write them, and they are the `@param teamId the team ID` shape
this standard exists to stop.

Both scanners blank string and character literals before looking for
comment markers, because a partial lex desynchronises everything after
it: one apostrophe in a Java comment, or one Python template whose
closing quotes start a line, is enough to read dozens of lines of code
as a single comment. Two fixtures carry canaries that stop being
reported if either engine ever desynchronises again.

The [fixture
corpus](https://github.com/Stirling-Tools/Stirling-PDF/tree/claude/ai-pr-comment-quality-dd970e/scripts/lint/fixtures)
pins all 9 rules against both engines, and `--selftest` fails if the two
disagree about the same file.

## Two things reviewers should know

**The oxlint JS plugin API is alpha.** oxlint itself is stable and
already this repo's frontend linter; the plugin API is the new
dependency. Its documented failure mode
([oxc#25203](https://github.com/oxc-project/oxc/issues/25203)) is being
skipped silently while oxlint still reports success. That affects the
standalone release binary rather than the npm package this invokes, but
the class of failure reads exactly like clean code, so the run asserts
`number_of_rules >= 1` from oxlint's own report and a broken engine
exits 2 rather than passing. If the API ever breaks, the fallback is
folding these rules into the line scanner, which already implements all
nine for Java and Python.

**`.claude/settings.json` is now committed**, carrying the hook and
nothing else: 19 lines, no `permissions`, nothing machine-specific. That
partly reverts `c35546a212` ("Ignore claude dir"), which existed because
this file had twice been committed by accident with a personal
`permissions` allowlist, once with absolute machine paths. Personal
config still belongs in `.claude/settings.local.json`, which the new
pattern keeps ignored, and hook entries merge across the two so nobody's
own hooks are lost.

If you already hand-wrote a `.claude/settings.json`, copy it somewhere
first: that path used to be git-ignored, and git overwrites an ignored
file without warning when a commit starts tracking it. Across 19 local
checkouts here, 13 have `settings.local.json` and none has a
hand-written `settings.json`.

To turn the hook off, `{ "env": { "COMMENT_LINT_HOOK": "0" } }` in local
settings. Claude Code can only disable all hooks at once, hence the
switch. The commit-time gate still applies.

## How to test

```bash
task pre-commit:comment-lint:ci
```

The fixture corpus, then the diff. The corpus checks the rules
themselves rather than the code under review, so it runs on CI and
before a rule change, not on every local commit.

```bash
task comment-lint:branch
```

`clean (34 files in scope)`. `task comment-lint` is the same thing
scoped to uncommitted work, which is what the git hook and CI run.

To watch it bite, add `// Is banner` above `export function isBanner` in
`scripts/lint/comment-rules.mjs` and run `task comment-lint`: one
`CMT001`, exit 1. The gate covers its own source, which is why these
scripts have no section dividers.

```bash
task pre-commit:comment-lint:all
```

The standing backlog, report-only.

Verified on the pinned oxlint 1.77.0, not only the 1.79 the plugin was
prototyped against.
2026-08-28 10:56:50 +00:00
James Brunton 658aa54c20 Update tool models to fix main (#7725)
# Description of Changes
When #6697 merged, the CI didn't run for some reason so it was never
caught that the tool models were out of date. This PR updates them to
the correct state.
2026-08-28 10:35:41 +00:00
James BruntonandAnthony Stirling 0a3f0c1814 Fix redirect bugs in SaaS (#7721)
# Description of Changes
Fixes various bugs that affected SaaS (and some self-hosted):
- Refreshing on Editor caused the user to be redirected to Processor 
- User was unable to access Processor in SaaS
- Deep link hijacking fixes
- Fix double prefix `/app/app` issue
- Fix going from tool -> editor -> processor -> editor putting you back
into tool

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-28 10:03:52 +00:00
James Brunton 849d616451 Fix refreshing causing you to go to the Processor (#7694) 2026-08-27 23:45:10 +01:00
Anthony Stirling a48356a2d2 Deploy a dev SaaS server alongside the PR previews and main demo (#7697) 2026-08-27 23:17:21 +01:00
Reece Browne f71b0247da Quick access bar and old school sidebars (#7695) 2026-08-27 23:15:39 +01:00
Ludy be13028209 chore(logging): enable gzipped log rotation and adjust test logging (#7648) 2026-08-27 18:41:40 +01:00
Andrei BlajandAndrei Blaj d4b1862654 feat(ocr): add rotatePages option for automatic page orientation correction (#6697)
Co-authored-by: Andrei Blaj <andrei@atta.systems>
Signed-off-by: Andrei Blaj <andrei@atta.systems>
2026-08-27 18:40:26 +01:00
jayakrishnaandJames Brunton 51835a7b5e fix: clean up Add Stamp image preview blob URLs (#6779)
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2026-08-27 18:34:43 +01:00
briosandCopilot Autofix powered by AI 97c0ccf582 refactor(deps): optimize dependency footprints, and add lazy initialization with platform-specific JPDFium bundling (#7620)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-08-27 18:31:54 +01:00
Ludy 4e46ba3b5a chore: prevent duplicate Dependabot Gradle PRs (#7657)
## Description of Changes

- Removed overlapping Gradle subdirectory entries from
.github/dependabot.yml.
- Dependabot now monitors the root Gradle project through /.
- Prevents duplicate pull requests for dependencies declared in Gradle
subprojects.

Closes: Not applicable

---

## Checklist

### General

- [ ] I have read the Contribution Guidelines
- [ ] I have read the Stirling-PDF Developer Guide (if applicable)
- [x] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant documentation (if applicable)
- [ ] I have read the translation tag documentation (for new translation
tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos are attached

### Testing (if applicable)

- [ ] I have tested my changes locally
2026-08-27 17:16:24 +00:00
Ludy cbe3ef8f69 fix: validate frontend dependency installation (#7625)
# Description of Changes

The current check only verifies the existence of the `node_modules`
directory. After an incomplete or corrupted installation, this can lead
to the task being incorrectly marked as complete.

`npm ls --depth=0` instead checks whether the direct frontend
dependencies are actually installed and consistent. This reliably
detects and automatically repairs corrupted installations.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-27 16:59:58 +00:00
EthanHealy01 5b5e922069 Make the upgrade banner neutral instead of gradient purple (#7696)
## What

The `promo` banner tone was a full-bleed `indigo-500 → purple-500`
gradient with white text and a black drop-shadow on the CTA. It was the
only saturated fill in the app, and against the warm neutral palette it
read as a foreign object above the workbench.

The bar is now app chrome:

| | Before | After |
|---|---|---|
| Background | 135° indigo→purple gradient | `--c-bg-raised` |
| Border | `transparent` | `--c-border-subtle` hairline |
| Icon | white glyph, no container | neutral glyph in a
`--c-surface-sunken` chip |
| Text | forced white | `--c-text` / `--c-text-muted` |
| CTA | `premium` accent (violet gradient) | `default` accent (same
primary button as the rest of the app) |


Before

<img width="1504" height="739" alt="Screenshot 2026-08-27 at 4 49 00 PM"
src="https://github.com/user-attachments/assets/a912d9e9-9590-4e1d-8202-1abb22a00f23"
/>


After

<img width="1061" height="665" alt="Screenshot 2026-08-27 at 4 48 30 PM"
src="https://github.com/user-attachments/assets/3be14aec-ccfe-4448-93b3-339a57be4937"
/>


Only caller is the friendly variant of `UpgradeBanner` (self-hosted,
under the free-tier user limit).

## Notes

- **No new theme tokens.** Every value is an existing `--c-*` semantic
token, so light and dark both follow automatically with no per-theme
overrides.
- The `premium` accent itself is untouched, so the upgrade CTAs in
`OfflineActivationCard` and `PairingPanel` are unaffected.
- `--c-hue-indigo` / `--c-hue-purple` are still used by
`SaaSOnboardingSlides`, `PaygFree` and `UpgradeModal`, so no tokens are
orphaned.
- Deleted comments describe rules that no longer exist (the gradient,
the white-on-gradient text overrides, the CTA shadow). No new comments
added.

## Verification

- `task frontend:check:all` passes (typecheck, oxlint, all four theme
linters, stylelint, format, tests, build, storybook build).
- `task frontend:storybook:a11y:changed` passes light and dark: 7
AppBanner stories, 0 violations. Both a11y baselines are empty, so this
is zero known violations rather than a baselined pass.
- Checked in Storybook under **Shared / AppBanner → All Top Bars**,
which renders every top bar the app can show side by side, in both
themes.
2026-08-27 15:57:15 +00:00
EthanHealy01 a215c30068 Add the missing en-US translations for classification labels (#7692)
Our classification labels were rendering their hardcoded English names
because the en-US locale file had no `classification` section at all, so
this adds the missing keys (labels and category names).

Also wires the category names through i18n, since those had no `t()`
call, and adds a test so a new label can't ship without its key.
2026-08-27 14:10:56 +00:00
Anthony Stirling 897c72e9d9 Fix tool panel scrolling so the action button stays reachable (#7688)
# Description of Changes

After ui rework all scrolling in all tool panels stopped working
This fixes this to allow tool panels to be scrollabe again




## What was wrong

PDF/UA is the only convert target whose settings panel overflows the
tool rail. Measured at 1920×1080: overflow was 0px for pdfa, pdfx, png,
docx, epub, and 158px for pdfua. Its action button sat at bottom: 1220
in a 1080px viewport — 140px below the fold — and the info alert was
clipped mid-sentence. The panel could be scrolled, but nothing said so
(Mantine's scrollbar auto-hides).

Normally the app would scroll the button into view for you. It didn't,
because both mechanisms built to do that were dead

## Cause:
Two separate mechanisms, both broken since the same commit (0a50e765b7,
frontend editor restructure, 2026-05-22):

1. ReviewToolStep - shared by all 47 tools. It looked for its scroll
container with:

stepRef.current.closest('[style*="overflow: auto"]')

Mantine's ScrollArea viewport sets inline overflow: scroll, not auto. I
measured it live - closest() returns null, and
document.querySelectorAll('[style*="overflow: auto"]') finds exactly 1
element anywhere in the page, and it isn't an ancestor of the panel. So
the lookup silently found nothing and the scrollTo never ran, for every
tool.

2. Convert.tsx - Convert only. It declared scrollContainerRef and a
scrollToBottom() wired to two useEffects, but the ref was never attached
to any element - createToolFlow() builds the JSX and no ref is passed
through. Always null, so both effects were no-ops.

Nothing else in the codebase has this pattern - I grepped for other
closest('[style*="overflow…"]') lookups and other
scrollToBottom/scrollContainerRef uses and both came back empty.



## The fix

createToolFlow.module.css (new) + createToolFlow.tsx:156 — the execute
button gets a position: sticky; bottom: 0 footer, the house pattern
already used by FormFill.module.css. Applied only when the review step
isn't visible, so it can never float over results. Sticky is inert when
content fits, so the other 46 tools are untouched.
ReviewToolStep.tsx:21 — real findScrollParent() walk replacing the
broken selector, scrolling by the minimum delta needed and only the
panel itself (never scrollIntoView(), which drags every ancestor). Also
added the missing clearTimeout cleanup.
Convert.tsx — deleted the dead ref and its two effects.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-27 11:55:31 +00:00
ConnorYohandJames Brunton 732ef18ae5 feat(account-link): redirect-based connect handshake for self-hosted linking (#7494)
Links a self-hosted instance to a SaaS team over an ordinary redirect,
and leaves the admin's browser holding a Stirling session at the same
time.

## The problem

A self-hosted server needs a device credential bound to a SaaS team, and
the admin's Supabase JWT must never reach the instance backend. Three
things ruled out the obvious approaches:

- **A customer hostname can never be in Supabase's redirect
allow-list**, so the sign-in cannot happen on the instance's own origin.
That is why SSO and sign-up did not work for linking at all.
- **A device credential identifies a server, not a person.** Every
attended portal read (Usage, Billing, Documents, Infrastructure) goes
through `getPortalSaasToken()` and needs a *user* session, so a
credential-only link left all of them asking for a second sign-in.
- **The previous design relayed a JWT** from the browser into the
instance, which is the thing we wanted to avoid. That path is deleted
here.

## The solution

Redirect and nonce, modelled on desktop's
`authService.loginWithSelfHostedOAuth`: mint a nonce, hand the browser
off, accept only a callback carrying that nonce back. Desktop has the OS
route the reply; self-hosted has no OS hop, so our own approval page
performs it. That is the point — the human half happens on an origin we
control.

```
instance                     SaaS                        admin's browser
   |  POST connect/request     |                                |
   |  (name, callback, nonce,  |                                |
   |   claim-secret hash)      |                                |
   |-------------------------->|                                |
   |  <- requestId + authorizeUrl                               |
   |                           |      GET /link?request=...     |
   |                           |<-------------------------------|
   |                           |  sign in (SSO works here),     |
   |                           |  see ACCOUNT + ORIGIN, approve |
   |                           |------------------------------->|
   |                           |   302 callback#nonce+session    |
   |  POST connect/claim       |                                |
   |  (requestId, claim secret)|                                |
   |-------------------------->|                                |
   |  <- device credential     |                                |
```

Four properties carry the safety, and each is stated in the code because
each is easy to lose in a refactor:

- **The redirect target is never caller-supplied.** Validated once at
creation, then read back from the stored row, so nothing in the approval
page's URL can steer the token elsewhere.
- **Approval and minting are separate.** Approval records the team and
hands out nothing usable; the credential is minted only on claim,
authenticated by a secret that never entered a browser.
- **A re-authentication cannot move a server between teams.** The team
is pinned at creation from the credential only that instance holds, so
an approver from another team gets `WRONG_TEAM` instead of a rebind.
- **The approver has to confirm what they are binding.** The page shows
the address and the signed-in account, with a way to switch, and a
checkbox naming the address gates the approve button. The name the
server reports is deliberately not shown: the requester picks it on an
unauthenticated endpoint, and its honest value is the hostname already
in the address.

The session rides the URL fragment, so it stays out of access logs and
`Referer`, and is stripped before anything awaits. The claim is
row-locked, so one approval mints once. A request lives 30 minutes; a
settled one is not offered again, since approving it fails server-side.

Signing in mid-flow no longer loses the request. The id is kept on the
SaaS origin and resumed after any sign-in, which is what makes creating
an account work: the confirmation email opens a new tab, where the
`next` parameter is gone. Reading it does not consume it — the request
may be open in two tabs — and only a recorded decision retires it.

The result lands as a modal over the portal the admin started from, and
the portal re-reads its link status so the page behind agrees with the
modal.

Plaintext `http://` callbacks are accepted rather than refused, because
many self-hosted instances legitimately run plain HTTP on a private
network; the address carries a warning icon explaining the risk, derived
server-side so a requester cannot suppress it. Hard-refusing `http://`
to a public IP literal is a reasonable follow-up; a bare hostname can't
be classified without a DNS lookup, so the warning stays the general
mechanism.

## Configuration

Four surfaces. Placeholders below, not values.

**SaaS backend**

| Setting | Needed | Why |
|---|---|---|
| `stirling.billing.account-link.enabled` | Yes, `true` | The connect
controller and service are `@ConditionalOnProperty` with no default, so
without it the endpoints do not exist. |
| `system.frontendUrl` | Only when the approval page is not on the API's
own origin | Where the approver is sent. Must include the app's base
path if it is served under one, or the redirect misses `/link`. |

**SaaS frontend**

| Setting | Needed | Why |
|---|---|---|
| `VITE_SUPABASE_URL`, `VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY` | Yes |
Its own sign-in. Must be the project the SaaS backend validates tokens
against. |
| `RUN_SUBPATH` | Only if served under a subpath | Moves the approval
page to `<base>/<subpath>/link`, so `system.frontendUrl` has to agree. |

**Self-hosted backend**

| Setting | Needed | Why |
|---|---|---|
| `stirling.billing.account-link.enabled` | Yes, `true` | Defaults to
`false`. |
| `stirling.billing.account-link.saas-base-url` | Yes | Origin of the
SaaS API it links to. Not the SaaS frontend. |
| `system.frontendUrl` | Optional | Externally reachable base URL for
the callback. Otherwise derived from the request's `Origin`, which is
right for ordinary deployments and wrong behind a rewriting proxy. |

**Self-hosted frontend**

| Setting | Needed | Why |
|---|---|---|
| `VITE_SUPABASE_URL`, `VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY` | Yes |
Accepts the session handed over in the callback fragment. |
| `VITE_SAAS_API_URL` | For Usage and Billing | Attended reads go to the
SaaS API with the admin's token. Absent, those surfaces stay on the
mock. |
| `VITE_INCLUDE_PORTAL` | Production builds | Dev builds include the
portal automatically; without it there is no link UI and no callback
route. |

Two things worth stating because neither fails loudly:

- **Both frontends must use the URL *and* key of the same Supabase
project**, and the same one the SaaS backend validates against. A key
from one project with a URL from another is accepted by the browser and
rejected by Supabase, which surfaces much later as "session expired" on
Usage rather than as an error at hand-over.
- **The Supabase redirect allow-list must contain the SaaS app's
`/auth/callback`**, since a confirmation email returns through it.
Entries are matched exactly.

- **`system.frontendUrl` is the existing setting for this**, not a new
one, so each side reads its own value and there is nothing extra to
configure. It also gates share links, so on a stack with storage and
sharing already on, setting it here turns those on too.

The self-hosted side deliberately does **not** configure where the
approval page lives — SaaS answers that in the connect-request reply,
being the only party that knows.

Also here, because testing this needs two stacks side by side:
`linked:staging` / `linked:dev` (which derive `system.frontendUrl` and
`RUN_SUBPATH` themselves), the missing `frontend:staging:saas`, and a
per-mode vite `cacheDir` — two dev servers in different modes otherwise
re-optimise over one shared dep cache.

## How to test

Automated and green: `task frontend:check:all` plus both backend
modules. `ConnectRequestServiceTest` covers callback validation, the
per-IP cap, single-use approval, claim outcomes, expiry, `WRONG_TEAM`
and reauth confirming without minting; `ConnectServiceTest` covers
callback-resolution precedence including a foreign-origin callback being
discarded; `ConnectControllerTest` covers the authorize URL, including
the forwarded-header path and only the first hop being trusted;
`ConnectCallback.test.tsx` covers the fragment being stripped
synchronously and malformed fragments refused;
`LinkAccountModal.test.tsx` covers link and reauth hitting different
endpoints.

Manual walkthrough:

1. `task linked:staging` — added here; brings up a SaaS stack and a
self-hosted instance pointed at it, on discovered ports, and prints the
four addresses.
2. Open the link-account modal in the self-hosted portal and continue.
Expect the SaaS approval page at `/link?request=<id>`.
3. Sign in as a team leader, or create an account and confirm the email.
Either way you should come back to the approval page.
4. Tick the acknowledgement and approve. Expect the fragment gone from
the address bar immediately, a result modal over the portal, the portal
showing linked without a reload, and attended reads (Usage, Billing)
working without a second sign-in.
5. Repeat, approving as a member of a different team. Expect a refusal,
not a rebind.

## Outstanding

- #7415 to be reworked against this design once this lands.
- **No SaaS-side UI to disconnect a server.** `GET
/account-link/instances` and `POST /account-link/instances/{id}/revoke`
are already team-scoped and leader-gated, and the portal has a panel
that uses them, but
`portal-saas/components/settings/accountLinkSettings.tsx` exports `null`
on the reasoning that "SaaS has no account-link concept". That held when
linking was a self-hosted admin managing their own instance; here a
leader approves a server they may not administer, and has no way to
withdraw it. The seam to fill is that one file. Expected to land with
the CTA work in #7415.

---------

Co-authored-by: James Brunton <jbrunton96@gmail.com>
2026-08-27 10:32:32 +00:00
EthanHealy01andClaude f7a2c626c9 Persist the workbench session across the editor/processor switch (#7654)
## What

Switching editor -> processor (or reloading) unmounts every editor
provider, which emptied the workbench. This PR mirrors the workbench
into per-tab sessionStorage and refills an empty one from that record on
the next mount:

- **Files, selection, view and active document survive** the shell
switch and reloads. Each recorded file is resolved to its *current leaf*
version on restore, so a file versioned by a policy or another tab comes
back at its latest state.
- **The switch back lands where the user left**: the processor sidebar's
"editor" button consumes a one-shot return path saved at switch time.
- **The app switch respects unsaved changes**: `useOtherAppSwitch`
(proprietary + saas) now routes through `requestNavigation`, so the same
warning guards it as any other navigation.
- Desktop shadows `WorkbenchSessionPersistence` with a stub (OS-launched
files own boot there).

## How to test

I've run through each of these manually:

- Upload several PDFs in the editor, select a couple, and switch to the
Active Files grid. Click "Open PDF Processor" in the sidebar footer,
then switch back to the editor. The same files, selection and view
should return, and you should land on the editor page you left.
- Open a document in the viewer, then reload the tab. The workbench
should refill and come back on the viewer with the same document active.
- With unsaved changes in a tool, click the processor switch. The
unsaved-changes warning should appear, and the switch should only
proceed if you confirm.
- Open a second browser tab with different files. Each tab should
restore its own workbench independently (the record is per-tab
sessionStorage).
- While in the processor, delete one of the open files from storage,
then switch back. The remaining files should restore and a warning toast
should report "Restored X of Y files".

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-08-27 10:12:48 +00:00
EthanHealy01 caeca0b88a Fix classification escalation: the local pass was claiming the server dispatch key (#7667)
Follow-up to #7580: the escalation it added could never fire.

## What's broken

The auto-run skips a policy that has already run on a file, keyed on
`(categoryId, fileId)`. `recordRunStart` claims that key — and #7580 has
the **browser-side first pass** record its own run under `categoryId:
"classification"` for the uploaded file. So the local heuristic ticks
the very key the server escalation checks, and the AI is never asked, at
any confidence.

Trigger is the default seeded setup: **Classification as the only
on-upload policy**, and a local verdict below `high`. Any other
on-upload policy masks it, because classification then targets that
policy's output — a new file id whose key was never claimed. That's why
this went unnoticed.

Two smaller faults in the same path:

- A chained output carried no `classificationConfidence`, so
`shouldDispatchToAi` waited for a verdict that could never arrive (a
tool-derived file gets no local pass).
- Browser-local runs were polled against the server: 3 × 404 per file,
after which `MAX_NOT_FOUND` marked a local run that had actually
**succeeded** as `FAILED`.

## The fix

- `PolicyRunRecord.browserLocal`; `recordRunStart` skips the dispatch
claim for such a run. It is the first pass, not the policy's run.
- The local pass meters under `classification:local-meter` instead of
the category id, so metering dedupe survives without suppressing
dispatch.
- The poll effect skips browser-local runs.
- `CONSUME_FILES` inherits `classificationConfidence` alongside the
labels, so the verdict survives a version bump.

## How to test

Download
[`low-confidence-classification.pdf`](https://github.com/Stirling-Tools/Stirling-PDF/raw/fix/chained-classification-confidence/frontend/editor/src/proprietary/services/heuristic/fixtures/low-confidence-classification.pdf)
(checked in as a fixture, verdict pinned by a test).

With **Classification as the only on-upload policy**, upload it and
watch the Network tab:

- **Before:** no `POST /api/v1/policies/{id}/run` for classification,
ever. Console shows `local-classification-*` 404s.
- **After:** exactly one, and the engine receives `POST
/api/v1/documents/classify`.

Judge it on that request, not on the resulting label — the model's
answer varies, so a label comparison can pass or fail for the wrong
reason.

Headless equivalent:

```
npx vitest run --project proprietary src/proprietary/components/policies/usePolicyAutoRun.escalation.test.tsx
```

Passes here, fails on `main` on "asks the AI about an unsure verdict
even though the local pass already ran". Its other two cases pass on
both, so the guards still hold: a confident verdict still costs nothing,
and a file with no verdict yet still waits rather than racing the free
pass.

New tests drive the **real** run store — mocking it is what let this
through.

`task frontend:check`: 255 files / 2202 tests.
2026-08-26 17:34:13 +00:00
James Brunton c93feb5dfc Remove a bunch of unnecessary casts from the frontend (#7662)
# Description of Changes
Originally, I wanted to re-enable typed linting on our repo but using
Oxlint this time to avoid the memory and speed issues that ESLint was
causing. Unfortunately, it's not stable enough yet to actually use on
our repo (although it is close, I suspect it'll be stable enough fairly
soon). I was able to remove many of the unnecessary casts that it found
though, so even though this won't be enforced, it's still worth cleaning
up what I've found.
2026-08-26 14:09:55 +00:00
Anthony Stirling f945cc7dc6 Regenerate expired test certificates and guard against future expiry (#7682)
The bundled signing test certificates expired at **07:41:10 UTC on
2026-08-26**. They were issued exactly one year earlier, so they went
from fine to fatal mid-morning with no warning, and they take down
`main` and every open branch, not just one PR.

First casualty was the `docker-compose-tests` job on #6802, which
started at 07:45:

```
java.security.cert.CertificateExpiredException: NotAfter: Wed Aug 26 07:41:10 UTC 2026
    at CreateSignatureBase.checkValidity(CreateSignatureBase.java:159)
    at CertSignControllerTest.testSignPdfWithPkcs12(CertSignControllerTest.java:205)
```

```
$ openssl x509 -in app/core/src/test/resources/certs/test-cert.pem -noout -dates
notBefore=Aug 26 07:41:10 2025 GMT
notAfter =Aug 26 07:41:10 2026 GMT
```

## What was broken

`CertSignControllerTest` (7 tests) and `PdfSigningServiceImplTest` (2)
fail outright. `ValidateSignatureControllerMoreTest` and
`CertificateValidationServiceMoreTest` read the same fixtures.

Auditing the rest of the repo turned up three more time bombs that had
not gone off yet:

| Fixture | Was | Problem |
|---|---|---|
| `app/core/.../certs/test-cert.*` + `test-key.*` | expired 2026-08-26 |
**already breaking every branch** |
| `test-certs/valid-test.p12`, `valid-test.jks` (proprietary + frontend
copies) | expire 2027-03-25 | same failure, seven months out |
| `test-certs/not-yet-valid-test.p12` | valid **from** 2027-03-25 |
becomes valid, so its test silently stops proving anything, on the same
day |

## What this does

**Regenerates every fixture** with the identical subject DN, alias,
password, key size and signature algorithm as before, changing only the
validity window. Nothing that any test asserts on has moved.

- valid fixtures: `2025-01-01` to `2125-01-01`
- `not-yet-valid-test.p12`: `2125-01-01` to `2126-01-01`, so it stays in
the future
- `expired-test.p12`: pinned to its permanently-past 2024 window

**Adds `scripts/generate-test-certs.sh`** as the source of truth, so the
next regeneration is one command instead of archaeology. It documents
every DN, alias and password, pins the validity windows, and runs on
Linux, macOS and Git Bash.

**Adds two guard tests** that fail with an actionable message, naming
the script, while there is still a year of runway:

- `BundledTestCertificateExpiryTest` (app/core) checks all seven formats
parse, are in their validity window, and have more than 365 days left
- `BundledWorkflowCertificateExpiryTest` (proprietary) does the same for
the valid pair, and additionally asserts the expired fixture is still
expired and the not-yet-valid one is still in the future

That last pair matters: those two fixtures exist to test a validity
outcome, and each one silently stops testing anything once the clock
passes its window.

## Verification

Run locally against the regenerated bytes, on the exact content
committed here:

```
./gradlew :stirling-pdf:test --tests '*CertSignControllerTest*' --tests '*BundledTestCertificateExpiryTest*' \
  --tests '*PdfSigningServiceImplTest*' --tests '*ValidateSignatureControllerMoreTest*' \
  --tests '*CertificateValidationServiceMoreTest*'
BUILD SUCCESSFUL

./gradlew :proprietary:test --tests '*BundledWorkflowCertificateExpiryTest*' --tests '*CertificateValidationIntegrationTest*' \
  --tests '*SigningFinalizationServiceMoreTest*' --tests '*ServerCertificateServiceTest*' \
  --tests '*CertificateSubmissionValidatorTest*' --tests '*WorkflowSessionServiceTest*'
BUILD SUCCESSFUL
```

`spotlessCheck` passes on both modules.
2026-08-26 10:43:17 +00:00
dependabot[bot]andAnthony Stirling 72b7892312 Translations + com.squareup.okhttp3:okhttp-bom from 5.3.2 to 5.4.0 (#7599)
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Signed-off-by: dependabot[bot] <support@github.com>
2026-08-26 08:05:45 +01:00
James Brunton 353df7a647 Improve modals in Sources page in Processor (#7664)
# Description of Changes

Various changes throughout to try and convert the bulk of the dev UI
sources modals to production quality. Changes include:

- Fixing inconsistencies between different modals
- Hide things users will rarely need to change behind advanced
- Removed clutter in the UI
- Renaming settings in terms that the user will understand and care
about

<img width="2360" height="3068" alt="image"
src="https://github.com/user-attachments/assets/2c637e8f-bc1b-4c7e-98cb-d836ae626ba5"
/>

<img width="2360" height="3008" alt="image"
src="https://github.com/user-attachments/assets/d894aadc-7c83-41d1-b614-881637a6bd34"
/>
2026-08-25 15:59:16 +00:00
James Brunton 0d75715af2 Fix flaky e2e tests (#7595)
# Description of Changes
e2e Playwright tests are currently failing intermittently on all
platforms for different reasons, most notably WebKit, which seems to
fail much more often than the others. This PR attempts to fix the
issues. I've ran the e2e tests a few times now and they don't seem to be
inconsistent any more, but it's difficult to tell if all the issues are
genuinely fixed due to the inconsistent nature. As far as I can tell,
I've not broken anything though.
2026-08-25 15:56:44 +00:00
github-actions[bot]andFrooodle b44202185a chore: update Gradle to 9.7.1 (#7673)
Automated update of the Gradle wrapper and Gradle Docker build images.

Gradle version: `9.7.1`
Docker image: `gradle:9.7.1-jdk25`

Co-authored-by: Frooodle <77850077+Frooodle@users.noreply.github.com>
2026-08-25 12:36:38 +00:00
EthanHealy01 49c1e75ced Surface recorded failures in a notification bell (Review Flow PR 4) (#7478)
Review Flow PR 4. Stacked on #7477. Recorded failures appear in a
notification bell, showing each reader the failures they are allowed to
see and the actions they can actually take.

Scope is deliberately viewing and routing only. Resolving a failure —
retry, decrypt-and-retry — is #7479, which also brings the write path
for it; nothing resolution-shaped ships here, not even dark.

## What's added

**A notification bell** in the editor and the processor shell. Polls
`GET /api/v1/notifications` every 30 seconds, shows an unread badge, and
lists open failures newest first. Each row shows the failure's title,
its message with **Copy error** and **Show full message** chips, an
occurrence count, and its available actions.

**A notification API** (`stirling.software.proprietary.notification`),
derived from failures on read rather than stored in its own table:

| Route | Purpose |
|---|---|
| `GET /api/v1/notifications` | the caller's open failures, newest first
|

Read-only by design: every action the bell offers is one the client runs
on its own device, so there is nothing to post back. Every id is
prefixed (`failure:<uuid>`), so the bell never holds a raw failure id it
could hand to a failure endpoint.

**Per-reader actions.** A `FailureKind` declares each action with an
audience (`OWNER`, `TEAM_REVIEWER`, `ANYONE_WHO_SEES`). The server
resolves that against the reader and derives `Ownership` (`MINE` /
`THEIRS` / `UNOWNED`) from the row's actor, so an admin reviewing
someone else's failure is not offered a document their browser does not
hold. Adding a failure kind requires no frontend change.

**Server-run and client-run actions are distinguished.**
`FailureActionId` carries an `Execution` facet; the registry requires a
bean only for server actions, and dispatching a client action on the
failure surface returns 400. The notification projection goes further:
it carries only client-run offers, so the bell cannot be sent a button
it would refuse to draw.

**Actions in the bell:** at most two. The owner of the document gets
**View file** (opens it in the editor); a team reviewer gets **View in
processor** (dev builds only). Dismiss stays on the failure queue in
`/processor/documents` — deciding a failure's fate belongs to the review
surface, not the panel that announces it. An action id the build has not
wired is skipped rather than rendered dead, so the server can ship new
kinds ahead of the clients that understand them.

**Attended policy runs record their document.** `POST
/api/v1/policies/{id}/run` accepts an optional opaque `fileId`, recorded
when the run carries exactly one primary document. This is what lets a
repeat fold onto one incident instead of opening a new one per upload,
lets deleting the file clear its failure, and lets the owner open the
document from the row.

## Behaviour changes

- **The bell re-reads as soon as a failure you caused is recorded**,
rather than leaving you to wait out a poll interval for news of your own
upload. Applies to a failed tool run and to a policy run reaching
`FAILED`. Other people's failures still arrive on the poll, which is
what it is for.
- **An action the reader cannot use is not rendered.** Where the server
gave a reason for withholding it, that reason appears as the row's
one-line note. An action that was never offered to that reader produces
no note.
- **Deleting a document closes every incident about it that the deleter
caused**, including a failed policy run on their own upload, so a user's
own errors leave the bell with the file rather than lingering with a
dead button.
- **The failures list in `/processor/documents` stays behind
`import.meta.env.DEV`**, and View in processor is gated to match so it
cannot navigate to a section that is not mounted. Both lift when
failures get their own review screen.
- **One poll for all bells.** The bell is mounted in three places; the
list, document lookups and read marker are shared, so mounting more than
one does not multiply requests.
- `ACKNOWLEDGE` is no longer offered by any kind. The id, bean and
status remain so existing rows stay readable.

## Known limits

- The poll does not pause when the tab is hidden.
- No retention or per-team cap on `file_run_events`.

## How to test

Needs a proprietary or SaaS build with login enabled. `task dev:all`,
then sign in.

1. **Create a failure.** Add a password-protected PDF to the editor and
choose **Skip for now** when it asks to unlock. The upload starts a
policy run that fails on it.
2. **Watch the bell.** The badge should appear within a second or two,
not after 30 — this is the refresh-on-failure path. Open it: a row
titled "Password-protected document" with the error message and the two
chips.
3. **The buttons should be View file and View in processor, nothing
else.** No Dismiss and no retries: dispositions live on the review
surface, resolutions in #7479.
4. **View file** closes the panel and selects that document in the
editor.
5. **Dismiss from the queue instead.** Open `/processor/documents` (dev
build), find the row in the failures list and dismiss it there; the bell
drops it on its next read.
6. **Confirm the local-document probe.** Create a second failure, then
delete that file from the editor and reload. Its incident closes with
it; a row whose document is still present keeps **View file**.
7. **Confirm attribution end to end.** Sign in as a plain member, run a
shared policy on your own upload so it fails. The member sees their own
row in the bell. Sign in as the team leader: they see it too, but with
**View in processor** instead of **View file**, because the document is
not in their browser.
8. **Confirm folding.** Add the same locked PDF again and skip again.
The existing row's occurrence count increases rather than a second row
appearing.
9. **Confirm one poll for many bells.** Open the editor and the
processor in two tabs. Each tab issues its own poll, but within a tab
the several mounted bells share one — the Network tab should show one
`GET /api/v1/notifications` per 30s per tab, not three.

## Migration

None. No new column and no new value in any CHECK-constrained enum;
`CheckConstrainedEnumsTest` fails if that changes.
2026-08-24 22:29:41 +00:00
stirlingbot[bot] 826e487f00 Update Frontend 3rd Party Licenses (#7650)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-24 22:16:25 +00:00
stirlingbot[bot] bcad2cd486 Update Backend 3rd Party Licenses (#7653)
Auto-generated by stirlingbot[bot]

This PR updates the backend license report based on dependency changes.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-24 22:04:00 +00:00
79686a3a09 form field editing (#6655)
# Description of Changes

Building ontop of a users draft PR for form creation tools

**Fill Form** becomes a full **Form Editor**: fill, create, modify and
delete AcroForm fields visually. Builds on the community form-creation
draft, plus a UX/UI rework pass.

- **Backend**: `/api/v1/form` endpoints — `fields-with-coordinates`,
`add/modify/delete-fields`, combined `edit-fields` (one round-trip),
`fill`, `extract-csv/xlsx`; supports text (multiline, comb), checkbox,
dropdown, list box, radio, button actions (reset/print/URL/submit) and
signature placeholders
- **Create**: type palette, click-or-drag placement with snap guides,
inline property editor, batch "Add N fields"
- **Modify**: move/resize on the page, arrow-nudge + Delete key, X/Y/W/H
inputs, staged edits/deletes with chips, discard
- **Fill**: live progress + required tracking, flatten toggle, Export
menu (JSON/CSV/XLSX), Ctrl/Cmd+S
- **Safety**: confirm dialog before discarding staged work; empty
required fields warn with "Save anyway" instead of blocking
- **UI**: consistent panel skeleton (fixed header / scrolling list /
pinned actions), empty states that link into Create, full i18n with
plural keys



[walkthrough.html](https://github.com/user-attachments/files/30508976/walkthrough.html)



---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: Denys Vitali <denys@denv.it>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-08-24 20:45:54 +00:00
dependabot[bot] e51885b379 build(deps): bump step-security/harden-runner from 2.20.0 to 2.21.0 (#7623)
Bumps
[step-security/harden-runner](https://github.com/step-security/harden-runner)
from 2.20.0 to 2.21.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/step-security/harden-runner/releases">step-security/harden-runner's
releases</a>.</em></p>
<blockquote>
<h2>v2.21.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Support for denied endpoints in block mode. This is included in the
enterprise tier. Customers can deny outbound calls, for example, to
public package registries.</li>
<li>Improved Support for AWS CodeBuild GitHub Actions Runners.</li>
<li>Bug fixes.</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/step-security/harden-runner/compare/v2.20.1...v2.21.0">https://github.com/step-security/harden-runner/compare/v2.20.1...v2.21.0</a></p>
<h2>v2.20.1</h2>
<h2>What's Changed</h2>
<ul>
<li>AWS CodeBuild-hosted runner support</li>
<li>Implicitly allow single-labeled (internal) domains in
block-mode</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/step-security/harden-runner/compare/v2.20.0...v2.20.1">https://github.com/step-security/harden-runner/compare/v2.20.0...v2.20.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/step-security/harden-runner/commit/05e31511f85b41b11d1cf0ef85d0992719546e2c"><code>05e3151</code></a>
Merge pull request <a
href="https://redirect.github.com/step-security/harden-runner/issues/684">#684</a>
from step-security/rc-42</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/0f37afa338f57c61ee3dfc274daca8834963d83e"><code>0f37afa</code></a>
fix: ignore denied-endpoints on non-enterprise tier</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/93b58ee491c5b6cf3a5324966fca2908f8d447f3"><code>93b58ee</code></a>
fix: resolve cache host read-first and never downgrade egress
policy</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/e7399dd3e93d6c159d314af54b4704bc48abf6bc"><code>e7399dd</code></a>
fix: align deny-list mode detection with agent and log when both
endpoint inp...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/c16689f716a10cdfd9cfe22e63938b8c6c0657de"><code>c16689f</code></a>
test: add denied_endpoints to Configuration fixtures and cover deny-list
merge</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/40b99cf0c7161e4dcdc6c5508927188b65028df9"><code>40b99cf</code></a>
Merge pull request <a
href="https://redirect.github.com/step-security/harden-runner/issues/682">#682</a>
from rohan-stepsecurity/rp/feat/codebuild-self-v2</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/fedec027a205365a7d64001a81931e4c36a1af6e"><code>fedec02</code></a>
Merge branch 'rc-42' into rp/feat/codebuild-self-v2</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/5361fb178b926b2be6df52e11ee257823821567b"><code>5361fb1</code></a>
feat: add build artifacts</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/286474fffe0b8fe7c9db855f132d04a9b48ab564"><code>286474f</code></a>
feat: Support Bravo agent install on CodeBuild runners</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/051ec05283d064bd82f41279db4f70f0717bf778"><code>051ec05</code></a>
Merge pull request <a
href="https://redirect.github.com/step-security/harden-runner/issues/683">#683</a>
from h0x0er/jatin/deny-list</li>
<li>Additional commits viewable in <a
href="https://github.com/step-security/harden-runner/compare/v2.20.0...05e31511f85b41b11d1cf0ef85d0992719546e2c">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-08-24 17:39:57 +00:00
Anthony Stirling ddee690c58 Exclude Python virtualenvs from the Docker build context (#7658)
# Description of Changes

Stirling engine docker slimming

Exclude Python virtualenvs from the Docker build context
Drop unused provider SDKs from the engine dependency set
Retry the SQLite WAL switch when workers race on startup
Build the engine image in two stages and run it unprivileged
Swap voyage SDK for api call removing 200MB bloat
Bundle the AI engine in the fat image
Publish the AI engine as a standalone image


886MB to 295MB in docker file

And Docker fat is only 230MB bigger after adding (since it already has
python and some deps)

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-24 16:23:55 +00:00
James Brunton 158187ac46 Fix Documents tab in Processor (#7569)
# Description of Changes
The Documents tab in the Processor is supposed to be available to all
Processor users, but because the API is built on top of the Audit data,
which is only for enterprise users, the API call always fails with 403.
This means that it never fills the query cache, so every time you go
back to the tab it has to reload all the data for a couple of seconds
(and will fail again). This fixes the API so that it's available to any
Processor user instead of just enterprise users. Also, the documents
data was only being written to the log on an enterprise license, so I've
changed it so that data is always tracked in the audit log because
otherwise the Documents tab would still be useless to non-enterprise
users.

The Audit Log tab was also available to all Processor users, but would
have the same issue where the table would never load because the API
would 403 as well. I've just made the Audit Log tab disabled for
non-enterprise users now. We might want to do something to signpost it a
bit more that it's an enterprise-specific feature, but it's better than
nothing for now.
2026-08-24 15:06:22 +00:00
Anthony StirlingandJames Brunton e50c3de0a9 Run classification locally first and only escalate an unsure verdict to the AI (#7580)
Split out of #7574 — this is the classification half, which is
independent of the editor-source work and can land on its own.

## What this does

- **Runs the local heuristic first and only escalates an unsure verdict
to the AI.** A high-confidence local answer stands; anything less (or a
file the heuristic hasn't reached yet) goes to the engine. A wrong label
costs more than an engine call, so the bar is deliberately strict.
- **Makes `classify` an authorable pipeline task**, so it can be used as
a step like any other tool, and skips files that are already classified.
- **Leaves the seeded Classification policy unowned** rather than naming
a `system` placeholder that was never a real user; existing seeds are
repaired on boot.

## Review feedback applied

From @jbrunton96 on #7574:

- **The generic runner no longer names classification.** Everything
classification-specific moved into
`proprietary/data/classificationPolicy.ts`, and `usePolicyAutoRun` now
asks capability questions instead: `policyRewritesDocument`,
`policyDeliversOutputFiles`, `policyRequiresAiEngine`,
`shouldDispatchToAi`. There is no `id === "classification"` left in the
runner.
- **Ordering is no longer a name in the runner.**
`pinClassificationLast` is gone; the runner sorts annotating policies
after rewriting ones. The constraint is real: an annotating policy is
non-blocking, so a rewriting one running after it forks from the
pre-annotation version and drops the labels. To be straight about what
this is and isn't - see "Still open" below - `policyRewritesDocument` is
still keyed on the category id, not on a property each policy declares.
The check moved out of the runner; it did not stop being a check on one
id.
- **Confidence is typed.** New `ClassificationConfidence` union in
`core/types/fileContext.ts`, reused by `fileStorage`,
`HeuristicConfidence`, and the trusted-verdict constant instead of being
respelled at each site.
- **Comments trimmed** to the repo's 2-line guideline, and a stale
seeder javadoc that still claimed an internal-user owner was corrected.

## Still open, deliberately

`classificationPolicy.ts` answers its capability questions with
`categoryId === "classification"`. That is the same check relocated, not
removed, and the module doc now says so outright.

Deliberate, for two reasons:

- **The concept it would be declared against is going away.** Policies
are becoming pipelines with labels behind a separate enforcement layer,
which removes the category the flag would live on. A capability system
built on `categoryId` today gets migrated twice.
- **Classification is genuinely privileged, not accidentally special.**
It is the only policy with a browser-side implementation, so it can
answer without the server. That is a product decision, and a local-only
mode for set scenarios is planned - the flag for it should be designed
with that feature, not guessed at now.

The end state for the rest: an in-place output mode retires the ordering
rule and `policyDeliversOutputFiles`, and a run result that can carry
findings as well as files retires the remainder. Both touch the import
path, which is the most delicate code in `usePolicyAutoRun` - not
something to bolt on to a PR that has already been split once.

Nothing is broken by leaving it. A user-built classify pipeline still
gets its labels: the generic import path reads them off the returned
PDF. It versions the file instead of labelling in place, and it misses
the local-heuristic shortcut, so it always bills the engine.

## Testing

- `classificationPolicy.test.ts` — 12 cases covering each capability and
the escalation rule
- Full frontend `proprietary` project: 39 files / 442 tests
- `:proprietary:test` for `DefaultClassificationPolicySeederTest` +
`ClassifyLabelControllerTest`
- `tsc --noEmit` on core, proprietary, portal, saas, desktop, cloud

---------

Co-authored-by: James Brunton <jbrunton96@gmail.com>
2026-08-24 14:16:34 +00:00
dependabot[bot] 8e7501aec1 build(deps): bump org.snakeyaml:snakeyaml-engine from 3.0.1 to 3.1.1 (#7655)
Bumps org.snakeyaml:snakeyaml-engine from 3.0.1 to 3.1.1.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=org.snakeyaml:snakeyaml-engine&package-manager=gradle&previous-version=3.0.1&new-version=3.1.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-24 12:38:54 +00:00
James Brunton dbd60d4765 Replace Prettier with Oxfmt (#7422)
# Description of Changes
Prettier takes about 10 seconds to run over our frontend folder, but
[Oxfmt](https://oxc.rs/docs/guide/usage/formatter.html) does an (almost)
identical job in 0.2 seconds. This PR converts our Prettier integration
to an equivalent Oxfmt integration. There's exactly 2 files in the
frontend folder that Oxfmt formats differently to Prettier so it'll
barely cause any disruption to the source.

I've removed the `--check` option from the frontend tool models
generator as part of this because we can do the same thing with Task
easily enough and Oxfmt isn't directly importable like Prettier since
it's a Rust binary instead of a JS library. Originally I was shelling
out to Oxfmt on single-file mode to keep it all in memory but it just
seemed more likely that there'd be config mismatches between that script
and the task so I think it's better this way.
2026-08-24 10:34:12 +00:00
James Brunton 629f501e9c Fix any type usages in frontend (#7617)
# Description of Changes
Follow-on from #7334. Fix more `any` type usages and ban them in the
linter. We're starting to get down to only difficult folders left now,
so some of these fixes replace an excluded folder with a couple of
individual files to reduce scope to manageable levels.

There are two real behaviour changes in this PR because of bugs that
were never caught due to the lack of proper typing:
- In the Google Drive service, `lastModified` was always `undefined`
because it should have been read via `lastModifiedUtc`, which it now is.
This means that files being read from Google Drive should now accurately
retain their last modified date from Drive.
- In the error toasts, there was translation logic to try and make
friendlier error messages, but it'd never actually fire since it relied
on `i18n` being written to `globalThis`, which it never was. It now
imports the singleton instead so that translation should start working.

I also had to tweak the way that FitText works because it was relying on
`any` typing to mix refs between different places where they weren't
technically compatible but I've changed it to go via a function and the
behaviour doesn't change.
2026-08-24 10:33:50 +00:00
EthanHealy01 1df372764f Mobile follow-ups to #7518: tool-list search, and drop the empty overflow menu (#7660)
# Description of Changes

Follow-up to #7518, picking up two mobile rough edges found while going
over that branch. Two changes, one commit each.

## 1. Tool search back in the tool list (mobile)

Tool search lives in the workbench bar's super search, which on mobile
sits on the Workspace slide. So searching for a tool meant swiping off
the tool list, typing, then swiping back. This puts a filter at the head
of the tool panel on mobile. Reuses the existing `ToolSearch` component
in `mode="filter"`, the same one the desktop fullscreen picker uses.
Drives `setSearchQuery` on `ToolWorkflowContext`, so the query,
filtering and grouped results are all existing paths. `ToolPanel` takes
a new `showSearch` prop; `RightSidebar` passes `showSearch={isMobile}`.
Desktop renders exactly as before.

**To test:**

- Open the editor at a phone-width viewport (under 1024px).
- A "Search tools..." field should sit above Favourites / Recommended in
the Tools pane.
- Typing filters into grouped results. Clearing goes back to the compact
list.
- It hides once a tool is open, and comes back on the way out.
- On desktop the field should not appear at all.

## 2. The mobile overflow menu opened with nothing in it

`WorkbenchBarMobileActions` rendered its kebab trigger unconditionally.
But every item inside is gated on `currentView === "viewer"` or
`!isCustomView`. In a `custom:*` workbench both are false, so the
dropdown was empty. `WorkbenchBarDesktopActions` renders nothing in that
case, so this only showed on phones. Now returns `null` when neither
group applies, with the two conditions named so the trigger and the
items can't drift apart again.

**To test:**

- Phone-width viewport, load a PDF.
- Open a tool with its own workbench view: Compare, Get Info report,
Show JS, Validate Signature, Edit Table of Contents, or PDF Text Editor.
- The kebab at the right of the workbench bar should be gone entirely,
rather than opening an empty menu.
- Back in the viewer or page editor it should still be there, with Print
/ Download / Save As / Close.
2026-08-23 22:07:35 +00:00
dependabot[bot] 5f0fe06bbc build(deps): bump logback from 1.6.1 to 1.6.3 (#7622)
Bumps `logback` from 1.6.1 to 1.6.3.
Updates `ch.qos.logback:logback-core` from 1.6.1 to 1.6.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/qos-ch/logback/releases">ch.qos.logback:logback-core's
releases</a>.</em></p>
<blockquote>
<h2>Logback 1.6.3</h2>
<h1>2026-08-14 Release of logback version 1.6.3</h1>
<ul>
<li>
<p>In response <a
href="https://www.cve.org/cverecord?id=CVE-2026-19880">CVE-2026-19880</a>,
<code>MDCBasedDiscriminator</code> (used by
<code>SiftingAppender</code>) now strips forward and backward slashes
(<code>/</code>, <code>\</code>) from MDC values before they are used as
discriminating keys. This prevents path segments from escaping into
destinations controlled by an attacker. When sanitisation actually
changes a value, a warning is emitted; the warning is rate-limited (a
small batch, then a lull of about ten minutes).</p>
</li>
<li>
<p>Colour console support is split out into a dedicated <a
href="https://logback.qos.ch/manual/appenders.html#JansiConsoleAppender"><code>JansiConsoleAppender</code></a>.
It wraps stdout or stderr with Jansi so ANSI escape sequences (for
example coloured patterns) render correctly on terminals that need it,
notably Windows. Prefer this class over the older path described next.
See the <a
href="https://logback.qos.ch/manual/appenders.html#JansiConsoleAppender">appenders
documentation</a>.</p>
</li>
<li>
<p>The <code>withJansi</code> property on <code>ConsoleAppender</code>
is <strong>deprecated</strong>. Existing configurations that still set
<code>&lt;withJansi&gt;true&lt;/withJansi&gt;</code> continue to work
for compatibility, but new setups should use
<code>JansiConsoleAppender</code> instead.</p>
</li>
<li>
<p><code>ConsoleAppender</code> no longer treats the process console as
an exclusive resource: stopping it does not close
<code>System.out</code> / <code>System.err</code>.
<code>JansiConsoleAppender</code> pairs each
<code>AnsiConsole.systemInstall()</code> with
<code>systemUninstall()</code> on stop, so repeated start/stop cycles do
not leave Jansi installed or tear down streams shared with the rest of
the JVM. Related behavior is covered by tests for <a
href="https://redirect.github.com/qos-ch/logback/issues/1063">issues/1063</a>.</p>
</li>
<li>
<p>Invocation throttling helpers were reworked:
<code>SimpleInvocationGate</code> is renamed
<code>FixedIntervalInvocationGate</code>, and
<code>BatchedFixedIntervalInvocationGate</code> allows a short burst of
invocations before applying a fixed lull. The sanitisation
warning above uses the batched gate.</p>
</li>
<li>
<p>The JPMS <code>module-info</code> for logback-core now exports the
<code>ch.qos.logback.core.property</code> package, which had been
missing from the module descriptor.</p>
</li>
<li>
<p>A bit-wise identical binary of this version can be reproduced by
building from <a href="https://github.com/qos-ch/logback">source
code</a> at commit <code>e8e824dede022a6d7208b36cfa875b0d1b7772f3</code>
associated with the tag <code>v_1.6.3</code>. The release was built
using Java &quot;21&quot; 2023-10-17 LTS build 21.0.1.+12-LTS-29 under
Linux Debian 11.6.</p>
</li>
</ul>
<p>--
Sponsoring SLF4J/logback/reload4j at <a
href="https://github.com/sponsors/qos-ch">https://github.com/sponsors/qos-ch</a></p>
<h2>Logback 1.6.2</h2>
<p><a
href="https://github.com/user-attachments/assets/9ceaf157-b758-4188-815d-edfe4e1b4edd">https://github.com/user-attachments/assets/9ceaf157-b758-4188-815d-edfe4e1b4edd</a></p>
<h1>2026-08-10 Release of logback version 1.6.2</h1>
<ul>
<li>
<p>Configuration analysis now detects <em>contradictory caller-data
inclusion instructions</em>. For example, an <code>AsyncAppender</code>,
<code>SocketAppender</code> or <code>SMTPAppender</code> with
<code>includeCallerData</code> left at the default <code>false</code> is
incompatible with a layout or encoder pattern that uses a caller-data
converter such as <code>%C</code>, <code>%M</code>, <code>%L</code>,
<code>%F</code>, <code>%l</code> or <code>%caller</code>. At runtime
those converters would print question marks and still incur extraction
cost on a worker thread. Logback now emits a configuration-time warning
when such instructions disagree. See <a
href="https://logback.qos.ch/codes.html#callerContradiction">codes.html#callerContradiction</a>
for details. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1059">issues/1059</a>
by <a href="https://github.com/leeychee">leeychee</a>. The initial
analysis was contributed by <a
href="https://github.com/seonwooj0810">seonwoo_jung</a>.</p>
</li>
<li>
<p>Caller-contradiction analysis can be turned off by setting the
<code>logback.skipCallerContradictionAnalysis</code> variable to
<code>true</code>, either as a system property
(<code>-Dlogback.skipCallerContradictionAnalysis=true</code>) or as a
property in the configuration file:</p>
<pre lang="xml"><code>&lt;property
name=&quot;logback.skipCallerContradictionAnalysis&quot;
value=&quot;true&quot;/&gt;
</code></pre>
</li>
<li>
<p><code>SimpleSocketServer</code> and
<code>SimpleSSLSocketServer</code> now require an explicit client IP
whitelist. On the command line, pass one or more allowed addresses
(single IPs or CIDR ranges) after the configuration file. An empty
whitelist means no clients are accepted. When embedding the server
programmatically, register allowed addresses with
<code>addAllowedClientAddress(String)</code> or
<code>setAllowedClientAddresses(Collection)</code> before clients
connect. See the documentation on <a
href="https://logback.qos.ch/manual/appenders.html#simpleSocketServerClientAccess">restricting
client access</a>.</p>
</li>
<li>
<p>Added <code>ThrowableProxyVOBuilder</code> for assembling a
<code>ThrowableProxyVO</code> field by field, with a corresponding
<code>ThrowableProxyVO.builder()</code> entry point.</p>
</li>
<li>
<p>Dependency analysis handlers now run their <code>postHandle</code>
method after child models have been processed, so checks that depend on
nested appenders (such as caller-contradiction analysis) see a complete
picture.</p>
</li>
<li>
<p>Updated several dependencies, including Angus Mail to 2.0.4 and Jetty
(test) to 12.1.12.</p>
</li>
<li>
<p>A bit-wise identical binary of this version can be reproduced by
building from <a href="https://github.com/qos-ch/logback">source
code</a> at commit e3d78330ad1ba024fd987fd00c3ffb9cfcdb07dc associated
with the tag <code>v_1.6.2</code>. The release was built using Java
&quot;21&quot; 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian
11.6.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/qos-ch/logback/commit/e8e824dede022a6d7208b36cfa875b0d1b7772f3"><code>e8e824d</code></a>
prepare release 1.6.3</li>
<li><a
href="https://github.com/qos-ch/logback/commit/761821bfaacac3a0ad44fa546cfc814429bf9312"><code>761821b</code></a>
MDCBasedDiscriminator has a gated warning mechanism</li>
<li><a
href="https://github.com/qos-ch/logback/commit/53ed1229008d8b1902f5c234deaa07d742890879"><code>53ed122</code></a>
update copyright year</li>
<li><a
href="https://github.com/qos-ch/logback/commit/c7e2db244671ffa916182b5da8c89579eb54a645"><code>c7e2db2</code></a>
rename SimpleInvocationGate as FixedIntervalInvocationGate</li>
<li><a
href="https://github.com/qos-ch/logback/commit/b5aa931b096a4b0b6a9e140b74fabe7da152cbf0"><code>b5aa931</code></a>
added BatchedSimpleInvocationGate</li>
<li><a
href="https://github.com/qos-ch/logback/commit/1f22af7686aadd25c08b4bd1e6943a906a743ad4"><code>1f22af7</code></a>
add javadocs to SimpleInvocationGate</li>
<li><a
href="https://github.com/qos-ch/logback/commit/638ffa7e7852478b605a91b3e91238ff26f8158c"><code>638ffa7</code></a>
prevent forward and backward slashes to escape to other directories</li>
<li><a
href="https://github.com/qos-ch/logback/commit/7d6b9a4f8c8996834c0a694f6c141705a003d7bb"><code>7d6b9a4</code></a>
add missing ch.qos.logback.core.property package</li>
<li><a
href="https://github.com/qos-ch/logback/commit/fa25930346f35636fb6a077c1f66ebb06edd3b6f"><code>fa25930</code></a>
add an extension path in ConsoleAppender for JansiConsoleAppender</li>
<li><a
href="https://github.com/qos-ch/logback/commit/c73b43f2011f9d4545abc7ea461172276a0a43b3"><code>c73b43f</code></a>
deprecate the withJansi path</li>
<li>Additional commits viewable in <a
href="https://github.com/qos-ch/logback/compare/v_1.6.1...v_1.6.3">compare
view</a></li>
</ul>
</details>
<br />

Updates `ch.qos.logback:logback-classic` from 1.6.1 to 1.6.3
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/qos-ch/logback/releases">ch.qos.logback:logback-classic's
releases</a>.</em></p>
<blockquote>
<h2>Logback 1.6.3</h2>
<h1>2026-08-14 Release of logback version 1.6.3</h1>
<ul>
<li>
<p>In response <a
href="https://www.cve.org/cverecord?id=CVE-2026-19880">CVE-2026-19880</a>,
<code>MDCBasedDiscriminator</code> (used by
<code>SiftingAppender</code>) now strips forward and backward slashes
(<code>/</code>, <code>\</code>) from MDC values before they are used as
discriminating keys. This prevents path segments from escaping into
destinations controlled by an attacker. When sanitisation actually
changes a value, a warning is emitted; the warning is rate-limited (a
small batch, then a lull of about ten minutes).</p>
</li>
<li>
<p>Colour console support is split out into a dedicated <a
href="https://logback.qos.ch/manual/appenders.html#JansiConsoleAppender"><code>JansiConsoleAppender</code></a>.
It wraps stdout or stderr with Jansi so ANSI escape sequences (for
example coloured patterns) render correctly on terminals that need it,
notably Windows. Prefer this class over the older path described next.
See the <a
href="https://logback.qos.ch/manual/appenders.html#JansiConsoleAppender">appenders
documentation</a>.</p>
</li>
<li>
<p>The <code>withJansi</code> property on <code>ConsoleAppender</code>
is <strong>deprecated</strong>. Existing configurations that still set
<code>&lt;withJansi&gt;true&lt;/withJansi&gt;</code> continue to work
for compatibility, but new setups should use
<code>JansiConsoleAppender</code> instead.</p>
</li>
<li>
<p><code>ConsoleAppender</code> no longer treats the process console as
an exclusive resource: stopping it does not close
<code>System.out</code> / <code>System.err</code>.
<code>JansiConsoleAppender</code> pairs each
<code>AnsiConsole.systemInstall()</code> with
<code>systemUninstall()</code> on stop, so repeated start/stop cycles do
not leave Jansi installed or tear down streams shared with the rest of
the JVM. Related behavior is covered by tests for <a
href="https://redirect.github.com/qos-ch/logback/issues/1063">issues/1063</a>.</p>
</li>
<li>
<p>Invocation throttling helpers were reworked:
<code>SimpleInvocationGate</code> is renamed
<code>FixedIntervalInvocationGate</code>, and
<code>BatchedFixedIntervalInvocationGate</code> allows a short burst of
invocations before applying a fixed lull. The sanitisation
warning above uses the batched gate.</p>
</li>
<li>
<p>The JPMS <code>module-info</code> for logback-core now exports the
<code>ch.qos.logback.core.property</code> package, which had been
missing from the module descriptor.</p>
</li>
<li>
<p>A bit-wise identical binary of this version can be reproduced by
building from <a href="https://github.com/qos-ch/logback">source
code</a> at commit <code>e8e824dede022a6d7208b36cfa875b0d1b7772f3</code>
associated with the tag <code>v_1.6.3</code>. The release was built
using Java &quot;21&quot; 2023-10-17 LTS build 21.0.1.+12-LTS-29 under
Linux Debian 11.6.</p>
</li>
</ul>
<p>--
Sponsoring SLF4J/logback/reload4j at <a
href="https://github.com/sponsors/qos-ch">https://github.com/sponsors/qos-ch</a></p>
<h2>Logback 1.6.2</h2>
<p><a
href="https://github.com/user-attachments/assets/9ceaf157-b758-4188-815d-edfe4e1b4edd">https://github.com/user-attachments/assets/9ceaf157-b758-4188-815d-edfe4e1b4edd</a></p>
<h1>2026-08-10 Release of logback version 1.6.2</h1>
<ul>
<li>
<p>Configuration analysis now detects <em>contradictory caller-data
inclusion instructions</em>. For example, an <code>AsyncAppender</code>,
<code>SocketAppender</code> or <code>SMTPAppender</code> with
<code>includeCallerData</code> left at the default <code>false</code> is
incompatible with a layout or encoder pattern that uses a caller-data
converter such as <code>%C</code>, <code>%M</code>, <code>%L</code>,
<code>%F</code>, <code>%l</code> or <code>%caller</code>. At runtime
those converters would print question marks and still incur extraction
cost on a worker thread. Logback now emits a configuration-time warning
when such instructions disagree. See <a
href="https://logback.qos.ch/codes.html#callerContradiction">codes.html#callerContradiction</a>
for details. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1059">issues/1059</a>
by <a href="https://github.com/leeychee">leeychee</a>. The initial
analysis was contributed by <a
href="https://github.com/seonwooj0810">seonwoo_jung</a>.</p>
</li>
<li>
<p>Caller-contradiction analysis can be turned off by setting the
<code>logback.skipCallerContradictionAnalysis</code> variable to
<code>true</code>, either as a system property
(<code>-Dlogback.skipCallerContradictionAnalysis=true</code>) or as a
property in the configuration file:</p>
<pre lang="xml"><code>&lt;property
name=&quot;logback.skipCallerContradictionAnalysis&quot;
value=&quot;true&quot;/&gt;
</code></pre>
</li>
<li>
<p><code>SimpleSocketServer</code> and
<code>SimpleSSLSocketServer</code> now require an explicit client IP
whitelist. On the command line, pass one or more allowed addresses
(single IPs or CIDR ranges) after the configuration file. An empty
whitelist means no clients are accepted. When embedding the server
programmatically, register allowed addresses with
<code>addAllowedClientAddress(String)</code> or
<code>setAllowedClientAddresses(Collection)</code> before clients
connect. See the documentation on <a
href="https://logback.qos.ch/manual/appenders.html#simpleSocketServerClientAccess">restricting
client access</a>.</p>
</li>
<li>
<p>Added <code>ThrowableProxyVOBuilder</code> for assembling a
<code>ThrowableProxyVO</code> field by field, with a corresponding
<code>ThrowableProxyVO.builder()</code> entry point.</p>
</li>
<li>
<p>Dependency analysis handlers now run their <code>postHandle</code>
method after child models have been processed, so checks that depend on
nested appenders (such as caller-contradiction analysis) see a complete
picture.</p>
</li>
<li>
<p>Updated several dependencies, including Angus Mail to 2.0.4 and Jetty
(test) to 12.1.12.</p>
</li>
<li>
<p>A bit-wise identical binary of this version can be reproduced by
building from <a href="https://github.com/qos-ch/logback">source
code</a> at commit e3d78330ad1ba024fd987fd00c3ffb9cfcdb07dc associated
with the tag <code>v_1.6.2</code>. The release was built using Java
&quot;21&quot; 2023-10-17 LTS build 21.0.1.+12-LTS-29 under Linux Debian
11.6.</p>
</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/qos-ch/logback/commit/e8e824dede022a6d7208b36cfa875b0d1b7772f3"><code>e8e824d</code></a>
prepare release 1.6.3</li>
<li><a
href="https://github.com/qos-ch/logback/commit/761821bfaacac3a0ad44fa546cfc814429bf9312"><code>761821b</code></a>
MDCBasedDiscriminator has a gated warning mechanism</li>
<li><a
href="https://github.com/qos-ch/logback/commit/53ed1229008d8b1902f5c234deaa07d742890879"><code>53ed122</code></a>
update copyright year</li>
<li><a
href="https://github.com/qos-ch/logback/commit/c7e2db244671ffa916182b5da8c89579eb54a645"><code>c7e2db2</code></a>
rename SimpleInvocationGate as FixedIntervalInvocationGate</li>
<li><a
href="https://github.com/qos-ch/logback/commit/b5aa931b096a4b0b6a9e140b74fabe7da152cbf0"><code>b5aa931</code></a>
added BatchedSimpleInvocationGate</li>
<li><a
href="https://github.com/qos-ch/logback/commit/1f22af7686aadd25c08b4bd1e6943a906a743ad4"><code>1f22af7</code></a>
add javadocs to SimpleInvocationGate</li>
<li><a
href="https://github.com/qos-ch/logback/commit/638ffa7e7852478b605a91b3e91238ff26f8158c"><code>638ffa7</code></a>
prevent forward and backward slashes to escape to other directories</li>
<li><a
href="https://github.com/qos-ch/logback/commit/7d6b9a4f8c8996834c0a694f6c141705a003d7bb"><code>7d6b9a4</code></a>
add missing ch.qos.logback.core.property package</li>
<li><a
href="https://github.com/qos-ch/logback/commit/fa25930346f35636fb6a077c1f66ebb06edd3b6f"><code>fa25930</code></a>
add an extension path in ConsoleAppender for JansiConsoleAppender</li>
<li><a
href="https://github.com/qos-ch/logback/commit/c73b43f2011f9d4545abc7ea461172276a0a43b3"><code>c73b43f</code></a>
deprecate the withJansi path</li>
<li>Additional commits viewable in <a
href="https://github.com/qos-ch/logback/compare/v_1.6.1...v_1.6.3">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-23 12:41:05 +00:00
dependabot[bot] d34b9f0256 build(deps): bump license-report from 6.8.2 to 6.8.5 in /frontend (#7645)
Bumps [license-report](https://github.com/bepo65/license-report) from
6.8.2 to 6.8.5.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/BePo65/license-report/blob/main/CHANGELOG.md">license-report's
changelog</a>.</em></p>
<blockquote>
<h2><a
href="https://github.com/bepo65/license-report/compare/v6.8.4...v6.8.5">6.8.5</a>
(2026-05-28)</h2>
<h2><a
href="https://github.com/kessler/license-report/compare/v6.8.3...v6.8.4">6.8.4</a>
(2026-04-02)</h2>
<h2><a
href="https://github.com/kessler/license-report/compare/v6.8.1...v6.8.3">6.8.3</a>
(2026-04-01)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>update packages to fix dependabot security warnings (<a
href="https://github.com/kessler/license-report/commit/90d39679d5c458c7d59545bb06b61c32a16bf746">90d3967</a>)</li>
<li>update packages to fix github dependabot security warnings (<a
href="https://github.com/kessler/license-report/commit/02ab3bcd7baaa7985ed3a3164aef5b66a3c58ad0">02ab3bc</a>)</li>
</ul>
<h2><a
href="https://github.com/kessler/license-report/compare/v6.8.0...v6.8.1">6.8.1</a>
(2025-10-22)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>update packages to fix security warnings (<a
href="https://github.com/kessler/license-report/commit/de28bd74a0c98b24b09470fd48ce4324fa3e193b">de28bd7</a>)</li>
</ul>
<h2><a
href="https://github.com/kessler/license-report/compare/v6.7.2...v6.8.0">6.8.0</a>
(2025-05-23)</h2>
<h3>Features</h3>
<ul>
<li>enable usage of .npmrc file (solves issue <a
href="https://redirect.github.com/kessler/license-report/issues/205">#205</a>)
(<a
href="https://redirect.github.com/kessler/license-report/issues/208">#208</a>)
(<a
href="https://github.com/kessler/license-report/commit/935608872386d531226e41bb90a4f1b71148aac5">9356088</a>)</li>
</ul>
<h2><a
href="https://github.com/kessler/license-report/compare/v6.7.1...v6.7.2">6.7.2</a>
(2025-03-01)</h2>
<h3>Bug Fixes</h3>
<ul>
<li>replace path.join for uri with own function (<a
href="https://github.com/kessler/license-report/commit/2f30a8bae9050fc2a75c228f5eea10cf91a88ab5">2f30a8b</a>)</li>
</ul>
<h2><a
href="https://github.com/kessler/license-report/compare/v6.7.0...v6.7.1">6.7.1</a>
(2024-12-10)</h2>
<h2><a
href="https://github.com/kessler/license-report/compare/v6.6.1...v6.7.0">6.7.0</a>
(2024-09-13)</h2>
<h3>Features</h3>
<ul>
<li>use properties of object entries as custom field (<a
href="https://redirect.github.com/kessler/license-report/issues/194">#194</a>)
(<a
href="https://github.com/kessler/license-report/commit/d9519b23d4cdf11c8659c28644eb4fe206df1b5a">d9519b2</a>)</li>
</ul>
<h2><a
href="https://github.com/kessler/license-report/compare/v6.6.0...v6.6.1">6.6.1</a>
(2024-08-02)</h2>
<h2><a
href="https://github.com/kessler/license-report/compare/v6.5.1...v6.6.0">6.6.0</a>
(2024-06-23)</h2>
<h3>Features</h3>
<ul>
<li>change the project from 'default' to named exports (<a
href="https://github.com/kessler/license-report/commit/1e526eb2ebd4395a45473e588a6d3e774fc9dd0d">1e526eb</a>)</li>
</ul>
<h2><a
href="https://github.com/ironSource/license-report/compare/v6.5.0...v6.5.1">6.5.1</a>
(2024-04-26)</h2>
<h2><a
href="https://github.com/ironSource/license-report/compare/v6.4.0...v6.5.0">6.5.0</a>
(2023-10-09)</h2>
<h3>Features</h3>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/BePo65/license-report/commit/a3bc6e5487f833629372a7ee9971dee94de765bd"><code>a3bc6e5</code></a>
Merge pull request <a
href="https://redirect.github.com/bepo65/license-report/issues/262">#262</a>
from BePo65/pu/create-release</li>
<li><a
href="https://github.com/BePo65/license-report/commit/f68eb4be91802b095b033407b1147cf5af1bc9f1"><code>f68eb4b</code></a>
chore(release): 6.8.5</li>
<li><a
href="https://github.com/BePo65/license-report/commit/97767e43ee578aa97696a58d78432f6cf20d1649"><code>97767e4</code></a>
Merge pull request <a
href="https://redirect.github.com/bepo65/license-report/issues/250">#250</a>
from BePo65/dependabot/github_actions/actions/stale-1...</li>
<li><a
href="https://github.com/BePo65/license-report/commit/5aa73e676f80f3bb5d9db535433fc8a6cb8ee6ef"><code>5aa73e6</code></a>
build(deps): bump actions/stale from 10.2.0 to 10.3.0</li>
<li><a
href="https://github.com/BePo65/license-report/commit/b14534d2f366c69b9617dda21e8a0ab16301c255"><code>b14534d</code></a>
Merge pull request <a
href="https://redirect.github.com/bepo65/license-report/issues/261">#261</a>
from BePo65/pu/update-gh-workflow-stale</li>
<li><a
href="https://github.com/BePo65/license-report/commit/1acbb74b7ca80c12a9308da78f309e749d926bcf"><code>1acbb74</code></a>
build: update actions/stale to v 10.2.0 and switch to double quotes</li>
<li><a
href="https://github.com/BePo65/license-report/commit/97d384bcecda69631e824b921bcc81d993e91934"><code>97d384b</code></a>
Revert &quot;chore(release): 6.8.5&quot;</li>
<li><a
href="https://github.com/BePo65/license-report/commit/3ed874213f6920c69c6d3566dfe86d05142264c5"><code>3ed8742</code></a>
Merge pull request <a
href="https://redirect.github.com/bepo65/license-report/issues/260">#260</a>
from BePo65/pu/change-ownership</li>
<li><a
href="https://github.com/BePo65/license-report/commit/5dd5be3b9f7eaa47869bf1ae675a5635647e0ecd"><code>5dd5be3</code></a>
chore(release): 6.8.5</li>
<li><a
href="https://github.com/BePo65/license-report/commit/b0851a8c7df87a956e1b1cf32790ed9486976e57"><code>b0851a8</code></a>
docs: changes caused by transfer of ownership</li>
<li>Additional commits viewable in <a
href="https://github.com/bepo65/license-report/compare/v6.8.2...v6.8.5">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=license-report&package-manager=npm_and_yarn&previous-version=6.8.2&new-version=6.8.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-23 10:35:29 +00:00
Anthony Stirling e9a9dbf644 fix(desktop): inset macOS and Linux app icons to platform icon grids (#7646)
# Description of Changes

The macOS Dock icon renders noticeably larger than every other app. The
cause is that
`icon.icns` was **100% full-bleed** - the red rounded square filled all
1024x1024 with zero
margin. macOS does not mask or inset legacy `.icns` icons, so the
artwork has to carry Apple's
grid itself: an **824x824 body centred on a 1024x1024 canvas**.
Full-bleed therefore rendered
**24% wider and 54% larger in area** than its neighbours.

Linux had the same defect for the same reason - the hicolor PNGs were
94.9-100% full-bleed,
and GNOME's HIG says an app icon is drawn within the canvas but must not
fill it (~10% margin,
so a body around 80%). Those small existing margins were resampling
artifacts, not padding.

Windows is deliberately **left full-bleed**. Microsoft imposes no inset:
target-size assets are
drawn without tile padding and the taskbar simply scales the bitmap into
the slot. `app.ico` is
a pure rename here, byte-identical to before.

## What changed

Icons are now split per platform, since the three platforms disagree
about how much of the
canvas the artwork may fill:

| Path | Owner | Treatment |
| --- | --- | --- |
| `icons/macos/app.icns` | `dmg`, `app` | 824/1024 Apple grid |
| `icons/macos/app-512.png` | build-time only | see note below |
| `icons/linux/app-{16..512}.png` | `deb`, `rpm`, `appimage` | ~10%
margin, KDE's small-size exception at 16/32 |
| `icons/windows/app.ico` | `msi`, NSIS | unchanged, full-bleed |

Linux is selected by a new `tauri.linux.conf.json`. Tauri merges
platform configs with
JSON Merge Patch (RFC 7396), so `bundle.icon` is **replaced wholesale**
rather than appended.

## Notes for reviewers

- **`icons/macos/app-512.png` is build ballast, not a real asset.**
`tauri-codegen` requires a
PNG in the icon list for every non-Windows target, with a hardcoded
fallback to
`icons/icon.png` - a file this PR deletes. Without it the build fails.
It is embedded as
`default_window_icon`, which tao's macOS backend discards
(`set_window_icon` there is a no-op:
  "macOS doesn't have window icons"). Nothing renders it.
- **Linux icon order matters.** The bundler derives the hicolor
directory from each PNG's real
pixel dimensions, so `app-128.png` lands in `128x128/`. `app-512.png` is
listed first because
the first PNG in the list also becomes the window icon, which GTK does
honour.
- **`.imgbotconfig` had to be repointed.** Its previous entry named
`icons/icon.png`, a path this
PR deletes. That exclusion is load-bearing: ImgBot once optimised the
icon to an indexed
palette and `tauri::generate_context!()` rejects non-RGBA icons,
breaking the desktop build
(#6990). All 15 generated PNGs, including the eight inside the `.icns`,
are verified colour
  type 6.
- **Not fixed here:** our corner radius is 14.3% of the body where macOS
and GNOME neighbours sit
near 22%, so the icon still reads squarer than its neighbours. That is a
brand-silhouette
  decision rather than the sizing bug, so it was left alone.
- The 15 pre-existing unused assets (`Square*Logo.png`, `mstile-*`,
`android-chrome-*`,
`android/`, `ios/`) are untouched. No configured bundle target consumes
them.

## Verification

`task check` was **not** run - this PR touches no Java, TypeScript or
engine Python, so it
cannot exercise the change. What was verified directly instead:

- Simulated the RFC 7396 merge and Tauri's `find_icon` resolution per
platform: Windows resolves
to `app.ico`, macOS to `app.icns` plus the stub PNG, Linux to its own
six PNGs. Every path exists.
- Both configs validate against the bundled
`@tauri-apps/cli/config.schema.json`, base and merged.
- Every PNG's real dimensions match its filename, and every body
measures exactly its nominal
  inset (410/512, 154/192, 102/128, 52/64, 28/32, 14/16).
- An overlay diff of the new macOS body against the old artwork shows
only 1px antialiasing
  hairlines - the mark itself is unchanged, only inset.
- Pre-commit hooks pass.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-23 01:01:08 +00:00
dependabot[bot] 207410b50e build(deps): bump astral-sh/setup-uv from 9.0.0 to 10.0.1 (#7624)
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from
9.0.0 to 10.0.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/astral-sh/setup-uv/releases">astral-sh/setup-uv's
releases</a>.</em></p>
<blockquote>
<h2>v10.0.1 🌈 Tolerate transient manifest timeouts</h2>
<h2>Changes</h2>
<p>Thank you <a
href="https://github.com/arguile"><code>@​arguile</code></a>- for making
this action more resilient.</p>
<h2>🐛 Bug fixes</h2>
<ul>
<li>Tolerate transient manifest timeouts <a
href="https://github.com/arguile"><code>@​arguile</code></a>- (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1016">#1016</a>)</li>
</ul>
<h2>🧰 Maintenance</h2>
<ul>
<li>chore: update known checksums for 0.12.4 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1017">#1017</a>)</li>
</ul>
<h2>📚 Documentation</h2>
<ul>
<li>docs: update version references to v10.0.0 @<a
href="https://github.com/apps/github-actions">github-actions[bot]</a>
(<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1014">#1014</a>)</li>
</ul>
<h2>v10.0.0 🌈 Disable automatic caching for sensitive events and new QOL
features</h2>
<h2>Changes</h2>
<p>Another breaking release, directly after v9.0.0 but we think the
added security justifies that.</p>
<h3>Extra security by default</h3>
<p>If you use the default <code>enable-cache: auto</code> this will now
<strong>DISABLE THE CACHE</strong> to protect against cache poisoning
for the following events:</p>
<ul>
<li><code>pull_request_target</code></li>
<li><code>workflow_run</code></li>
<li><code>release</code></li>
</ul>
<p>You can read the full reasoning in <a
href="https://redirect.github.com/astral-sh/setup-uv/issues/984">astral-sh/setup-uv#984</a></p>
<h3><code>version: latest-known</code></h3>
<pre lang="yaml"><code>- name: Install the latest version of uv known to
setup-uv
  uses: astral-sh/setup-uv@v10.0.0
  with:
    version: &quot;latest-known&quot;
</code></pre>
<p>This will now install the latest version with a checksum that is
known by this action. The <a
href="https://github.com/astral-sh/setup-uv/blob/4f6036f71cec78afb113b323f220c9185d983c12/src/download/checksum/known-checksums.ts">known
<code>uv</code> checksums</a> are automatically updated but will take a
release of this action to take effect. You won't be always using the
latest &amp; greatest but you will have an extra level of security.</p>
<h3>Read python version from <code>.tool-versions</code></h3>
<pre lang="yaml"><code>- name: Install uv based on the version defined
in .tool-versions and also set python
  uses: astral-sh/setup-uv@v10.0.0
  with:
    version-file: &quot;pyproject.toml&quot;
&lt;/tr&gt;&lt;/table&gt; 
</code></pre>
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/20cfd1bf945f4377ade1205e4dbc17946fc9a30d"><code>20cfd1b</code></a>
chore: update known checksums for 0.12.4 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1017">#1017</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/d73a0cab66a532d7afa440d9df4a67ea9fe65a30"><code>d73a0ca</code></a>
Tolerate transient manifest timeouts (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1016">#1016</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/ae3b92d1bdb308a10adfe7b8f408e5cc8c30f3f6"><code>ae3b92d</code></a>
docs: update version references to v10.0.0 (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1014">#1014</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/ae62891fec2bb8e7d6c99fc78c9fec3a63790f8d"><code>ae62891</code></a>
chore(deps): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1013">#1013</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/f9cdb47d487aee2be8925d1e57290177ad9e1ac2"><code>f9cdb47</code></a>
Reject paths in .tool-versions (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1007">#1007</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/4f6036f71cec78afb113b323f220c9185d983c12"><code>4f6036f</code></a>
Require pull requests for Dependabot rollups (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1005">#1005</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/8d6402c9b71205b2d8d0b82de531d8fed8430182"><code>8d6402c</code></a>
chore(deps): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/1004">#1004</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/46f427bd47c794e99536b75ffaa9f27602425027"><code>46f427b</code></a>
Read Python version from .tool-versions (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/996">#996</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/8ed89c51143f65ea13eaba62db51dbb8ea52d0a3"><code>8ed89c5</code></a>
ci: pin Alpine container image (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/995">#995</a>)</li>
<li><a
href="https://github.com/astral-sh/setup-uv/commit/8473c7fea42cdfd540f4b01317a17ac5f54126ae"><code>8473c7f</code></a>
chore(deps): roll up Dependabot updates (<a
href="https://redirect.github.com/astral-sh/setup-uv/issues/994">#994</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/astral-sh/setup-uv/compare/c771a70e6277c0a99b617c7a806ffedaca235ff9...20cfd1bf945f4377ade1205e4dbc17946fc9a30d">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=astral-sh/setup-uv&package-manager=github_actions&previous-version=9.0.0&new-version=10.0.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-22 22:18:17 +00:00
Ludy 63ff3afb41 fix: restrict share link route tokens to UUID length (#7626) 2026-08-22 19:07:40 +01:00
Anthony StirlingandEthanHealy01 4457260c60 Make the editor and settings menu mobile friendly-er (#7518)
Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
2026-08-22 18:47:14 +01:00
dependabot[bot] 41e4b67f1d build(deps): bump the simple-java-mail group across 2 directories with 2 updates (#7621)
Bumps the simple-java-mail group with 1 update in the / directory:
[org.simplejavamail:simple-java-mail](https://github.com/bbottema/simple-java-mail).
Bumps the simple-java-mail group with 1 update in the /app/common
directory:
[org.simplejavamail:simple-java-mail](https://github.com/bbottema/simple-java-mail).

Updates `org.simplejavamail:simple-java-mail` from 9.3.1 to 9.3.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/releases">org.simplejavamail:simple-java-mail's
releases</a>.</em></p>
<blockquote>
<h2>v9.3.2</h2>
<p>Fixed <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/702">#702</a>:
clarified that <code>RecipientBuilder</code> accepts one address, while
<code>RecipientsBuilder</code> handles comma- or semicolon-delimited
address lists; see the <a
href="https://www.simplejavamail.org/features.html#section-recipient-builders">recipient
builder examples</a>.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/blob/master/RELEASE_HISTORY.md">org.simplejavamail:simple-java-mail's
changelog</a>.</em></p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/982007485db7a5397e7c2782bfc296c530636a14"><code>9820074</code></a>
released 9.3.2 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/12013d9057026ab2a634f5cfa8adf584653e7a89"><code>12013d9</code></a>
docs(release): prepare 9.3.2 release notes</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/8746cffb54b0957b667a3fca7bd2e199611dfbbd"><code>8746cff</code></a>
fix(recipients): clarify single-address parameter (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/702">#702</a>)</li>
<li>See full diff in <a
href="https://github.com/bbottema/simple-java-mail/compare/9.3.1...9.3.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `org.simplejavamail:outlook-module` from 9.3.1 to 9.3.2

Updates `org.simplejavamail:simple-java-mail` from 9.3.1 to 9.3.2
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/releases">org.simplejavamail:simple-java-mail's
releases</a>.</em></p>
<blockquote>
<h2>v9.3.2</h2>
<p>Fixed <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/702">#702</a>:
clarified that <code>RecipientBuilder</code> accepts one address, while
<code>RecipientsBuilder</code> handles comma- or semicolon-delimited
address lists; see the <a
href="https://www.simplejavamail.org/features.html#section-recipient-builders">recipient
builder examples</a>.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/blob/master/RELEASE_HISTORY.md">org.simplejavamail:simple-java-mail's
changelog</a>.</em></p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/982007485db7a5397e7c2782bfc296c530636a14"><code>9820074</code></a>
released 9.3.2 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/12013d9057026ab2a634f5cfa8adf584653e7a89"><code>12013d9</code></a>
docs(release): prepare 9.3.2 release notes</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/8746cffb54b0957b667a3fca7bd2e199611dfbbd"><code>8746cff</code></a>
fix(recipients): clarify single-address parameter (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/702">#702</a>)</li>
<li>See full diff in <a
href="https://github.com/bbottema/simple-java-mail/compare/9.3.1...9.3.2">compare
view</a></li>
</ul>
</details>
<br />

Updates `org.simplejavamail:outlook-module` from 9.3.1 to 9.3.2


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-22 14:13:02 +00:00
admiralXS 1cb914023c fix: allow anonymous access to /invite/:token accept page (#7612)
## Problem

In the self-hosted build with login enabled, admin-generated invite
links point to the SPA route `/invite/<token>`, but that route is not
covered by the anonymous whitelist. Anonymous users get 401 / redirected
to `/login` before the React app can mount - even though the APIs the
page calls (`/api/v1/invite/validate`, `/api/v1/invite/accept`) are
already whitelisted. Since accepting an invite is how a *new* account is
created, requiring authentication first makes the feature unusable.

## Fix

Add `INVITE_LINK_PATTERN` (`^/invite/[^/]+/?$`) in
`RequestUriUtils.java`, matched at the end of `isPublicAuthEndpoint()` -
mirroring the existing `SHARE_LINK_PATTERN` handling. The invite data
APIs remain protected by their own token validation; only the SPA
bootstrap page becomes anonymously reachable.

## Tests

Added unit tests in `RequestUriUtilsTest.java` mirroring the share-link
tests:

- `/invite/<token>` (with/without trailing slash, with context path) ?
public
- bare `/invite` and `/invite/` ? NOT public (token segment required)
- `/invite/<token>/foo` nested paths ? NOT public
- `/inviteX` prefix over-match ? NOT public

## Verification

Pattern behavior validated against all test cases above. Live-tested on
2.14.3 self-hosted: anonymous `GET /invite/<token>` returned 401 before
the fix; the whitelisted accept flow itself (`validate` + `accept` APIs)
works anonymously end-to-end.
2026-08-21 21:47:57 +00:00
stirlingbot[bot] f5cf5f1077 Update Frontend 3rd Party Licenses (#7616)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-21 20:50:12 +00:00
Ludy 7fb29d002d deps(frontend): upgrade i18next ecosystem to v26 (#7356)
# Description of Changes

- Upgraded `i18next` to 26.3.6.
- Upgraded `react-i18next` to 17.0.11.
- Upgraded `i18next-browser-languagedetector` to 8.2.1.
- Replaced the removed `initImmediate` option with `initAsync`.
- Verified compatibility with the updated `<Trans>` behavior and
language detector APIs.
- No translation changes were required because all `<Trans>` usages use
explicit `i18nKey` values.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-21 11:32:16 +00:00
Anthony Stirling 5f9c396fdd test: remove PdfUaBenchmarkTest (#7613) 2026-08-21 08:41:58 +01:00
stirlingbot[bot] e092b487ec Update Backend 3rd Party Licenses (#7608)
Auto-generated by stirlingbot[bot]

This PR updates the backend license report based on dependency changes.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 21:38:26 +00:00
dependabot[bot] a7c6fa6ef6 build(deps): bump io.swagger.core.v3:swagger-core-jakarta from 2.2.46 to 2.2.53 (#7526)
Bumps io.swagger.core.v3:swagger-core-jakarta from 2.2.46 to 2.2.53.


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=io.swagger.core.v3:swagger-core-jakarta&package-manager=gradle&previous-version=2.2.46&new-version=2.2.53)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 21:29:49 +00:00
brios 4e5b1102b6 refactor(search): improve TextInput clear button styling and add component unit tests (#7578)
# Description of Changes

FIxes minor stylistic problem about the search bar. Mainly the X and the
spacing on the result's icons vs text.


### New
<img width="1594" height="600" alt="image"
src="https://github.com/user-attachments/assets/f3710cad-f1a6-4aa1-9c2a-f3f474fd2dd6"
/>


### Old
<img width="1584" height="618" alt="image"
src="https://github.com/user-attachments/assets/2d9c26a8-2d68-4d86-9d51-b12f90b17fe0"
/>


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 21:21:50 +00:00
dependabot[bot] b3acdc4aab build(deps): bump actions/setup-java from 5.2.0 to 5.7.0 (#7530)
Bumps [actions/setup-java](https://github.com/actions/setup-java) from
5.2.0 to 5.7.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/setup-java/releases">actions/setup-java's
releases</a>.</em></p>
<blockquote>
<h2>v5.7.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Fix npm audit failures on releases/v5 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1154">actions/setup-java#1154</a></li>
<li>Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1151">#1151</a>:
Fix missing wrapper cache distributions by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1153">actions/setup-java#1153</a></li>
<li>Deprecate legacy Adopt distributions in v5 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1186">actions/setup-java#1186</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0">https://github.com/actions/setup-java/compare/v5.6.0...v5.7.0</a></p>
<h2>v5.6.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Backport to v5: Add Maven compiler problem matcher for javac
diagnostics by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1087">actions/setup-java#1087</a></li>
<li>feat: expose cache-primary-key output (<a
href="https://redirect.github.com/actions/setup-java/issues/597">#597</a>)
[v5 backport] by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1089">actions/setup-java#1089</a></li>
<li>dist: Cover Tencent Kona JDK 25 (<a
href="https://redirect.github.com/actions/setup-java/issues/1108">#1108</a>)
[v5 backport] by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1110">actions/setup-java#1110</a></li>
<li>Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1111">#1111</a>:
Preserve Maven toolchains across repeated setup-java runs (<a
href="https://redirect.github.com/actions/setup-java/issues/1099">#1099</a>)
by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1113">actions/setup-java#1113</a></li>
<li>Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1097">#1097</a>/<a
href="https://redirect.github.com/actions/setup-java/issues/1098">#1098</a>
to v5: cache Maven and Gradle wrapper distributions separately by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1122">actions/setup-java#1122</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-java/compare/v5...v5.6.0">https://github.com/actions/setup-java/compare/v5...v5.6.0</a></p>
<h2>v5.5.0</h2>
<h2>What's Changed</h2>
<ul>
<li>chore: enforce pre-PR validation (aggregate scripts, git hooks, PR
checklist) by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1061">actions/setup-java#1061</a></li>
<li>Bump github/codeql-action from 3 to 4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1069">actions/setup-java#1069</a></li>
<li>Bump actions/checkout from 6 to 7 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1068">actions/setup-java#1068</a></li>
<li>Bump actions/setup-python from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1067">actions/setup-java#1067</a></li>
<li>Bump <code>@​typescript-eslint/parser</code> from 8.61.1 to 8.62.0
by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1062">actions/setup-java#1062</a></li>
<li>feat: Add verify-signature plumbing and Temurin+Microsoft
verification support by <a
href="https://github.com/johnoliver"><code>@​johnoliver</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/1060">actions/setup-java#1060</a></li>
<li>Updated jetbrains test: https.request() now catches errors. This
fixes leaking tests as well by <a
href="https://github.com/jmjaffe37"><code>@​jmjaffe37</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/1070">actions/setup-java#1070</a></li>
<li>Fix arm64 e2e workflow tests mislabeled as x64 by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1073">actions/setup-java#1073</a></li>
<li>feat: suppress Maven transfer progress via MAVEN_ARGS by default
(add show-download-progress input) by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1053">actions/setup-java#1053</a></li>
<li>feat: Disable interactiveMode in generated Maven settings.xml by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a>
with <a href="https://github.com/Copilot"><code>@​Copilot</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1052">actions/setup-java#1052</a></li>
<li>Bump prettier from 3.6.2 to 3.9.1 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1066">actions/setup-java#1066</a></li>
<li>chore(deps-dev): bump eslint-plugin-jest from 29.0.1 to 29.15.4 by
<a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1074">actions/setup-java#1074</a></li>
<li>fix: Maven Toolchains grows unexpectedly by <a
href="https://github.com/Okeanos"><code>@​Okeanos</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/534">actions/setup-java#534</a></li>
<li>dist: Support Tencent Kona JDK by <a
href="https://github.com/johnshajiang"><code>@​johnshajiang</code></a>
in <a
href="https://redirect.github.com/actions/setup-java/pull/672">actions/setup-java#672</a></li>
<li>feat: Add set-default option by <a
href="https://github.com/gsmet"><code>@​gsmet</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/1017">actions/setup-java#1017</a></li>
<li>docs: document problem matcher (and how to disable it), Maven
Wrapper caching, and generated interactiveMode by <a
href="https://github.com/brunoborges"><code>@​brunoborges</code></a> in
<a
href="https://redirect.github.com/actions/setup-java/pull/1075">actions/setup-java#1075</a></li>
<li>feat: Add distribution detection support to .sdkmanrc file by <a
href="https://github.com/lukaszgyg"><code>@​lukaszgyg</code></a> in <a
href="https://redirect.github.com/actions/setup-java/pull/975">actions/setup-java#975</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/jmjaffe37"><code>@​jmjaffe37</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-java/pull/1070">actions/setup-java#1070</a></li>
<li><a href="https://github.com/gsmet"><code>@​gsmet</code></a> made
their first contribution in <a
href="https://redirect.github.com/actions/setup-java/pull/1017">actions/setup-java#1017</a></li>
<li><a href="https://github.com/lukaszgyg"><code>@​lukaszgyg</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/setup-java/pull/975">actions/setup-java#975</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/setup-java/compare/v5...v5.5.0">https://github.com/actions/setup-java/compare/v5...v5.5.0</a></p>
<h2>v5.4.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Bump <code>@​typescript-eslint/parser</code> from 8.48.0 to 8.61.1
by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/actions/setup-java/pull/1021">actions/setup-java#1021</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/setup-java/commit/b6effb05e454b25005698d916606bdc6ffcbf961"><code>b6effb0</code></a>
Deprecate legacy Adopt distributions in v5 (<a
href="https://redirect.github.com/actions/setup-java/issues/1186">#1186</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/e498d2a66a953492f322542257b22125c989b422"><code>e498d2a</code></a>
Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1151">#1151</a>:
Fix missing wrapper cache distributions (<a
href="https://redirect.github.com/actions/setup-java/issues/1153">#1153</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/6a3384db745932178632d0e22b2bd28cad1678e6"><code>6a3384d</code></a>
Fix npm audit failures on releases/v5 (<a
href="https://redirect.github.com/actions/setup-java/issues/1154">#1154</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/03ad4de0992f5dab5e18fcb136590ce7c4a0ac95"><code>03ad4de</code></a>
Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1097">#1097</a>/<a
href="https://redirect.github.com/actions/setup-java/issues/1098">#1098</a>:
cache Maven and Gradle wrapper distributions separately...</li>
<li><a
href="https://github.com/actions/setup-java/commit/d229d2e858d9137cc0b3f118fa5184b9f0a44ac4"><code>d229d2e</code></a>
Backport <a
href="https://redirect.github.com/actions/setup-java/issues/1111">#1111</a>:
Preserve Maven toolchains across repeated setup-java runs (<a
href="https://redirect.github.com/actions/setup-java/issues/1">#1</a>...</li>
<li><a
href="https://github.com/actions/setup-java/commit/bbf0f6967066506f72571a96d5d6c67ca42ab460"><code>bbf0f69</code></a>
dist: Cover Tencent Kona JDK 25 (<a
href="https://redirect.github.com/actions/setup-java/issues/1110">#1110</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/513edc4f8710565e4ad696f3b7d8e3bda584a46c"><code>513edc4</code></a>
feat: expose cache-primary-key output (<a
href="https://redirect.github.com/actions/setup-java/issues/597">#597</a>)
[v5 backport] (<a
href="https://redirect.github.com/actions/setup-java/issues/1089">#1089</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/62df799a9c6e3022bb466697c66c36e9a2dbf347"><code>62df799</code></a>
Add Maven compiler problem matcher for javac diagnostics (<a
href="https://redirect.github.com/actions/setup-java/issues/1087">#1087</a>)</li>
<li><a
href="https://github.com/actions/setup-java/commit/176156a187714aaf460b0a3c8f21e8b4f784b978"><code>176156a</code></a>
chore: bump version to 5.6.0 for v5 release line</li>
<li><a
href="https://github.com/actions/setup-java/commit/bf7b8deac240b9cee05eb15ccdb1d2f424a54b9f"><code>bf7b8de</code></a>
build: rebuild dist for backported changes (<a
href="https://redirect.github.com/actions/setup-java/issues/1079">#1079</a>,
<a
href="https://redirect.github.com/actions/setup-java/issues/1083">#1083</a>,
<a
href="https://redirect.github.com/actions/setup-java/issues/1084">#1084</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/setup-java/compare/v5.2.0...b6effb05e454b25005698d916606bdc6ffcbf961">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-08-20 21:10:12 +00:00
dependabot[bot] b549964529 build(deps-dev): bump fpdf2 from 2.8.7 to 2.8.8 in /engine (#7560)
Bumps [fpdf2](https://github.com/py-pdf/fpdf2) from 2.8.7 to 2.8.8.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/py-pdf/fpdf2/releases">fpdf2's
releases</a>.</em></p>
<blockquote>
<h2>Add resource access controls, SVG complexity limits, Optional
Content Groups, and other features and bug fixes</h2>
<h2>[2.8.8] - 2026-08-09</h2>
<blockquote>
<p>[!WARNING]
Upgrading to <strong>fpdf2 2.8.8</strong> is strongly recommended for
applications that render user-provided images, SVGs, or HTML
content.</p>
</blockquote>
<h3>Added</h3>
<ul>
<li>Punjabi (pa) tutorial translation - thanks to <a
href="https://github.com/Pawansingh3889"><code>@​Pawansingh3889</code></a></li>
<li><code>FPDF.svg_limits</code> and <code>SVGLimits</code> to configure
SVG complexity limits while rendering SVG images</li>
<li><code>resource_access_policy</code> and <a
href="https://py-pdf.github.io/fpdf2/Security.html">Security
considerations</a> documentation</li>
<li><a
href="https://py-pdf.github.io/fpdf2/OptionalContent.html"><code>FPDF.optional_content()</code></a>
context manager to mark content as visible on screen only or in print
only, using PDF Optional Content Groups - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/441">#441</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/441">py-pdf/fpdf2#441</a>),
based on a recipe by <a
href="https://github.com/digidigital"><code>@​digidigital</code></a> -
thanks to <a
href="https://github.com/eugen-goebel"><code>@​eugen-goebel</code></a></li>
<li>basic support for SVG <code>&lt;symbol&gt;</code> elements in the
SVG parser - thanks to <a
href="https://github.com/Theo1335"><code>@​Theo1335</code></a></li>
<li>basic support for SVG <code>&lt;switch&gt;</code> elements in the
SVG parser - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/537">#537</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/537">py-pdf/fpdf2#537</a>)
- thanks to <a
href="https://github.com/dannymaaz"><code>@​dannymaaz</code></a></li>
<li>support for keeping aspect ratio for images in templates -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1118">#1118</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1118">py-pdf/fpdf2#1118</a>)
- thanks to <a
href="https://github.com/prateek-dagar"><code>@​prateek-dagar</code></a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li>custom height passed to <code>Paragraph.ln()</code> in a text region
is now applied to the line it terminates instead of the first line of
the following paragraph - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1786">#1786</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1786">py-pdf/fpdf2#1786</a>)
- thanks to <a
href="https://github.com/Sanjays2402"><code>@​Sanjays2402</code></a></li>
<li>the optional <code>numpy</code> import in
<code>image_parsing.py</code> no longer crashes on CPUs unsupported by
numpy's <code>manylinux</code> wheel baseline; <code>RuntimeError</code>
is now treated the same as <code>ImportError</code>, so
<code>numpy</code> degrades to unavailable instead of taking down
<code>import fpdf</code> - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1908">#1908</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1908">py-pdf/fpdf2#1908</a>)
- thanks to <a
href="https://github.com/stumpylog"><code>@​stumpylog</code></a></li>
<li>font state (family, style, size, current font, and the page-level
&quot;font is set&quot; flag) no longer leaks back onto the
<code>FPDF</code> instance after a <code>text_columns()</code> /
<code>text_region()</code> context exits, so a subsequent
<code>pdf.cell()</code> / <code>pdf.write()</code> renders at the
caller's font instead of the last paragraph's - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1804">#1804</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1804">py-pdf/fpdf2#1804</a>)
- thanks to <a
href="https://github.com/Pawansingh3889"><code>@​Pawansingh3889</code></a></li>
<li>text rendering when the first text on a page starts with a fallback
glyph - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1772">#1772</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1772">py-pdf/fpdf2#1772</a>)</li>
<li>preserve boundary-neutral formatting during bidirectional text
preprocessing - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1779">#1779</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1779">py-pdf/fpdf2#1779</a>)</li>
<li>transform application on user space gradients - <em>cf.</em> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1784">#1784</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1784">py-pdf/fpdf2#1784</a>)</li>
<li>dependency extras for camelot-py and endesive on pyproject.toml -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1792">#1792</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1792">py-pdf/fpdf2#1792</a>)</li>
<li>preserve link annotations during dry-run of
<code>FPDF.multi_cell</code> - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1807">#1807</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1807">py-pdf/fpdf2#1807</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>preserve two consecutive markdown links (without space inbetween) -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1814">#1814</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1814">py-pdf/fpdf2#1814</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>support markdown style around markdown links - <em>cf.</em> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1826">#1826</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1826">py-pdf/fpdf2#1826</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>Reset gstate for ToC-rendering - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1837">#1837</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1837">py-pdf/fpdf2#1837</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>preserve markdown format in <code>FPDF.multi_cell</code> in dry-run
- <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1840">#1840</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1840">py-pdf/fpdf2#1840</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>fix page order after dry-run of <code>FPDF.multi_cell</code> in ToC
- <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1836">#1836</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1836">py-pdf/fpdf2#1836</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>rendering SVG arcs with very small sweeps that previously rounded to
zero - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1831">#1831</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1831">py-pdf/fpdf2#1831</a>)</li>
<li>spurious &quot;Not enough horizontal space to render a single
character&quot; error when text without break opportunities is split
into many small fragments, e.g. by a fallback font alternating with the
main font - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1250">#1250</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1250">py-pdf/fpdf2#1250</a>)
- thanks to <a
href="https://github.com/uttam12331"><code>@​uttam12331</code></a></li>
<li>number of surviving escape characters - <strong>cf.</strong> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1215">#1215</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1215">py-pdf/fpdf2#1215</a>)
- thanks to <a
href="https://github.com/amidou-naba"><code>@​amidou-naba</code></a></li>
<li>leading spaces on new lines inside <code>&lt;pre&gt;</code> and
<code>&lt;pre&gt;&lt;code&gt;</code> blocks are no longer dropped -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1063">#1063</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1063">py-pdf/fpdf2#1063</a>)
- thanks to <a
href="https://github.com/eugen-goebel"><code>@​eugen-goebel</code></a></li>
<li><code>FPDF.set_font()</code> can restore <code>current_font</code>
when the selected font state diverged - <em>cf.</em> [PR <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1872">#1872</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/pull/1872">py-pdf/fpdf2#1872</a>)
- thanks to <a
href="https://github.com/gaoflow"><code>@​gaoflow</code></a></li>
<li>embed CID-keyed CFF fonts as raw CFF programs so browser PDF viewers
render them correctly - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1874">#1874</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1874">py-pdf/fpdf2#1874</a>)</li>
<li>fixed broken links on documentation not directly leading to the API
reference - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1876">#1876</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1876">py-pdf/fpdf2#1876</a>)
- thanks to <a
href="https://github.com/iamfazakb"><code>@​iamfazakb</code></a></li>
<li>reject SVG <code>&lt;use&gt;</code> cycles and excessive nested
expansion to prevent resource exhaustion in
<code>FPDF.image()</code></li>
<li>count SVG <code>&lt;switch&gt;</code> elements in SVG complexity
limits</li>
<li>declare the default base state and display order for Optional
Content Groups so PDF viewers can list layers correctly - <em>cf.</em>
[issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1895">#1895</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1895">py-pdf/fpdf2#1895</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>skip byte-for-byte compressed data comparison when zlib-ng is
detected, regardless of OS</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/py-pdf/fpdf2/blob/master/CHANGELOG.md">fpdf2's
changelog</a>.</em></p>
<blockquote>
<h2>[2.8.8] - 2026-08-09</h2>
<h3>Added</h3>
<ul>
<li>Punjabi (pa) tutorial translation - thanks to <a
href="https://github.com/Pawansingh3889"><code>@​Pawansingh3889</code></a></li>
<li><code>FPDF.svg_limits</code> and <code>SVGLimits</code> to configure
SVG complexity limits while rendering SVG images</li>
<li><code>resource_access_policy</code> and <a
href="https://py-pdf.github.io/fpdf2/Security.html">Security
considerations</a> documentation</li>
<li><a
href="https://py-pdf.github.io/fpdf2/OptionalContent.html"><code>FPDF.optional_content()</code></a>
context manager to mark content as visible on screen only or in print
only, using PDF Optional Content Groups - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/441">#441</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/441">py-pdf/fpdf2#441</a>),
based on a recipe by <a
href="https://github.com/digidigital"><code>@​digidigital</code></a> -
thanks to <a
href="https://github.com/eugen-goebel"><code>@​eugen-goebel</code></a></li>
<li>basic support for SVG <code>&lt;symbol&gt;</code> elements in the
SVG parser - thanks to <a
href="https://github.com/Theo1335"><code>@​Theo1335</code></a></li>
<li>basic support for SVG <code>&lt;switch&gt;</code> elements in the
SVG parser - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/537">#537</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/537">py-pdf/fpdf2#537</a>)
- thanks to <a
href="https://github.com/dannymaaz"><code>@​dannymaaz</code></a></li>
<li>support for keeping aspect ratio for images in templates -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1118">#1118</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1118">py-pdf/fpdf2#1118</a>)
- thanks to <a
href="https://github.com/prateek-dagar"><code>@​prateek-dagar</code></a></li>
</ul>
<h3>Fixed</h3>
<ul>
<li>custom height passed to <code>Paragraph.ln()</code> in a text region
is now applied to the line it terminates instead of the first line of
the following paragraph - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1786">#1786</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1786">py-pdf/fpdf2#1786</a>)
- thanks to <a
href="https://github.com/Sanjays2402"><code>@​Sanjays2402</code></a></li>
<li>the optional <code>numpy</code> import in
<code>image_parsing.py</code> no longer crashes on CPUs unsupported by
numpy's <code>manylinux</code> wheel baseline; <code>RuntimeError</code>
is now treated the same as <code>ImportError</code>, so
<code>numpy</code> degrades to unavailable instead of taking down
<code>import fpdf</code> - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1908">#1908</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1908">py-pdf/fpdf2#1908</a>)
- thanks to <a
href="https://github.com/stumpylog"><code>@​stumpylog</code></a></li>
<li>font state (family, style, size, current font, and the page-level
&quot;font is set&quot; flag) no longer leaks back onto the
<code>FPDF</code> instance after a <code>text_columns()</code> /
<code>text_region()</code> context exits, so a subsequent
<code>pdf.cell()</code> / <code>pdf.write()</code> renders at the
caller's font instead of the last paragraph's - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1804">#1804</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1804">py-pdf/fpdf2#1804</a>)
- thanks to <a
href="https://github.com/Pawansingh3889"><code>@​Pawansingh3889</code></a></li>
<li>text rendering when the first text on a page starts with a fallback
glyph - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1772">#1772</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1772">py-pdf/fpdf2#1772</a>)</li>
<li>preserve boundary-neutral formatting during bidirectional text
preprocessing - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1779">#1779</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1779">py-pdf/fpdf2#1779</a>)</li>
<li>transform application on user space gradients - <em>cf.</em> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1784">#1784</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1784">py-pdf/fpdf2#1784</a>)</li>
<li>dependency extras for camelot-py and endesive on pyproject.toml -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1792">#1792</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1792">py-pdf/fpdf2#1792</a>)</li>
<li>preserve link annotations during dry-run of
<code>FPDF.multi_cell</code> - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1807">#1807</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1807">py-pdf/fpdf2#1807</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>preserve two consecutive markdown links (without space inbetween) -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1814">#1814</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1814">py-pdf/fpdf2#1814</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>support markdown style around markdown links - <em>cf.</em> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1826">#1826</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1826">py-pdf/fpdf2#1826</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>Reset gstate for ToC-rendering - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1837">#1837</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1837">py-pdf/fpdf2#1837</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>preserve markdown format in <code>FPDF.multi_cell</code> in dry-run
- <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1840">#1840</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1840">py-pdf/fpdf2#1840</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>fix page order after dry-run of <code>FPDF.multi_cell</code> in ToC
- <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1836">#1836</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1836">py-pdf/fpdf2#1836</a>)
- thanks to <a
href="https://github.com/CoLa5"><code>@​CoLa5</code></a></li>
<li>rendering SVG arcs with very small sweeps that previously rounded to
zero - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1831">#1831</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1831">py-pdf/fpdf2#1831</a>)</li>
<li>spurious &quot;Not enough horizontal space to render a single
character&quot; error when text without break opportunities is split
into many small fragments, e.g. by a fallback font alternating with the
main font - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1250">#1250</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1250">py-pdf/fpdf2#1250</a>)
- thanks to <a
href="https://github.com/uttam12331"><code>@​uttam12331</code></a></li>
<li>number of surviving escape characters - <strong>cf.</strong> [issue
<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1215">#1215</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1215">py-pdf/fpdf2#1215</a>)
- thanks to <a
href="https://github.com/amidou-naba"><code>@​amidou-naba</code></a></li>
<li>leading spaces on new lines inside <code>&lt;pre&gt;</code> and
<code>&lt;pre&gt;&lt;code&gt;</code> blocks are no longer dropped -
<em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1063">#1063</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1063">py-pdf/fpdf2#1063</a>)
- thanks to <a
href="https://github.com/eugen-goebel"><code>@​eugen-goebel</code></a></li>
<li><code>FPDF.set_font()</code> can restore <code>current_font</code>
when the selected font state diverged - <em>cf.</em> [PR <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1872">#1872</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/pull/1872">py-pdf/fpdf2#1872</a>)
- thanks to <a
href="https://github.com/gaoflow"><code>@​gaoflow</code></a></li>
<li>embed CID-keyed CFF fonts as raw CFF programs so browser PDF viewers
render them correctly - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1874">#1874</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1874">py-pdf/fpdf2#1874</a>)</li>
<li>fixed broken links on documentation not directly leading to the API
reference - <em>cf.</em> [issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1876">#1876</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1876">py-pdf/fpdf2#1876</a>)
- thanks to <a
href="https://github.com/iamfazakb"><code>@​iamfazakb</code></a></li>
<li>reject SVG <code>&lt;use&gt;</code> cycles and excessive nested
expansion to prevent resource exhaustion in
<code>FPDF.image()</code></li>
<li>count SVG <code>&lt;switch&gt;</code> elements in SVG complexity
limits</li>
<li>declare the default base state and display order for Optional
Content Groups so PDF viewers can list layers correctly - <em>cf.</em>
[issue <a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1895">#1895</a>](<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1895">py-pdf/fpdf2#1895</a>)</li>
</ul>
<h3>Changed</h3>
<ul>
<li>skip byte-for-byte compressed data comparison when zlib-ng is
detected, regardless of OS</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/e1fc653d8fce65d2fb118755bda8581a9bf1e4d9"><code>e1fc653</code></a>
release v2.8.8 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1916">#1916</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/42ec66442bf278b9768ca787fe601d3d334c62e4"><code>42ec664</code></a>
add Sanjays2402 as a contributor for code (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1915">#1915</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/af9d7dc11c36db49364c0096ad1edca5d569c676"><code>af9d7dc</code></a>
fix(text_region): apply Paragraph.ln(h) height to the line it terminates
(<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1904">#1904</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/81951f837b46782df38f7c4d6f4c8f2644ac9b7e"><code>81951f8</code></a>
add stumpylog as a contributor for bug, and code (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1913">#1913</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/b30c38c8cfc804342f429fffb26cc44f34f20af3"><code>b30c38c</code></a>
Fix: catch RuntimeError alongside ImportError for the optional numpy
import (...</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/63df0d0f4f085d3a014166d2d752d9c1e77305e4"><code>63df0d0</code></a>
Update github/codeql-action action to v4.37.6 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1910">#1910</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/11a30e672cad2157b657642ed290cb27eb57e482"><code>11a30e6</code></a>
Update crate-ci/typos action to v1.49.0 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1911">#1911</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/f65707b369a7ca7aaa6226a77eabf69b9903eac4"><code>f65707b</code></a>
Update step-security/harden-runner action to v2.20.1 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1912">#1912</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/a7f7c8945139fb5a20d1993fcff6484b5c2267c7"><code>a7f7c89</code></a>
Update github/codeql-action action to v4.37.4 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1907">#1907</a>)</li>
<li><a
href="https://github.com/py-pdf/fpdf2/commit/fe9d8f52643c5dcb88370813f51c29410463e299"><code>fe9d8f5</code></a>
Update pypa/gh-action-pypi-publish action to v1.14.2 (<a
href="https://redirect.github.com/py-pdf/fpdf2/issues/1906">#1906</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/py-pdf/fpdf2/compare/2.8.7...2.8.8">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=fpdf2&package-manager=uv&previous-version=2.8.7&new-version=2.8.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 21:10:05 +00:00
github-actions[bot]andLudy87 8e3ecb1fe4 chore: update Gradle to 9.7.1 (#7607)
Automated update of the Gradle wrapper and Gradle Docker build images.

Gradle version: `9.7.1`
Docker image: `gradle:9.7.1-jdk25`

Co-authored-by: Ludy87 <4592558+Ludy87@users.noreply.github.com>
2026-08-20 20:46:50 +00:00
Ludy cb0cafabd9 ci: Extract Gradle cache priming into a reusable workflow (#7572)
# Description of Changes

- What was changed
- Moved the `gradle-cache-prime` job from `build.yml` into a dedicated
reusable workflow.
  - Added `workflow_call` support for invocation from other workflows.
  - Added a `push` trigger for the `main` branch.
  - Updated `build.yml` to call the new reusable workflow.

- Why the change was made
- Keeps the shared Gradle cache warm after changes are pushed to `main`.
- Allows pull request builds to reuse the same cache and reduce
dependency resolution time.
  - Separates cache maintenance from the main build workflow.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 20:03:54 +00:00
stirlingbot[bot] 90d39aec0b 🌐 Sync Translations + Update README Progress Table (#7583)
### Description of Changes

This Pull Request was automatically generated to synchronize updates to
translation files and documentation. Below are the details of the
changes made:

#### **1. Synchronization of Translation Files**
- Updated translation files
(`frontend/editor/public/locales/*/translation.toml`) to reflect changes
in the reference file `en-US/translation.toml`.
- Ensured consistency and synchronization across all supported language
files.
- Highlighted any missing or incomplete translations.
- **Format**: TOML

#### **2. Update README.md**
- Generated the translation progress table in `README.md` using
`counter_translation_v3.py`.
- Added a summary of the current translation status for all supported
languages.
- Included up-to-date statistics on translation coverage.

#### **Why these changes are necessary**
- Keeps translation files aligned with the latest reference updates.
- Ensures the documentation reflects the current translation progress.

---

Auto-generated by [create-pull-request][1].

[1]: https://github.com/peter-evans/create-pull-request

Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 19:44:56 +00:00
Ludy 6376d1ff8b ci: share Docker base image across test-build matrix (#7584)
# Description of Changes

This PR decouples the Docker base-image preparation from the embedded
Docker image matrix builds in `.github/workflows/test-build-docker.yml`.

- Added a dedicated `prepare-base-image` job that runs once when a pull
request changes the Docker base image.
- Builds `stirling-pdf-base:pr-test` once for `linux/amd64` instead of
rebuilding the same image independently in every matrix job.
- Exports the prepared image with `docker save`, compresses it, and
uploads it as a short-lived GitHub Actions artifact.
- Added a dependency from `test-build-docker-images` to
`prepare-base-image`, while still allowing the matrix job to run when
base-image preparation is skipped.
- Each matrix entry downloads and loads the prepared Docker image when
`docker-base-changed` is enabled.
- Removed the previous per-matrix `Build base image locally` step.
- Keeps the prepared image available to the embedded Docker builds
through the local Docker daemon.

The change was made to eliminate redundant base-image builds across the
Docker test matrix. Previously, pull requests modifying `docker/base`
caused each matrix entry to build the identical base image independently
and in parallel. Preparing the image once reduces duplicated CI work,
improves consistency between matrix entries, and should reduce CI
resource usage and execution time for Docker-related pull requests.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 19:43:33 +00:00
dependabot[bot] b8cfde3a7a build(deps): bump imageioVersion from 3.13.1 to 3.14.0 (#7598)
Bumps `imageioVersion` from 3.13.1 to 3.14.0.
Updates `com.twelvemonkeys.imageio:imageio-batik` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-bmp` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-jpeg` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-tiff` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-webp` from 3.13.1 to 3.14.0

Updates `com.twelvemonkeys.imageio:imageio-psd` from 3.13.1 to 3.14.0


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 19:42:11 +00:00
dependabot[bot] 43f7db0234 build(deps): bump github/codeql-action/upload-sarif from 4.37.6 to 4.37.7 (#7591)
Bumps
[github/codeql-action/upload-sarif](https://github.com/github/codeql-action)
from 4.37.6 to 4.37.7.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/upload-sarif's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.7</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/upload-sarif's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.7 - 13 Aug 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd"><code>ff2f1c6</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4093">#4093</a>
from github/update-v4.37.7-be7a3dbb8</li>
<li><a
href="https://github.com/github/codeql-action/commit/951a133f96aa2114dd747e9e437305335d0bde16"><code>951a133</code></a>
Update changelog for v4.37.7</li>
<li><a
href="https://github.com/github/codeql-action/commit/be7a3dbb8147b82cd6d27e0707105b36aa190fc1"><code>be7a3db</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4087">#4087</a>
from github/dependabot/npm_and_yarn/npm-minor-0aa561...</li>
<li><a
href="https://github.com/github/codeql-action/commit/9310334b11405b305d9444edfa56cd86e2f1e4fe"><code>9310334</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4086">#4086</a>
from github/mbg/thread-action-state-to-codeql</li>
<li><a
href="https://github.com/github/codeql-action/commit/b4d8a54218a8792de9af2f6f32e33af899ca5212"><code>b4d8a54</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/ab5db2519c3344f2fa61c711fa2d6ad135829200"><code>ab5db25</code></a>
Bump the npm-minor group across 1 directory with 8 updates</li>
<li><a
href="https://github.com/github/codeql-action/commit/38055a3c3cf3979323eaf70fc6c73a8690250bde"><code>38055a3</code></a>
Drop <code>logger</code> from <code>databaseInitCluster</code> in
interface</li>
<li><a
href="https://github.com/github/codeql-action/commit/1f87aed5e66849f0c43ae147377cc77f2d98ac99"><code>1f87aed</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4085">#4085</a>
from github/update-bundle/codeql-bundle-v2.26.3</li>
<li><a
href="https://github.com/github/codeql-action/commit/dc1b98ad1c2f13ccf9fc33fb82f32fc76f944253"><code>dc1b98a</code></a>
Make <code>logger</code> available to <code>getCodeQLForCmd</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/6f0220ee37121218af472efbde25f06907a4da4f"><code>6f0220e</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4084">#4084</a>
from github/navntoft/bump-undici</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/5595ccaf912efad79be6eef63a5619ff05969be3...ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/upload-sarif&package-manager=github_actions&previous-version=4.37.6&new-version=4.37.7)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 19:32:53 +00:00
dependabot[bot] 1f337c06e4 build(deps-dev): bump uvicorn from 0.52.1 to 0.52.3 in /engine (#7600)
Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.52.1 to
0.52.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/Kludex/uvicorn/releases">uvicorn's
releases</a>.</em></p>
<blockquote>
<h2>Version 0.52.3</h2>
<h3>Changed</h3>
<ul>
<li>Update <code>zttp</code> to 0.0.24 and use its combined receive
path, improving HTTP/1.1 request parsing performance (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3067">#3067</a>)</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/Kludex/uvicorn/compare/0.52.2...0.52.3">https://github.com/Kludex/uvicorn/compare/0.52.2...0.52.3</a></p>
<h2>Version 0.52.2</h2>
<h3>Fixed</h3>
<ul>
<li>Update <code>zttp</code> to 0.0.22, fixing bodyless request receives
and improving HTTP/1 request parsing performance (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3063">#3063</a>)</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/Kludex/uvicorn/compare/0.52.1...0.52.2">https://github.com/Kludex/uvicorn/compare/0.52.1...0.52.2</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md">uvicorn's
changelog</a>.</em></p>
<blockquote>
<h2>0.52.3 (August 13, 2026)</h2>
<h3>Changed</h3>
<ul>
<li>Update <code>zttp</code> to 0.0.24 and use its combined receive
path, improving HTTP/1.1 request parsing performance (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3067">#3067</a>)</li>
</ul>
<h2>0.52.2 (August 13, 2026)</h2>
<h3>Fixed</h3>
<ul>
<li>Update <code>zttp</code> to 0.0.22, fixing bodyless request receives
and improving HTTP/1 request parsing performance (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3063">#3063</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/Kludex/uvicorn/commit/a68da608147c5f79d962352dce11de8f6e32d972"><code>a68da60</code></a>
Version 0.52.3 (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3068">#3068</a>)</li>
<li><a
href="https://github.com/Kludex/uvicorn/commit/6e3bb4c5c22531c16d341f2da49f2653307f412f"><code>6e3bb4c</code></a>
Use zttp 0.0.24 fast receive path (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3067">#3067</a>)</li>
<li><a
href="https://github.com/Kludex/uvicorn/commit/2a5ff183e02e6dce62f363009c85574bd5a23de2"><code>2a5ff18</code></a>
Version 0.52.2 (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3066">#3066</a>)</li>
<li><a
href="https://github.com/Kludex/uvicorn/commit/1920297df92b9cf5e6befb00327c1e34276db46f"><code>1920297</code></a>
Update zttp to 0.0.22 (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3063">#3063</a>)</li>
<li><a
href="https://github.com/Kludex/uvicorn/commit/5c560dae6a87f6109d958c6154277f1dc727f704"><code>5c560da</code></a>
chore(deps): bump pymdown-extensions from 11.0 to 11.0.1 (<a
href="https://redirect.github.com/Kludex/uvicorn/issues/3061">#3061</a>)</li>
<li>See full diff in <a
href="https://github.com/Kludex/uvicorn/compare/0.52.1...0.52.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=uvicorn&package-manager=uv&previous-version=0.52.1&new-version=0.52.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 19:32:41 +00:00
Vivek Maddheshiya fb0bffaa31 fix(billing): enforce en-US locale for billing money formatting (#7487) (#7498)
# Description of Changes

Fixes #7487

- **What was changed:** Explicitly specified the `'en-US'` locale in
`Intl.NumberFormat` and `toLocaleString` within `formatMinor` and
`formatMoneyMajor` in
`frontend/editor/src/proprietary/billing/format.ts`. Also added test
coverage for `formatMoneyMajor` in
`frontend/editor/src/portal/billing/sharedBillingFormat.test.ts`.
- **Why the change was made:** Previously, `formatMinor` used `new
Intl.NumberFormat(undefined, ...)`, which inherited the host/browser
environment locale. On non-US locales (e.g. German `de-DE`), this
produced comma-separated decimals (`$2,24`) instead of dot-separated
decimals (`$2.24`), breaking unit tests and leading to inconsistent
money formatting.
- **Any challenges encountered:** None.

Closes #7487

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [x] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [x] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 19:27:58 +00:00
Ludy df74c46211 ci: clean up temporary files in GitHub Actions workflows (#7582)
# Description of Changes

This change adds cleanup steps to GitHub Actions workflows that create
temporary files or sensitive build resources.

Changes include:

- Removing temporary backend helper files, logs, and PID files.
- Cleaning up database migration temporary directories after failure
logs are uploaded.
- Removing locally generated deployment files and Storybook archives.
- Deleting temporary signing certificates, MSI extraction directories,
and Apple signing keychains.
- Ensuring cleanup runs even when earlier workflow steps fail.

The cleanup reduces temporary data retention on runners and ensures
sensitive signing material is removed after builds. No functional
application code was changed.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 18:57:03 +00:00
Ludy 4f2b1bb381 Cache Docker dependency downloads in embedded images (#7535)
# Description of Changes

Adds Docker BuildKit syntax and shared Gradle/npm cache mounts to the
fat and ultra-lite embedded Dockerfiles. This reduces repeated
dependency downloads during rebuilds and speeds up image builds without
changing runtime behavior.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 18:56:59 +00:00
Ludy a24fcb0489 chore(dependabot): refresh dependency update configuration (#7561)
# Description of Changes

- Added `/testing/compose/mcp-client-check` to the existing Docker
Compose dependency monitoring configuration so dependencies used by the
MCP client check environment are covered by Dependabot.
- Added a `tanstack` dependency group matching `@tanstack/*` packages,
allowing related TanStack updates to be reviewed together.
- Added a `typescript` dependency group covering both `typescript` and
`@typescript/*` packages.
- Removed the obsolete `pip` Dependabot configuration for
`/testing/cucumber`, as dependency management for this area has moved to
the current `uv`-based setup.
- Kept the existing Dependabot scheduling, cooldown, grouping, and
rebase strategy conventions unchanged where applicable.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 18:33:06 +00:00
Ludy 17938ff5bc ci: automate Gradle and Docker image updates (#7464)
## Summary

- Add a scheduled GitHub Actions workflow that checks for the latest
stable Gradle release.
- Update the Gradle wrapper and pinned Gradle Docker build images
automatically.
- Open an automated pull request when an update is available.
- Update the developer prerequisites to Node.js 22+ and Gradle 9.0+.

## Details

The workflow runs every Monday at 03:00 UTC and can also be triggered
manually. It:

1. Resolves the latest stable Gradle version.
2. Finds the matching `gradle:<version>-jdk25` Docker image digest.
3. Updates the Gradle wrapper and Dockerfiles.
4. Verifies the resolved wrapper version and checks the resulting diff.
5. Creates or updates an automated dependency pull request.

The current wrapper and Docker image changes are included as the initial
update generated by this workflow.

## Testing

- Verified the generated changes with `git diff --check`.
- The workflow validates the wrapper version before opening the
automated pull request.
2026-08-20 19:32:54 +01:00
Ludy 73a78ab423 ci: gate backend builds and cache priming (#7405)
# Description of Changes

- Added a dedicated backend path filter for Java and Gradle-related
changes.
- Gated backend builds on backend-relevant file changes.
- Limited Gradle cache priming to project changes.
- Made Docker image tests resilient to skipped upstream jobs.
- Included the Gradle cache prime job in the final CI status check.
- Removed the unnecessary build dependency from the license check.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 19:32:11 +01:00
stirlingbot[bot] 6d549c39dc Update Frontend 3rd Party Licenses (#7594)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 16:04:27 +00:00
dependabot[bot] 71af798da4 build(deps): bump the simple-java-mail group across 2 directories with 2 updates (#7585)
Bumps the simple-java-mail group with 1 update in the / directory:
[org.simplejavamail:simple-java-mail](https://github.com/bbottema/simple-java-mail).
Bumps the simple-java-mail group with 1 update in the /app/common
directory:
[org.simplejavamail:simple-java-mail](https://github.com/bbottema/simple-java-mail).

Updates `org.simplejavamail:simple-java-mail` from 9.2.0 to 9.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/releases">org.simplejavamail:simple-java-mail's
releases</a>.</em></p>
<blockquote>
<h2>v9.3.1</h2>
<p>Simple Java Mail 9.3.1 is a Java 8-compatible build-tool maintenance
release.</p>
<h2>Changes</h2>
<ul>
<li>Updated Maven Antrun Plugin from 3.1.0 to 3.2.0 for the JPMS
consumer-compilation check.</li>
<li>Updated Maven Dependency Plugin from 3.8.1 to 3.11.0 for
construction of the JPMS module path.</li>
</ul>
<p>These changes affect project build tooling only. This release
contains no runtime-dependency changes, public API changes, or intended
mail-sending behavior changes. Java 8 remains the minimum supported
runtime.</p>
<p>The maintenance pull requests are <a
href="https://redirect.github.com/bbottema/simple-java-mail/pull/700">#700</a>
and <a
href="https://redirect.github.com/bbottema/simple-java-mail/pull/701">#701</a>.</p>
<h2>v9.3.0</h2>
<p>Simple Java Mail 9.3.0 exposes <code>batch-module</code> as a
supported standalone Jakarta Mail orchestration API.</p>
<ul>
<li><a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/698">#698</a>
adds <code>BatchTransportExecutor&lt;K&gt;</code> for applications that
create their own <code>Session</code> and <code>MimeMessage</code>
objects without adopting <code>EmailBuilder</code> or
<code>Mailer</code>. The main <code>simple-java-mail</code> facade is
not required.</li>
<li>Register Sessions by cluster key, then run cluster-selected or
exact-Session callbacks synchronously or submit them as
<code>CompletableFuture</code> work. Each callback receives the actually
selected <code>Session</code> and connected <code>Transport</code>.</li>
<li>The facade keeps raw leases private, releases connections after
successful callbacks, invalidates them after escaping failures, resolves
OAuth2 credentials from the selected Session, and provides deterministic
graceful or forced shutdown. Its default executor is module-owned; an
injected executor remains caller-owned.</li>
<li>The existing pooled <code>Mailer</code> path and the standalone
facade now share one transport engine. <code>smtp-connection-pool</code>
remains the only physical pool owner; do not stack batch/direct
orchestration over the Jakarta <code>smtppool</code> provider.</li>
<li>The supporting chain is updated to <code>smtp-connection-pool
4.0.1</code>, <code>clustered-object-pool 4.0.3</code>, and
<code>generic-object-pool 2.4.2</code>. The published JPMS names are
<code>org.simplejavamail.batch</code>,
<code>org.simplejavamail.smtpconnectionpool</code>,
<code>org.bbottema.clusteredobjectpool</code>, and
<code>org.bbottema.genericobjectpool</code>.</li>
</ul>
<p>See the <a
href="https://www.simplejavamail.org/smtp-connection-pooling.html">SMTP
connection pooling and batch orchestration guide</a> for the comparison
matrix, ownership rules, and complete examples.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/blob/master/RELEASE.txt">org.simplejavamail:simple-java-mail's
changelog</a>.</em></p>
<blockquote>
<p><a
href="https://www.simplejavamail.org">https://www.simplejavamail.org</a></p>
<!-- raw HTML omitted -->
<p>v9.3.0 - v9.3.2</p>
<ul>
<li><strong>v9.3.2:</strong> <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/702">#702</a>:
clarified the single-address contract of RecipientBuilder by renaming
its misleading implementation parameter and validation label; use
RecipientsBuilder for comma- or semicolon-delimited address lists.</li>
<li><strong>v9.3.1:</strong> Updated Maven Antrun Plugin from 3.1.0 to
3.2.0 (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/700">#700</a>)
and Maven Dependency Plugin from 3.8.1 to 3.11.0 (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/701">#701</a>),
retaining Java 8 compatibility.</li>
<li><strong>v9.3.0:</strong> <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/698">#698</a>:
exposed batch-module as a supported standalone Jakarta Mail
orchestration API with clustered and exact-Session callbacks,
asynchronous submission, automatic lease release/invalidation, and
deterministic graceful or forced shutdown.</li>
<li><strong>v9.3.0:</strong> Updated smtp-connection-pool from 3.1.0 to
4.0.1 and migrated the existing Mailer integration to its explicit
SmtpTransportLease contract. The complete generic, clustered, SMTP, and
batch dependency chain now publishes stable JPMS automatic module
names.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/e44c4c983e48ec9372ff745ccb9ab01d731867d0"><code>e44c4c9</code></a>
released 9.3.1 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/461e5f7c3b97d4882b21bca7ecb4843530184f32"><code>461e5f7</code></a>
docs(release): prepare 9.3.1 release notes</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/68728f3457f7b0e85cf66e758f41985b86c4a125"><code>68728f3</code></a>
build(deps-dev): bump org.apache.maven.plugins:maven-dependency-plugin
(<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/701">#701</a>)</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/d491c334bea647a99538984d917cd6ffdcb3cbce"><code>d491c33</code></a>
build(deps-dev): bump org.apache.maven.plugins:maven-antrun-plugin (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/700">#700</a>)</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b3111e232a23f9187c2b0c6b98a0bb1a895d9309"><code>b3111e2</code></a>
docs(website): publish pooling guidance update [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/cb3a9a133b91372d112c1b6a07c92e721f1a6f9e"><code>cb3a9a1</code></a>
docs(website): publish pooling guide follow-up [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/29f25e83c654d5dbadd4da321ce9511a4eceeeef"><code>29f25e8</code></a>
released 9.3.0 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b9c0cb8b58f4477e92fdd7ea395583863f875310"><code>b9c0cb8</code></a>
feat(batch): expose standalone transport orchestration</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/92549b27f44a0042ca908d22c25db58ccc1e1630"><code>92549b2</code></a>
merge(release): reconcile master with develop [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b499d76ec28e0566b2000ec6418473da66e1398a"><code>b499d76</code></a>
docs(readme): rebuild developer landing page [skip ci]</li>
<li>Additional commits viewable in <a
href="https://github.com/bbottema/simple-java-mail/compare/9.2.0...9.3.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `org.simplejavamail:outlook-module` from 9.2.0 to 9.3.1

Updates `org.simplejavamail:simple-java-mail` from 9.2.0 to 9.3.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/releases">org.simplejavamail:simple-java-mail's
releases</a>.</em></p>
<blockquote>
<h2>v9.3.1</h2>
<p>Simple Java Mail 9.3.1 is a Java 8-compatible build-tool maintenance
release.</p>
<h2>Changes</h2>
<ul>
<li>Updated Maven Antrun Plugin from 3.1.0 to 3.2.0 for the JPMS
consumer-compilation check.</li>
<li>Updated Maven Dependency Plugin from 3.8.1 to 3.11.0 for
construction of the JPMS module path.</li>
</ul>
<p>These changes affect project build tooling only. This release
contains no runtime-dependency changes, public API changes, or intended
mail-sending behavior changes. Java 8 remains the minimum supported
runtime.</p>
<p>The maintenance pull requests are <a
href="https://redirect.github.com/bbottema/simple-java-mail/pull/700">#700</a>
and <a
href="https://redirect.github.com/bbottema/simple-java-mail/pull/701">#701</a>.</p>
<h2>v9.3.0</h2>
<p>Simple Java Mail 9.3.0 exposes <code>batch-module</code> as a
supported standalone Jakarta Mail orchestration API.</p>
<ul>
<li><a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/698">#698</a>
adds <code>BatchTransportExecutor&lt;K&gt;</code> for applications that
create their own <code>Session</code> and <code>MimeMessage</code>
objects without adopting <code>EmailBuilder</code> or
<code>Mailer</code>. The main <code>simple-java-mail</code> facade is
not required.</li>
<li>Register Sessions by cluster key, then run cluster-selected or
exact-Session callbacks synchronously or submit them as
<code>CompletableFuture</code> work. Each callback receives the actually
selected <code>Session</code> and connected <code>Transport</code>.</li>
<li>The facade keeps raw leases private, releases connections after
successful callbacks, invalidates them after escaping failures, resolves
OAuth2 credentials from the selected Session, and provides deterministic
graceful or forced shutdown. Its default executor is module-owned; an
injected executor remains caller-owned.</li>
<li>The existing pooled <code>Mailer</code> path and the standalone
facade now share one transport engine. <code>smtp-connection-pool</code>
remains the only physical pool owner; do not stack batch/direct
orchestration over the Jakarta <code>smtppool</code> provider.</li>
<li>The supporting chain is updated to <code>smtp-connection-pool
4.0.1</code>, <code>clustered-object-pool 4.0.3</code>, and
<code>generic-object-pool 2.4.2</code>. The published JPMS names are
<code>org.simplejavamail.batch</code>,
<code>org.simplejavamail.smtpconnectionpool</code>,
<code>org.bbottema.clusteredobjectpool</code>, and
<code>org.bbottema.genericobjectpool</code>.</li>
</ul>
<p>See the <a
href="https://www.simplejavamail.org/smtp-connection-pooling.html">SMTP
connection pooling and batch orchestration guide</a> for the comparison
matrix, ownership rules, and complete examples.</p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bbottema/simple-java-mail/blob/master/RELEASE.txt">org.simplejavamail:simple-java-mail's
changelog</a>.</em></p>
<blockquote>
<p><a
href="https://www.simplejavamail.org">https://www.simplejavamail.org</a></p>
<!-- raw HTML omitted -->
<p>v9.3.0 - v9.3.2</p>
<ul>
<li><strong>v9.3.2:</strong> <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/702">#702</a>:
clarified the single-address contract of RecipientBuilder by renaming
its misleading implementation parameter and validation label; use
RecipientsBuilder for comma- or semicolon-delimited address lists.</li>
<li><strong>v9.3.1:</strong> Updated Maven Antrun Plugin from 3.1.0 to
3.2.0 (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/700">#700</a>)
and Maven Dependency Plugin from 3.8.1 to 3.11.0 (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/701">#701</a>),
retaining Java 8 compatibility.</li>
<li><strong>v9.3.0:</strong> <a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/698">#698</a>:
exposed batch-module as a supported standalone Jakarta Mail
orchestration API with clustered and exact-Session callbacks,
asynchronous submission, automatic lease release/invalidation, and
deterministic graceful or forced shutdown.</li>
<li><strong>v9.3.0:</strong> Updated smtp-connection-pool from 3.1.0 to
4.0.1 and migrated the existing Mailer integration to its explicit
SmtpTransportLease contract. The complete generic, clustered, SMTP, and
batch dependency chain now publishes stable JPMS automatic module
names.</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/e44c4c983e48ec9372ff745ccb9ab01d731867d0"><code>e44c4c9</code></a>
released 9.3.1 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/461e5f7c3b97d4882b21bca7ecb4843530184f32"><code>461e5f7</code></a>
docs(release): prepare 9.3.1 release notes</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/68728f3457f7b0e85cf66e758f41985b86c4a125"><code>68728f3</code></a>
build(deps-dev): bump org.apache.maven.plugins:maven-dependency-plugin
(<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/701">#701</a>)</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/d491c334bea647a99538984d917cd6ffdcb3cbce"><code>d491c33</code></a>
build(deps-dev): bump org.apache.maven.plugins:maven-antrun-plugin (<a
href="https://redirect.github.com/bbottema/simple-java-mail/issues/700">#700</a>)</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b3111e232a23f9187c2b0c6b98a0bb1a895d9309"><code>b3111e2</code></a>
docs(website): publish pooling guidance update [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/cb3a9a133b91372d112c1b6a07c92e721f1a6f9e"><code>cb3a9a1</code></a>
docs(website): publish pooling guide follow-up [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/29f25e83c654d5dbadd4da321ce9511a4eceeeef"><code>29f25e8</code></a>
released 9.3.0 [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b9c0cb8b58f4477e92fdd7ea395583863f875310"><code>b9c0cb8</code></a>
feat(batch): expose standalone transport orchestration</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/92549b27f44a0042ca908d22c25db58ccc1e1630"><code>92549b2</code></a>
merge(release): reconcile master with develop [skip ci]</li>
<li><a
href="https://github.com/bbottema/simple-java-mail/commit/b499d76ec28e0566b2000ec6418473da66e1398a"><code>b499d76</code></a>
docs(readme): rebuild developer landing page [skip ci]</li>
<li>Additional commits viewable in <a
href="https://github.com/bbottema/simple-java-mail/compare/9.2.0...9.3.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `org.simplejavamail:outlook-module` from 9.2.0 to 9.3.1


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 15:00:25 +00:00
dependabot[bot] d9a5c9ac7e build(deps): bump the embedpdf group across 1 directory with 23 updates (#7469)
Bumps the embedpdf group with 21 updates in the /frontend directory:

| Package | From | To |
| --- | --- | --- |
|
[@embedpdf/core](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/core/main)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/models](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/models)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-annotation](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-annotation)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-attachment](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-attachment)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-bookmark](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-bookmark)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-document-manager](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-document-manager)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-export](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-download)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-history](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-history)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-interaction-manager](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-interaction-manager)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-pan](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-pan)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-print](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-print)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-redaction](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-redaction)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-render](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-render)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-rotate](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-rotate)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-scroll](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-scroll)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-search](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-search)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-spread](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-spread)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-thumbnail](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-thumbnail)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-tiling](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-tiling)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-viewport](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-viewport)
| `2.14.4` | `2.15.0` |
|
[@embedpdf/plugin-zoom](https://github.com/embedpdf/embed-pdf-viewer/tree/HEAD/packages/plugin-zoom)
| `2.14.4` | `2.15.0` |


Updates `@embedpdf/core` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/core's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/core/main">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/engines` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/engines's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/engines/CHANGELOG.md">@​embedpdf/engines's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/engines">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/models` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/models's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/models/CHANGELOG.md">@​embedpdf/models's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/models">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-annotation` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-annotation's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-annotation/CHANGELOG.md">@​embedpdf/plugin-annotation's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-annotation">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-attachment` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-attachment's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-attachment/CHANGELOG.md">@​embedpdf/plugin-attachment's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-attachment">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-bookmark` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-bookmark's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-bookmark/CHANGELOG.md">@​embedpdf/plugin-bookmark's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-bookmark">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-document-manager` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-document-manager's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-document-manager/CHANGELOG.md">@​embedpdf/plugin-document-manager's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-document-manager">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-export` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-export's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-download">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-history` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-history's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-history/CHANGELOG.md">@​embedpdf/plugin-history's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-history">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-interaction-manager` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-interaction-manager's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-interaction-manager/CHANGELOG.md">@​embedpdf/plugin-interaction-manager's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-interaction-manager">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-pan` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-pan's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-pan/CHANGELOG.md">@​embedpdf/plugin-pan's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-pan">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-print` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-print's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-print/CHANGELOG.md">@​embedpdf/plugin-print's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-print">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-redaction` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-redaction's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-redaction/CHANGELOG.md">@​embedpdf/plugin-redaction's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-redaction">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-render` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-render's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ start, end
}</code> glyph pointers) shape emitted by
<code>onSelectionChange</code>, so a saved selection can be passed
straight back in to restore it; passing <code>null</code> clears the
selection. Page geometry is loaded on demand, so the returned task
resolves only once the highlight rects are computed. The range is
normalized (start/end may be given in any order), invalid input
(malformed range, non-integer/negative indices, out-of-bounds pages) is
rejected, glyph indices are clamped to the available page geometry, and
previously highlighted pages are repainted so switching to a disjoint
selection no longer leaves stale highlights behind.</p>
</li>
</ul>
<h2><code>@​embedpdf/core</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Patch Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/703">#703</a>
by <a
href="https://github.com/berkayozdin"><code>@​berkayozdin</code></a> –
Make the precompiled Svelte output work on every Svelte 5 runtime</p>
<p>Svelte 5.56 changed the <code>exclude</code> argument of the private
<code>rest_props</code> runtime helper from an array
(<code>exclude.includes(key)</code>) to a <code>Set</code>
(<code>exclude.has(key)</code>). The <code>*/svelte</code> entry points
ship precompiled component code, so output built against one side of
that change throws on the other: the currently published packages fail
with <code>TypeError: exclude.has is not a function</code> on Svelte
&gt;= 5.56, which aborts the render of every EmbedPDF Svelte
component.</p>
<p>The Svelte build now routes those calls through a wrapper that hands
the runtime an <code>exclude</code> value satisfying both contracts, so
one published build stays valid across the whole <code>svelte:
&quot;&gt;=5 &lt;6&quot;</code> peer range.</p>
</li>
</ul>
<h2><code>@​embedpdf/engines</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/models</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/pdfium</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-annotation</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-attachment</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-bookmark</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-capture</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-commands</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-document-manager</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-export</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h2><code>@​embedpdf/plugin-form</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/blob/v2.15.0/packages/plugin-render/CHANGELOG.md">@​embedpdf/plugin-render's
changelog</a>.</em></p>
<blockquote>
<h2>2.15.0</h2>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/embedpdf/embed-pdf-viewer/commit/a8faf04b15291c895118b819d7395c3bca8a4959"><code>a8faf04</code></a>
chore: version packages</li>
<li>See full diff in <a
href="https://github.com/embedpdf/embed-pdf-viewer/commits/v2.15.0/packages/plugin-render">compare
view</a></li>
</ul>
</details>
<br />

Updates `@embedpdf/plugin-rotate` from 2.14.4 to 2.15.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/embedpdf/embed-pdf-viewer/releases">@​embedpdf/plugin-rotate's
releases</a>.</em></p>
<blockquote>
<h2>Release v2.15.0</h2>
<h2><code>@​embedpdf/plugin-selection</code><a
href="https://github.com/2"><code>@​2</code></a>.15.0</h2>
<h3>Minor Changes</h3>
<ul>
<li>
<p><a
href="https://redirect.github.com/embedpdf/embed-pdf-viewer/pull/685">#685</a>
by <a href="https://github.com/simonmysun"><code>@​simonmysun</code></a>
– Add <code>setSelection(range, documentId?)</code> to the selection
capability and document scope for programmatically applying or restoring
a text selection.</p>
<p>It accepts the same <code>SelectionRangeX</code> (<code>{ star...

_Description has been truncated_

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-20 14:41:25 +00:00
brios c19926456c build(deps): update built from source dep versions in Dockerfiles (#7412)
# Description of Changes

PR updates built from source dep versions in Dockerfiles

Changes:

* Updated `CALIBRE_VERSION` from 9.4.0 to 9.13.0 in
`docker/base/Dockerfile`.
* Updated `GS_VERSION` (Ghostscript) from 10.06.0 to 10.07.1 in
`docker/base/Dockerfile`.
* Updated `IM_VERSION` (ImageMagick) from 7.1.2-13 to 7.1.2-29 in
`docker/base/Dockerfile`.
* Updated `QPDF_VERSION` is already at 12.3.2, no change.
* Updated `UNOSERVER_VERSION` from 3.6 to 3.7 in both
`docker/base/Dockerfile` and `docker/unoserver/Dockerfile` to align with
the client version and avoid wire mismatches.
* Updated `TASK_VERSION` from 3.49.1 to 3.52.0 in
`docker/embedded/Dockerfile`, `docker/embedded/Dockerfile.fat`,
`docker/embedded/Dockerfile.ultra-lite`, and `engine/Dockerfile`.
<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 14:41:07 +00:00
brios 383710c1d2 style(navigation): improve unsaved changes modal (#7508)
# Description of Changes

Improves the design of the unsaved changes modal.


### New
<img width="802" height="466" alt="image"
src="https://github.com/user-attachments/assets/f73bc51d-fb30-4075-a516-1aa0007d2735"
/>


### Old
<img width="908" height="450" alt="image"
src="https://github.com/user-attachments/assets/ef004923-4121-4f28-81f8-e3ee3cd68f78"
/>

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [X] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 14:40:39 +00:00
Ludy d8fc68a1a7 ci(e2e): use Playwright container for stubbed matrix (#7576)
# Description of Changes

- Use the pinned official Playwright `v1.58.2-noble` container image.
- Remove the per-browser `playwright install --with-deps` step from the
matrix jobs.
- Keep the browser matrix unchanged for independent Chromium, Firefox,
and WebKit reporting.
- Configure Playwright to use the container's root home directory while
directing Docker to a separate configuration path.
- Add npm retry and offline-cache settings to reduce transient
dependency installation failures.

This prevents three matrix jobs from concurrently downloading Playwright
browsers and Linux dependencies, reducing network-related CI failures
while preserving separate per-browser test results.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 14:21:56 +00:00
Anthony Stirling 96a00cebd1 Pdf ua converter testing (#7301)
# Description of Changes

Adds a PDF/UA converter, an accessibility report, and PDF/A conformance
level A.

**New: `POST /api/v1/convert/pdf/ua`** (Convert tool, "PDF/UA" target).
Tags an untagged PDF, marks
decorative content as artifacts, embeds missing fonts and applies the
document-level PDF/UA
requirements (title, language, tab order, form-field descriptions), then
validates with veraPDF. The
`pdfuaid` declaration is written only if validation passes, so a
returned file never claims more
than it delivers; response headers report whether it was declared, how
many checks still fail and
how many images still need a description.

**New: `POST /api/v1/security/accessibility-report`.** Reports what
fails, what the converter can fix
on its own, what needs a person, and lists the figures needing a
description with the keys the
conversion accepts back. Read-only; does not modify the file. Capped at
100 MB / 2000 pages and
weighted `LARGE_WEIGHT`, since it runs a full veraPDF pass plus the
converter's layout analysis over
every page.

**PDF/A level A.** `pdfa-1a`, `pdfa-2a` and `pdfa-3a` output formats on
the existing
`/api/v1/convert/pdf/pdfa` endpoint. Level A is level B plus tagging, so
the document is tagged
after Ghostscript (which discards any structure tree it is given) and
the level A claim is written
only if veraPDF agrees. Optional `pdfUa=true` additionally declares
PDF/UA alongside PDF/A, again
only if it validates.

Honesty rules the implementation holds to:

- **Never claim a level that was not reached.** If tagging fails, the
file is returned at level B and
is named `_PDFA-2b.pdf`, not `_PDFA-2a.pdf`. With `strict=true` the
request fails outright rather
than returning a level B file against a level A request, and a level B
pass no longer satisfies a
  strict level A request.
- **Never relabel a document's language.** The requested language
(default `en-GB`) is applied only
when the document declares none; a French PDF stays French unless the
caller sets
`overrideLanguage`, and ignoring a requested language is reported as a
warning.
- **Never invent alternative text.** Descriptions come from the caller.
The Convert panel can list
the images needing one (via the report endpoint) and send them back per
figure; any image left
undescribed blocks the conformance claim rather than being papered over.
- **Never certify hidden content.** Marking images decorative, or
suppressing text that could not be
tagged reliably, withdraws the claim instead of passing the checker by
hiding content.

PDF/UA-1 and PDF/UA-2 are both offered; UA-2 raises the file to PDF 2.0
and namespaces the structure
tree, and its test asserts conformance rather than merely reporting it.

Convert steps saved in Automations/Pipelines round-trip their PDF/UA
settings (profile, language,
override, title, font embedding, descriptions).

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 12:00:03 +00:00
Anthony Stirling 50d34fcca5 Add download, rename and duplicate to the file actions menu (#7536)
# Description of Changes
Adds expanded dropdown menu for download, rename and duplicate 

<img width="560" height="380" alt="image"
src="https://github.com/user-attachments/assets/84464f0a-46e1-42cf-8098-26f77888710f"
/>

<img width="560" height="480" alt="image"
src="https://github.com/user-attachments/assets/ed871f98-7f89-4560-869e-9ff514000b6f"
/>

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 11:47:05 +00:00
stirlingbot[bot] 4791d558c5 Update Backend 3rd Party Licenses (#7579)
Auto-generated by stirlingbot[bot]

This PR updates the backend license report based on dependency changes.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 11:16:18 +00:00
Ludy 91fc26f10c chore: Bump version to 2.14.3 (#7554)
# Description of Changes

This PR bumps the Stirling PDF application version from `2.14.2` to
`2.14.3` across the project.

Changes include:

- Updated the Gradle project version in `build.gradle` to `2.14.3`.
- Updated the Tauri desktop application version in
`frontend/editor/src-tauri/tauri.conf.json`.
- Updated the AUR package version for `stirling-pdf-desktop`.
- Updated the AUR package version for `stirling-pdf-server-bin`.
- Updated the mocked `appVersion` used by the core frontend server
experience simulations.
- Updated the mocked `appVersion` used by the proprietary frontend
server experience simulations.
- Kept all application, desktop, packaging, and test/simulation version
references synchronized for the `2.14.3` release.

The change prepares the project metadata and packaging configuration for
the `2.14.3` release and prevents different components from reporting or
packaging the previous `2.14.2` version.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-20 09:53:28 +00:00
EthanHealy01 f7ed1822c7 Float the editor search when no file is open (#7575)
## What

The super-search work pinned the editor's `WorkbenchBar` visible on
every view except My Files, even with no file open. That left an empty
workbench showing a fully painted bar whose only live control was the
search — download / close / print / save were all disabled, because
those actions only make sense with a file open.

This stops forcing the bar. When nothing is open, only the global search
floats (unpainted, centered), mirroring how the Processor already works.
When a file **is** open, the `WorkbenchBar` renders exactly as before.

Also fixes a smaller Processor issue: its floating search strip was
shorter than the sidebar logo row, so the search sat higher than the
brand. Its height now matches the logo row (51px) so they line up.

## Changes

- **`Workbench.tsx`** — render the `WorkbenchBar` only when a file is
open (or a custom view supplies content); otherwise render the new
floating search. My Files and `hideTopControls` custom views are
unchanged.
- **`WorkbenchFloatingSearch.tsx` / `.css`** (new) — the editor's
`SuperSearch` floated in an unpainted strip, mirroring
`PortalSearchBar`. Its vertical band matches the bar's so opening a file
swaps in the bar without a shift.
- **`PortalSearchBar.css`** — strip height matched to
`.portal-sidebar__logo` (51px) so the Processor search aligns with the
logo.

The notification bell is intentionally out of scope — it ships in a
separate PR.

## Before / after

(ignore the bell icon in the after that’s not live yet)

<img width="2056" height="1077" alt="Screenshot 2026-08-20 at 2 28
17 AM"
src="https://github.com/user-attachments/assets/144f5216-4784-42b2-8c09-afda43577ad0"
/>
<img width="2056" height="1071" alt="Screenshot 2026-08-20 at 2 28
31 AM"
src="https://github.com/user-attachments/assets/63af9303-af8a-48dd-b113-485169fb4924"
/>

- **Editor, no file:** painted bar with disabled buttons → just a
floating search.
- **Editor, file open:** unchanged.
- **Processor:** search now vertically aligned with the logo.

## Testing

- `task frontend:check` — lint (incl. colour linters) + typecheck +
tests (247 files / 2137 tests) all pass.
- Processor alignment verified in Storybook (`Portal/Shell/AppShell`):
logo row, search strip, and search pill share the same vertical center.
- Editor float not verified in-browser (local backend is behind a login
gate); covered by types/tests and reuses the verified Processor pattern.
2026-08-20 09:33:26 +00:00
James Brunton a744102cb6 Support Supporting Files in Pipelines (#7547)
# Description of Changes
Currently in the Processor's Pipelines page, none of the tools which
require supporting files are usable because it's never been hooked up to
the new API to upload supporting files. This PR hooks it up to that so
all tools using supporting files work in the processor. I had to tweak
the type generation a little for this so we have a static map of which
params are for supporting files so we know to handle them differently.
The `Test with a file` button has to work a little differently than the
main run since it's running an ad-hoc pipeline so the files haven't
necessarily been saved to the server yet. In this case, it'll use
whatever local changes the user has made for those pipeline steps, and
for all other steps, it'll just use what's saved in the server.
2026-08-20 08:23:20 +00:00
stirlingbot[bot] 1690cc25cc Update Frontend 3rd Party Licenses (#7573)
Auto-generated by stirlingbot[bot]

This PR updates the frontend license report based on changes to
package.json dependencies.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-20 05:15:37 +00:00
Ludy 088e0ef4e2 deps(frontend): upgrade Cantoo PDF library to 2.8.2 (#7493)
# Description of Changes

This pull request upgrades the frontend PDF dependency from
`@cantoo/pdf-lib` 2.6.5 to 2.8.2.

- Updated `frontend/package.json` to require `@cantoo/pdf-lib` `^2.8.2`.
- Regenerated `frontend/package-lock.json` with `@cantoo/pdf-lib@2.8.2`,
`pako@2.2.0`, and `node-html-better-parser@1.5.9`.
- Added the root npm `pako` override recommended by the upstream
release.
- The upgrade brings upstream parser, object-stream, encryption, form,
PNG, and PDF serialization fixes into the frontend dependency.
- No application API migration was required because the project does not
use the newly added PDF/A, XFA, Factur-X, incremental-update, fontkit,
or page-content-extraction APIs.

The main challenge was validating the broad upstream change set against
the project's actual usage. The frontend typecheck and a direct PDF
create/save/load smoke test passed. The complete `frontend:check` and
`frontend:test` tasks exceeded the available execution timeout without
reporting a test failure.

No related issue.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#6-testing)
for more details.
2026-08-19 18:29:02 +00:00
ConnorYoh 6bae9d516d chore(saas): one task per environment, and make the frontend follow it (#7483)
## The problem

The `dev` profile hardcoded one project ref (`qacaivhsjtftfwtgjvva`) in
five places: the ref, the Supabase URL, the publishable key, the
datasource host and the meter endpoint.

That made it both the shared environment everyone relies on *and* the
only thing you could point the backend at. Testing an open SaaS PR meant
hand-overriding all five via env just to reach that PR's Supabase
preview branch, which is the only place the PR's migrations have
actually been applied. Get it wrong and you see `relation
"stirling_pdf.<new table>" does not exist` for a table the PR added,
which is what happened on
[#7414](https://github.com/Stirling-Tools/Stirling-PDF/pull/7414).

## One task per environment

```bash
task dev:saas         # backend + frontend + engine, against this PR's preview branch
task staging:saas     # backend + frontend + engine, against the shared v3 project

task backend:dev:saas       # backend only, preview branch
task backend:staging:saas   # backend only, v3
```

| | how | vars | project |
|---|---|---|---|
| prod | `PROFILES=none` | `SAAS_DB_*` | the live one |
| staging | `PROFILES=staging` | `SAAS_STAGING_*` | pinned to v3, always
there |
| dev | `PROFILES=dev` | `SAAS_DEV_*` | follows a SaaS PR's preview
branch |

`PROFILES` is still the underlying switch, so the old spelling keeps
working. Production deliberately has no named task: reaching it should
take a conscious `PROFILES=none`, not a tab-complete.

**staging** is the old `dev` configuration, moved and kept pinned. The
value of a shared environment is that it is still there tomorrow:
reproduce a bug, paste a link to a colleague, share data.

**dev** is parameterised by `SAAS_DEV_PROJECT_REF` and derives the
Supabase URL, JWT issuer, JWKS, meter endpoint and (unless overridden)
the database host from it. Switching which PR you are testing is one
variable instead of five. With no ref set, `task backend:dev:saas` stops
and says what to set rather than falling back.

## The frontend was the real gap

`frontend/editor/.env` is committed and pins the **production** Supabase
project, and nothing in the frontend knew about dev or staging. So `task
dev:saas` gave you a backend on a preview branch and a login against
prod, unless you happened to have hand-written
`frontend/editor/.env.saas.local`.

The dev tasks now read the backend's env files and derive
`VITE_SUPABASE_URL` and `VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY` from the
same project ref the backend resolved, so the two halves cannot point at
different projects. Nothing to keep in sync by hand, no new vite mode,
and `SAAS_ENV=prod` opts back out to the committed values.

## Where to put your local values

Two files, both gitignored, neither ever committed:

**`app/.env.saas.local`** is the only one you normally need. The tasks
load it for the backend *and* the frontend.

```bash
# staging: everything else is already defaulted, so this is all it takes
SAAS_STAGING_DB_PASSWORD=...

# dev: the preview branch of the PR you are testing, from its "Supabase Preview" check.
# A branch has its OWN password and API keys; the parent project's will not authenticate.
SAAS_DEV_PROJECT_REF=...
SAAS_DEV_DB_PASSWORD=...
SAAS_DEV_PUBLISHABLE_KEY=...

# prod, if you ever need it
SAAS_DB_PROJECT_REF=...
SAAS_DB_URL=...
SAAS_DB_PASSWORD=...
SUPABASE_EDGE_FUNCTION_SECRET=...
```

**`frontend/editor/.env.saas.local`** is no longer needed for choosing a
Supabase project, and is best left empty or deleted. If you have one
from before this PR, note that the task-supplied values now win, which
is the point: the frontend follows the backend.

**A blank is not the same as absent.** A dotenv line with an empty value
still *sets* the variable, and Spring's `${VAR:default}` only falls back
when a variable is absent. So `.env.saas` lists what you must set as
blanks, and leaves out the two `*_DB_URL` overrides, which have real
defaults to fall back to. This is not theoretical, see below.

Committed `app/.env.saas` holds non-secret defaults only. Real secrets
are passwords, the edge-function secret and service-role keys. Project
refs and publishable keys are neither: a ref is the public
`<ref>.supabase.co` subdomain and a publishable key ships in the browser
bundle by design, which is why `frontend/editor/.env` has always carried
prod's.

## Three bugs found while building the tasks

All three were in this PR's own earlier commits, and all three were
caught by actually booting things rather than by reading the config.

**staging could not boot at all.** A blank `SAAS_STAGING_DB_URL=` in
`.env.saas` set the variable to empty, so
`${SAAS_STAGING_DB_URL:jdbc:...}` resolved to `""` and startup failed
with `spring.datasource.url is required when the saas profile is
active`. The file already carried a comment warning about exactly this;
it had only been applied to the dev block. The original verification for
this PR was "placeholders resolve" and "the task parses", neither of
which boots anything.

**The dev to staging fallback ran `ddl-auto=update` against shared v3.**
The dev profile sets `update`, which is right for a disposable preview
branch, and separately fell back to staging's project ref. Together that
meant Hibernate was free to reconcile tables that RLS policies depend
on. `application-staging.properties` pins `none`, but that only applies
when the staging profile is the active one, which it was not on the
fallback path. There is no fallback now: with no ref the task stops
before gradle, and the frontend fails the same way, both naming the
variable.

**`PROFILES=` never selected production.** Go template `default` treats
`""` as absent, so it silently resolved back to `dev`. It is
`PROFILES=none` now.

## Two choices worth reviewing

**Staging keeps its committed project ref**, now as a
`${SAAS_STAGING_PROJECT_REF:...}` default in one place, with the URL,
database host and meter endpoint all derived from it. So staging still
works with zero setup, and repointing it is one variable. Nothing in CI
referenced the ref or the profile. Its publishable key default carries
no inline `gitleaks:allow`: a trailing comment in a `.properties` file
is part of the value, so the pragma ended up inside the key. It is in
`.gitleaksignore` instead.

**`SAAS_DEV_DB_URL` still overrides the whole URL**, so a branch needing
the pooler host rather than the direct one is reachable without touching
committed config.

## Verification

- `task backend:staging:saas` boots against v3 and serves `200`. It
could not boot before this commit.
- `task backend:dev:saas` with no ref stops before gradle naming the
variable, and `PROFILES=none` still reaches production. `task
frontend:dev:saas` fails the same way; `SAAS_ENV=staging` still resolves
with no local config.
- Frontend routing picks the SaaS runner for dev/staging and the plain
runner for prod; the derivation returns the right URL and key for each.
- Vite's `process.env` precedence and Task's dotenv/env semantics were
measured, not assumed. That is how one trap surfaced: Task sets an
`env:` key even when its value resolves to empty, and Vite treats an
empty `process.env` `VITE_*` as authoritative over a committed `.env`.
Putting the Supabase vars on the shared `dev:_run` would have blanked
Supabase config for the core, proprietary and desktop dev servers, so
the SaaS path has its own runner.
- `:saas:spotlessApply` and `:saas:compileJava` green.

`DevProfileProjectNotice` becomes `SaasProjectNotice` and covers both
profiles, stating the project ref and `ddl-auto` at startup so which
environment you are on is never a guess.

No behaviour change for prod: the `saas` profile is untouched.
2026-08-19 14:46:00 +00:00
EthanHealy01 0f8803f35f Require the policy-management role to run a policy against its sources (#7565)
## What

Running a stored policy against its **configured sources** (`POST
/api/v1/policies/{id}/trigger`, the manual "run now") now requires the
policy-management role — global admin self-hosted, team leader on SaaS —
alongside the existing team scoping.

## Why

A source sweep operates on the team's configured sources using the
server's stored connection credentials, so it belongs with the other
policy-management capabilities rather than with ordinary use. Team
scoping on its own didn't express that distinction.

## Not changed

- `POST /{id}/run` — running a policy over documents the **caller
supplied** stays open to every team member. That's ordinary editor
enforcement on upload and export, and gating it would break it.
- Ad-hoc pipelines (`/run`, `/run/stream`).
- The scheduled, folder-watch and webhook triggers.
- Single-user deployments (login disabled), which have no roles.

## Implementation

`PolicyManagementAuthority` gains `canTriggerPolicies()`, kept separate
from `canEditPolicies()` so the two capabilities can diverge later. Both
current implementations grant it to the same principals that may edit
policies.

## Tests

- role absent → 403, rejected before any run starts
- role present → 202
- login disabled → check skipped entirely
- `/{id}/run` asserted to consult neither authority method, so the gate
can't quietly extend to the editor path later
2026-08-19 14:27:39 +00:00
Anthony Stirling 6f7f28946c Set deployment: false on environment jobs that do not deploy (#7562)
# Description of Changes

thanks ludy for the tip :P 

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-19 14:19:26 +00:00
Ludy ec3de16c08 ci: centralize Gradle caching across GitHub Actions workflows (#7546)
## Summary

This pull request restructures Gradle dependency caching across the
GitHub Actions workflows.

The central `gradle-cache-prime` job is responsible for preparing the
shared backend Gradle cache. Reusable workflows restore that shared
cache without writing to the same key, while independently triggered
workflows use isolated cache namespaces.

## What changed

### Shared Gradle cache

- Added a stable `gradle-v1-` cache namespace for the shared backend
cache.
- The cache key includes the runner OS, runner architecture, JDK
version, and the relevant Gradle configuration files.
- The cache key is calculated before Gradle runs and reused for the
later save step.
- The prime job performs a lookup first and resolves backend
dependencies only when the exact cache is missing.
- This prevents Gradle or Spotless changes during the prime step from
producing a different save key from the key used by downstream jobs.

### Reusable workflows

- Backend, OpenAPI, license, Docker, E2E, and migration workflows
restore the shared cache instead of writing to the shared key.
- The backend build matrix includes `matrix.jdk-version` in its cache
key.
- Enterprise, Tauri, and generated-model workflows support the
`use_shared_cache` boolean input.
- When `use_shared_cache` is enabled, those workflows restore the shared
cache.
- When it is disabled, they use workflow-specific cache namespaces.

### Independent workflows

Independent workflows now use separate cache prefixes, including:

- `gradle-license-report-v1-`
- `gradle-swagger-v1-`
- `gradle-push-docker-v1-`
- `gradle-tauri-releases-v1-`
- `gradle-deploy-pr-v1-`
- `gradle-playwright-e2e-v1-`
- `gradle-generated-models-v1-`

This prevents them from creating or affecting the shared backend cache
before the prime job.

### Build and E2E flow

- Removed the `-PnoSpotless` option from the central Gradle
dependency-resolution command.
- Removed the separate Gradle dependency prime/retry logic from the live
E2E workflow.
- Connected the Tauri build and generated-models check to the central
cache-prime job.

## Motivation

Previously, multiple workflows could use and save the same Gradle cache
key independently. The first workflow to save the cache could therefore
determine its contents, even if it had resolved a different or
incomplete set of dependencies.

The cache key was also evaluated after some Gradle tasks had run. If
Gradle or Spotless modified a file covered by `hashFiles(...)`, the save
key could differ from the restore key used by downstream jobs.

This change gives the shared cache a single owner, isolates
workflow-specific caches, and makes cache usage deterministic across the
CI pipeline.

## Expected result

- `gradle-cache-prime` is the single writer for the shared backend
Gradle cache.
- Downstream jobs restore the same cache without competing cache writes.
- Independently triggered workflows remain isolated through their own
cache namespaces.
- Changes to the monitored Gradle configuration files produce a new
cache key.
- The normal Gradle/Spotless path is included when the shared cache is
populated.

## Validation

- Compared the cache key expressions and `hashFiles(...)` inputs across
the affected workflows.
- Verified that the central restore and save steps use the same
precomputed key.
- CI should confirm that the prime job populates the shared cache and
downstream workflows only restore it.

## Checklist

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have performed a self-review of my changes
- [ ] I have run the relevant CI checks
- [ ] I have tested the workflow changes
2026-08-18 19:25:55 +01:00
EthanHealy01 913601ff03 Consolidate the editor + processor sidebar footers into one component (#7539)
## What

Both sidebars ended in a different bottom section. The editor showed an
account row (avatar, name, settings); the processor showed a "Link
Stirling account" CTA plus a `Settings` nav item and no identity at all.
They are now **one shared `<NavFooter>`** rendering the same rows in
both apps, in this order:

1. the link-account CTA (self-hosted, when unlinked)
2. free credits remaining
3. **Open \<the other app\>**
4. the account row — avatar, name, settings

It's a **single surface** with hairline dividers between rows, not
stacked cards. Rows are assembled as a list, so a row this build doesn't
show (no wallet, no processor access, nothing to link) takes its divider
with it rather than leaving a stray line.

This also fixes the profile-picture/initials desync between the sidebar
and the account settings page.

## Screenshots

Captured with the stubbed Playwright harness at 1600x900, scoped to the
sidebar and auto-cropped to the region that actually changed. Base is
`origin/main`; every state is driven by dummy backend stubs so all the
nav-bar permutations are covered.

<img width="2104" height="3044" alt="montage_cloud-dark"
src="https://github.com/user-attachments/assets/667c9a71-3ab7-4582-9259-08cda521e238"
/>
<img width="2104" height="3044" alt="montage_cloud-light"
src="https://github.com/user-attachments/assets/126bc994-efa3-436b-bf50-31d76f574eaa"
/>
<img width="2104" height="1492" alt="montage_editor-dark"
src="https://github.com/user-attachments/assets/c725726d-ea90-4fa9-bf0a-6014f3729869"
/>
<img width="2104" height="1490" alt="montage_editor-light"
src="https://github.com/user-attachments/assets/d0d34e0a-ac60-4f8c-af15-afb66fbd679e"
/>
<img width="2104" height="1066" alt="montage_processor-dark"
src="https://github.com/user-attachments/assets/51ccfe63-c327-41f4-ab91-74555b6f7148"
/>
<img width="2104" height="1068" alt="montage_processor-light"
src="https://github.com/user-attachments/assets/c90d7aba-90fa-4998-ac8f-26ce666dde71"
/>


The free-credits meter is a cloud-build surface, so the self-hosted
capture can't reach it. Those states come from the new Storybook stories
with dummy wallet data (`Shared/NavFooter`), which is also where the
credit tone bands and the collapsed rail are easiest to review.

## How it's wired

`NavFooter` is purely presentational. Each app resolves its own data
through three `@app/*` seams, so core carries no build-specific gating
and any box whose data is absent is dropped rather than rendered empty.

| Seam | core | cloud / proprietary / saas |
|---|---|---|
| `useFreeCreditsSummary` | `null` — self-hosted editor installs aren't
metered | cloud reads `freeRemaining` / `freeAllowance` off the same
`useWallet()` the Plan page's free meter uses, so the sidebar and Plan
can't disagree |
| `useOtherAppSwitch` | `null` — core ships no processor | gated on
`portalAccess` (`/api/v1/auth/me` in SaaS, the Spring session flag
self-hosted) |
| Link-account CTA | n/a | unchanged conditions — passed in as
`accountExtras`, still only when `linkState === "unlinked"`, still a
no-op in SaaS |

- The processor reads the meter through its own
`@portal/hooks/useFreeCreditsSummary` rather than the editor's `@app`
one. Self-hosted resolves `@app/*` as proprietary → core, where the
cloud wallet hook isn't in the cascade, and the implementation can't
live in `proprietary/` because core/desktop builds ship no portal and
must never resolve `@portal`. Keeping it in `portal/` gets the figure to
the linked self-hosted processor without weakening that rule; it reads
the same `GET /api/v1/payg/wallet` the Usage page's trial meter already
renders, gated on link state and behind the portal's query cache.
`portal-saas/` just re-exports the cloud hook, so both footers share one
fetch.

The processor-access gate previously lived in two near-identical
`AppSwitcher` copies. It moves into `useOtherAppSwitch`, `AppSwitcher`
now reads it too, and the duplicate
`saas/components/shared/AppSwitcher.tsx` is deleted — the logo switcher
and the footer row can no longer disagree about access.

## Profile picture sync

One `useAccountIdentity` hook now backs the editor footer, the processor
footer and the account settings page. Previously settings derived its
initial from `email[0]` while the sidebar used `displayName[0]`, and the
two drew different blue discs. Alongside that, the shared `Avatar`:

- falls back to initials when a picture URL fails to load, instead of
leaving an empty disc
- renders one letter for single-word names (`admin` → "A", not "AD")
- gains an `xl` size so the settings hero disc is the same component

## Notes

- Labelled **"Free credits"** rather than "free monthly credits":
`freeAllowance` is documented as a one-time lifetime grant, not a
monthly reset, so "monthly" would misdescribe the data. Happy to change
if the backend semantics differ from the type comments.

## Testing

- `task frontend:check` and `task frontend:typecheck:all` pass (all 9
build variants).
- 9 new `Shared/NavFooter` stories pass the Chromium + axe story scan;
`frontend:storybook:a11y:changed` reports no regressions.
- Stubbed E2E suite passes, including the `config-button` tour/settings
specs that target the account row. Two failures (`console-clean ›
landing`, `viewer-text-selection › Ctrl+C`) also fail on `origin/main`
locally — they need a backend on :8080 and clipboard permissions.
2026-08-18 14:00:43 +00:00
EthanHealy01 cf49742d97 Fix the top bar styling (#7544)
Every top bar styled itself, so none of them matched the new UI. Also,
colors on the premium banner (and possibly others) clashed since the
theme changes.

## Before Example Issue

<img width="1934" height="348" alt="Screenshot 2026-08-17 at 11 47
20 PM"
src="https://github.com/user-attachments/assets/b6f13207-2f47-4084-bd3b-2392f572c1a1"
/>


## After (all)

<img width="2880" height="800" alt="danger__dark"
src="https://github.com/user-attachments/assets/6b311dec-23e7-4059-a6bb-75527cbd2e34"
/>
<img width="2880" height="800" alt="danger__light"
src="https://github.com/user-attachments/assets/3b04b109-5146-4874-88b3-d99770ea51f8"
/>
<img width="2880" height="800" alt="default-app__dark"
src="https://github.com/user-attachments/assets/b0101b98-fce8-467a-99a6-dd40b8864da1"
/>
<img width="2880" height="800" alt="default-app__light"
src="https://github.com/user-attachments/assets/8aa21f01-6549-4d78-b217-c5547d60ab5b"
/>
<img width="2880" height="800" alt="free-tier-limit__dark"
src="https://github.com/user-attachments/assets/07fd7498-f44f-408f-8c79-9b5ea55e13df"
/>
<img width="2880" height="800" alt="free-tier-limit__light"
src="https://github.com/user-attachments/assets/f38c527b-9f64-4db0-b84c-56ca48e464cc"
/>
<img width="2880" height="800" alt="server-attention__dark"
src="https://github.com/user-attachments/assets/447a36fa-056d-4ca9-8b30-04aa0ccd6ed1"
/>
<img width="2880" height="800" alt="server-attention__light"
src="https://github.com/user-attachments/assets/f0311d45-a218-4846-b0ac-47996e2637c5"
/>
<img width="2880" height="800" alt="team-invitation__dark"
src="https://github.com/user-attachments/assets/cc368473-3b9f-4ab0-878a-67da993874c2"
/>
<img width="2880" height="800" alt="team-invitation__light"
src="https://github.com/user-attachments/assets/19d52a3e-4028-46f0-8562-9bd9cef9397a"
/>
<img width="2880" height="800" alt="upgrade-prompt__dark"
src="https://github.com/user-attachments/assets/e9d20daa-f41f-46d9-b827-a84f276e8af1"
/>
<img width="2880" height="800" alt="upgrade-prompt__light"
src="https://github.com/user-attachments/assets/9b6250c1-6a61-40d6-a7ab-e37398d67322"
/>


## What changed

- `InfoBanner` exposed 8 colour-override props (`background`,
`borderColor`, `textColor`, `iconColor`, `buttonColor`,
`buttonTextColor`, `closeIconColor`, `buttonVariant`), so every caller
invented its own look. Replaced with a closed tone set: `info` · `promo`
· `warning` · `danger`.
- Tone drives the whole bar — fill, border, icon and the button — so a
CTA can't drift from the bar it sits on. Text is neutral in every tone;
only the icon carries the tone colour.
- All colour comes from `--c-*` tokens mixed over `--c-surface`, so the
bars follow light and dark instead of ignoring them. The old bars were
hardcoded: in dark mode the two licence warnings stayed cream-on-white.
- `promo` keeps the gradient it was always meant to have, built from the
existing `--c-hue-indigo`/`--c-hue-purple` stops (documented in
`colors.css` as gradient hues, deliberately not accent-following), with
the existing `premium` button accent on it.
- Deleted the hardcoded colours from all four callers: the purple
gradient (`#667eea`→`#764ba2`), the orange soup (`#FFF4E6` / `#9A3412` /
`#EA580C`) duplicated across the urgent banner and the admin plan
section, and the fixed dark bar (`--mantine-color-dark-7`) on the team
invitation.
- `UpgradeBanner|AdminPlanSection` sat on the theme linter's exemption
list, which is how those colours survived the theme migration. Exemption
removed, so `code-colors` now guards them.
- The banner's class was colliding with `core/ui/Banner.css`'s
`.sui-banner` (16 live rules), which restyled it in the app but not in
Storybook — that's why the two disagreed on radius, border and tone.
Renamed to `.app-banner`; the two surfaces now render identically.
- Bar is square and full-bleed with a single hairline rule underneath;
button labels are optically centred.
- Added `--c-warning-subtle`, matching the existing `--c-danger-subtle`
/ `--c-success-subtle`.
- New `Shared → Top bars` story renders all six bars at once, so a
change to the shared component is visible against the whole set.
- Unrelated one-liner: `frontend/.prettierignore` now ignores the
gitignored `editor/screenshots/` capture artifacts, which were failing
`format:check` locally. Happy to drop it if you'd rather keep this PR to
the bars.

## Testing

- `task frontend:check` — typecheck, lint (oxlint + 4 theme-lint passes
+ stylelint), format, 244 files / 2119 tests.
- `frontend:storybook:a11y:changed` — clean in light and dark.
- The a11y gate caught a real defect mid-change: giving each banner
`role="region"` with the same label produced duplicate landmarks, which
the app hits for real whenever two banners show at once. Landmark
removed.
- All six bars captured in the running editor, light and dark, and
diffed against `origin/main`'s component rendered with each caller's
original props.
2026-08-18 13:56:47 +00:00
James Brunton fb70fc13da Fix tools which crash in the Pipelines page (#7538)
# Description of Changes
Overlay PDFs and Change Metadata both crashed in the Processor because
they required `FilesModalContext` and `ViewerContext` respectively.
Neither of those contexts make sense to provide in the Processor because
there are no files in context and there is no Viewer, so redesign both
tool settings to only optionally require these contexts. Their behaviour
is unchanged in the Editor but they now work in the Processor (just
without the extra info about the active files, since there are none).

Also hooks up the Reorganise Pages settings so that it can be used from
Automate. The component already existed but just wasn't being used,
which just looks like an oversight.
2026-08-18 13:08:23 +00:00
James Brunton a14eec94ec Fix corner radius on Mantine checkboxes in Processor (#7537)
# Description of Changes

## Before
<img width="314" height="311" alt="image"
src="https://github.com/user-attachments/assets/93239e47-8dd4-4174-8a51-7a4a2494048f"
/>

## After
<img width="399" height="324" alt="image"
src="https://github.com/user-attachments/assets/e2318559-006f-41e4-ac69-8c577ae60b89"
/>
2026-08-18 09:02:17 +00:00
Anthony Stirling 89d8ffec5d Ci/environments cleanups, new envs and master to release naming (#7511)
# Description of Changes

Ci/environments cleanups, new envs and master to release naming

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-17 17:49:13 +00:00
EthanHealy01 526bb85e17 Translate the failures debug panel strings (#7500)
Follow-up to #7296, addressing a missing translation.
2026-08-17 17:00:35 +00:00
James Brunton 99dda7a9f6 Convert Processor tables to be consistent with each other (#7312)
# Description of Changes
Currently, the tables in the processor are inconsistently styled and
function differently to each other because they don't all share the same
table component. This is a big problem since the Processor is a largely
table-based UI and most of most pages in it are tables. This PR creates
a new common `DataTable` component and converts all existing tables in
the Processor (other than those in the docs pages) to use it, tweaking
the individual tables content as necessary so they all work the same.

The `DataTable` component is designed to take minimal JSX as arguments.
Instead, it takes typed data from the caller and it converts that into
JSX, which makes it a lot easier to guarantee that the tables will all
render the same and if we want to update the table styling in the
future, we just need to do it in one place. I also added sorting
capabilities to many of the tables since it was easy with the library
and it was appropriate for many of the existing table columns.

## Table Comparisons

<img width="2296" height="861" alt="AuditTab-comparison"
src="https://github.com/user-attachments/assets/92473508-3f93-45c4-9485-ee21d0d1ecd0"
/>

<img width="2296" height="617" alt="DocumentExtractions-comparison"
src="https://github.com/user-attachments/assets/cf194210-f4c9-42d1-bce1-5a70c0f8fdc5"
/>

<img width="2296" height="369" alt="InstanceHealthTable-comparison"
src="https://github.com/user-attachments/assets/4b048c62-4237-4043-ae89-047df0cd7e60"
/>

<img width="2296" height="2304" alt="Integrations-comparison"
src="https://github.com/user-attachments/assets/9d9edead-8da0-4d49-a249-253579cfdb0e"
/>

<img width="2296" height="521" alt="InvoicesList-comparison"
src="https://github.com/user-attachments/assets/2b6d40b7-d63d-404b-a834-79d5fc62ba9d"
/>

<img width="2296" height="348" alt="LinkedInstancesTable-comparison"
src="https://github.com/user-attachments/assets/fb65c660-baa4-4fcd-8893-05be1962fdab"
/>

<img width="2296" height="356" alt="PendingInvitations-comparison"
src="https://github.com/user-attachments/assets/1f0105ed-b1f8-4e4e-998f-57024875a28f"
/>

<img width="2296" height="306" alt="PipelinesTable-comparison"
src="https://github.com/user-attachments/assets/d84255bd-37f3-4245-b2b5-ef8c06c000ae"
/>

<img width="2296" height="652" alt="PolicyCatalogue-comparison"
src="https://github.com/user-attachments/assets/55f25f20-46f0-47c0-aa40-516cbd5acab5"
/>

<img width="2296" height="1221" alt="ReviewQueueTable-comparison"
src="https://github.com/user-attachments/assets/2206a50c-731f-48cc-958f-aad5e30e47b0"
/>

<img width="2296" height="344" alt="SourcesTable-comparison"
src="https://github.com/user-attachments/assets/76b54efb-54ea-42a8-a0b1-b0cb21f73296"
/>

<img width="2296" height="659" alt="UsersDirectory-comparison"
src="https://github.com/user-attachments/assets/524d1b63-efcf-4ac8-a07d-11aa1849c189"
/>
2026-08-17 11:47:08 +00:00
ConnorYoh f15832b2bb chore(saas): make schema ownership explicit and enforce it (#7489)
## The problem

The SaaS database has two writers and always has: the Supabase
migrations in the SaaS repo, and Hibernate's `ddl-auto`. That was a
convention rather than a rule, and it leaked twice.

- An older `ddl-auto` run widened `team_memberships.role` to
varchar(255), which needed [a dedicated
migration](https://github.com/Stirling-Tools/Stirling-PDF-SaaS/blob/v3/supabase/migrations/20260804000000_fix_team_memberships_role_varchar50.sql)
to repair, because RLS policies depended on the column.
- `payg_instance_usage` shipped with an entity and **no migration**, and
nobody noticed for months — staging already had the table from an
earlier `ddl-auto` run. It surfaced only when a fresh preview branch,
built from migrations alone, threw `relation does not exist`.

Both are the same bug: nobody had to *say* who owned a table, so the
answer got decided by accident.

## The fix

`SaasSchemaOwnership` is the register — **29 migration-owned, 29
inherited** and left to Hibernate.

`MigrationOwnedSchemaFilter` applies it via Hibernate's
`hbm2ddl.schema_filter_provider`, wired on the **saas profile only**.
Hibernate is never shown a migration-owned table, so it cannot create,
alter, drop or truncate one whatever `ddl-auto` is set to. Inherited
tables stay managed, so a fresh preview branch still heals itself on
first boot. Self-hosted is untouched — there Hibernate rightly owns
everything.

**Why a filter rather than just `ddl-auto=none`:** off, and a fresh
branch is missing the 29 inherited tables. On, and Hibernate can reach
the other 29. The filter is what lets both be true at once.

**Why per-table, not per-schema:** Hibernate's schema management runs
over every mapped entity regardless of namespace. Moving SaaS tables to
their own schema would *not* by itself keep Hibernate out of them —
worth knowing, because that was the intuitive fix and it doesn't work.

## The part that makes it stick

`SaasSchemaOwnershipTest` makes the register binding: every `@Entity` on
the SaaS classpath must appear in exactly one set, so **a new entity
fails the build until someone states who owns its table**. That's the
forcing function that would have caught `payg_instance_usage`.

I verified it bites rather than assuming it — removing a single entry
fails with:

```
These entity tables are not declared in SaasSchemaOwnership, so nobody owns them.
Offending tables -> entities: [policies (stirling.software.proprietary.policy.store.PolicyEntity)]
```

naming both the table and the class, which is what the next person
actually needs.

## One debatable call

The **validate** filter excludes them too. Letting validation through
would flag drift, which is genuinely useful — but `ddl-auto=validate`
fails startup, and it would fail on differences we've deliberately
accepted (`ai_create_sessions` carries columns from a reverted Typst
feature that nothing maps). A boot failure over a table we chose not to
manage is noise. Argued in the javadoc; happy to flip it if you'd rather
have the signal.

## Dependency

Depends on
[Stirling-PDF-SaaS#324](https://github.com/Stirling-Tools/Stirling-PDF-SaaS/pull/324),
which adds migrations for the four SaaS-owned tables that had none.
They're listed here as migration-owned on that basis, so #324 should
land first.

Companion to
[#7483](https://github.com/Stirling-Tools/Stirling-PDF/pull/7483)
(dev/staging profiles with per-profile `ddl-auto`).

## Verification

`:saas:test` green including the 5 new tests, `spotlessCheck` green, and
the mutation check above.
2026-08-17 10:08:09 +00:00
EthanHealy01 08b08aa8a1 Let everyone read the failures they caused (Review Flow PR 3) (#7477)
Review Flow PR 3. Stacked on #7296. A recorded failure becomes readable
by the person who caused it.

## What changes

Before this, reading or triaging a failure required leader permissions:
`FileRunEventController.requireFailureReviewAllowed()` returned 403 to
anyone who could not edit policies. #7296 lets any user report a
failure, so they could file into a queue they could never read.

That gate is removed from the endpoints and the decision moves into
`FileRunEventService`:

| Caller | Reads and closes |
|---|---|
| Team leader or admin | the whole team's failures (unchanged) |
| Anyone else | only failures where `actor` is them |
| Team unresolvable | nothing |
| Name unresolvable | nothing |

`GET /kinds` is also opened. It returns static enum metadata, and a
member needs it to render failures they can already see.

## Additions

- An `actor` predicate on both list queries in `FileRunEventRepository`,
threaded through `FileRunEventStore.list`.
- `ReadScope` (permitted, teamId, actor) replacing `TeamScope`, with
`wholeTeam` / `mine` / `denied` factories.
- An actor filter on `dispatch`, so acting on another person's row
answers **404, not 403** — the same response as an id that does not
exist.

## Fixes

- **`report()` filed rows under the wrong team.** It took the team from
the read scope, which returns null for a caller who cannot be named, so
such a report landed unteamed in the bucket every team shares. It now
uses a dedicated `currentTeamId()`.
- **`forgetFiles` narrows to the caller even for a leader.** File ids
are minted by each client, so scoping on team alone would let one caller
close a colleague's incidents by naming ids.
- The controller no longer injects `PolicyManagementAuthority` or
`ApplicationProperties`; with the gate gone it decides nothing.

## Team isolation

Unchanged and covered by database-backed tests rather than mocks.
`FileRunEventStoreDbTest` asserts that a caller with a team sees only
their own team's rows and never the unteamed ones, and that the actor
predicate narrows within a team without ever widening across one. Delete
either clause from the JPQL and one of those tests fails.

No endpoint accepts a team parameter; the team always comes from the
authenticated principal.

**Attribution is fixed here too, because this PR depends on it.** A
failure's actor was read from the MDC audit principal, which carries the
BILLING identity — for a stored policy, always its owner. Since reads
are now narrowed to the rows you are the actor on, a wrong actor means
the member who caused a failure and holds the document reads nothing,
while the policy owner is handed incidents from runs they never
triggered. The triggering user is now carried on the run, separate from
the billing principal and the output owner, and is null for a
trigger-fired sweep so an unattended failure stays ownerless.

`PolicyFailureAttributionTest` runs the real engine, recorder, store and
service together. The two sides used to assert independently — the
engine's test matched the actor with `any()`, which is how this went
unnoticed.

## How to test

Needs a proprietary or SaaS build with login enabled and two accounts in
the same team, one a leader and one not. `task dev:all` gives you the
stack.

1. **As the member**, fail a tool: open a PDF and run **Remove
Password** with a wrong password.
2. **Still as the member**, go to `/processor/documents` → **Failures**.
Before this PR you got nothing here. Now you see your own row, and only
yours.
3. **As the leader**, open the same view. You see the whole team's rows,
including the member's.
4. **Member cannot reach a colleague's row.** As the leader, copy a
row's id from **Show raw JSON**. As the member, `POST
/api/v1/file-run-events/{thatId}/actions/DISMISS`. It answers **404**,
and the row is untouched — it must not answer 403, which would confirm
the row exists.
5. **Member can close their own.** Dismiss your own row as the member.
It leaves the default view.
6. **Deleting a file only closes your own rows.** As the leader, delete
a file in your editor. The member's incidents are untouched even if the
leader's client happened to name the same ids.

## Migration

None. `actor` is an existing column; this only adds predicates to
existing queries.
2026-08-16 22:27:20 +00:00
Ludy 2c89f5fe52 fix(ci): locale path and docs (#7525)
# Description of Changes

Update the TOML locale check to match the current frontend path and
align comments/docs with the relocated editor and proprietary source
layout. This keeps CI filtering and internal references accurate after
the frontend reorganization.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-16 17:17:16 +01:00
dependabot[bot] 9a90797041 build(deps): bump awsSdkVersion from 2.51.2 to 2.51.3 (#7529)
Bumps `awsSdkVersion` from 2.51.2 to 2.51.3.
Updates `software.amazon.awssdk:s3` from 2.51.2 to 2.51.3

Updates `software.amazon.awssdk:url-connection-client` from 2.51.2 to
2.51.3


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 17:15:47 +01:00
dependabot[bot] 9c10204cb0 build(deps): bump Swatinem/rust-cache from 2.9.1 to 2.9.2 (#7531)
Bumps [Swatinem/rust-cache](https://github.com/swatinem/rust-cache) from
2.9.1 to 2.9.2.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/swatinem/rust-cache/releases">Swatinem/rust-cache's
releases</a>.</em></p>
<blockquote>
<h2>v2.9.2</h2>
<h2>What's Changed</h2>
<ul>
<li>Typofix by <a
href="https://github.com/23Skidoo"><code>@​23Skidoo</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/316">Swatinem/rust-cache#316</a></li>
<li>fix: include target names in build/ and .fingerprint/ cleanup by <a
href="https://github.com/eitsupi"><code>@​eitsupi</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/317">Swatinem/rust-cache#317</a></li>
<li>fix: include cdylib/rlib/dylib/staticlib targets in build and
fingerprint cleanup by <a
href="https://github.com/eitsupi"><code>@​eitsupi</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/320">Swatinem/rust-cache#320</a></li>
<li>Scan content of <code>$CARGO_HOME/bin</code> on restore instead of
relying on <code>cargo install</code> metadata by <a
href="https://github.com/clechasseur"><code>@​clechasseur</code></a> in
<a
href="https://redirect.github.com/Swatinem/rust-cache/pull/325">Swatinem/rust-cache#325</a></li>
<li>docs: Update checkout action version to latest by <a
href="https://github.com/sondrelg"><code>@​sondrelg</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/345">Swatinem/rust-cache#345</a></li>
<li>Fix Windows cache path validation after Rollup migration by <a
href="https://github.com/eitsupi"><code>@​eitsupi</code></a> in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/355">Swatinem/rust-cache#355</a></li>
<li>fix: support Cargo V2 build dir layout by <a
href="https://github.com/claytonwramsey"><code>@​claytonwramsey</code></a>
in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/371">Swatinem/rust-cache#371</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/23Skidoo"><code>@​23Skidoo</code></a>
made their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/316">Swatinem/rust-cache#316</a></li>
<li><a href="https://github.com/eitsupi"><code>@​eitsupi</code></a> made
their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/317">Swatinem/rust-cache#317</a></li>
<li><a
href="https://github.com/clechasseur"><code>@​clechasseur</code></a>
made their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/325">Swatinem/rust-cache#325</a></li>
<li><a href="https://github.com/sondrelg"><code>@​sondrelg</code></a>
made their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/345">Swatinem/rust-cache#345</a></li>
<li><a
href="https://github.com/claytonwramsey"><code>@​claytonwramsey</code></a>
made their first contribution in <a
href="https://redirect.github.com/Swatinem/rust-cache/pull/371">Swatinem/rust-cache#371</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/Swatinem/rust-cache/compare/v2.9.1...v2.9.2">https://github.com/Swatinem/rust-cache/compare/v2.9.1...v2.9.2</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/Swatinem/rust-cache/blob/master/CHANGELOG.md">Swatinem/rust-cache's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>2.9.2</h2>
<ul>
<li>Fix <code>credentials.toml</code> cleanup</li>
<li>Improvements to cleanup, preserving more valid targets</li>
<li>Improvements to <code>cargo install</code> handling</li>
<li>Correctly sort/dedupe Rust versions</li>
</ul>
<h2>2.9.1</h2>
<ul>
<li>Fix regression in hash calculation</li>
</ul>
<h2>2.9.0</h2>
<ul>
<li>Update to <code>node24</code></li>
<li>Support running from within a <code>nix</code> shell</li>
<li>Consider all installed toolchains for cache key</li>
<li>Use case-insensitive comparison to determine exact cache hit</li>
</ul>
<h2>2.8.2</h2>
<ul>
<li>Don't overwrite env for cargo-metadata call</li>
</ul>
<h2>2.8.1</h2>
<ul>
<li>Set empty <code>CARGO_ENCODED_RUSTFLAGS</code> when retrieving
metadata</li>
<li>Various dependency updates</li>
</ul>
<h2>2.8.0</h2>
<ul>
<li>Add support for <code>warpbuild</code> cache provider</li>
<li>Add new <code>cache-workspace-crates</code> feature</li>
</ul>
<h2>2.7.8</h2>
<ul>
<li>Include CPU arch in the cache key</li>
</ul>
<h2>2.7.7</h2>
<ul>
<li>Also cache <code>cargo install</code> metadata</li>
</ul>
<h2>2.7.6</h2>
<ul>
<li>Allow opting out of caching $CARGO_HOME/bin</li>
<li>Add runner OS in cache key</li>
<li>Adds an option to do lookup-only of the cache</li>
</ul>
<h2>2.7.5</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/6323deb102c322ba6fcbdcafc7e3dddab59af2b6"><code>6323deb</code></a>
2.9.2</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/b16e8d71b289c3b8fc03fc09764563df03712036"><code>b16e8d7</code></a>
bump rollup and rebuild</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/3bf42ac996de475743278f3187c4fd89f23b8630"><code>3bf42ac</code></a>
invert target/profile check in cleanup</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/6e5b278ead409e28cd5a784014d7ce55007a81d2"><code>6e5b278</code></a>
correctly sort and dedupe Rust versions</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/5adc05f6aaa7c92756cb2e2c9b7b3c1d0df9312b"><code>5adc05f</code></a>
Bump the actions group across 1 directory with 3 updates (<a
href="https://redirect.github.com/swatinem/rust-cache/issues/368">#368</a>)</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/66b1e9526150e74ddd7e4190356facd783fb3b44"><code>66b1e95</code></a>
fix: support Cargo V2 build dir layout (<a
href="https://redirect.github.com/swatinem/rust-cache/issues/371">#371</a>)</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/72d126e709cad40056a62344eee609d696d62d33"><code>72d126e</code></a>
Merge pull request <a
href="https://redirect.github.com/swatinem/rust-cache/issues/367">#367</a>
from Swatinem/dependabot/npm_and_yarn/dev-patch-2b495...</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/48968d2131215f1c516b89399d23900d554f71fa"><code>48968d2</code></a>
Bump the dev-patch group with 2 updates</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/9f151aca7c3990bab7afe2d82ac58088d3b01074"><code>9f151ac</code></a>
update dependencies, rebuild</li>
<li><a
href="https://github.com/Swatinem/rust-cache/commit/0e24e5dcecfbbdcea69e0f528cab00df34fbd231"><code>0e24e5d</code></a>
Bump the actions group across 1 directory with 6 updates (<a
href="https://redirect.github.com/swatinem/rust-cache/issues/364">#364</a>)</li>
<li>Additional commits viewable in <a
href="https://github.com/swatinem/rust-cache/compare/c19371144df3bb44fab255c43d04cbc2ab54d1c4...6323deb102c322ba6fcbdcafc7e3dddab59af2b6">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=Swatinem/rust-cache&package-manager=github_actions&previous-version=2.9.1&new-version=2.9.2)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 17:15:37 +01:00
dependabot[bot] beea37cb92 build(deps): bump step-security/harden-runner from 2.20.0 to 2.20.1 (#7466)
Bumps
[step-security/harden-runner](https://github.com/step-security/harden-runner)
from 2.20.0 to 2.20.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/step-security/harden-runner/releases">step-security/harden-runner's
releases</a>.</em></p>
<blockquote>
<h2>v2.20.1</h2>
<h2>What's Changed</h2>
<ul>
<li>AWS CodeBuild-hosted runner support</li>
<li>Implicitly allow single-labeled (internal) domains in
block-mode</li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/step-security/harden-runner/compare/v2.20.0...v2.20.1">https://github.com/step-security/harden-runner/compare/v2.20.0...v2.20.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/step-security/harden-runner/commit/b09bb98e06d4d774595224525879c09bc6e98c40"><code>b09bb98</code></a>
Merge pull request <a
href="https://redirect.github.com/step-security/harden-runner/issues/680">#680</a>
from step-security/aws-code-build</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/35cd77bcf669054f67ffd3d2802ee54a4f13b5b6"><code>35cd77b</code></a>
docs: document the Global Block List in the features list</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/bb6dbef4bf53876cd2710acd1d36413620d20fb3"><code>bb6dbef</code></a>
chore: rebuild dist with clean dependency install</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/98f73c5a0d2b2cc518e6fb8d973a0a4dde00ba13"><code>98f73c5</code></a>
chore: update eBPF agent to v1.8.14</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/54193c17a4fa3883977217b9afe20378ebe60b19"><code>54193c1</code></a>
Reapply &quot;feat(runners): detect AWS CodeBuild-hosted runners as
third-party pr...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/d22dd481cea4e96cedde031cfe600c248b592d54"><code>d22dd48</code></a>
Revert &quot;fix(self-hosted): flush agent events at job end when
deploy-on-self-h...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/0ff09412fb572363b483a3c86ffe52fe61d9fd19"><code>0ff0941</code></a>
fix(self-hosted): flush agent events at job end when
deploy-on-self-hosted-vm...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/a3c333d110c8d95f34488a22e0e56742cfb1b14f"><code>a3c333d</code></a>
Revert &quot;feat(runners): detect AWS CodeBuild-hosted runners as
third-party pro...</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/bf94c00d6bba2ae7c4a479b86653039811569968"><code>bf94c00</code></a>
feat(runners): detect AWS CodeBuild-hosted runners as third-party
provider</li>
<li><a
href="https://github.com/step-security/harden-runner/commit/514522c5e449f9e28fc901f770e08a573d413e67"><code>514522c</code></a>
fix(self-hosted): resolve runner user when USER env var is unset</li>
<li>See full diff in <a
href="https://github.com/step-security/harden-runner/compare/bf7454d06d71f1098171f2acdf0cd4708d7b5920...b09bb98e06d4d774595224525879c09bc6e98c40">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-08-16 14:18:51 +01:00
dependabot[bot] fb0281fc80 build(deps-dev): bump codespell from 2.4.2 to 2.4.3 in /engine (#7456)
Bumps [codespell](https://github.com/codespell-project/codespell) from
2.4.2 to 2.4.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/codespell-project/codespell/releases">codespell's
releases</a>.</em></p>
<blockquote>
<h2>v2.4.3</h2>
<!-- raw HTML omitted -->
<h2>What's Changed</h2>
<ul>
<li>Add 'radback' to dictionary with correction by <a
href="https://github.com/Flo3561"><code>@​Flo3561</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3883">codespell-project/codespell#3883</a></li>
<li>Add 'repetirion' to dictionary corrections by <a
href="https://github.com/Flo3561"><code>@​Flo3561</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3885">codespell-project/codespell#3885</a></li>
<li>Need to specify a version of Python version after all by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3887">codespell-project/codespell#3887</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3889">codespell-project/codespell#3889</a></li>
<li>Add cases for &quot;modulle&quot; -&gt; &quot;module&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3888">codespell-project/codespell#3888</a></li>
<li>Add case &quot;auido&quot; -&gt; &quot;audio&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3890">codespell-project/codespell#3890</a></li>
<li>Add credentilas-&gt;credentials and friends by <a
href="https://github.com/peternewman"><code>@​peternewman</code></a> in
<a
href="https://redirect.github.com/codespell-project/codespell/pull/3895">codespell-project/codespell#3895</a></li>
<li>Add the case &quot;cubid&quot; -&gt; &quot;cubic&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3891">codespell-project/codespell#3891</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3897">codespell-project/codespell#3897</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3900">codespell-project/codespell#3900</a></li>
<li>Bump codecov/codecov-action from 5 to 6 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3902">codespell-project/codespell#3902</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3904">codespell-project/codespell#3904</a></li>
<li>Add <code>magntiude-&gt;magnitude</code> by <a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3899">codespell-project/codespell#3899</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3909">codespell-project/codespell#3909</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3912">codespell-project/codespell#3912</a></li>
<li>Add the case &quot;instanc&quot; -&gt; &quot;instance&quot; by <a
href="https://github.com/utzcoz"><code>@​utzcoz</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3896">codespell-project/codespell#3896</a></li>
<li>gampad -&gt; gamepad (and plural) by <a
href="https://github.com/julianstirling"><code>@​julianstirling</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3906">codespell-project/codespell#3906</a></li>
<li>Add typos of <code>monotonic</code> and <code>monotonicity</code> by
<a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3898">codespell-project/codespell#3898</a></li>
<li>Add spelling correction for multipile(s)/vulnerabities. by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3905">codespell-project/codespell#3905</a></li>
<li>Add 'simpilfy -&gt; simplify' by <a
href="https://github.com/alexreinking"><code>@​alexreinking</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3913">codespell-project/codespell#3913</a></li>
<li>fix(packaging): prevent unwanted files and tests from being
installed by <a
href="https://github.com/mikelolasagasti"><code>@​mikelolasagasti</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3911">codespell-project/codespell#3911</a></li>
<li>Add skarhoj-&gt;SKAARHOJ to dictionary corrections by <a
href="https://github.com/peternewman"><code>@​peternewman</code></a> in
<a
href="https://redirect.github.com/codespell-project/codespell/pull/3908">codespell-project/codespell#3908</a></li>
<li>Improve the dictionary by <a
href="https://github.com/algonell"><code>@​algonell</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3914">codespell-project/codespell#3914</a></li>
<li>Add spelling correction for accorss/accors. by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3916">codespell-project/codespell#3916</a></li>
<li>Bump autofix-ci/action from 1.3.3 to 1.3.4 by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3921">codespell-project/codespell#3921</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3923">codespell-project/codespell#3923</a></li>
<li>Add <code>influecer-&gt;influencer</code> and <code>influnce*</code>
typos to dictionary by <a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3925">codespell-project/codespell#3925</a></li>
<li>Add typos for <code>excavate</code> and variants by <a
href="https://github.com/nathanjmcdougall"><code>@​nathanjmcdougall</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3926">codespell-project/codespell#3926</a></li>
<li>Dict: Add corrections for memoy by <a
href="https://github.com/mdeweerd"><code>@​mdeweerd</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3924">codespell-project/codespell#3924</a></li>
<li><code>overheda -&gt; overhead</code> by <a
href="https://github.com/George-Ogden"><code>@​George-Ogden</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3919">codespell-project/codespell#3919</a></li>
<li><code>inclusize-&gt;inclusive</code> and variants by <a
href="https://github.com/George-Ogden"><code>@​George-Ogden</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3918">codespell-project/codespell#3918</a></li>
<li>Add spelling corrections for authorization by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3922">codespell-project/codespell#3922</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3927">codespell-project/codespell#3927</a></li>
<li>Don't fix Voight by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3929">codespell-project/codespell#3929</a></li>
<li>Add spelling corrections for interstect and interstection by <a
href="https://github.com/korli"><code>@​korli</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3928">codespell-project/codespell#3928</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3930">codespell-project/codespell#3930</a></li>
<li>Improve output in interactive mode by <a
href="https://github.com/darkmattercoder"><code>@​darkmattercoder</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3884">codespell-project/codespell#3884</a></li>
<li>feat: support codespell:ignore-next-line directive by <a
href="https://github.com/SAY-5"><code>@​SAY-5</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3931">codespell-project/codespell#3931</a></li>
<li>Add woork-&gt;work and formace-&gt;format and friends by <a
href="https://github.com/peternewman"><code>@​peternewman</code></a> in
<a
href="https://redirect.github.com/codespell-project/codespell/pull/3828">codespell-project/codespell#3828</a></li>
<li>shortctu -&gt; shortcut by <a
href="https://github.com/George-Ogden"><code>@​George-Ogden</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3934">codespell-project/codespell#3934</a></li>
<li>A couple typos by <a
href="https://github.com/DimitriPapadopoulos"><code>@​DimitriPapadopoulos</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3935">codespell-project/codespell#3935</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3937">codespell-project/codespell#3937</a></li>
<li>[pre-commit.ci] pre-commit autoupdate by <a
href="https://github.com/pre-commit-ci"><code>@​pre-commit-ci</code></a>[bot]
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3942">codespell-project/codespell#3942</a></li>
<li>reclaculate-&gt;recalculate by <a
href="https://github.com/adamgann"><code>@​adamgann</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3936">codespell-project/codespell#3936</a></li>
<li>Add spelling correction for improprt. by <a
href="https://github.com/cfi-gb"><code>@​cfi-gb</code></a> in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3939">codespell-project/codespell#3939</a></li>
<li>Dictionary plasic-plastic by <a
href="https://github.com/julianstirling"><code>@​julianstirling</code></a>
in <a
href="https://redirect.github.com/codespell-project/codespell/pull/3938">codespell-project/codespell#3938</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/codespell-project/codespell/commit/57b21406f092110c18776e39b0bda50d37c945c8"><code>57b2140</code></a>
Read only [tool.codespell] from TOML config (<a
href="https://redirect.github.com/codespell-project/codespell/issues/3975">#3975</a>)</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/23b8d940332bcff6369872b53964d4137c586728"><code>23b8d94</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/926c4d6ff083acc62bc04c102e0a9b2091d754f1"><code>926c4d6</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/820a3011e0bfe3f90789ba41e751a26656bea7ab"><code>820a301</code></a>
Merge pull request <a
href="https://redirect.github.com/codespell-project/codespell/issues/3967">#3967</a>
from codespell-project/peternewman-patch-1</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/9fe42c964c90f65a9a2841847af4e093c67ec817"><code>9fe42c9</code></a>
Add common misspellings for reseeve-&gt;reserve to dictionary</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/725173c815cee1b781b85ec51eb43dc4f51dcd0f"><code>725173c</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/86c13ee4dce0010e8d8119ded33694806e37ca8f"><code>86c13ee</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/7b9f36ce634c551107a3a4a5c25782a2245d7658"><code>7b9f36c</code></a>
Bump actions/checkout from 6 to 7</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/bcc1b804a83b8a146586101431b063c07cfcc699"><code>bcc1b80</code></a>
Add spelling corrections for simpe and variants.</li>
<li><a
href="https://github.com/codespell-project/codespell/commit/89584cc1479d0f8dbfd501925673b6be6d460fe1"><code>89584cc</code></a>
[pre-commit.ci] pre-commit autoupdate</li>
<li>Additional commits viewable in <a
href="https://github.com/codespell-project/codespell/compare/v2.4.2...v2.4.3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=codespell&package-manager=uv&previous-version=2.4.2&new-version=2.4.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:18:23 +01:00
dependabot[bot] 88db26b001 build(deps): bump the eclipse-temurin group across 3 directories with 1 update (#7453)
Bumps the eclipse-temurin group with 1 update in the /docker/backend
directory: eclipse-temurin.
Bumps the eclipse-temurin group with 1 update in the /docker/base
directory: eclipse-temurin.
Bumps the eclipse-temurin group with 1 update in the /docker/embedded
directory: eclipse-temurin.

Updates `eclipse-temurin` from `2f1da10` to `fbcf915`

Updates `eclipse-temurin` from `2f1da10` to `fbcf915`

Updates `eclipse-temurin` from `2f1da10` to `fbcf915`

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:17:29 +01:00
dependabot[bot] 0deb6fc3d5 build(deps): bump dorny/paths-filter from 4.0.2 to 4.0.3 (#7465)
Bumps [dorny/paths-filter](https://github.com/dorny/paths-filter) from
4.0.2 to 4.0.3.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/releases">dorny/paths-filter's
releases</a>.</em></p>
<blockquote>
<h2>v4.0.3</h2>
<h2>What's Changed</h2>
<ul>
<li>Update Outputs in readme to account for the 'every'
predicate-quantifier by <a
href="https://github.com/hintron"><code>@​hintron</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/247">dorny/paths-filter#247</a></li>
<li>fix: scope base-ignored warning to API path by <a
href="https://github.com/saschabratton"><code>@​saschabratton</code></a>
in <a
href="https://redirect.github.com/dorny/paths-filter/pull/319">dorny/paths-filter#319</a></li>
<li>docs: add contents permission to PR example by <a
href="https://github.com/134130"><code>@​134130</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/248">dorny/paths-filter#248</a></li>
<li>feat: add 'some-with-excludes' predicate quantifier by <a
href="https://github.com/arxeiss"><code>@​arxeiss</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/322">dorny/paths-filter#322</a></li>
<li>Document safe handling of file list outputs in workflows by <a
href="https://github.com/dorny"><code>@​dorny</code></a> in <a
href="https://redirect.github.com/dorny/paths-filter/pull/326">dorny/paths-filter#326</a></li>
</ul>
<h2>Security</h2>
<ul>
<li>Escape multi-line filenames in list-files shell and csv output] by
<a href="https://github.com/ken-matsui"><code>@​ken-matsui</code></a>
and <a href="https://github.com/tjswlsgg"><code>@​tjswlsgg</code></a> in
<a
href="https://github.com/advisories/GHSA-7hc6-8hq5-9q2m">https://github.com/advisories/GHSA-7hc6-8hq5-9q2m</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a href="https://github.com/hintron"><code>@​hintron</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/247">dorny/paths-filter#247</a></li>
<li><a href="https://github.com/134130"><code>@​134130</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/248">dorny/paths-filter#248</a></li>
<li><a href="https://github.com/arxeiss"><code>@​arxeiss</code></a> made
their first contribution in <a
href="https://redirect.github.com/dorny/paths-filter/pull/322">dorny/paths-filter#322</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/dorny/paths-filter/compare/v4...v4.0.3">https://github.com/dorny/paths-filter/compare/v4...v4.0.3</a></p>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/dorny/paths-filter/blob/master/CHANGELOG.md">dorny/paths-filter's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>v4.0.3</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/326">Document
safe handling of file list outputs in workflows</a></li>
<li><a href="https://github.com/advisories/GHSA-7hc6-8hq5-9q2m">Escape
multi-line filenames in list-files shell and csv output</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/322">Add
'some-with-excludes' predicate quantifier</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/248">Add
contents permission to PR example</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/319">Scope
base-ignored warning to API path</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/247">Update
outputs in readme to account for the 'every'
predicate-quantifier</a></li>
</ul>
<h2>v4.0.2</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/317">Work
around git dubious ownership errors in container jobs</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/303">Use
rev-parse instead of branch --show-current for older git compat</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/282">Fix
warning message</a></li>
</ul>
<h2>v4.0.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/255">Support
merge queue</a></li>
</ul>
<h2>v4.0.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/294">Update
action runtime to node24</a></li>
</ul>
<h2>v3.0.4</h2>
<ul>
<li><a href="https://github.com/advisories/GHSA-7hc6-8hq5-9q2m">Escape
multi-line filenames in list-files shell and csv output</a></li>
</ul>
<h2>v3.0.3</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/279">Add
missing predicate-quantifier</a></li>
</ul>
<h2>v3.0.2</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/224">Add
config parameter for predicate quantifier</a></li>
</ul>
<h2>v3.0.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/133">Compare
base and ref when token is empty</a></li>
</ul>
<h2>v3.0.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/210">Update to
Node.js 20</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/215">Update
all dependencies</a></li>
</ul>
<h2>v2.11.1</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/167">Update
@​actions/core to v1.10.0 - Fixes warning about deprecated
set-output</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/168">Document
need for pull-requests: read permission</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/164">Updating
to actions/checkout@v3</a></li>
</ul>
<h2>v2.11.0</h2>
<ul>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/157">Set
list-files input parameter as not required</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/161">Update
Node.js</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/162">Fix
incorrect handling of Unicode characters in exec()</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/163">Use
Octokit pagination</a></li>
<li><a
href="https://redirect.github.com/dorny/paths-filter/pull/160">Updates
real world links</a></li>
</ul>
<h2>v2.10.2</h2>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/dorny/paths-filter/commit/ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d"><code>ceb8a2b</code></a>
Update CHANGELOG.md for v4.0.3 and v3.0.4 (<a
href="https://redirect.github.com/dorny/paths-filter/issues/327">#327</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/ef09b88f3eacdbec6ce135a7c9a193a6849545c1"><code>ef09b88</code></a>
Document safe handling of file list outputs in workflows (<a
href="https://redirect.github.com/dorny/paths-filter/issues/326">#326</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/44adc5b06dc135dba334efce9bf3cf0624512d2d"><code>44adc5b</code></a>
Merge commit from fork</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/4711b7a31b4aa89103d8c6ffab2e3b8e7b6381c7"><code>4711b7a</code></a>
feat: add 'some-with-excludes' predicate quantifier (<a
href="https://redirect.github.com/dorny/paths-filter/issues/322">#322</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/93c889f9e58fca66f35a0c83d8673ac7e88bb70a"><code>93c889f</code></a>
fix: escape multi-line filenames in list-files shell and csv output</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/b41dfa943b1939b9b646f67753bfe35cf6e4de03"><code>b41dfa9</code></a>
docs: add contents permission to PR example (<a
href="https://redirect.github.com/dorny/paths-filter/issues/248">#248</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/9af6e5a9d010d1ae8ec570390b3d793e2b70a402"><code>9af6e5a</code></a>
fix: scope base-ignored warning to API path (<a
href="https://redirect.github.com/dorny/paths-filter/issues/319">#319</a>)</li>
<li><a
href="https://github.com/dorny/paths-filter/commit/cae9006b65a1a53044b518c68e13e835c54948a7"><code>cae9006</code></a>
docs: update outputs in readme to account for the 'every'
predicate-quantifie...</li>
<li>See full diff in <a
href="https://github.com/dorny/paths-filter/compare/7b450fff21473bca461d4b92ce414b9d0420d706...ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dorny/paths-filter&package-manager=github_actions&previous-version=4.0.2&new-version=4.0.3)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:17:18 +01:00
dependabot[bot] 42c84a1693 build(deps): bump base64 from 0.23.0 to 0.23.1 in /frontend/editor/src-tauri (#7460)
Bumps [base64](https://github.com/marshallpierce/rust-base64) from
0.23.0 to 0.23.1.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/marshallpierce/rust-base64/blob/master/RELEASE-NOTES.md">base64's
changelog</a>.</em></p>
<blockquote>
<h1>0.23.1</h1>
<ul>
<li>Make the tests build again on non-SIMD architectures</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/069bf7067b949f5c0a92b6ceb82492920502f2c2"><code>069bf70</code></a>
v0.23.1</li>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/6ab1fb0a5843013557a52c45c84b91f5d1bb87af"><code>6ab1fb0</code></a>
Merge pull request <a
href="https://redirect.github.com/marshallpierce/rust-base64/issues/310">#310</a>
from musicinmybrain/test-on-non-simd-arches</li>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/7cffce6f971acdf48f83112cbdd63bd61125ba06"><code>7cffce6</code></a>
Fix testing on architectures without unsafe SIMD support</li>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/e34f9a08c5c89a4641350ac22033f3fa4f5d4d97"><code>e34f9a0</code></a>
Merge pull request <a
href="https://redirect.github.com/marshallpierce/rust-base64/issues/308">#308</a>
from atouchet/com</li>
<li><a
href="https://github.com/marshallpierce/rust-base64/commit/e9240c9a01e0a4934c5619e74740caa6d1f67ce9"><code>e9240c9</code></a>
Remove outdated comment</li>
<li>See full diff in <a
href="https://github.com/marshallpierce/rust-base64/compare/v0.23.0...v0.23.1">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=base64&package-manager=cargo&previous-version=0.23.0&new-version=0.23.1)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:17:01 +01:00
dependabot[bot] e2700bf391 build(deps): bump go-task/setup-task from 2.0.0 to 2.1.0 (#7468)
Bumps [go-task/setup-task](https://github.com/go-task/setup-task) from
2.0.0 to 2.1.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/go-task/setup-task/releases">go-task/setup-task's
releases</a>.</em></p>
<blockquote>
<h2>v2.1.0</h2>
<h2>What's Changed</h2>
<ul>
<li>Replaced <code>typed-rest-client</code> with
<code>@actions/http-client</code> for GitHub API calls
to eliminate the Node 24 <code>DEP0169</code> deprecation warning about
<code>url.parse()</code> (<a
href="https://redirect.github.com/go-task/setup-task/issues/5">#5</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
<li>Modernized the TypeScript tooling stack (vitest, oxlint,
<code>@actions/core@2</code>,
<code>@actions/io@2</code>, updated <code>@types/node</code>,
<code>@vercel/ncc</code>, <code>prettier</code>, etc.) (<a
href="https://redirect.github.com/go-task/setup-task/issues/5">#5</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
<li>Migrated the project to ESM (sources + bundle). Aligns with the new
<code>@actions/*</code> ESM-only majors and produces a ~47% smaller
<code>dist/index.js</code> (<a
href="https://redirect.github.com/go-task/setup-task/issues/5">#5</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
<li>Upgraded <code>@actions/core</code> 2 → 3,
<code>@actions/http-client</code> 2 → 4,
<code>@actions/io</code> 2 → 3, <code>@actions/tool-cache</code> 2 → 4,
<code>typescript</code> 5 → 6, and
<code>markdownlint-cli</code> 0.47 → 0.48 (<a
href="https://redirect.github.com/go-task/setup-task/issues/5">#5</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/go-task/setup-task/blob/main/CHANGELOG.md">go-task/setup-task's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2>Unreleased</h2>
<h2>v2.2.0 - 2026-08-09</h2>
<ul>
<li>Added a <code>checksum</code> input to verify the SHA256 digest of
the downloaded Task
archive (<a
href="https://redirect.github.com/go-task/setup-task/pull/16">#16</a> by
<a href="https://github.com/illera88"><code>@​illera88</code></a>).</li>
<li>Updated development dependencies: <code>typescript</code> 6 → 7,
<code>@vercel/ncc</code> 0.38 →
0.44, <code>markdownlint-cli</code> 0.48 → 0.49, and <code>oxfmt</code>
0.50 → 0.62
(<a
href="https://redirect.github.com/go-task/setup-task/pull/15">#15</a>).</li>
</ul>
<h2>v2.1.0 - 2026-05-17</h2>
<ul>
<li>Replaced <code>typed-rest-client</code> with
<code>@actions/http-client</code> for GitHub API calls
to eliminate the Node 24 <code>DEP0169</code> deprecation warning about
<code>url.parse()</code>.</li>
<li>Modernized the TypeScript tooling stack (vitest, oxlint,
<code>@actions/core@2</code>,
<code>@actions/io@2</code>, updated <code>@types/node</code>,
<code>@vercel/ncc</code>, <code>prettier</code>, etc.).</li>
<li>Migrated the project to ESM (sources + bundle). Aligns with the new
<code>@actions/*</code> ESM-only majors and produces a ~47% smaller
<code>dist/index.js</code>.</li>
<li>Upgraded <code>@actions/core</code> 2 → 3,
<code>@actions/http-client</code> 2 → 4,
<code>@actions/io</code> 2 → 3, <code>@actions/tool-cache</code> 2 → 4,
<code>typescript</code> 5 → 6, and
<code>markdownlint-cli</code> 0.47 → 0.48.</li>
</ul>
<h2>v2.0.0 - 2026-03-18</h2>
<ul>
<li><strong>BREAKING</strong>: Upgraded to Node 24. Requires a GitHub
Actions runner with
Node.js 24 support
(<a
href="https://redirect.github.com/go-task/setup-task/pull/10">#10</a> by
<a href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
</ul>
<h2>v1.1.0 - 2026-03-17</h2>
<ul>
<li>Added configurable HTTP retry for API requests
(<a href="https://redirect.github.com/go-task/setup-task/pull/7">#7</a>
by <a
href="https://github.com/vmaerten"><code>@​vmaerten</code></a>).</li>
</ul>
<h2>v1.0.0 - 2025-09-12</h2>
<ul>
<li>Forked <a
href="https://github.com/arduino/setup-task">arduino/setup-task</a> (by
<a href="https://github.com/pd93"><code>@​pd93</code></a>).</li>
<li>Default <code>repo-token</code> to <code>{{github.token}}</code>
(<a
href="https://redirect.github.com/arduino/setup-task/pull/642">arduino/setup-task#642</a>
by
<a href="https://github.com/shrink"><code>@​shrink</code></a>).</li>
<li>Fixed a bug where the action would fail is Task pushed a tag without
a release
(<a
href="https://redirect.github.com/arduino/setup-task/pull/490">arduino/setup-task#490</a>,
<a
href="https://redirect.github.com/arduino/setup-task/pull/1193">arduino/setup-task#1193</a>
by
<a href="https://github.com/trim21"><code>@​trim21</code></a>).</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/go-task/setup-task/commit/54fb0b2f4e4f3b1c1e1c0b2050ab8f28d794ca5f"><code>54fb0b2</code></a>
fix test</li>
<li><a
href="https://github.com/go-task/setup-task/commit/a3ce905e2b21a672e0f8b0109be2a38c5ffbd461"><code>a3ce905</code></a>
fix test</li>
<li><a
href="https://github.com/go-task/setup-task/commit/533258619790ff7048a24bc82b7d097ffbfbde10"><code>5332586</code></a>
fix test</li>
<li><a
href="https://github.com/go-task/setup-task/commit/7d25465fe82d5ceaa018b136e5117b6f8cd37eb2"><code>7d25465</code></a>
fix test</li>
<li><a
href="https://github.com/go-task/setup-task/commit/7a0f90a08f2d4d3a3bb462b3e976240197d8925d"><code>7a0f90a</code></a>
fix: fetch versions from release instead of tags</li>
<li>See full diff in <a
href="https://github.com/go-task/setup-task/compare/v2...01a4adf9db2d14c1de7a560f09170b6e0df736aa">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-08-16 14:16:50 +01:00
dependabot[bot] eece9fbdf8 build(deps): bump commons-net:commons-net from 3.11.1 to 3.13.0 (#7517)
Bumps [commons-net:commons-net](https://github.com/apache/commons-net)
from 3.11.1 to 3.13.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/apache/commons-net/blob/master/RELEASE-NOTES.txt">commons-net:commons-net's
changelog</a>.</em></p>
<blockquote>
<h2>Apache Commons Net 3.13.0 Release Notes</h2>
<p>The Apache Commons Net team is pleased to announce the release of
Apache Commons Net 3.13.0.</p>
<p>Apache Commons Net library contains a collection of network utilities
and protocol implementations.
Supported protocols include Echo, Finger, FTP, NNTP, NTP, POP3(S),
SMTP(S), Telnet, and Whois.</p>
<p>This is a feature and maintenance release. Java 8 or later is
required.</p>
<p>For complete information on Apache Commons Net, including
instructions on how to submit bug reports,
patches, or suggestions for improvement, see the Apache Commons Net
website:</p>
<p><a
href="https://commons.apache.org/proper/commons-net/">https://commons.apache.org/proper/commons-net/</a></p>
<p>Download page: <a
href="https://commons.apache.org/proper/commons-net/download_net.cgi">https://commons.apache.org/proper/commons-net/download_net.cgi</a></p>
<h2>New features</h2>
<p>o Add DatagramSocketClient.getDefaultTimeoutDuration() and deprecate
getDefaultTimeout(). Thanks to Gary Gregory.
o NET-741: Add subnet IPv6 handling with SubnetUtils6 <a
href="https://redirect.github.com/apache/commons-net/issues/391">#391</a>.
Thanks to Maros Orsak, Gary Gregory.</p>
<h2>Fixed Bugs</h2>
<p>o DaytimeTCPClientTest now should now pass inside most VPNs. Thanks
to Henri Biestro, Gary Gregory.
o Migrate tests to JUnit5 <a
href="https://redirect.github.com/apache/commons-net/issues/358">#358</a>,
<a
href="https://redirect.github.com/apache/commons-net/issues/359">#359</a>.
Thanks to Jakub Kupczyk, Gary Gregory.
o           Fix malformed Javadoc comments. Thanks to Gary Gregory.
o IMAPExportMbox now restores the current thread's interrupt flag when
catching InterruptedException. Thanks to Gary Gregory.
o IOUtil.readWrite() now restores the current thread's interrupt flag
when catching InterruptedException. Thanks to Gary Gregory.
o TelnetInputStream now restores the current thread's interrupt flag
when catching InterruptedException. Thanks to Gary Gregory.
o NET-740: FTP fails to parse listings for Linux vsftpd in Chinese or
Japanese <a
href="https://redirect.github.com/apache/commons-net/issues/393">#393</a>.
Thanks to Jianwei Guo, Gary Gregory.
o TelnetInputStream.read() doesn't preserve the original
InterruptedException as the cause of its InterruptedIOException. Thanks
to Gary Gregory.
o FTPClient._storeFile(String, String, InputStream) doesn't always close
it's internal socket when an exception is thrown early in processing.
Thanks to Gary Gregory.
o ListenerList.removeListener(T) now ignores null input to avoid a
NullPointerException. Thanks to Gary Gregory.
o ListenerList.addListener(T) now ignores null input. Thanks to Gary
Gregory.
o Fix typo in FTPConnectionClosedException message from
FTP.getReply(boolean). Thanks to Gary Gregory.
o Reimplement Util.copyReader() with IOUtils.copyLarge(). Thanks to Gary
Gregory.
o Reimplement Util.copyStream() with IOUtils.copyLarge(). Thanks to Gary
Gregory.
o Reimplement Util.copyStream() with IOUtils.copyLarge(). Thanks to Gary
Gregory.
o Deprecate Util.copyReader(Reader, Writer) in favor of
IOUtils.copyLarge(Reader, Writer). Thanks to Gary Gregory.</p>
<h2>Changes</h2>
<p>o Bump org.apache.commons:commons-parent from 85 to 97 <a
href="https://redirect.github.com/apache/commons-net/issues/371">#371</a>,
<a
href="https://redirect.github.com/apache/commons-net/issues/388">#388</a>,
<a
href="https://redirect.github.com/apache/commons-net/issues/389">#389</a>.
Thanks to Gary Gregory, Dependabot.
o Bump org.apache.commons:commons-lang3 from 3.18.0 to 3.19.0. Thanks to
Gary Gregory, Dependabot.</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/apache/commons-net/commit/35fa0dff3e8660359832e72631ff3cf557b543a8"><code>35fa0df</code></a>
Prepare for the release candidate 3.13.0 RC1</li>
<li><a
href="https://github.com/apache/commons-net/commit/1cc5b6e77d86bb54131742fee175e7c700bbf2ca"><code>1cc5b6e</code></a>
Prepare for the next release candidate</li>
<li><a
href="https://github.com/apache/commons-net/commit/a4b246e085c1d3ce5936806d8f85d2e826daca8a"><code>a4b246e</code></a>
Reuse IOUtils.copyLarge()</li>
<li><a
href="https://github.com/apache/commons-net/commit/e5ca262cea38b2f889eb003818cf9f97a6395ad6"><code>e5ca262</code></a>
Javadoc</li>
<li><a
href="https://github.com/apache/commons-net/commit/3cb18ec82ef0b3640536137639763a682994a61e"><code>3cb18ec</code></a>
Deprecate Util.copyReader(Reader, Writer) in favor of</li>
<li><a
href="https://github.com/apache/commons-net/commit/3373d77db2376665a034e753e3646251b3d23aa4"><code>3373d77</code></a>
Reimplement Util.copyReader() with IOUtils.copyLarge()</li>
<li><a
href="https://github.com/apache/commons-net/commit/3be2077ccf1da69614a7883ed352037e30f3366f"><code>3be2077</code></a>
Reimplement Util.copyStream() with IOUtils.copyLarge()</li>
<li><a
href="https://github.com/apache/commons-net/commit/7b41144008427acceea596ac74f36e716ea2586e"><code>7b41144</code></a>
Javadoc</li>
<li><a
href="https://github.com/apache/commons-net/commit/1b584b493ffa6a1b42cbda3c7f683e073f1a3bdf"><code>1b584b4</code></a>
Fix typo in FTPConnectionClosedException message from</li>
<li><a
href="https://github.com/apache/commons-net/commit/3792023843b95dabe60fb745287cae94c8a44be5"><code>3792023</code></a>
Javadoc</li>
<li>Additional commits viewable in <a
href="https://github.com/apache/commons-net/compare/rel/commons-net-3.11.1...rel/commons-net-3.13.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=commons-net:commons-net&package-manager=gradle&previous-version=3.11.1&new-version=3.13.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:16:32 +01:00
dependabot[bot] fb362f70ab build(deps): bump github/codeql-action/upload-sarif from 4.37.4 to 4.37.6 (#7467)
Bumps
[github/codeql-action/upload-sarif](https://github.com/github/codeql-action)
from 4.37.4 to 4.37.6.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/upload-sarif's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.6</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>v4.37.5</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/upload-sarif's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.3">2.26.3</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4085">#4085</a></li>
</ul>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/5595ccaf912efad79be6eef63a5619ff05969be3"><code>5595cca</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4071">#4071</a>
from github/update-v4.37.6-6a9359a1b</li>
<li><a
href="https://github.com/github/codeql-action/commit/ec9c75796a7f2cee5af0c5ffa0b81dc3bb58754b"><code>ec9c757</code></a>
Add change note for PR 4070</li>
<li><a
href="https://github.com/github/codeql-action/commit/45c8742e17cbd668814137f95e605d925b8722a2"><code>45c8742</code></a>
Update changelog for v4.37.6</li>
<li><a
href="https://github.com/github/codeql-action/commit/6a9359a1bd054c53cae7bb737bd8d796cfbf3014"><code>6a9359a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4070">#4070</a>
from github/mbg/remote-address/change-file-default</li>
<li><a
href="https://github.com/github/codeql-action/commit/065cdc0394d424981db720df63ebc570e41b775f"><code>065cdc0</code></a>
Change <code>DEFAULT_CONFIG_FILE_NAME</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/f99dd5aeee9cf92e92d0c700cb0aa7afd7bbf431"><code>f99dd5a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4066">#4066</a>
from github/dependabot/npm_and_yarn/js-yaml-5.2.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/1804b211a343d69a6584d26fb3a68a8fe6ca39d4"><code>1804b21</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4068">#4068</a>
from github/mergeback/v4.37.5-to-main-d1ba80a1</li>
<li><a
href="https://github.com/github/codeql-action/commit/3020a2f46286abb1704269b22ada83bd0e81c64f"><code>3020a2f</code></a>
Rebuild</li>
<li><a
href="https://github.com/github/codeql-action/commit/93c3a5a40b7affbf8ea6a480767ed0db8e8d3c5c"><code>93c3a5a</code></a>
Update changelog and version after v4.37.5</li>
<li><a
href="https://github.com/github/codeql-action/commit/d1ba80a13dd99fba24a470575428917156a28b43"><code>d1ba80a</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4067">#4067</a>
from github/update-v4.37.5-1cd4d01d5</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/f205ea1c3313d32999d8d6a48b4f6530d4437b38...5595ccaf912efad79be6eef63a5619ff05969be3">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github/codeql-action/upload-sarif&package-manager=github_actions&previous-version=4.37.4&new-version=4.37.6)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:16:15 +01:00
dependabot[bot] 88afdd07af build(deps): bump awsSdkVersion from 2.44.12 to 2.51.2 (#7371)
Bumps `awsSdkVersion` from 2.44.12 to 2.51.2.
Updates `software.amazon.awssdk:s3` from 2.44.12 to 2.51.2

Updates `software.amazon.awssdk:url-connection-client` from 2.44.12 to
2.51.2

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:15:58 +01:00
dependabot[bot] 3bb3b721de build(deps): bump logback from 1.5.32 to 1.6.1 (#7339)
Bumps `logback` from 1.5.32 to 1.6.1.
Updates `ch.qos.logback:logback-core` from 1.5.32 to 1.6.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/qos-ch/logback/releases">ch.qos.logback:logback-core's
releases</a>.</em></p>
<blockquote>
<h2>Logback 1.6.1</h2>
<p><strong>2026-07-28 Release of logback version 1.6.1</strong></p>
<p>• In TimeBasedRollingPolicy, when the file option is set, the
intermediate file renamed before asynchronous compression now receives
the target archive name without the compression suffix (e.g.
<code>.gz</code>, <code>.zip</code>, <code>.xz</code>). Previously it
used a nanotime-based <code>.tmp</code> suffix. This makes the file
easier to identify if compression fails during rollover. (See also the
following paragraph.)</p>
<p>• On GZ, ZIP, or XZ compression failure, the original (uncompressed)
log file is no longer deleted. Compression strategies now delete the
source file only after successful compression and emit a warning that
the original was left intact.</p>
<p>• ConsoleAppender with <!-- raw HTML omitted --> now probes JLine's
org.jline.jansi.AnsiConsole first and falls back to the legacy
FuseSource org.fusesource.jansi.AnsiConsole class. This keeps ANSI
coloring working after Jansi moved under the JLine project. The optional
org.jline:jansi-core artifact is declared as a dependency alongside the
existing FuseSource jansi dependency. A preferredJansiClassName property
was added for tests. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1043">issues/1043</a>
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a> who also
provided the relevant PR.</p>
<p>• LayoutWrappingEncoder now reports an error at start() when no
layout is set and guards encode() against a null layout. Previously, a
missing layout (for example after an ignored <!-- raw HTML omitted
-->/<!-- raw HTML omitted -->/<!-- raw HTML omitted --> branch) allowed
the encoder to start and then fail with a NullPointerException on every
event, resulting in silent log loss. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1046">issues/1046</a>
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a> who also
provided the relevant PR.</p>
<p>• FileCollisionAnalyser now detects file collisions involving nested
appenders of SiftingAppender. When the nested file or fileNamePattern
does not textually reference the discriminator key (e.g. ${userId}), a
warning is issued at configuration time naming the appender, the key,
and the shared target. This closes a gap where statically declared file
appenders were checked but sifted nested appenders were not. This
enhancement was contributed in [PR <a
href="https://redirect.github.com/qos-ch/logback/issues/1041">#1041</a>](<a
href="https://redirect.github.com/qos-ch/logback/issues/1041">qos-ch/logback#1041</a>)
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a>.</p>
<p>• More defensive handling in SyslogOutputStream and
SyslogAppenderBase: the close() method now ensures that resources are
closed, writes and flushes check that the underlying resources are in a
valid state and fallback to no-op otherwise.</p>
<p>• A bit-wise identical binary of this version can be reproduced by
building from source code at commit
57759f433000a133088ef0441038963134437fbd associated with the tag
v_1.6.1. The release was built using Java &quot;21&quot; 2023-10-17 LTS
build 21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<p>• See <a
href="https://logback.qos.ch/news.html#1.6.1">https://logback.qos.ch/news.html#1.6.1</a>
for the original text.</p>
<h2>Logback 1.6.0</h2>
<p><strong>2026-07-23 Release of logback version 1.6.0</strong></p>
<p>• Removed certain deprecated variables, methods, and classes. For the
list of removed members see <a
href="https://logback.qos.ch/notes/release_1.6.0.txt">release_1.6.0.txt.</a></p>
<p>• In <code>AsyncAppenderBase</code>, the
<code>put(ILoggingEvent)</code> method now has the protected modifier to
allow access from derived classes. This change was requested by Thomas
Skjølberg in <a
href="https://redirect.github.com/qos-ch/logback/pull/1053">pr#1053</a>.</p>
<p>• Bump SLF4J dependency to version 2.0.18.</p>
<p>• <strong>See also the overview of the <a
href="https://logback.qos.ch/news.html#latest_stable">1.6.x
series</a>.</strong></p>
<p>• A bit-wise identical binary of this version can be reproduced by
building from source code at commit
b07adf36019b51a10f824fdd94009985c587b1d3 associated with the tag
v_1.6.0. The release was built using Java &quot;21&quot; 2023-10-17 LTS
build 21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.38</h2>
<p><strong>2026-07-09 Release of logback version 1.5.38</strong></p>
<p>• In <code>HardenedObjectInputStream</code>, fixed a typo preventing
<code>Throwable</code> objects from being white-filtered. This issue was
reported in [PR <a
href="https://redirect.github.com/qos-ch/logback/issues/1045">#1045</a>](<a
href="https://redirect.github.com/qos-ch/logback/pull/1045">qos-ch/logback#1045</a>)
by <a href="https://github.com/t0rchwo0d">t0rchwo0d</a>.</p>
<p>• A bitwise identical binary of this version can be reproduced by
building from source code at commit
d04984a41fce42977466f45a2f076f0ee5cc4207 associated with the tag
v_1.5.38. Release built using Java &quot;21&quot; 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.37</h2>
<p><strong>2026-06-26 Release of logback version 1.5.37</strong></p>
<ol>
<li>• Given the numerous vulnerabilities related to conditional
configuration processing based on the evaluation of Java expressions
using the Janino library, support for such expressions has been removed.
Users are offered the an <a
href="https://logback.qos.ch/translator/services/conditionalConfigMigrator.html">online
migration service</a> or the <code>&lt;condition&gt;</code> element
introduced in version 1.5.20. See the <a
href="https://logback.qos.ch/manual/configuration.html#conditional">relevant
documentation</a> for more details.</li>
</ol>
<p>• A bitwise identical binary of this version can be reproduced by
building from source code at commit
c1df7f522e648eec7b4ef6a12c8758fec0f00048 associated with the tag
v_1.5.37. Release built using Java &quot;21&quot; 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.36</h2>
<p><strong>2026-06-25 Release of logback version 1.5.36</strong></p>
<p>• The 'condition' attribute in <code>&lt;if&gt;</code> elements now
reject certain references that are associated with ACE attacks. This
issue was reported by &quot;yulate&quot; (<a
href="mailto:yulate531@gmail.com.com">yulate531@gmail.com.com</a>) and
registered as <a
href="https://www.cve.org/cverecord?id=CVE-2026-13006">CVE-2026-13006</a>.
<strong>Please note that version 1.5.37 provides the full fix to this
vulnerability.</strong></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/qos-ch/logback/commit/57759f433000a133088ef0441038963134437fbd"><code>57759f4</code></a>
prepare release 1.6.1</li>
<li><a
href="https://github.com/qos-ch/logback/commit/175f99f2093ae07f4c8d44f93b800f65b03a6e19"><code>175f99f</code></a>
fix imports</li>
<li><a
href="https://github.com/qos-ch/logback/commit/4b8773ed127fdc62b85a7c7ddaef10f25830788c"><code>4b8773e</code></a>
add compressionFailureLeavesOriginalFileIntact test for XZ
compression</li>
<li><a
href="https://github.com/qos-ch/logback/commit/cafaf1115fd20be19b2f7a4a09184446e8bbc04d"><code>cafaf11</code></a>
do not delete original file if compression fails</li>
<li><a
href="https://github.com/qos-ch/logback/commit/ee50125b293f5a731543de9f9f5fb72756a43464"><code>ee50125</code></a>
let the temporary file before compression be target file without the .gz
or ....</li>
<li><a
href="https://github.com/qos-ch/logback/commit/5626acc301f4039537a6c668f0f2d51989472785"><code>5626acc</code></a>
minor refactoring</li>
<li><a
href="https://github.com/qos-ch/logback/commit/d97da4fbc0de00ca901ef78d91b9fc1850ae803f"><code>d97da4f</code></a>
minor refactoring</li>
<li><a
href="https://github.com/qos-ch/logback/commit/159c045d8f045ccf8b382775d81d83919c228cca"><code>159c045</code></a>
more defensive coding in SyslogOutputStream and in
SyslogAppenderBase</li>
<li><a
href="https://github.com/qos-ch/logback/commit/9427d6b23d5a692c8a76a994c076ef68ded7835c"><code>9427d6b</code></a>
slight refactoring for clarity</li>
<li><a
href="https://github.com/qos-ch/logback/commit/79c4179c0b440a9dcf35bc9bda1ead2b2f90966c"><code>79c4179</code></a>
slight refactoring</li>
<li>Additional commits viewable in <a
href="https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.6.1">compare
view</a></li>
</ul>
</details>
<br />

Updates `ch.qos.logback:logback-classic` from 1.5.32 to 1.6.1
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/qos-ch/logback/releases">ch.qos.logback:logback-classic's
releases</a>.</em></p>
<blockquote>
<h2>Logback 1.6.1</h2>
<p><strong>2026-07-28 Release of logback version 1.6.1</strong></p>
<p>• In TimeBasedRollingPolicy, when the file option is set, the
intermediate file renamed before asynchronous compression now receives
the target archive name without the compression suffix (e.g.
<code>.gz</code>, <code>.zip</code>, <code>.xz</code>). Previously it
used a nanotime-based <code>.tmp</code> suffix. This makes the file
easier to identify if compression fails during rollover. (See also the
following paragraph.)</p>
<p>• On GZ, ZIP, or XZ compression failure, the original (uncompressed)
log file is no longer deleted. Compression strategies now delete the
source file only after successful compression and emit a warning that
the original was left intact.</p>
<p>• ConsoleAppender with <!-- raw HTML omitted --> now probes JLine's
org.jline.jansi.AnsiConsole first and falls back to the legacy
FuseSource org.fusesource.jansi.AnsiConsole class. This keeps ANSI
coloring working after Jansi moved under the JLine project. The optional
org.jline:jansi-core artifact is declared as a dependency alongside the
existing FuseSource jansi dependency. A preferredJansiClassName property
was added for tests. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1043">issues/1043</a>
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a> who also
provided the relevant PR.</p>
<p>• LayoutWrappingEncoder now reports an error at start() when no
layout is set and guards encode() against a null layout. Previously, a
missing layout (for example after an ignored <!-- raw HTML omitted
-->/<!-- raw HTML omitted -->/<!-- raw HTML omitted --> branch) allowed
the encoder to start and then fail with a NullPointerException on every
event, resulting in silent log loss. This issue was reported in <a
href="https://redirect.github.com/qos-ch/logback/issues/1046">issues/1046</a>
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a> who also
provided the relevant PR.</p>
<p>• FileCollisionAnalyser now detects file collisions involving nested
appenders of SiftingAppender. When the nested file or fileNamePattern
does not textually reference the discriminator key (e.g. ${userId}), a
warning is issued at configuration time naming the appender, the key,
and the shared target. This closes a gap where statically declared file
appenders were checked but sifted nested appenders were not. This
enhancement was contributed in [PR <a
href="https://redirect.github.com/qos-ch/logback/issues/1041">#1041</a>](<a
href="https://redirect.github.com/qos-ch/logback/issues/1041">qos-ch/logback#1041</a>)
by <a href="https://github.com/seonwooj0810">seonwoo_jung</a>.</p>
<p>• More defensive handling in SyslogOutputStream and
SyslogAppenderBase: the close() method now ensures that resources are
closed, writes and flushes check that the underlying resources are in a
valid state and fallback to no-op otherwise.</p>
<p>• A bit-wise identical binary of this version can be reproduced by
building from source code at commit
57759f433000a133088ef0441038963134437fbd associated with the tag
v_1.6.1. The release was built using Java &quot;21&quot; 2023-10-17 LTS
build 21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<p>• See <a
href="https://logback.qos.ch/news.html#1.6.1">https://logback.qos.ch/news.html#1.6.1</a>
for the original text.</p>
<h2>Logback 1.6.0</h2>
<p><strong>2026-07-23 Release of logback version 1.6.0</strong></p>
<p>• Removed certain deprecated variables, methods, and classes. For the
list of removed members see <a
href="https://logback.qos.ch/notes/release_1.6.0.txt">release_1.6.0.txt.</a></p>
<p>• In <code>AsyncAppenderBase</code>, the
<code>put(ILoggingEvent)</code> method now has the protected modifier to
allow access from derived classes. This change was requested by Thomas
Skjølberg in <a
href="https://redirect.github.com/qos-ch/logback/pull/1053">pr#1053</a>.</p>
<p>• Bump SLF4J dependency to version 2.0.18.</p>
<p>• <strong>See also the overview of the <a
href="https://logback.qos.ch/news.html#latest_stable">1.6.x
series</a>.</strong></p>
<p>• A bit-wise identical binary of this version can be reproduced by
building from source code at commit
b07adf36019b51a10f824fdd94009985c587b1d3 associated with the tag
v_1.6.0. The release was built using Java &quot;21&quot; 2023-10-17 LTS
build 21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.38</h2>
<p><strong>2026-07-09 Release of logback version 1.5.38</strong></p>
<p>• In <code>HardenedObjectInputStream</code>, fixed a typo preventing
<code>Throwable</code> objects from being white-filtered. This issue was
reported in [PR <a
href="https://redirect.github.com/qos-ch/logback/issues/1045">#1045</a>](<a
href="https://redirect.github.com/qos-ch/logback/pull/1045">qos-ch/logback#1045</a>)
by <a href="https://github.com/t0rchwo0d">t0rchwo0d</a>.</p>
<p>• A bitwise identical binary of this version can be reproduced by
building from source code at commit
d04984a41fce42977466f45a2f076f0ee5cc4207 associated with the tag
v_1.5.38. Release built using Java &quot;21&quot; 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.37</h2>
<p><strong>2026-06-26 Release of logback version 1.5.37</strong></p>
<ol>
<li>• Given the numerous vulnerabilities related to conditional
configuration processing based on the evaluation of Java expressions
using the Janino library, support for such expressions has been removed.
Users are offered the an <a
href="https://logback.qos.ch/translator/services/conditionalConfigMigrator.html">online
migration service</a> or the <code>&lt;condition&gt;</code> element
introduced in version 1.5.20. See the <a
href="https://logback.qos.ch/manual/configuration.html#conditional">relevant
documentation</a> for more details.</li>
</ol>
<p>• A bitwise identical binary of this version can be reproduced by
building from source code at commit
c1df7f522e648eec7b4ef6a12c8758fec0f00048 associated with the tag
v_1.5.37. Release built using Java &quot;21&quot; 2023-10-17 LTS build
21.0.1.+12-LTS-29 under Linux Debian 11.6.</p>
<h2>Logback 1.5.36</h2>
<p><strong>2026-06-25 Release of logback version 1.5.36</strong></p>
<p>• The 'condition' attribute in <code>&lt;if&gt;</code> elements now
reject certain references that are associated with ACE attacks. This
issue was reported by &quot;yulate&quot; (<a
href="mailto:yulate531@gmail.com.com">yulate531@gmail.com.com</a>) and
registered as <a
href="https://www.cve.org/cverecord?id=CVE-2026-13006">CVE-2026-13006</a>.
<strong>Please note that version 1.5.37 provides the full fix to this
vulnerability.</strong></p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/qos-ch/logback/commit/57759f433000a133088ef0441038963134437fbd"><code>57759f4</code></a>
prepare release 1.6.1</li>
<li><a
href="https://github.com/qos-ch/logback/commit/175f99f2093ae07f4c8d44f93b800f65b03a6e19"><code>175f99f</code></a>
fix imports</li>
<li><a
href="https://github.com/qos-ch/logback/commit/4b8773ed127fdc62b85a7c7ddaef10f25830788c"><code>4b8773e</code></a>
add compressionFailureLeavesOriginalFileIntact test for XZ
compression</li>
<li><a
href="https://github.com/qos-ch/logback/commit/cafaf1115fd20be19b2f7a4a09184446e8bbc04d"><code>cafaf11</code></a>
do not delete original file if compression fails</li>
<li><a
href="https://github.com/qos-ch/logback/commit/ee50125b293f5a731543de9f9f5fb72756a43464"><code>ee50125</code></a>
let the temporary file before compression be target file without the .gz
or ....</li>
<li><a
href="https://github.com/qos-ch/logback/commit/5626acc301f4039537a6c668f0f2d51989472785"><code>5626acc</code></a>
minor refactoring</li>
<li><a
href="https://github.com/qos-ch/logback/commit/d97da4fbc0de00ca901ef78d91b9fc1850ae803f"><code>d97da4f</code></a>
minor refactoring</li>
<li><a
href="https://github.com/qos-ch/logback/commit/159c045d8f045ccf8b382775d81d83919c228cca"><code>159c045</code></a>
more defensive coding in SyslogOutputStream and in
SyslogAppenderBase</li>
<li><a
href="https://github.com/qos-ch/logback/commit/9427d6b23d5a692c8a76a994c076ef68ded7835c"><code>9427d6b</code></a>
slight refactoring for clarity</li>
<li><a
href="https://github.com/qos-ch/logback/commit/79c4179c0b440a9dcf35bc9bda1ead2b2f90966c"><code>79c4179</code></a>
slight refactoring</li>
<li>Additional commits viewable in <a
href="https://github.com/qos-ch/logback/compare/v_1.5.32...v_1.6.1">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-16 14:15:34 +01:00
Anthony Stirling 60741e305d Update CONTRIBUTING.md to include license information (#7512)
Added a section about licensing contributions to the project.

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-16 11:22:19 +01:00
Anthony StirlingandSaul 6143610608 fix(frontend): preserve pdf link targets in desktop viewer (#7235)
Same PR as #6396, just with the conflicts resolved and some fixes on
top. Original commit by @saul1310 is preserved as-is; everything else is
a follow-up commit.

Refs #6272

## Conflicts

#6396 was written before the frontend was restructured, so all four
files it touched moved (`frontend/src/**` -> `frontend/editor/src/**`)
and `LinkLayer.tsx` had drifted. Cherry-picked with rename detection and
re-resolved against current `main`.

## Fixes on top

- **Reuse the existing platform seam instead of adding a second one.**
`main` already has `@app/platform/*` seams with per-flavour
implementations; #6396 added a parallel `@app/utils/openExternalUrl`
core+desktop pair that re-implemented the Tauri shell call already in
`desktop/platform/openExternal.ts`. Split into a pure sanitiser
(`@app/utils/externalUrl`) and a platform seam
(`@app/platform/openExternalTab`), with the desktop impl delegating to
the existing `openExternal`.
- **Kept PDF links off the `openExternal` seam.** That seam is for
leave-and-return redirects (Stripe) and its saas impl is
`window.location.assign` - routing PDF links through it would navigate
the whole app away from the user's document. `openExternalTab` always
opens alongside the app; desktop shadows it to escape the webview.
- **Fixed the same defect in two sibling call sites** that #6396 didn't
cover: `BookmarkSidebar` (bookmark URI / LaunchAppOrOpenFile actions)
and `useAnnotationMenuHandlers` (annotation menu "go to link"). Both
called `window.open` on an unsanitised PDF-supplied URI, so on desktop
they trapped the link in the webview exactly like the viewer did.
- **Dropped the unguarded fallback.** The old code fell back to
`window.open(uri)` when `new URL()` threw, so an unparseable URI
bypassed the allowlist entirely. It is now blocked.
- Empty/whitespace URIs are blocked rather than silently resolving to
the app's own page via the base URL.
- Tests: sanitiser cases (casing, leading whitespace, `data:`,
`vbscript:`, unparseable), a core seam test asserting
new-tab-not-navigate, and a desktop seam regression test asserting the
URL goes to the OS rather than `window.open`.
- **`openExternalTab` now re-validates its own input.** Every caller
sanitises first, so nothing reached it unvalidated - but it is the sink
that hands a URL to `window.open` (executes `javascript:` in our origin)
or to an OS handler on desktop, and its safety shouldn't depend on
callers remembering. Both impls fail closed, with tests that call them
directly with `javascript:`/`data:`/`file:`/`ftp:`.

## Unrelated fix included (flagged deliberately)

The last commit fixes `frontend/editor/vitest.config.ts`: `testTimeout:
10000` was set on the root `test` block, but tests all run under
`projects`, which do not inherit it - so the whole suite has silently
been running at vitest's 5s default.

This is not cosmetic. It made `task check` fail intermittently on
unrelated portal specs (`demoData`, `ConnectionModal`); the ConsignO
test takes 2966ms with only the portal project running, i.e. 59% of a
budget it was never meant to have, so any CPU contention tips it over.
Proven with an identical 6.5s probe test: times out at 5000ms on the old
config, passes at 6512ms on the fixed one.

Happy to split this into its own PR if preferred - it is here because
the gate could not be trusted without it.

## Validation

Typecheck passes for all 7 build flavours (core, proprietary, saas,
desktop, cloud, prototypes, portal); ESLint, Prettier, dpdm and the full
1662-test vitest suite pass.

Driven live against the dev server + backend with a PDF carrying five
URI annotations (https, `javascript:`, mailto, `file:`, relative). 14/14
behavioural checks pass on this branch; 5 of them fail on `main`:

| check | main | this PR |
| --- | --- | --- |
| safe https link exposes real href (copy-link) | `href="#"` |
`https://example.com/safe-link?a=1` |
| link opens in new tab / tabnabbing-proof | no `target`/`rel` |
`_blank` + `noopener noreferrer` |
| mailto link exposes real href | `href="#"` | `mailto:test@example.com`
|
| relative URI resolved against app origin | `href="#"` | resolved |
| `javascript:` / `file:` never reach href | blocked | blocked |
| clicking blocked link doesn't execute or navigate | ok | ok |
| clicking safe link opens new tab at source URL | - | ok, app not
navigated away |

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally

---------

Co-authored-by: Saul <saulifshin.cs@gmail.com>
2026-08-14 18:07:42 +01:00
Anthony Stirling bff1ea916d Add n8n connection preset and workflow step operations (#7447)
# Description of Changes

Add n8n, simple reuse of webhook call etc and custom API  etc etc


<img width="2104" height="1090" alt="image"
src="https://github.com/user-attachments/assets/0d0df39a-31b3-4e1b-ac63-847aefa06ffe"
/>

<img width="2104" height="1592" alt="image"
src="https://github.com/user-attachments/assets/4074cce2-0f4b-4300-813c-d49576b33940"
/>

<img width="1021" height="797" alt="image"
src="https://github.com/user-attachments/assets/370f54f7-3066-4454-9618-9ddd2cb91b83"
/>

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-14 18:07:12 +01:00
EthanHealy01 2483e9f37a Report editor-originated failures into the same queue (Review Flow PR 2) (#7296)
Review Flow PR 2 of 5. Editor tool failures now reach the same durable
queue as failures from folders, buckets and webhooks.

## What's added

**A report endpoint** — `POST /api/v1/file-run-events/reports`, open to
any authenticated user. Takes four fields: `operation`, `errorCode`,
`fileIds`, `detail`. No team, no actor, no filename: the first two come
from the session, the third is never a field. Refused with 400 above 200
file ids, and nothing is written when refused.

**Automatic reporting from every tool** — wired into `useToolOperation`,
so no per-tool work is needed. Client-side refusals (an unsupported
format that never reaches the server) are reported too. User
cancellations are not.

**Error codes parsed from Blob bodies as well as JSON** — a
download-typed tool call fails with a Blob, so `errorCodeOf` handles
both shapes.

**Source attribution for unattended runs** — `sourceId` is threaded from
`PolicyRunner` through `PolicyRun` to the recorded row and out to the
wire, so a folder, bucket or webhook failure names what fed it.
Previously it had none.

**Deleting a file closes its failures** — `FileContext.removeFiles`
notifies `POST /removed-files`, which transitions those incidents to
`FILE_REMOVED`. Terminal, so they leave every reviewer's queue. The rows
stay for audit.

**The queue can be emptied** — reads now default to open statuses only;
ask for a status explicitly to see closed rows.

## Behaviour changes

- **Editor failures dedup per person.** `RecordFailure.scopeRef()`
includes the actor for TOOL-origin rows, so two people hitting the same
failure on the same file are two incidents rather than one. Processor
rows are unaffected and their dedup key is byte-identical to before.
- **`UNKNOWN` offers only Dismiss.** Acknowledge is no longer offered on
it.
- **Background reports no longer raise a toast.** Both calls pass
`suppressErrorToast`, so a failed report is silent as intended;
previously a core build showed the user a "Not Found" toast on every
tool failure.

## What is stored

File ids only, never names. The request type has no filename field, and
a `fileNames` value handed to the client reporter is accepted and
ignored.

One caveat to review deliberately: the free-text `detail` is stored
**verbatim**. `RecordFailure` truncates it at 2000 characters and
nothing else; the redaction that used to strip name-shaped text was
reverted in `024899f3f6` because it made an unclassified failure
impossible to act on. A backend message that embeds a filename
(LibreOffice conversion errors, IO errors) will therefore persist that
text and show it to a team leader.

## How to test

Needs a proprietary or SaaS build with login enabled. `task dev:all`
gives you one.

1. **Report a failure from a tool.** Open a PDF, run **Remove Password**
on it with a wrong password. Nothing visible changes for you: reporting
is silent by design.
2. **See it recorded.** Go to `/processor/documents` and scroll to
**Failures** (dev builds only). A row appears titled "Password-protected
document", with `Hit by <your user>`. Press **Show raw JSON** to see
exactly what was stored.
3. **Confirm no filename is stored as data.** In that JSON, `fileId` is
an opaque uuid and there is no name field. Note the `detail` string may
contain a filename if the backend put one in its message, per the caveat
above.
4. **Confirm the request is capped.** In DevTools, POST to
`/api/v1/file-run-events/reports` with 201 entries in `fileIds`. It
returns 400 naming the limit, and no rows are added.
5. **Deleting a file clears its failure.** Back in the editor, delete
the file you just failed on. Refresh the failures list: its row is gone
from the default view. Filter by `FILE_REMOVED` to see it still exists.
6. **Two people, two incidents.** Have a colleague fail the same tool on
their own copy of the same file. Two rows, not one occurrence count.

## Migration

`source_id` is a new column and `FILE_REMOVED` a new status value. Both
are already in the SaaS migration ([Stirling-PDF-SaaS
#322](https://github.com/Stirling-Tools/Stirling-PDF-SaaS/pull/322));
self-hosted picks them up from `ddl-auto`.
2026-08-14 13:24:41 +00:00
James Brunton 6f2b829f72 Filter Pipelines page to only show what the user thinks as pipelines (#7495)
# Description of Changes
Currently, the Pipelines page shows all backend Policies, which was the
desired behaviour when we first designed this, but as it's come along,
it doesn't feel right anymore. This adds a filter so the Pipelines table
only shows things that have been defined by the user as a New Pipeline,
so not Policies etc.

## Before
<img width="1510" height="789" alt="image"
src="https://github.com/user-attachments/assets/5aebb065-3d42-4483-be3c-253fd8918d49"
/>

## After
<img width="1512" height="790" alt="image"
src="https://github.com/user-attachments/assets/2966a0de-ec9f-43e6-bb1d-c4aeb30dfbf8"
/>
2026-08-14 13:11:02 +00:00
James Brunton 588afb6306 Fix a11y violations in nightlies and improve a11y config (#7499)
# Description of Changes
This fixes the a11y violations that are currently failing in the
nightlies in dark mode. Now that we're down to 0 baseline, we can
require the a11y tests to pass in PRs before they merge, so I've changed
that, and I've also made it so that the nightly will report failures in
both light and dark mode instead of just light mode if that fails.

<img width="2560" height="838" alt="image"
src="https://github.com/user-attachments/assets/b0322182-f6ff-4dea-9a1c-5a6c9c9c5439"
/>
2026-08-14 14:02:16 +01:00
Anthony Stirling 0be10b2dff Cucumber concurrency validation plus fix (#7379)
# Description of Changes

cucumber tests to run multiple threads of commands at same time 

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-14 14:01:45 +01:00
EthanHealy01 55087313b7 Processor UI snags: fat CTAs, real Infrastructure tabs, one surface style (#7497)
Five unrelated snags in the processor (portal) UI, plus fixes they
turned up. No backend changes.

`84 files changed, +892 / −3364`

## Fat CTA buttons

- New `fat` prop on the SUI `Button`: 2.75rem tall, 1.25rem side
padding, 0.75rem corners, semibold. Composes with all four
variants/accents.
- Applied to the page-header CTA on Sources, Documents, Pipelines, Users
(both), Usage, Integrations, Infrastructure — 8 buttons, all in line
with a page title. Nothing else.
- `LandingActions` migrated onto the prop; `.landing-btn-primary` /
`.landing-btn-secondary` and their four `!important`s deleted. The
editor landing CTAs come down 4px with everything else.
- Infrastructure's header CTA is now primary; its "Create key" dropped
to secondary so they stop competing.

<!--IMG:buttons-->

## Documents empty state

- "Connect a source" opened the Sources *page*; it now opens the
`SourceModal` connect flow in place, no route change.
- No extra cache wiring: `SourceModal` already invalidates the sources
query.

<!--IMG:documents-->

## Infrastructure tabs

- Only API Keys and Audit Logs hit real endpoints. Deployments,
Security, Models and Storage read mock-only `/v1/infrastructure/*` that
no backend serves.
- Those four are now disabled: native `disabled`, out of the keyboard
tab order, `aria-disabled`, with the view refusing non-enabled keys as a
second guard.
- Real tabs moved leftmost; API Keys is the default; `?tab=` deep links
validated against the enabled set (the home flow's audit link still
works).
- Deleted: 4 tab components, their fetch fns and ~25 dead types, MSW
handlers, fixtures (908 → 253 lines), dead CSS, unused formatters, 240
lines of `en-US` strings. Most of the −3364.
- Page subtitle no longer advertises the disabled tabs.

<!--IMG:infrastructure-->

## Surface consolidation

- New `Surface` primitive (`sui-surface`): fill, hairline, radius, no
shadow. Kept separate from `sui-nav-surface` so nav chrome can diverge
later.
- `Card` composes it and no longer draws its own shadow — this changes
editor Card usages too, by design.
- SUI primitives that are surfaces adopt it: `MetricCard`,
`MetricStrip`, `NodeCard`, `Table`, `Collapsible`, `CodeBlock`.
- The portal gets its own `.portal-surface` with the same three
declarations, applied to 19 elements. A `sui-` class belongs to the
component that emits it, so feature markup doesn't wear one.
- `raised` variant = one subtle shadow for a surface in front of another
surface (the flow diagram's tiles). Same fill as its parent, so nesting
never shifts a region's colour. Dark has its own value.
- Floating chrome (modals, drawers, dropdowns, assistant, sidebar) keeps
its elevation; sunken wells stay sunken.

<!--IMG:surfaces-->

## Sources list

- Centred "No sources connected yet" empty state removed — it duplicated
the header CTA and pushed the table down the page. The header's "Connect
source" is the single way in.

## Drive-by fixes

- The connect flow rendered unstyled outside the Sources view:
`.portal-conn-picker__*` / `.portal-sources__connection-*` lived in
`views/Sources.css`, which none of the five components rendering them
imported. Moved to `components/sources/connections.css`.
- Three inert custom properties (`--surface-input`, `--color-border-2`,
`--text-default`) are defined nowhere in the codebase —
`.portal-conn-picker__card` had no fill at all as a result.
- Dead CSS removed from `Sources.css` (grep-verified unused): old
expanded-row panel + its keyframes, type-card block.

## Testing

- `task frontend:check` — typecheck, lint (oxlint + 4 theme-lint passes
+ stylelint), format, 238 files / 2063 tests.
- `frontend:typecheck:all` across all 9 tsconfigs.
- `frontend:storybook:a11y:changed` — 119 stories, light and dark, zero
violations, no regressions vs baseline.
- New tests: `Infrastructure.test.tsx` (tab order, default, disabled
behaviour, deep-link filtering) and a Documents test that the
empty-state CTA opens the modal without navigating.
- Merged `origin/main` (#7438 replaced `PipelineHeader` with the new
Create/Edit headers); full suite green at 240 files / 2072 tests after
the merge.
2026-08-14 10:55:15 +00:00
James Brunton 4b26797ad8 Redesign New/Edit Pipeline top bars (#7438)
# Description of Changes
Replace the dev-UI top-bar in the New Pipeline and Edit Pipeline pages
with a redesigned layout appropriate for users. I've got a big list of
extra tweaks I'd like to do to the rest of the page including graph
tweaks etc. but this is the only thing on the New/Edit Pipelines pages
that is blocking for the release.

## Before
### New Pipeline
<img width="1510" height="788" alt="image"
src="https://github.com/user-attachments/assets/23f8a3a6-a8db-4d81-8c23-fd7a78864f2d"
/>

### Edit Pipeline
<img width="1512" height="791" alt="image"
src="https://github.com/user-attachments/assets/4c0a0c3c-81d8-4d15-91c7-6aa4f3de69c7"
/>

## After
### New Pipeline
<img width="757" height="395" alt="image"
src="https://github.com/user-attachments/assets/8a287cce-336c-4624-b4e9-a6a98eabb01d"
/>

### Edit Pipeline
<img width="1512" height="789" alt="image"
src="https://github.com/user-attachments/assets/9e096dbb-0a95-4a03-8d32-d5d71d2b57c5"
/>
2026-08-14 07:44:41 +01:00
Anthony Stirlinganddagecko 929ded41a8 Harden actions secret handling (#7435)
# Description of Changes

## Harden GitHub Actions secret handling

Moves secrets behind deployment environments, removes the GitHub App
token from
workflows that only comment and label, and moves PR preview images to
GHCR so the
preview path needs no registry credential.

Builds on #6005 by @dagecko — that commit is preserved with original
authorship,
rebased onto current main.

### Extract secrets from `run:` blocks (@dagecko, #6005 rebased)

- Secrets referenced in shell bodies moved to step-level `env:` so
values never
  reach a rendered command line
- Two `workflow_dispatch` inputs moved out of shell interpolation
  (`multiOSReleases`, `push-docker-base`)
- Dropped the hunks main has since solved — `setup-uv`, `reviewdog`,
`build-push-action` and `github-script` are all pinned newer on main now
- Fixed a bug in the original: `PR-Demo-cleanup.yml` uses a **quoted**
`<< 'ENDSSH'`
heredoc, so rewriting `${{ secrets.DOCKER_HUB_USERNAME }}` to
`${DOCKER_HUB_USERNAME}`
would have sent the literal string to the VPS and expanded to empty,
silently
  orphaning preview images behind `|| true`

### Gate secret-bearing jobs behind environments

- `environment:` added to 15 jobs across 10 workflows, mapping to
`release-signing`,
  `docker-publish`, `package-publish`, `pr-preview` and `bot-identity`
- Environment branch/tag policies are enforced by GitHub before the job
starts, so
  editing the workflow file cannot bypass them
- Four jobs deliberately **not** gated — `tauri-build`,
`frontend-backend-licenses-update`,
`swagger` and `push-docker-base` would fail their own triggers under the
current
  policies and need restructuring first
- Removed the `testMain` trigger from `push-docker` — the branch doesn't
exist and
  isn't in the environment's policy

### Publish PR previews to GHCR instead of Docker Hub

- Preview images now go to `ghcr.io/stirling-tools/stirling-pdf-test`,
authenticated
  with `GITHUB_TOKEN` rather than `DOCKER_HUB_API`
- Docker Hub personal access tokens cannot be scoped to a single
repository, so the
preview path was holding the same credential that publishes `s-pdf` and
`stirling-pdf`
- `DOCKER_HUB_API` no longer appears in any PR-reachable workflow
- Login now precedes every `docker manifest inspect` —
`deploy-on-v2-commit` had them
  reversed, which only worked because the Docker Hub repo was public

### Use `GITHUB_TOKEN` for comment and label workflows

- Seven workflows no longer mint a GitHub App token; only
`sync_files_v2`,
`sync-portal-docs` and `frontend-backend-licenses-update` still do, so
unattended
  auto-merge is unaffected
- `permissions:` blocks derived per job from the API calls each actually
makes —
these were previously inert, since an App installation token ignores
them, and one
  job had no block at all
- Comment-threading matchers updated to `github-actions[bot]` so
workflows still edit
  their own previous comment instead of posting duplicates
- Removed the App token from the `refs/pull/N/merge` checkout in
`PR-Demo-Comment-with-react` and set `persist-credentials: false` — it
was written
  into `.git/config` of an untrusted tree that the same job then builds
- Fixed a script injection in `check_toml.yml`: a fork-controlled branch
name was
interpolated into `actions/github-script` JS source, with validation
running after
the injected code had already executed. Values now come from
`process.env` and are
  validated before use.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: dagecko <cnyhuis@vigilantnow.com>
2026-08-13 23:03:47 +01:00
Ludy 5170509695 deps: upgrade mwiede JSch to 2.28.6 and adapt SFTP password handling (#7496)
# Description of Changes

This PR replaces #7490 and upgrades `com.github.mwiede:jsch` from
`0.2.23` to `2.28.6`.

In addition to the dependency bump from the original Dependabot PR, this
PR includes the required compatibility adjustment for SFTP password
authentication:

- Updated `jschVersion` in `build.gradle` from `0.2.23` to `2.28.6`.
- Updated `SftpFileClient` to pass the configured password to JSch as
UTF-8 encoded bytes instead of using the `String` overload.
- Preserved the existing SFTP connection and host-key verification
behavior.
- Addresses the API compatibility changes introduced by the newer JSch
version that prevented the dependency upgrade from being used unchanged.

This supersedes #7490

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 22:25:36 +01:00
briosandAnthony Stirling 4a2329ab6d refactor(hibernate): implement manual Hibernate-compliant equals/hashCode for entity classes (#6433)
# Description of Changes


This PR refactors our JPA entity classes to replace Lombok's `@Data` and
auto-generated `@EqualsAndHashCode` annotations with explicit Lombok
annotations and custom, JPA-compliant `equals()` and `hashCode()`
implementations.

### Rationale
Lombok's default `@Data` and `@EqualsAndHashCode` annotations are not
recommended for JPA entities. They often lead to:
- Severe performance issues (e.g., loading lazy collections when
evaluating `hashCode` or `toString`).
- Identity mismatches or collection bugs (e.g., when database-generated
IDs transition from `null` to assigned, breaking the entity's lookup in
a `Set` or `Map`).
This change ensures all JPA entities use safe Hibernate proxy checking
and use only the entity's database identifier for equality and hash code
calculations.


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-13 22:24:45 +01:00
EthanHealy01 9ef20dcab8 Fix WebKit PDF-engine and storage failures, and catch them in cross-browser CI (#7366)
# Description of Changes

Follow-up to #7314, which fixed the IndexedDB blob rejection itself.
This one fixes the remaining WebKit engine gaps, fixes the ways that
class of failure surfaced to the user, and adds the cross-browser signal
that would have caught them on the PR instead of six weeks later.

## Why this exists

Two total WebKit outages sat on `main` for weeks:

1. pdf.js reads its text stream with `for await (… of readableStream)`,
and WebKit has no `ReadableStream[Symbol.asyncIterator]`. **All** pdf.js
text extraction threw `TypeError: undefined is not a function` —
Compare, read-aloud and the PDF text editor were dead on Safari.
2. IndexedDB in WebKit rejects Blob/File values with `UnknownError:
Error preparing Blob/File data to be stored in object store`, so nothing
persisted and every reload came back empty.

Neither was caught, because the existing specs never did the work. The
Compare specs filled both slots and asserted the button was enabled;
none of them clicked it. The persistence specs asserted a *filename*
reappeared after a reload, which only needs the metadata record, not the
bytes.

Every failure here **looked like success** — empty panes, blank
thumbnails, a `src` that was set but empty. That shapes the tests more
than the fixes.

## WebKit engine gaps

- **`ReadableStream[Symbol.asyncIterator]`**, installed at the entry
point before any PDF work starts. The lock discipline is the subtle
part: releasing is idempotent, is *not* done after a successful read,
and *is* done in the read's error steps — `for await` never calls
`return()` when `next()` rejects, so nothing else would ever unlock an
errored stream.
- **`requestIdleCallback`**, installed once instead of guarded at each
call site. This one wasn't broken, it was mistimed: the local fallbacks
fired at 200ms and 1000ms, landing the pdfium WASM compile on top of the
app's first renders. The shim honours the caller's full timeout, so
`{timeout: 2000}` means 2000ms.
- **`convertToBlob()` does not fail on a format it can't encode.** Per
spec it silently serialises to PNG, so asking for WebP and getting PNG
back looks like success. Canvas output now probes what the engine really
produced (once per realm) and uses the best lossy format it honours. PNG
of a rendered page is several times the size of the equivalent WebP or
JPEG, held as object URLs for every page on screen, on the engine with
the tightest renderer memory budget.

## WebKit storage failures

These read as generic transaction hygiene. They aren't — a refused blob
write **aborts its transaction**, which is the mechanism that turned a
WebKit rejection into a hang.

- **Blob refusal is remembered from any write**, not just the initial
`add`. WebKit reports it when it can't write the blob's *backing file*,
which is per-operation — an engine that accepted the add can still
refuse the rewrite, and every read-modify-write rewrites the record with
its body attached.
- **Aborted transactions no longer hang.** Read-modify-write moves to a
single `updateRecord` helper that owns its transaction, guards it once,
and resolves on **commit** rather than on the put's `onsuccess`. The
previous shape — two promises over one shared transaction, with an
`await` between the get and the put — put the abort guard on the read,
leaving the write with no handler at all. `persistVersionedOutputs`
awaits that, and `.catch` can't rescue a promise that never settles, so
tool outputs could silently stop persisting.
- **Stored blobs are no longer re-wrapped on read.** Since #7175 the
record holds the `File` itself; wrapping it in `new Blob([record.data])`
can cost WebKit the backing handle, giving you an object that looks
valid and reads as empty.
- **The file sidebar reaches a resting state** when the library can't be
read, instead of spinning forever on a rejection nobody observes. It
carries on with the in-memory workbench files: an unreadable library
should cost the user their history, not the file they're working on.
- **Thumbnail failures are logged.** Three `catch {}` blocks returned
`""`, and an empty thumbnail is indistinguishable from "this file has no
preview" — which is how outage #1 hid as a cosmetic nicety.

## CI

`main` now runs the whole stubbed suite once per engine (#7304), so the
new `@engine-capability` specs get chromium, firefox and webkit for
free. They assert the primitives actually work — a **counted**
comparison, a raster thumbnail data URL with real payload, and a page
rendered from a file restored by a reload — rather than that the UI
rendered. Deliberately small: anything added there is paid for three
times per PR, so add depth, not breadth. Run them alone with `task
e2e:cross-browser -- --grep @engine-capability`.

The cross-browser projects now share the stubbed project's viewport. At
the device presets' default 1280x720 a layout difference would fail
these specs on Firefox/WebKit only, which reads as an engine outage.

`vite.config.ts` gains a `worker.plugins` entry so `@app/*` resolves
inside worker bundles. Worker bundles are a separate Rollup pass and
don't inherit `plugins`, so the alias worked in the app and failed in a
worker — previously worked around with a relative import plus a lint
exemption, which silently bypasses the layer cascade.

## Verification

- `task frontend:check` green: typecheck, oxlint, theme lint, stylelint,
prettier, 215 test files / 1841 tests.
- The `@engine-capability` suite passes on Chromium and WebKit locally.
- **Negative control:** with the `ReadableStream` shim removed, the
WebKit comparison spec fails at the Deletions/Additions assertion — the
exact reported Safari symptom. Restored, and it passes. Both the fix and
the test that guards it are load-bearing.
- The worker alias change verified both ways: the build inlines the
encoding probe into the worker chunk, and removing `worker.plugins`
fails with `Rollup failed to resolve import
"@app/utils/canvasImageEncoding"`.
- The abort regression test aborts the transaction mid-write and asserts
`markFileAsProcessed` settles. Before the fix it never settles and the
test times out.

## Split out of this PR

Two things in earlier revisions of this branch were engine-agnostic —
found via the same symptom, not the same cause — and now have their own
PRs:

- **#7416** — blocked IndexedDB upgrades hanging the file library
(multi-tab lifecycle, the concurrent-open race, `onversionchange`).
- **#7417** — the thumbnail TTL rewriting the whole library on every
listing.

`FileSidebar`'s try/catch appears in both this PR and #7416,
identically: a WebKit rejection and a blocked-open rejection both have
to stop stranding the spinner. Whichever merges second is a no-op for
that file.

## Known gaps

- The blob-refused **rewrite** recovery in `updateRecord` isn't
unit-tested. `fake-indexeddb` never returns Blob values from a read, so
the branch that converts to a copy can't be reached there. Noted in the
test file.
- For the same reason, `fileFromRecord`'s "hand the stored File back
untouched" path is only covered on a real engine, by the reload spec.
- Nothing asserts that `src/index.tsx` imports the shims. The unit suite
installs the same module via `setupTests.ts` (jsdom has the same gaps
WebKit does), so a future regression where the entry point drops the
import would still be green under vitest.
- `FileSidebar`'s resting-state fix loses its E2E coverage until #7416
lands — forcing WebKit's blob refusal from a spec isn't practical, which
is why that spec blocks the database instead.

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [x] My changes generate no new warnings

### Documentation

- [x] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
2026-08-13 16:25:01 +00:00
imgbot[bot]andImgBotApp a7eb6ebcc3 [ImgBot] Optimize images (#7488)
## Beep boop. Your images are optimized!

Your image file size has been reduced by **16%** 🎉

<details>
<summary>
Details
</summary>

| File | Before | After | Percent reduction |
|:--|:--|:--|:--|
| /frontend/editor/src-tauri/icons/ios/AppIcon-512@2x.png | 32.25kb |
9.86kb | 69.44% |
| /frontend/editor/src/core/assets/brand/modern-logo/logo512.png |
7.96kb | 4.49kb | 43.61% |
| /app/core/src/main/resources/static/apple-touch-icon.png | 6.05kb |
3.55kb | 41.39% |
| /frontend/editor/src-tauri/icons/Square150x150Logo.png | 5.10kb |
3.13kb | 38.52% |
| /frontend/editor/public/og_images/saas/app-processor.png | 125.15kb |
77.41kb | 38.15% |
| /frontend/editor/public/mstile-150x150.png | 4.32kb | 2.68kb | 37.99%
|
| /frontend/editor/src-tauri/icons/mstile-150x150.png | 4.32kb | 2.68kb
| 37.99% |
| /frontend/editor/src-tauri/icons/Square142x142Logo.png | 4.89kb |
3.07kb | 37.28% |
| /frontend/editor/public/og_images/saas/app.png | 118.49kb | 76.07kb |
35.80% |
| /frontend/editor/src-tauri/icons/mstile-310x150.png | 4.77kb | 3.09kb
| 35.16% |
| /frontend/editor/public/mstile-310x150.png | 4.77kb | 3.09kb | 35.16%
|
|
/frontend/editor/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png
| 3.81kb | 2.50kb | 34.32% |
| /frontend/editor/public/og_images/saas/app-editor.png | 119.24kb |
78.94kb | 33.80% |
| /frontend/editor/src/core/assets/brand/modern-logo/logo192.png |
3.09kb | 2.06kb | 33.22% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-60x60@3x.png | 3.91kb |
2.73kb | 30.17% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-76x76@2x.png | 3.53kb |
2.48kb | 29.84% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png
| 3.36kb | 2.36kb | 29.82% |
| /frontend/editor/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png
| 3.36kb | 2.36kb | 29.82% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png | 3.77kb
| 2.65kb | 29.66% |
| /frontend/editor/src-tauri/icons/64x64.png | 2.46kb | 1.86kb | 24.53%
|
| /frontend/editor/src-tauri/icons/ios/AppIcon-29x29@3x.png | 2.37kb |
1.82kb | 23.05% |
| /frontend/editor/src-tauri/icons/Square89x89Logo.png | 3.24kb | 2.50kb
| 22.64% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-40x40@2x-1.png | 2.27kb |
1.76kb | 22.22% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-40x40@2x.png | 2.27kb |
1.76kb | 22.22% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-76x76@1x.png | 2.19kb |
1.70kb | 22.17% |
| /frontend/editor/src/core/assets/brand/classic-logo/logo512.png |
99.72kb | 78.32kb | 21.47% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-60x60@2x.png | 2.35kb |
1.86kb | 20.92% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-40x40@3x.png | 2.35kb |
1.86kb | 20.92% |
| /frontend/editor/src/core/assets/brand/modern-logo/Firstpage.png |
210.88kb | 169.27kb | 19.73% |
| /frontend/editor/src-tauri/icons/Square71x71Logo.png | 2.73kb | 2.25kb
| 17.59% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-29x29@2x.png | 1.71kb |
1.43kb | 16.47% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-29x29@2x-1.png | 1.71kb |
1.43kb | 16.47% |
| /frontend/editor/src-tauri/icons/Square44x44Logo.png | 1.72kb | 1.46kb
| 15.31% |
| /frontend/editor/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png |
1.90kb | 1.62kb | 14.63% |
|
/frontend/editor/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png
| 1.90kb | 1.62kb | 14.63% |
|
/frontend/editor/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png
| 1.79kb | 1.54kb | 14.25% |
| /frontend/editor/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png |
1.79kb | 1.54kb | 14.25% |
| /frontend/editor/src-tauri/icons/Square284x284Logo.png | 9.32kb |
8.03kb | 13.78% |
| /app/core/src/main/resources/static/images/signature.png | 20.06kb |
17.39kb | 13.30% |
| /docs/stirling.png | 20.06kb | 17.39kb | 13.30% |
| /frontend/editor/src-tauri/icons/android-chrome-512x512.png | 20.06kb
| 17.39kb | 13.30% |
| /frontend/editor/public/android-chrome-512x512.png | 20.06kb | 17.39kb
| 13.30% |
| /frontend/editor/public/favicon.png | 20.06kb | 17.39kb | 13.30% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-20x20@3x.png | 1.67kb |
1.45kb | 13.03% |
| /frontend/editor/src-tauri/icons/Square310x310Logo.png | 10.43kb |
9.09kb | 12.79% |
| /frontend/editor/src/core/assets/brand/classic-logo/Firstpage.png |
405.84kb | 355.87kb | 12.31% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png
| 16.87kb | 14.83kb | 12.09% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png
| 10.69kb | 9.41kb | 11.98% |
| /frontend/editor/src-tauri/icons/128x128@2x.png | 8.59kb | 7.60kb |
11.55% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png
| 6.70kb | 5.99kb | 10.70% |
| /frontend/editor/src-tauri/icons/192x192.png | 6.10kb | 5.47kb |
10.36% |
| /frontend/editor/public/android-chrome-192x192.png | 6.10kb | 5.47kb |
10.36% |
| /frontend/editor/src-tauri/icons/android-chrome-192x192.png | 6.10kb |
5.47kb | 10.36% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png
| 6.29kb | 5.67kb | 9.85% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png
| 6.29kb | 5.67kb | 9.85% |
| /frontend/editor/src/core/assets/brand/classic-logo/logo192.png |
23.45kb | 21.19kb | 9.62% |
|
/frontend/editor/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png
| 5.21kb | 4.74kb | 9.06% |
| /frontend/editor/src-tauri/icons/mstile-310x310.png | 11.48kb |
10.45kb | 8.95% |
| /frontend/editor/public/mstile-310x310.png | 11.48kb | 10.45kb | 8.95%
|
| /frontend/editor/public/mstile-144x144.png | 4.98kb | 4.53kb | 8.95% |
| /frontend/editor/src-tauri/icons/mstile-144x144.png | 4.98kb | 4.53kb
| 8.95% |
|
/frontend/editor/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png
| 4.87kb | 4.44kb | 8.89% |
| /frontend/editor/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png
| 4.87kb | 4.44kb | 8.89% |
| /frontend/editor/src-tauri/icons/StoreLogo.png | 1.89kb | 1.76kb |
7.03% |
| /frontend/editor/src-tauri/icons/128x128.png | 4.16kb | 3.95kb | 5.07%
|
| /app/core/src/main/resources/static/favicon-32x32.png | 1.29kb |
1.23kb | 4.90% |
| /frontend/editor/src-tauri/icons/32x32.png | 1.29kb | 1.23kb | 4.90% |
| /frontend/editor/src/core/assets/login/microsoft.svg | 0.29kb | 0.27kb
| 4.79% |
| /frontend/editor/src-tauri/icons/Square107x107Logo.png | 3.72kb |
3.54kb | 4.78% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-40x40@1x.png | 1.13kb |
1.08kb | 4.25% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-20x20@2x-1.png | 1.13kb |
1.08kb | 4.25% |
| /frontend/editor/src-tauri/icons/ios/AppIcon-20x20@2x.png | 1.13kb |
1.08kb | 4.25% |
| /frontend/editor/public/og_images/shared-sign.png | 620.42kb |
597.33kb | 3.72% |
| /frontend/editor/src-tauri/icons/mstile-70x70.png | 3.34kb | 3.25kb |
2.66% |
| /frontend/editor/public/mstile-70x70.png | 3.34kb | 3.25kb | 2.66% |
| /frontend/editor/src/core/assets/login/authentik.svg | 6.85kb | 6.81kb
| 0.70% |
| /frontend/editor/src/core/assets/brand/classic-logo/logo-tooltip.svg |
1.49kb | 1.48kb | 0.59% |
| /frontend/editor/src/core/assets/login/oidc.svg | 11.32kb | 11.27kb |
0.43% |
| /frontend/editor/src/core/assets/login/github.svg | 1.44kb | 1.44kb |
0.20% |
| | | | |
| **Total :** | **2,144.28kb** | **1,792.57kb** | **16.40%** |
</details>

---

[📝 docs](https://imgbot.net/docs) | [:octocat:
repo](https://github.com/imgbot/ImgBot) | [🙋🏾
issues](https://github.com/imgbot/ImgBot/issues) | [🏪
marketplace](https://github.com/marketplace/imgbot)

<i>~Imgbot - Part of [Optimole](https://optimole.com/) family</i>

Signed-off-by: ImgBotApp <ImgBotHelp@gmail.com>
Co-authored-by: ImgBotApp <ImgBotHelp@gmail.com>
2026-08-13 14:09:25 +00:00
Ludy 1659ca964a ci: include buildSrc in dependency updates and Gradle cache keys (#7486)
## Description of Changes

- Add `/app/saas` and `/buildSrc` to Dependabot's Gradle update
directories.
- Include `buildSrc/**` in every Gradle `actions/cache` key.
- Ensure changes to shared Gradle build logic invalidate the dependency
cache.

This keeps dependency updates and CI caching aligned with the
repository's current Gradle build structure.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)

### Testing

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally.
2026-08-13 13:51:00 +00:00
Reece BrowneandEthanHealy01 d2844c4f90 Super search across Editor and Processor (#6734)
One search bar for the whole app, in both the Editor and the Processor.
**Cmd/Ctrl+K** from anywhere.

- **Editor** — your files, tools, settings, and everything in the
Processor: its pages plus live users, policies, pipelines, sources, and
full-text developer docs.
- **Processor** — the same bar over its own data (users, policies,
pipelines, sources, docs) plus tools and settings. Files stay
editor-only; pages stay out (the sidebar covers them).
- **Filter chips** (and typed prefixes like `policy: invoice`) narrow to
one lane; each group shows a few results with **Show more** to expand.
- **Access-gated**: users only see lanes and settings sections they can
actually open — no Processor chips, results, or entity fetches without
portal access, no admin settings for non-admins, no account-bound
sections for anonymous SaaS sessions. Asserted by unit tests and a
stubbed Playwright suite.
- Selecting a result takes you straight there, across apps when needed —
results group under collapsible sections.

Replaces the separate search boxes that previously lived in the file
sidebar, the tool panel header, the settings modal, and the docs page.

### Editor

<img
src="https://gist.githubusercontent.com/reecebrowne/5879e797c5ab6abc027a7bfd5cd4de17/raw/editor-search.png?v=2"
width="800" alt="Editor super search" />

### Processor

<img
src="https://gist.githubusercontent.com/reecebrowne/5879e797c5ab6abc027a7bfd5cd4de17/raw/portal-search.png?v=2"
width="800" alt="Processor super search" />

---------

Co-authored-by: EthanHealy01 <ethan.healy.21@gmail.com>
2026-08-13 12:38:45 +00:00
brios cd49daf5c6 refactor(api): move DeletingRandomAccessFile to CustomPDFDocumentFactory as a private static class (#7344)
# Description of Changes

It was in a seperate dir
(app/common/src/main/java/org/apache/pdfbox/examples/util/) which i felt
out of place for it.



<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 11:16:09 +00:00
Anthony Stirling 18e056ea8d feat: switch default branding to the modern Stirling logo (#7485)
# Description of Changes

logo changes!

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 11:03:28 +00:00
brios 5709edccc0 fix(ui): update MergeFileSorter button size from 'md' to 'sm' (#7328)
# Description of Changes


Fixes height of direction arrow button height to match the height of the
dropdown select element

### New

<img width="598" height="1158" alt="image"
src="https://github.com/user-attachments/assets/9461d4e1-060c-43b6-ac15-eb906f80134a"
/>


### Old
<img width="596" height="1162" alt="image"
src="https://github.com/user-attachments/assets/96ce8317-527d-4e1b-815d-15349adc9aea"
/>


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [X] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [X] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [X] I have performed a self-review of my own code
- [X] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [X] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [X] I have run `task check` to verify linters, typechecks, and tests
pass
- [X] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 10:34:57 +00:00
James Brunton 09e548fbac Fix a11y on DividerWithText (#7482)
# Description of Changes
Nightlies are currently failing with an a11y issue on the
DividerWithText story. This fixes the a11y issue.

<img width="1440" height="1240" alt="image"
src="https://github.com/user-attachments/assets/c1623583-a68c-46d9-8cdb-453aca5ac20d"
/>
2026-08-13 11:10:31 +01:00
brios 1447ed8b62 refactor(redact): replace PDFBox-based text redaction with JPDFium (#7364)
# Description of Changes

Refactors automatic text redaction to use JPDFium-based redaction/text
removal instead of PDFBox

Changes:

* The `RedactController` now uses the JPDFium native redaction engine
(`PdfRedactor.redact`) as the primary method for PDF redaction, with
automatic fallback to the manual redaction service if JPDFium fails or
throws an exception. This improves reliability and leverages more robust
native features when available.
* Regex patterns provided by the user are now validated before redaction
begins, ensuring invalid patterns are rejected early with clear error
messages.
* The code now trims and filters out empty or excessively long redaction
terms, preventing unnecessary processing and potential errors.


<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [x] I have run `task check` to verify linters, typechecks, and tests
pass
- [x] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-13 11:10:10 +01:00
dependabot[bot] b33c2d68d6 build(deps): bump springSecuritySamlVersion from 7.0.5 to 7.1.0 (#7399)
Bumps `springSecuritySamlVersion` from 7.0.5 to 7.1.0.
Updates `org.springframework.security:spring-security-core` from 7.0.5
to 7.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/spring-projects/spring-security/releases">org.springframework.security:spring-security-core's
releases</a>.</em></p>
<blockquote>
<h2>7.1.0</h2>
<h2>🪲 Bug Fixes</h2>
<ul>
<li>Opaque token introspectors should not allow empty credentials <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19201">#19201</a></li>
</ul>
<h2>🔨 Dependency Upgrades</h2>
<ul>
<li>Bump <code>@springio</code>/antora-extensions from 1.14.11 to
1.14.12 in /docs <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19235">#19235</a></li>
<li>Bump actions/checkout from 6.0.2 to 6.0.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19271">#19271</a></li>
<li>Bump antora from 3.2.0-alpha.11 to 3.2.0-alpha.12 in /docs <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19181">#19181</a></li>
<li>Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.33 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19228">#19228</a></li>
<li>Bump ch.qos.logback:logback-classic from 1.5.33 to 1.5.34 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19268">#19268</a></li>
<li>Bump com.fasterxml.jackson:jackson-bom from 2.21.2 to 2.21.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19133">#19133</a></li>
<li>Bump com.fasterxml.jackson:jackson-bom from 2.21.3 to 2.22.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19246">#19246</a></li>
<li>Bump com.google.code.gson:gson from 2.13.2 to 2.14.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19125">#19125</a></li>
<li>Bump com.nimbusds:oauth2-oidc-sdk from 11.37 to 11.37.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19157">#19157</a></li>
<li>Bump com.nimbusds:oauth2-oidc-sdk from 11.37 to 11.37.2 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19195">#19195</a></li>
<li>Bump com.webauthn4j:webauthn4j-core from 0.31.3.RELEASE to
0.31.5.RELEASE <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19148">#19148</a></li>
<li>Bump com.webauthn4j:webauthn4j-core from 0.31.5.RELEASE to
0.31.6.RELEASE <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19263">#19263</a></li>
<li>Bump gradle-wrapper from 9.4.1 to 9.5.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19135">#19135</a></li>
<li>Bump gradle-wrapper from 9.5.0 to 9.5.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19171">#19171</a></li>
<li>Bump io-micrometer from 1.16.5 to 1.17.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19287">#19287</a></li>
<li>Bump io.mockk:mockk from 1.14.9 to 1.14.11 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19244">#19244</a></li>
<li>Bump io.projectreactor:reactor-bom from 2025.0.5 to 2025.0.6 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19296">#19296</a></li>
<li>Bump org-jetbrains-kotlin from 2.3.20 to 2.3.21 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19126">#19126</a></li>
<li>Bump org-jetbrains-kotlin from 2.3.21 to 2.4.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19264">#19264</a></li>
<li>Bump org-opensaml5 from 5.2.1 to 5.2.2 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19176">#19176</a></li>
<li>Bump org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16
<a
href="https://redirect.github.com/spring-projects/spring-security/pull/19190">#19190</a></li>
<li>Bump org.apereo.cas.client:cas-client-core from 4.1.0 to 4.1.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19200">#19200</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.1.Final to
7.3.2.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19119">#19119</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.2.Final to
7.3.3.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19149">#19149</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.3.Final to
7.3.4.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19165">#19165</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.4.Final to
7.3.5.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19191">#19191</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.5.Final to
7.3.6.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19211">#19211</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.6.Final to
7.4.0.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19226">#19226</a></li>
<li>Bump org.jetbrains.kotlinx:kotlinx-coroutines-bom from 1.10.2 to
1.11.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19166">#19166</a></li>
<li>Bump org.junit:junit-bom from 6.0.3 to 6.1.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19197">#19197</a></li>
<li>Bump org.slf4j:slf4j-api from 2.0.17 to 2.0.18 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19169">#19169</a></li>
<li>Bump org.springframework.data:spring-data-bom from 2025.1.5 to
2025.1.6 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19290">#19290</a></li>
<li>Bump org.springframework.ldap:spring-ldap-core from 4.0.3 to 4.1.0
<a
href="https://redirect.github.com/spring-projects/spring-security/pull/19291">#19291</a></li>
<li>Bump org.springframework:spring-framework-bom from 7.0.7 to 7.0.8 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19285">#19285</a></li>
<li>Bump spring-io/spring-release-actions from 0.0.4 to 0.0.5 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19179">#19179</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.2 to 3.1.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19147">#19147</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.3 to 3.1.4 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19245">#19245</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.4 to 3.2.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19286">#19286</a></li>
<li>Update to spring-data-bom 2026.0.0 <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19303">#19303</a></li>
</ul>
<h2>🔩 Build Updates</h2>
<ul>
<li>Release 7.1.0 <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19218">#19218</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/spring-projects/spring-security/commit/8078781a6bdbd0c44755d24e13839d041caece34"><code>8078781</code></a>
Release 7.1.0</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/835ca03dd906e80ab1a97759f8ecc8dd30a93e04"><code>835ca03</code></a>
Merge branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/15e2488c84dbf0eac0f5d3f39cf8f1c49a07abd9"><code>15e2488</code></a>
Sync branch 'main'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/62c4d60524ba0b25d6f0e969ce08dca3987a5019"><code>62c4d60</code></a>
Merge branch '6.5.x' into 7.0.x</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/d884913175bcedb63ef1f7e44799de1ee2cd18f2"><code>d884913</code></a>
Sync branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/fd5dae5e4d3fece143d03f9ca2eba10ff3a420fc"><code>fd5dae5</code></a>
Sync branch '6.5.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/65f43d6894eb304ad2ecc33739aeae7f02d49efd"><code>65f43d6</code></a>
Merge branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/d29c76113545c22da5583c802ce419b116f5d678"><code>d29c761</code></a>
Merge branch '6.5.x' into 7.0.x</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/700a4533aa70603d4076dc5ebf052b72312a39e5"><code>700a453</code></a>
Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.34</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/831bfd7ffa3dc5d2790ed5ffedcfb9f6b78924da"><code>831bfd7</code></a>
Update to spring-data-bom 2026.0.0</li>
<li>Additional commits viewable in <a
href="https://github.com/spring-projects/spring-security/compare/7.0.5...7.1.0">compare
view</a></li>
</ul>
</details>
<br />

Updates
`org.springframework.security:spring-security-saml2-service-provider`
from 7.0.5 to 7.1.0
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/spring-projects/spring-security/releases">org.springframework.security:spring-security-saml2-service-provider's
releases</a>.</em></p>
<blockquote>
<h2>7.1.0</h2>
<h2>🪲 Bug Fixes</h2>
<ul>
<li>Opaque token introspectors should not allow empty credentials <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19201">#19201</a></li>
</ul>
<h2>🔨 Dependency Upgrades</h2>
<ul>
<li>Bump <code>@springio</code>/antora-extensions from 1.14.11 to
1.14.12 in /docs <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19235">#19235</a></li>
<li>Bump actions/checkout from 6.0.2 to 6.0.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19271">#19271</a></li>
<li>Bump antora from 3.2.0-alpha.11 to 3.2.0-alpha.12 in /docs <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19181">#19181</a></li>
<li>Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.33 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19228">#19228</a></li>
<li>Bump ch.qos.logback:logback-classic from 1.5.33 to 1.5.34 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19268">#19268</a></li>
<li>Bump com.fasterxml.jackson:jackson-bom from 2.21.2 to 2.21.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19133">#19133</a></li>
<li>Bump com.fasterxml.jackson:jackson-bom from 2.21.3 to 2.22.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19246">#19246</a></li>
<li>Bump com.google.code.gson:gson from 2.13.2 to 2.14.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19125">#19125</a></li>
<li>Bump com.nimbusds:oauth2-oidc-sdk from 11.37 to 11.37.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19157">#19157</a></li>
<li>Bump com.nimbusds:oauth2-oidc-sdk from 11.37 to 11.37.2 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19195">#19195</a></li>
<li>Bump com.webauthn4j:webauthn4j-core from 0.31.3.RELEASE to
0.31.5.RELEASE <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19148">#19148</a></li>
<li>Bump com.webauthn4j:webauthn4j-core from 0.31.5.RELEASE to
0.31.6.RELEASE <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19263">#19263</a></li>
<li>Bump gradle-wrapper from 9.4.1 to 9.5.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19135">#19135</a></li>
<li>Bump gradle-wrapper from 9.5.0 to 9.5.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19171">#19171</a></li>
<li>Bump io-micrometer from 1.16.5 to 1.17.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19287">#19287</a></li>
<li>Bump io.mockk:mockk from 1.14.9 to 1.14.11 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19244">#19244</a></li>
<li>Bump io.projectreactor:reactor-bom from 2025.0.5 to 2025.0.6 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19296">#19296</a></li>
<li>Bump org-jetbrains-kotlin from 2.3.20 to 2.3.21 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19126">#19126</a></li>
<li>Bump org-jetbrains-kotlin from 2.3.21 to 2.4.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19264">#19264</a></li>
<li>Bump org-opensaml5 from 5.2.1 to 5.2.2 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19176">#19176</a></li>
<li>Bump org.apache.maven:maven-resolver-provider from 3.9.15 to 3.9.16
<a
href="https://redirect.github.com/spring-projects/spring-security/pull/19190">#19190</a></li>
<li>Bump org.apereo.cas.client:cas-client-core from 4.1.0 to 4.1.1 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19200">#19200</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.1.Final to
7.3.2.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19119">#19119</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.2.Final to
7.3.3.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19149">#19149</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.3.Final to
7.3.4.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19165">#19165</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.4.Final to
7.3.5.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19191">#19191</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.5.Final to
7.3.6.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19211">#19211</a></li>
<li>Bump org.hibernate.orm:hibernate-core from 7.3.6.Final to
7.4.0.Final <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19226">#19226</a></li>
<li>Bump org.jetbrains.kotlinx:kotlinx-coroutines-bom from 1.10.2 to
1.11.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19166">#19166</a></li>
<li>Bump org.junit:junit-bom from 6.0.3 to 6.1.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19197">#19197</a></li>
<li>Bump org.slf4j:slf4j-api from 2.0.17 to 2.0.18 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19169">#19169</a></li>
<li>Bump org.springframework.data:spring-data-bom from 2025.1.5 to
2025.1.6 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19290">#19290</a></li>
<li>Bump org.springframework.ldap:spring-ldap-core from 4.0.3 to 4.1.0
<a
href="https://redirect.github.com/spring-projects/spring-security/pull/19291">#19291</a></li>
<li>Bump org.springframework:spring-framework-bom from 7.0.7 to 7.0.8 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19285">#19285</a></li>
<li>Bump spring-io/spring-release-actions from 0.0.4 to 0.0.5 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19179">#19179</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.2 to 3.1.3 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19147">#19147</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.3 to 3.1.4 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19245">#19245</a></li>
<li>Bump tools.jackson:jackson-bom from 3.1.4 to 3.2.0 <a
href="https://redirect.github.com/spring-projects/spring-security/pull/19286">#19286</a></li>
<li>Update to spring-data-bom 2026.0.0 <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19303">#19303</a></li>
</ul>
<h2>🔩 Build Updates</h2>
<ul>
<li>Release 7.1.0 <a
href="https://redirect.github.com/spring-projects/spring-security/issues/19218">#19218</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/spring-projects/spring-security/commit/8078781a6bdbd0c44755d24e13839d041caece34"><code>8078781</code></a>
Release 7.1.0</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/835ca03dd906e80ab1a97759f8ecc8dd30a93e04"><code>835ca03</code></a>
Merge branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/15e2488c84dbf0eac0f5d3f39cf8f1c49a07abd9"><code>15e2488</code></a>
Sync branch 'main'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/62c4d60524ba0b25d6f0e969ce08dca3987a5019"><code>62c4d60</code></a>
Merge branch '6.5.x' into 7.0.x</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/d884913175bcedb63ef1f7e44799de1ee2cd18f2"><code>d884913</code></a>
Sync branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/fd5dae5e4d3fece143d03f9ca2eba10ff3a420fc"><code>fd5dae5</code></a>
Sync branch '6.5.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/65f43d6894eb304ad2ecc33739aeae7f02d49efd"><code>65f43d6</code></a>
Merge branch '7.0.x'</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/d29c76113545c22da5583c802ce419b116f5d678"><code>d29c761</code></a>
Merge branch '6.5.x' into 7.0.x</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/700a4533aa70603d4076dc5ebf052b72312a39e5"><code>700a453</code></a>
Bump ch.qos.logback:logback-classic from 1.5.32 to 1.5.34</li>
<li><a
href="https://github.com/spring-projects/spring-security/commit/831bfd7ffa3dc5d2790ed5ffedcfb9f6b78924da"><code>831bfd7</code></a>
Update to spring-data-bom 2026.0.0</li>
<li>Additional commits viewable in <a
href="https://github.com/spring-projects/spring-security/compare/7.0.5...7.1.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 18:11:34 +00:00
dependabot[bot] 519b00ba35 build(deps): bump the gradle group across 2 directories with 1 update (#7458)
Bumps the gradle group with 1 update in the /docker/backend directory:
gradle.
Bumps the gradle group with 1 update in the /docker/embedded directory:
gradle.

Updates `gradle` from `934a520` to `e8aeffb`

Updates `gradle` from `934a520` to `e8aeffb`


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore <dependency name> major version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's major version (unless you unignore this specific
dependency's major version or upgrade to it yourself)
- `@dependabot ignore <dependency name> minor version` will close this
group update PR and stop Dependabot creating any more for the specific
dependency's minor version (unless you unignore this specific
dependency's minor version or upgrade to it yourself)
- `@dependabot ignore <dependency name>` will close this group update PR
and stop Dependabot creating any more for the specific dependency
(unless you unignore this specific dependency or upgrade to it yourself)
- `@dependabot unignore <dependency name>` will remove all of the ignore
conditions of the specified dependency
- `@dependabot unignore <dependency name> <ignore condition>` will
remove the ignore condition of the specified dependency and ignore
conditions


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 16:37:38 +00:00
dependabot[bot] d19c2a1143 build(deps): bump the ubuntu group across 2 directories with 1 update (#7452)
Bumps the ubuntu group with 1 update in the /docker/base directory:
ubuntu.
Bumps the ubuntu group with 1 update in the /docker/unoserver directory:
ubuntu.

Updates `ubuntu` from `4fbb8e6` to `561618e`

Updates `ubuntu` from `4fbb8e6` to `561618e`

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 16:24:48 +00:00
dependabot[bot] 833d951fd0 build(deps): bump actions/stale from 10.3.0 to 11.0.0 (#7376)
Bumps [actions/stale](https://github.com/actions/stale) from 10.3.0 to
11.0.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/stale/releases">actions/stale's
releases</a>.</em></p>
<blockquote>
<h2>v11.0.0</h2>
<h2>What's Changed</h2>
<h3>Enhancement</h3>
<ul>
<li>Migrate to ESM and update dependencies by <a
href="https://github-grid.enterprise.slack.com/team/U08CVLQ4JKE"><code>@​chiranjib-swain</code></a>
in <a
href="https://redirect.github.com/actions/stale/pull/1350">actions/stale#1350</a></li>
</ul>
<h3>Dependency Update</h3>
<ul>
<li>Override brace-expansion to 5.0.8 to address 24 high-severity
dependency vulnerabilities by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/stale/pull/1351">actions/stale#1351</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/stale/compare/v10...v11.0.0">https://github.com/actions/stale/compare/v10...v11.0.0</a></p>
<h2>v10.4.0</h2>
<h2>What's Changed</h2>
<h3>Bug Fix</h3>
<ul>
<li>Fixed <code>only-issue-types</code> validation by <a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>
in <a
href="https://redirect.github.com/actions/stale/pull/1338">actions/stale#1338</a></li>
</ul>
<h3>Dependency Updates</h3>
<ul>
<li>Bump undici to 6.27.0 via override, clean up stale license files,
and version to 10.4.0. by <a
href="https://github.com/dependabot"><code>@​dependabot</code></a> in <a
href="https://redirect.github.com/actions/stale/pull/1342">actions/stale#1342</a></li>
</ul>
<h2>New Contributors</h2>
<ul>
<li><a
href="https://github.com/trueberryless"><code>@​trueberryless</code></a>
made their first contribution in <a
href="https://redirect.github.com/actions/stale/pull/1338">actions/stale#1338</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/stale/compare/v10.3.0...v10.4.0">https://github.com/actions/stale/compare/v10.3.0...v10.4.0</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/stale/commit/4391f3da665fdf50b6810c1a66712fb9ba21aa93"><code>4391f3d</code></a>
Fix 24 high severity vulnerabilities by overriding brace-expansion to
5.0.8 (...</li>
<li><a
href="https://github.com/actions/stale/commit/eaf9131fae5eafd0c31a64ebe3a2e183266fec48"><code>eaf9131</code></a>
refactor: update imports to use ES module syntax and improve test
structure (...</li>
<li><a
href="https://github.com/actions/stale/commit/1e223db275d687790206a7acac4d1a11bd6fe629"><code>1e223db</code></a>
Bump undici to 6.27.0 via override, clean up stale license files, and
version...</li>
<li><a
href="https://github.com/actions/stale/commit/9461cb10066d1553762bac6a02599ab8c26b14dd"><code>9461cb1</code></a>
fix: <code>only-issue-types</code> does not affect PRs (<a
href="https://redirect.github.com/actions/stale/issues/1338">#1338</a>)</li>
<li>See full diff in <a
href="https://github.com/actions/stale/compare/eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899...4391f3da665fdf50b6810c1a66712fb9ba21aa93">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=actions/stale&package-manager=github_actions&previous-version=10.3.0&new-version=11.0.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 16:13:59 +00:00
dependabot[bot]andLudy 551af7fda3 build(deps): bump github/codeql-action/upload-sarif from 4.37.3 to 4.37.4 (#7373)
Bumps
[github/codeql-action/upload-sarif](https://github.com/github/codeql-action)
from 4.37.3 to 4.37.4.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/releases">github/codeql-action/upload-sarif's
releases</a>.</em></p>
<blockquote>
<h2>v4.37.4</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/github/codeql-action/blob/main/CHANGELOG.md">github/codeql-action/upload-sarif's
changelog</a>.</em></p>
<blockquote>
<h1>CodeQL Action Changelog</h1>
<p>See the <a
href="https://github.com/github/codeql-action/releases">releases
page</a> for the relevant changes to the CodeQL CLI and language
packs.</p>
<h2>[UNRELEASED]</h2>
<p>No user facing changes.</p>
<h2>4.37.6 - 04 Aug 2026</h2>
<ul>
<li>Changed the default filepath for the new remote file address format
that was introduced in CodeQL Action 4.37.0 / 3.37.0 to
<code>.github/codeql-config.yml</code> to align it with the suggested
path that is used elsewhere. <a
href="https://redirect.github.com/github/codeql-action/pull/4070">#4070</a></li>
</ul>
<h2>4.37.5 - 03 Aug 2026</h2>
<ul>
<li>Fixed a bug where a network error while streaming the download of
the CodeQL bundle could terminate the <code>init</code> Action instead
of falling back to downloading the bundle before extracting it. <a
href="https://redirect.github.com/github/codeql-action/pull/4061">#4061</a></li>
</ul>
<h2>4.37.4 - 29 Jul 2026</h2>
<ul>
<li>This version of the CodeQL Action adds support for the
<code>tools</code> input for the <code>codeql-action/init</code> step to
be specified using a <code>github-codeql-tools</code> <a
href="https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization">repository
property</a>. This feature will gradually be rolled out following the
release of this version. Once rolled out, this allows for the CodeQL CLI
version that is used in GitHub-managed workflows, such as Default Setup,
to be set to a custom value. For example, customers who run into issues
with rate limits when a new CodeQL CLI version is released can set the
value to <code>toolcache</code> to always use the CodeQL CLI version
that is available in the runner toolcache. For Advanced Setup workflows,
the value provided for <code>tools</code> in the workflow definition
always takes precedence unless the value of the repository property
starts with <code>!</code>. <a
href="https://redirect.github.com/github/codeql-action/pull/4037">#4037</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.2">2.26.2</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4051">#4051</a></li>
</ul>
<h2>4.37.3 - 22 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.37.2 - 21 Jul 2026</h2>
<ul>
<li>The new address format for the <code>config-file</code> input that
was introduced in CodeQL Action 4.37.0 is now enabled by default. In
addition to the format described there, the <code>remote=</code> prefix
can now be used to explicitly indicate that the input refers to a remote
file. All previous input formats continue to be accepted as well. <a
href="https://redirect.github.com/github/codeql-action/pull/4023">#4023</a></li>
<li>The CodeQL Action can now make use of <a
href="https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries">configured
private registries</a> in Default Setup to retrieve CodeQL configuration
files from remote repositories that require authentication. This will
allow customers to store their CodeQL configuration in a single
repository that can then be referenced by Default Setup workflows in
other repositories. We expect to roll this and other, related changes
out to everyone in July. <a
href="https://redirect.github.com/github/codeql-action/pull/4007">#4007</a></li>
</ul>
<h2>4.37.1 - 16 Jul 2026</h2>
<ul>
<li><em>Upcoming breaking change</em>: Add a deprecation warning for
customers using CodeQL version 2.20.6 and earlier. These versions of
CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise
Server 3.16, and will be unsupported by the next minor release of the
CodeQL Action. <a
href="https://redirect.github.com/github/codeql-action/pull/3956">#3956</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1">2.26.1</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/4019">#4019</a></li>
</ul>
<h2>4.37.0 - 08 Jul 2026</h2>
<ul>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0">2.26.0</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3995">#3995</a></li>
<li>In addition to the existing input format, the
<code>config-file</code> input for the <code>codeql-action/init</code>
step will soon support a new <code>[owner/]repo[@ref][:path]</code>
format. All components except the repository name are optional. If
omitted, <code>owner</code> defaults to the same owner as the repository
the analysis is running for, <code>ref</code> to <code>main</code>, and
<code>path</code> to <code>.github/codeql-action.yaml</code>. Support
for this format ships in this version of the CodeQL Action, but will
only be enabled over the coming weeks. <a
href="https://redirect.github.com/github/codeql-action/pull/3973">#3973</a></li>
</ul>
<h2>4.36.3 - 01 Jul 2026</h2>
<p>No user facing changes.</p>
<h2>4.36.2 - 04 Jun 2026</h2>
<ul>
<li>Cache CodeQL CLI version information across Actions steps. <a
href="https://redirect.github.com/github/codeql-action/pull/3943">#3943</a></li>
<li>Reduce requests while waiting for analysis processing by using
exponential backoff when polling SARIF processing status. <a
href="https://redirect.github.com/github/codeql-action/pull/3937">#3937</a></li>
<li>Update default CodeQL bundle version to <a
href="https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6">2.25.6</a>.
<a
href="https://redirect.github.com/github/codeql-action/pull/3948">#3948</a></li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/github/codeql-action/commit/f205ea1c3313d32999d8d6a48b4f6530d4437b38"><code>f205ea1</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4053">#4053</a>
from github/update-v4.37.4-9130ce0f7</li>
<li><a
href="https://github.com/github/codeql-action/commit/e40d079dd9dd4a5c74f625cecd83867c8208aa71"><code>e40d079</code></a>
Update changelog for v4.37.4</li>
<li><a
href="https://github.com/github/codeql-action/commit/9130ce0f733a0d17b9e1b9692c2af19be6c69dda"><code>9130ce0</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4051">#4051</a>
from github/update-bundle/codeql-bundle-v2.26.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/c62d82468641dca0f8df108ab73e2a8407ac9cf7"><code>c62d824</code></a>
Add changelog note</li>
<li><a
href="https://github.com/github/codeql-action/commit/da0c1901011e62af9c02aae8bf5b8885b11f7741"><code>da0c190</code></a>
Update default bundle to codeql-bundle-v2.26.2</li>
<li><a
href="https://github.com/github/codeql-action/commit/18420e3271f74589575af831a523c833acda327f"><code>18420e3</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4043">#4043</a>
from github/mbg/ts/changelog</li>
<li><a
href="https://github.com/github/codeql-action/commit/7e8d8970f03ec5a78ab372fc0778e8e4194111a5"><code>7e8d897</code></a>
Merge pull request <a
href="https://redirect.github.com/github/codeql-action/issues/4046">#4046</a>
from github/mbg/repo-prop/code-quality</li>
<li><a
href="https://github.com/github/codeql-action/commit/2d4c474c2ca5ea2965b9e53fabb7b67b0100016c"><code>2d4c474</code></a>
Log <code>!analysisKindSupported</code> case</li>
<li><a
href="https://github.com/github/codeql-action/commit/98c05a17d327d7c4055fca83114434ab56baacf6"><code>98c05a1</code></a>
Fix argument validation in <code>rollback-changelog.ts</code></li>
<li><a
href="https://github.com/github/codeql-action/commit/8289a49271cbb335d374e7e2e7a50c1576be0afe"><code>8289a49</code></a>
Ignore repository property for unsupported analysis kinds</li>
<li>Additional commits viewable in <a
href="https://github.com/github/codeql-action/compare/e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81...f205ea1c3313d32999d8d6a48b4f6530d4437b38">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>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
2026-08-12 15:56:30 +00:00
NathanandAnthony Stirling 9cfd24cf08 docs: request sample files in issue templates (#6770)
## Summary
- add sample/example file prompts to the bug report and feature request
issue forms
- remind reporters to remove sensitive information before attaching
files

Closes #4480

## Checks
- `git diff --check`
- parsed both updated issue template YAML files with PyYAML and verified
the `sample-files` field is present

Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-08-12 15:54:49 +00:00
dependabot[bot] bb9087f3f9 build(deps): bump KSXGitHub/github-actions-deploy-aur from 4.1.3 to 4.2.0 (#7374)
Bumps
[KSXGitHub/github-actions-deploy-aur](https://github.com/ksxgithub/github-actions-deploy-aur)
from 4.1.3 to 4.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/ksxgithub/github-actions-deploy-aur/releases">KSXGitHub/github-actions-deploy-aur's
releases</a>.</em></p>
<blockquote>
<h2>v4.2.0</h2>
<p>Add a feature to sync AUR repo (<a
href="https://redirect.github.com/KSXGitHub/github-actions-deploy-aur/pull/52">KSXGitHub/github-actions-deploy-aur#52</a>).</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/KSXGitHub/github-actions-deploy-aur/commit/084b0d9b15415bf9cdb65d44dad1efe37a354050"><code>084b0d9</code></a>
style: consistency (<a
href="https://redirect.github.com/ksxgithub/github-actions-deploy-aur/issues/55">#55</a>)</li>
<li><a
href="https://github.com/KSXGitHub/github-actions-deploy-aur/commit/9e2f21095c586521806151200746082d8e02bb90"><code>9e2f210</code></a>
fix: force-add <code>PKGBUILD</code> and <code>.SRCINFO</code> in the
asset_dir/assets path (<a
href="https://redirect.github.com/ksxgithub/github-actions-deploy-aur/issues/53">#53</a>)</li>
<li><a
href="https://github.com/KSXGitHub/github-actions-deploy-aur/commit/7acb32fe8c43848121eee16dd39d8294ec2bf25b"><code>7acb32f</code></a>
feat: full asset sync (<a
href="https://redirect.github.com/ksxgithub/github-actions-deploy-aur/issues/52">#52</a>)</li>
<li>See full diff in <a
href="https://github.com/ksxgithub/github-actions-deploy-aur/compare/da03e160361ce01bf087e790b6ffd196d7dccff7...084b0d9b15415bf9cdb65d44dad1efe37a354050">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=KSXGitHub/github-actions-deploy-aur&package-manager=github_actions&previous-version=4.1.3&new-version=4.2.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 15:53:38 +00:00
dependabot[bot] 2f61a8f4ff build(deps): bump actions/upload-artifact from 7.0.0 to 7.0.1 (#7375)
Bumps
[actions/upload-artifact](https://github.com/actions/upload-artifact)
from 7.0.0 to 7.0.1.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/upload-artifact/releases">actions/upload-artifact's
releases</a>.</em></p>
<blockquote>
<h2>v7.0.1</h2>
<h2>What's Changed</h2>
<ul>
<li>Update the readme with direct upload details by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/795">actions/upload-artifact#795</a></li>
<li>Readme: bump all the example versions to v7 by <a
href="https://github.com/danwkennedy"><code>@​danwkennedy</code></a> in
<a
href="https://redirect.github.com/actions/upload-artifact/pull/796">actions/upload-artifact#796</a></li>
<li>Include changes in typespec/ts-http-runtime 0.3.5 by <a
href="https://github.com/yacaovsnc"><code>@​yacaovsnc</code></a> in <a
href="https://redirect.github.com/actions/upload-artifact/pull/797">actions/upload-artifact#797</a></li>
</ul>
<p><strong>Full Changelog</strong>: <a
href="https://github.com/actions/upload-artifact/compare/v7...v7.0.1">https://github.com/actions/upload-artifact/compare/v7...v7.0.1</a></p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/actions/upload-artifact/compare/v7...043fb46d1a93c77aae656e7c1c64a875d1fc6a0a">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-08-12 15:45:37 +00:00
Samuel SaezandClaude Opus 5 ba06a1dc1b fix(desktop): stop Task from eating the PowerShell variables in jlink… (#7258)
…:runtime

The Windows branch of jlink:runtime clears the read-only attribute jlink
leaves on the bundled JRE, so Tauri can overwrite the staged copies. It
never worked.

Task does not hand the command to cmd.exe; it runs it through its own
POSIX shell, which expands `$_` and `$false` as shell variables. Neither
is set, so both became empty and PowerShell was asked to run

    ForEach-Object { .IsReadOnly = }

which errors on every file. Verified against a directory of read-only
files: the double-quoted form leaves 3 of 3 still read-only and exits
non-zero, the single-quoted form clears all 3 and exits 0.

Single quotes stop the expansion. Also add -File: without it
Get-ChildItem yields directories too, and DirectoryInfo has no
IsReadOnly property, so those iterations would fail even once the
variables survive.

The POSIX branch above is unaffected - chmod needs no variables.

# Description of Changes

<!--
Please provide a summary of the changes, including:

- What was changed
- Why the change was made
- Any challenges encountered

Closes #(issue_number)
-->

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-12 15:40:22 +00:00
staticdevandReece Browne c3802ac1ef fix(frontend): remove Automate operation timeout (#7082)
# Description of Changes

Remove the hard-coded five-minute client-side timeout from Automate
operations.

- Remove `AUTOMATION_CONSTANTS.OPERATION_TIMEOUT`.
- Let normal Automate requests use the API client's default no-timeout
behavior, matching regular tool execution.
- Preserve an explicitly supplied `AutomationProcessingOptions.timeout`
without applying a finite default.
- Add regression coverage verifying that Automate does not send a
client-side timeout by default.

Large PDF operations such as compression can legitimately take more than
five minutes. Previously, Axios aborted the frontend request after
300,000 ms even when the server and reverse proxy allowed the operation
to continue. The backend could continue processing while the frontend
discarded the result.

No significant implementation challenges were encountered.

Closes #7081 

## Testing

The following frontend checks passed:

- Proprietary frontend TypeScript typecheck
- ESLint with zero warnings
- Circular dependency check
- Theme colour lint
- Prettier formatting check
- Focused `automationExecutor.test.ts` regression test
- Complete Vitest suite: 165 test files and 1,354 tests passed

---

## Checklist

### General

- [x] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [x] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(not applicable)
- [x] I have performed a self-review of my own code
- [x] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(not applicable; no user-facing configuration changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(not applicable)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)
(not applicable)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(not applicable; no visual changes)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
2026-08-12 15:30:53 +00:00
Ludy fdc1682863 chore: relocate frontend static assets and improve backend-only resource handling (#7171)
# Description of Changes

Move favicons, SVGs, and images from backend resources to frontend
public directory. Update Gradle build to copy sample files in
backend-only mode. Fix og-metadata image references for shared-sign
feature. Update .gitignore to exclude generated/frontend-managed assets
from backend.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:27:20 +00:00
Ludy 10ed0f9f73 deps: upgrade Stripe frontend libraries to latest compatible versions (#7110)
# Description of Changes

## What was changed
- Upgraded `@stripe/react-stripe-js` from `4.0.2` to `6.8.0`.
- Upgraded `@stripe/stripe-js` from `7.9.0` to `9.10.0`.
- Updated `package-lock.json` to reflect the new dependency versions.

## Why the change was made
- Keep Stripe frontend dependencies up to date with the latest supported
releases.
- Align `@stripe/react-stripe-js` with its required peer dependency on
`@stripe/stripe-js` `>=9.5.0 <10.0.0`.
- Benefit from the latest bug fixes, improvements, and compatibility
updates provided by the Stripe SDKs.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:18:41 +00:00
Ludy a28a950aa4 feat: modernize codebase using Java switch expressions and List#getFirst/getLast APIs (#6334)
# Description of Changes

This PR introduces a broad modernization of the codebase by adopting
newer Java language features and improving code readability and
maintainability.

## What was changed

- Replaced traditional `switch` statements with modern switch
expressions (`case ->`) across multiple classes.
- Replaced usages of `List#get(0)` and `List#get(size - 1)` with
`getFirst()` and `getLast()` respectively.
- Simplified conditional logic using pattern matching (e.g.,
`instanceof` and switch pattern matching).
- Refactored various utility and controller classes to reduce
boilerplate and improve clarity.
- Removed unused or redundant code (e.g., `parseClientFileIds` method in
`MergeController`).
- Improved type safety (e.g., using `Class::isInstance` instead of
`instanceof` checks in streams).
- Cleaned up Spring annotations by removing unnecessary `@Autowired`
where constructor injection is already used.
- Added a new test (`UIDataControllerTest`) to ensure correct handling
of identical JSON configs with different filenames.
- Minor formatting and style fixes (e.g., Spotless formatting
adjustment).

## Why the change was made

- To align the codebase with modern Java standards (Java 17+ features).
- To improve readability and maintainability by reducing verbosity.
- To eliminate common indexing patterns that are more error-prone.
- To standardize coding style across the project.
- To improve test coverage for edge cases discovered during refactoring.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.

---------

Signed-off-by: Ludy87 <Ludy87@users.noreply.github.com>
2026-08-12 15:13:29 +00:00
Ludy 84804d7ce3 fix(frontend): allow null users in mock API options (#7357)
# Description of Changes

Updated the frontend test helper type to allow `user: null` in
`MockAppApiOptions`.

This aligns the TypeScript definition with the existing test usage for
unauthenticated scenarios and resolves the frontend typecheck error in
`login-agreement-modal.spec.ts`.

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:12:07 +00:00
Ludy 6d5d651432 chore(vscode): expand Java resource filters for workspace indexing (#7404)
Expand and reorganize java.project.resourceFilters in
.vscode/settings.json. Adds cache/build/bin/logs/storage exclusions
(app/core, app/common, app/proprietary, app/saas), frontend/engine,
docker and other generated/temp paths; removes duplicates and reorders
entries to reduce Java indexing of generated files and improve IDE
performance.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:10:04 +00:00
dependabot[bot] be684942b7 build(deps): bump dompurify from 3.4.12 to 3.4.13 in /frontend (#7367)
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.12 to
3.4.13.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cure53/DOMPurify/releases">dompurify's
releases</a>.</em></p>
<blockquote>
<h2>DOMPurify 3.4.13</h2>
<ul>
<li>Fixed an issue with hook removal during <code>IN_PLACE</code>
sanitization, thanks <a
href="https://github.com/koyokr"><code>@​koyokr</code></a></li>
<li>Fixed an issue with hooks potentially bypassing the clone guard,
thanks <a
href="https://github.com/AkshayjainG"><code>@​AkshayjainG</code></a></li>
<li>Fixed an issue with DOM clobbering via <code>ownerDocument</code>
during <code>IN_PLACE</code>, thanks <a
href="https://github.com/AkshayjainG"><code>@​AkshayjainG</code></a></li>
<li>Bumped several dependencies where possible</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/cure53/DOMPurify/commit/3067f774676975de12306effd6db6ad7a9a8c17f"><code>3067f77</code></a>
release: 3.4.13 (<a
href="https://redirect.github.com/cure53/DOMPurify/issues/1562">#1562</a>)</li>
<li>See full diff in <a
href="https://github.com/cure53/DOMPurify/compare/3.4.12...3.4.13">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dompurify&package-manager=npm_and_yarn&previous-version=3.4.12&new-version=3.4.13)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/Stirling-Tools/Stirling-PDF/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-08-12 15:08:59 +00:00
Ludy 2b6bb71186 fix(build): ensure ./gradlew clean removes stale SaaS build artifacts (#7434)
### Motivation
- `./gradlew clean` on the default/proprietary flavor did not remove
`app/saas/build` left behind by earlier SaaS builds, causing stale
artifacts to persist across flavors.

### Description
- Add a `clean` hook in `build.gradle` that deletes `app/saas/build`
(`tasks.named('clean') { delete
layout.projectDirectory.dir('app/saas/build') }`) so the root `clean`
always removes SaaS artifacts even when `:saas` is not included.

### Testing
- Created `app/saas/build/clean-regression-marker`, ran `./gradlew
clean`, and verified the `app/saas/build` directory was deleted
(success).
- Ran `./gradlew spotlessCheck test`; Spotless checks passed but the
full test run reported environment-dependent unit test failures
unrelated to this change (8 failures), and `task backend:check` could
not be executed because the `task` CLI is not available in the
environment.

------
[Codex
Task](https://chatgpt.com/codex/cloud/tasks/task_e_6a7b0a9aac888325aa78e05c953355dc)
2026-08-12 15:06:03 +00:00
Ludy c47fb9032e chore(dependabot): group Docker updates and enable uv dependency management (#7403)
Organize Docker image updates into logical groups (ubuntu,
eclipse-temurin, gradle, uv) to reduce dependency update noise. Add uv
package manager support for the Python engine with weekly update
schedule.


---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:05:59 +00:00
stirlingbot[bot] 8d0414acf3 Update Backend 3rd Party Licenses (#7401)
Auto-generated by stirlingbot[bot]

This PR updates the backend license report based on dependency changes.

Signed-off-by: stirlingbot[bot] <stirlingbot[bot]@users.noreply.github.com>
Co-authored-by: stirlingbot[bot] <195170888+stirlingbot[bot]@users.noreply.github.com>
2026-08-12 15:05:45 +00:00
Ludy 815c1db567 Group Storybook packages in Dependabot config (#7432)
### Motivation
- Group Storybook packages so Dependabot updates `storybook` and all
`@storybook/*` packages together and reduce churn from many separate
updates.

### Description
- Add a `storybook` group to the `npm` groups in
`.github/dependabot.yml` with patterns `storybook` and `@storybook/*`.
- No other configuration changes were made.

### Testing
- Validated the new group patterns with `ruby -e 'require "yaml"; ...'`,
which confirmed the `storybook` group and patterns and returned success.
- Ran `git diff --check` to ensure there are no whitespace or patch
errors, which succeeded.
- Inspected the updated file with `nl -ba .github/dependabot.yml | sed
-n '100,120p'` to confirm the inserted lines are present.

------
[Codex
Task](https://chatgpt.com/codex/cloud/tasks/task_e_6a7afe74fe7083259900e8d9018f595c)
2026-08-12 15:04:24 +00:00
Ludy ea8fc5ae52 chore: configure repository editor settings (#7411)
## Description

Moves the repository-wide VS Code settings out of PR #7386 into a
dedicated pull request.

## Changes

- Configure Ruff for Python files and point it at the engine project
configuration.
- Add repository-root paths for stylelint and CSS/SCSS/Less at-rule
handling.
- Enable Oxc safe fixes on save for JavaScript and TypeScript files.
- Add Even Better TOML formatter settings for the repository's TOML
files.

This keeps PR #7386 focused on the Python tooling migration while
preserving the editor configuration as a separately reviewable change.

## Validation

- Exact file diff matches the settings change from PR #7386.
- `task pre-commit:whitespace` was attempted; it is currently blocked by
pre-existing whitespace issues in
`engine/src/stirling/models/tool_io.py` and
`engine/src/stirling/models/tool_models.py`.
2026-08-12 15:04:12 +00:00
Anthony Stirling 7a748d4ad2 Add stored supporting files for pipeline steps (#7146)
# Description of Changes

Backend only change for pipelines to support files (ie pipeline to sign
all files with the same cert file etc)

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 15:02:10 +00:00
Anthony Stirling dc75d399bc Editor url role routing (#7409)
# Description of Changes

- `/` is now a router, not a page: signed-in users go to the processor
or the editor by role. The editor lives at `/editor`.
- `/editor` never routes — always the editor, so processor users have a
URL that won't bounce them.
- Core and desktop keep the editor at `/` (no processor, nothing to
route between).
- `/editor` signed out → `/login` → back to `/editor` after signing in.
- Signed-out visitors aren't redirected: `/` renders the app and Landing
owns it (login page / SaaS inline sign-in / backend-down screen).
- `RootGate` wraps the app instead of being its own route, so nothing
boots on the way to the processor and nothing remounts on the way to the
editor.
- Login resolves its own destination instead of bouncing through `/`.
- Replaces the old once-per-login `LoginLandingRedirect` +
sessionStorage flag. Landing flag and Settings preference unchanged.
- Separate commit: theme-lint crashed on files deleted in the working
tree (`git ls-files` is the index view). Any branch deleting a source
file hit it.
- Sign-out untouched. Tool routes stay top-level, so no deep links or
SEO break.


Future PR to allow users to configure their own routing from / for their
profile

---

## Checklist

### General

- [ ] I have read the [Contribution
Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md)
- [ ] I have read the [Stirling-PDF Developer
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md)
(if applicable)
- [ ] I have read the [How to add new languages to
Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md)
(if applicable)
- [ ] I have performed a self-review of my own code
- [ ] My changes generate no new warnings

### Documentation

- [ ] I have updated relevant docs on [Stirling-PDF's doc
repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/)
(if functionality has heavily changed)
- [ ] I have read the section [Add New Translation
Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags)
(for new translation tags only)

### Translations (if applicable)

- [ ] I ran
[`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md)

### UI Changes (if applicable)

- [ ] Screenshots or videos demonstrating the UI changes are attached
(e.g., as comments or direct attachments in the PR)

### Testing (if applicable)

- [ ] I have run `task check` to verify linters, typechecks, and tests
pass
- [ ] I have tested my changes locally. Refer to the [Testing
Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing)
for more details.
2026-08-12 10:09:18 +00:00
Reece Browne c2e8c3fa71 a11y: empty the grandfathered Storybook baseline (1,058 → 0) (#7309)
## What

Empties the light Storybook accessibility baseline — **1,058
grandfathered violations across 846 stories → 0** — so a new violation
fails the gate instead of being silently absorbed. Also burns the dark
baseline **812 → 56**; every entry left is one `main` already
grandfathers.

## The defect, repeated everywhere

A colour picked as a **fill**, chosen to carry a white label at 3:1,
reused as **text**, where the floor is 4.5:1. It recurred through status
accents, filled buttons, form labels, Mantine's light and outline
variants, CSS declarations, inline styles and the generated accent ramp.

Three systemic causes account for most of it:

- **Mantine's semantic slots were never bound.** `-text`, `-outline`,
`-light-color`, `-filled` and `-dimmed` all default to the hue's solid
fill. Both resolvers now pin them to the accessible ink for the active
scheme.
- **The tint ladder was compressed.** `--color-<hue>-50/100/200` pointed
at saturated 400-level primitives, so every "tint" background rendered
as a fill.
- **Text was faded with `opacity`**, pushing already-muted copy below
the floor. Each site now recedes via ink or surface, which is what
conveyed the state anyway.

## Dark mode

The colour resolver's dark half was empty, so dark fell through to
Mantine's stock palette — and fixing the naming violations unmasked the
contrast sitting underneath them. Both schemes now share one slot map,
since most slots are written in tokens that already flip.

The dark-only fixes: `--c-text-subtle` (3.0:1, used in 478 places), the
error and section-label inks, and the accent ramp's text step — which
light reaches by mixing toward black and dark has to reach by mixing
toward white.

## Also

- New `--c-*-solid` tokens for fills that must carry a white label,
distinct from the `--c-<tone>` values used for surfaces, borders and
icons.
- A `data-user-content-preview` opt-out for nodes rendering a facsimile
of the user's own document — WCAG governs the interface, not content
authored through it.

## Verification

- `task frontend:check:all` — green.
- Changed-set gate, both schemes, after the final rebase: **366 stories,
0 regressions**.
- Full sweep at the prior base — light **1,447 stories / 0 violations**,
dark **1,448 / 0 regressions**. The dark re-record was confirmed
key-by-key to be a strict subset of `main`'s, so nothing new is
grandfathered.

Roughly 28% of what this clears is naming and structure (`button-name`,
`label`, `aria-*`) and has no visual signature; the rest is contrast.
2026-08-12 09:06:05 +00:00
2126 changed files with 175903 additions and 53075 deletions
+19
View File
@@ -0,0 +1,19 @@
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "node",
"args": [
"${CLAUDE_PROJECT_DIR}/scripts/lint/comment-lint-hook.mjs"
],
"timeout": 60
}
]
}
]
}
}
+8
View File
@@ -96,6 +96,14 @@ configs/
__pycache__/
**/__pycache__/
# Python virtualenvs. Large, platform-specific, and their symlinks break the build.
.venv/
**/.venv/
venv/
**/venv/
*.egg-info/
**/*.egg-info/
# Local env
.env
.env.*
+2 -13
View File
@@ -22,26 +22,15 @@ indent_size = 4
[*.html]
indent_size = 2
insert_final_newline = false
trim_trailing_whitespace = false
[{*.js,*.jsx,*.mjs,*.ts,*.tsx}]
[{*.js,*.jsx,*.mjs,*.ts,*.tsx,*.mts}]
indent_size = 2
[*.css]
# CSS files typically use an indent size of 2 spaces for better readability and alignment with community standards.
indent_size = 2
[*.{yml,yaml}]
# YAML files use an indent size of 2 spaces to maintain consistency with common YAML formatting practices.
indent_size = 2
insert_final_newline = false
trim_trailing_whitespace = false
[*.json]
# JSON files use an indent size of 2 spaces, which is the standard for JSON formatting.
indent_size = 2
[*.jsonc]
# JSONC (JSON with comments) files also follow the standard JSON formatting with an indent size of 2 spaces.
[*.{json,jsonc}]
indent_size = 2
+7
View File
@@ -98,6 +98,13 @@ body:
description: |
If you have any additional information that might help us understand and resolve the issue, provide it here.
- type: textarea
id: sample-files
attributes:
label: Sample Files
description: |
If possible, attach the PDF or other input files needed to reproduce the issue. Remove any sensitive information before sharing.
- type: markdown
attributes:
value: |
+7
View File
@@ -67,6 +67,13 @@ body:
description: |
If you have any additional information, comments, or resources you think would support or be relevant to your feature request, include them here.
- type: textarea
id: sample-files
attributes:
label: Example Files
description: |
If the feature request depends on specific PDFs or other example files, attach them here when available. Remove any sensitive information before sharing.
- type: checkboxes
id: search-confirmation
attributes:
+1 -1
View File
@@ -1,6 +1,6 @@
# Maintainer: Stirling PDF Inc <contact@stirlingpdf.com>
pkgname=stirling-pdf-desktop
pkgver=2.14.2
pkgver=2.14.3
pkgrel=1
pkgdesc="Locally hosted, web-based PDF manipulation tool (Tauri desktop app, official Stirling PDF Inc build)"
arch=('x86_64')
+1 -1
View File
@@ -1,6 +1,6 @@
# Maintainer: Stirling PDF Inc <contact@stirlingpdf.com>
pkgname=stirling-pdf-server-bin
pkgver=2.14.2
pkgver=2.14.3
pkgrel=1
pkgdesc="Locally hosted, web-based PDF manipulation tool (server JAR, prebuilt)"
arch=('any')
+36 -19
View File
@@ -1,13 +1,15 @@
# CI routing infra. Editing the top-level router (build.yml) or this filter
# config re-runs every area's jobs, so every job-gating filter below includes
# *ci. That makes a change to how jobs are dispatched actually exercise those
# jobs (self-testing), instead of a router edit only matching the project filter.
# CI routing infrastructure. Changes to the top-level router (build.yml) or
# this filter configuration rerun every area's jobs. Every job-gating filter
# therefore includes *ci, so routing changes exercise the jobs they affect
# instead of matching only the project filter.
ci: &ci
- .github/workflows/build.yml
- .github/workflows/gradle-cache-prime.yml
- .github/config/.files.yaml
build: &build
- *ci
- buildSrc/**
- build.gradle
- gradle/spotless.gradle
- app/(common|core|proprietary|saas)/build.gradle
@@ -15,6 +17,22 @@ build: &build
- .taskfiles/backend.yml
- .github/workflows/check-licence.yml
# Backend build inputs. This is intentionally broader than `build`: Java and
# backend resource changes must exercise the backend matrix even when Gradle
# build scripts themselves are unchanged.
backend: &backend
- *ci
- *build
- gradle/**
- gradle.properties
- gradlew
- gradlew.bat
- settings.gradle
- app/(common|core|proprietary|saas)/src/(main|test)/java/**
- "app/(common|core|proprietary|saas)/src/(main|test)/resources/**/!(messages_*.properties|*.md)*"
- scripts/db-migration/**
- .github/workflows/backend-build.yml
openapi: &openapi
- *ci
- *build
@@ -24,9 +42,9 @@ openapi: &openapi
docker-base: &docker-base
- docker/base/Dockerfile
# Dockerfiles only (base + embedded + unoserver). Gates the slow multi-arch
# (arm64) leg of the PR docker test build: arm64 is only rebuilt when a
# Dockerfile itself changes, not on every code PR.
# Dockerfiles only (base, embedded, and unoserver). The slow multi-architecture
# (arm64) leg of the PR Docker test build runs only when a Dockerfile changes,
# rather than for every code PR.
dockerfiles: &dockerfiles
- docker/**/Dockerfile*
@@ -68,7 +86,6 @@ project: &project
frontend: &frontend
- *ci
- frontend/**
- .github/workflows/testdriver.yml
- testing/**
- docker/**
- scripts/translations/*.py
@@ -88,8 +105,8 @@ frontend: &frontend
- .github/workflows/e2e-stubbed.yml
- .github/workflows/e2e-live.yml
# Files that affect the Tauri desktop bundle. Gate the multi-OS Tauri build
# job on changes to any of these.
# Files that affect the Tauri desktop bundle. Changes to any of these files
# trigger the multi-OS Tauri build job.
tauri: &tauri
- *ci
- frontend/editor/src-tauri/**
@@ -102,9 +119,9 @@ tauri: &tauri
- Taskfile.yml
- .taskfiles/desktop.yml
# Files that affect the AI engine (Python tool models, fixers, tests). Gate
# the engine validation job on changes to engine sources or to the Java
# tool surfaces it generates models from.
# Files that affect the AI engine, including its Python tool models, fixers,
# and tests. The engine validation job also runs when the Java tool surfaces
# used to generate those models change.
engine: &engine
- *ci
- engine/**
@@ -114,10 +131,10 @@ engine: &engine
- .taskfiles/engine.yml
# Files that can make the committed generated API models (frontend tool API
# types + engine tool models) go stale: the Java tool surfaces they derive from,
# the generators, the generated files themselves (to catch a hand-edit), and the
# tasks that drive generation. Deliberately excludes the broad frontend/docker/
# testing globs, so a CSS-only PR does not boot the backend to rebuild the spec.
# types and engine tool models) stale: their Java sources, generators,
# generated outputs (to catch hand edits), and generation tasks. Broad
# frontend, Docker, and testing globs are intentionally excluded, so a CSS-only
# PR does not start the backend to rebuild the specification.
generated-models: &generated-models
- *ci
- *openapi
@@ -141,8 +158,8 @@ licenses-backend: &licenses-backend
- ".github/workflows/frontend-backend-licenses-update.yml"
- *build
# Files that can affect premium / enterprise behaviour. Gate the enterprise
# Playwright job on changes to any of these on PRs.
# Files that can affect premium or enterprise behaviour. Changes to any of
# these files trigger the enterprise Playwright job for pull requests.
proprietary: &proprietary
- *ci
- app/proprietary/**
+27 -5
View File
@@ -8,9 +8,6 @@ updates:
- package-ecosystem: "gradle" # See documentation for possible values
directories:
- "/" # Location of package manifests
- "/app/common"
- "/app/core"
- "/app/proprietary"
schedule:
interval: "weekly"
cooldown:
@@ -37,6 +34,19 @@ updates:
cooldown:
default-days: 7
rebase-strategy: "auto"
groups:
ubuntu:
patterns:
- "ubuntu"
eclipse-temurin:
patterns:
- "eclipse-temurin"
uv:
patterns:
- "ghcr.io/astral-sh/uv"
gradle:
patterns:
- "gradle"
- package-ecosystem: github-actions
directory: /
@@ -50,6 +60,7 @@ updates:
directories:
- /devTools
- /frontend
- /testing/compose/mcp-client-check
schedule:
interval: "weekly"
cooldown:
@@ -78,6 +89,13 @@ updates:
- "react-dom"
- "@types/react"
- "@types/react-dom"
tanstack:
patterns:
- "@tanstack/*"
typescript:
patterns:
- "typescript"
- "@typescript/*"
vite:
patterns:
- "vite"
@@ -106,6 +124,10 @@ updates:
patterns:
- "@posthog/*"
- "posthog-js"
storybook:
patterns:
- "storybook"
- "@storybook/*"
supabase:
patterns:
- "@supabase/*"
@@ -152,8 +174,8 @@ updates:
- "tokio"
- "tokio-*"
- package-ecosystem: pip
directory: /testing/cucumber
- package-ecosystem: "uv"
directory: "/engine"
schedule:
interval: "weekly"
cooldown:
+1
View File
@@ -67,6 +67,7 @@ labels:
- 'frontend/**'
- 'frontend/.*'
- 'frontend/**/.*'
- '.taskfiles/frontend.yml'
- label: 'Tauri'
files:
+1
View File
@@ -20,6 +20,7 @@ Closes #(issue_number)
- [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable)
- [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable)
- [ ] I have performed a self-review of my own code
- [ ] Every comment I added says something the code does not ([guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/CODE_COMMENTS.md))
- [ ] My changes generate no new warnings
### Documentation
+104 -43
View File
@@ -26,6 +26,10 @@ jobs:
check-pr:
if: (github.event_name == 'pull_request' && github.event.action != 'closed') || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
# Only reads the PR via pulls.get with the default GITHUB_TOKEN.
permissions:
contents: read
pull-requests: read
outputs:
should_deploy: ${{ steps.decide.outputs.should_deploy }}
is_fork: ${{ steps.resolve.outputs.is_fork }}
@@ -35,7 +39,7 @@ jobs:
pr_ref: ${{ steps.resolve.outputs.ref }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -97,6 +101,7 @@ jobs:
echo "allow_fork=${allow_fork:-false}" >> $GITHUB_OUTPUT
deploy-v2-pr:
environment: pr-preview
needs: check-pr
runs-on: ubuntu-latest
if: needs.check-pr.outputs.should_deploy == 'true' && (needs.check-pr.outputs.is_fork == 'false' || needs.check-pr.outputs.allow_fork == 'true')
@@ -107,6 +112,7 @@ jobs:
permissions:
contents: read
issues: write
packages: write
pull-requests: write
env:
# Single source of truth for whether this preview embeds the admin portal:
@@ -115,7 +121,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -125,20 +131,11 @@ jobs:
repository: ${{ github.repository }}
ref: main
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Add deployment started comment
id: deployment-started
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
@@ -180,11 +177,12 @@ jobs:
with:
repository: ${{ needs.check-pr.outputs.pr_repository }}
ref: ${{ needs.check-pr.outputs.pr_ref }}
token: ${{ secrets.GITHUB_TOKEN }}
# untrusted tree is built below - never leave credentials in .git/config
persist-credentials: false
fetch-depth: 0 # Fetch full history for commit hash detection
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Get version number
id: versionNumber
@@ -192,11 +190,16 @@ jobs:
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
- name: Login to GitHub Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Convert repository owner to lowercase
id: repoowner
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
- name: Get commit hash for app
id: commit-hash
@@ -217,10 +220,46 @@ jobs:
echo "app_short=${APP_HASH:0:8}" >> $GITHUB_OUTPUT
fi
# The Stirling account previews connect to. Derived from the ref rather than stored as a URL
# so it cannot drift from the key: a mismatched pair is accepted by the browser and rejected
# by Supabase, surfacing much later as "session expired" on Usage rather than at sign-in.
# Secret only to match Saas-Dev-Deploy.yml, which owns the same value; a project ref is not
# itself sensitive, which is why SAAS_API_BASE_URL next to it is a plain variable.
- name: Resolve Stirling account config
id: saas
env:
PROJECT_REF: ${{ secrets.SAAS_DB_PROJECT_REF }}
API_BASE_OVERRIDE: ${{ vars.SAAS_API_BASE_URL }}
run: |
# Set, this is the one value both halves use: the browser's portal reads and the backend's
# register/entitlement calls have to land on the same SaaS, and nothing checks that they
# do. Unset, only the backend gets a base, from its own compiled-in default.
API_BASE="${API_BASE_OVERRIDE:-https://stirling.com/app}"
echo "backend_base=${API_BASE}" >> "$GITHUB_OUTPUT"
if [ -z "${PROJECT_REF}" ]; then
echo "Not configured for this environment: the preview will build without a Stirling"
echo "account, and the connect dialog will say so. To wire one up, set on the"
echo "pr-preview environment the secrets SAAS_DB_PROJECT_REF and"
echo "SAAS_SUPABASE_PUBLISHABLE_KEY, both from the same Supabase project."
echo "supabase_url=" >> "$GITHUB_OUTPUT"
echo "frontend_base=" >> "$GITHUB_OUTPUT"
else
# Only whether, not which: the ref is a secret here, so Actions masks it out of any
# line it appears in, derived URL included.
echo "Stirling account configured, at ${API_BASE}."
echo "supabase_url=https://${PROJECT_REF}.supabase.co" >> "$GITHUB_OUTPUT"
# Deliberately the override and not API_BASE: the backend's default is a subpath URL
# nobody has confirmed answers /api/v1, and prod CORS does not list preview hostnames,
# so portal reads stay off until someone sets a base they have checked. Empty leaves the
# committed .env default alone, which is the clean "not configured" state.
echo "frontend_base=${API_BASE_OVERRIDE}" >> "$GITHUB_OUTPUT"
fi
- name: Check if image exists
id: check-image
run: |
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }} >/dev/null 2>&1; then
if docker manifest inspect ${IMAGE_BASE}:v2-${{ steps.commit-hash.outputs.app_short }} >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Image already exists, skipping build"
else
@@ -228,6 +267,8 @@ jobs:
echo "Image needs to be built"
fi
env:
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
- name: Build and push V2 image
if: steps.check-image.outputs.exists == 'false'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
@@ -237,18 +278,23 @@ jobs:
push: true
cache-from: type=gha,scope=stirling-pdf-latest
cache-to: type=gha,mode=max,scope=stirling-pdf-latest
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }}
tags: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:v2-${{ steps.commit-hash.outputs.app_short }}
build-args: |
VERSION_TAG=v2-alpha
BUILD_PORTAL=${{ env.BUILD_PORTAL }}
VITE_SUPABASE_URL=${{ steps.saas.outputs.supabase_url }}
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=${{ secrets.SAAS_SUPABASE_PUBLISHABLE_KEY }}
VITE_SAAS_API_URL=${{ steps.saas.outputs.frontend_base }}
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
echo "${NEW_VPS_SSH_KEY}" > ../private.key
sudo chmod 600 ../private.key
env:
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
- name: Deploy V2 to VPS
id: deploy
run: |
@@ -261,7 +307,7 @@ jobs:
services:
stirling-pdf-v2:
container_name: stirling-pdf-v2-pr-${{ needs.check-pr.outputs.pr_number }}
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-${{ steps.commit-hash.outputs.app_short }}
image: ${IMAGE_BASE}:v2-${{ steps.commit-hash.outputs.app_short }}
ports:
- "${V2_PORT}:8080"
volumes:
@@ -272,9 +318,16 @@ jobs:
environment:
DISABLE_ADDITIONAL_FEATURES: "false"
STIRLING_BILLING_ACCOUNT_LINK_ENABLED: "true"
STIRLING_BILLING_ACCOUNT_LINK_SAAS_BASE_URL: "${{ steps.saas.outputs.backend_base }}"
# Off so preview traffic never accrues against a real wallet or trips its cap. The
# 402 gate is separate and stays on, so gating is still testable here.
STIRLING_BILLING_ACCOUNT_LINK_METERING_ENABLED: "false"
# Stated rather than inferred from the request: the callback has to come back to the
# preview hostname, not to the container's own :8080 behind this proxy.
SYSTEM_FRONTENDURL: "https://${V2_PORT}.ssl.stirlingpdf.cloud"
SECURITY_ENABLELOGIN: "true"
SECURITY_INITIALLOGIN_USERNAME: "${{ secrets.TEST_LOGIN_USERNAME }}"
SECURITY_INITIALLOGIN_PASSWORD: "${{ secrets.TEST_LOGIN_PASSWORD }}"
SECURITY_INITIALLOGIN_USERNAME: "${TEST_LOGIN_USERNAME}"
SECURITY_INITIALLOGIN_PASSWORD: "${TEST_LOGIN_PASSWORD}"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: "Stirling-PDF V2 PR#${{ needs.check-pr.outputs.pr_number }}"
UI_HOMEDESCRIPTION: "V2 PR#${{ needs.check-pr.outputs.pr_number }} - Embedded Architecture"
@@ -288,9 +341,9 @@ jobs:
EOF
# Deploy to VPS
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/docker-compose-v2.yml
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${NEW_VPS_USERNAME}@${NEW_VPS_HOST}:/tmp/docker-compose-v2.yml
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << ENDSSH
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << ENDSSH
# Create V2 PR-specific directories
mkdir -p /stirling/V2-PR-${{ needs.check-pr.outputs.pr_number }}/{data,config,logs,storage}
@@ -315,12 +368,19 @@ jobs:
# Set port for output
echo "v2_port=${V2_PORT}" >> $GITHUB_OUTPUT
env:
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
TEST_LOGIN_USERNAME: ${{ secrets.TEST_LOGIN_USERNAME }}
TEST_LOGIN_PASSWORD: ${{ secrets.TEST_LOGIN_PASSWORD }}
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
# ---- Storybook preview (only when this PR touches stories/.storybook) ----
# Runs inside the same approved-contributor-gated deploy job, so it deploys
# under the exact same access rules as the app preview.
- name: Detect Storybook changes
id: sb-changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
with:
list-files: json
filters: |
@@ -339,7 +399,7 @@ jobs:
- name: Install Task for Storybook
if: steps.sb-changes.outputs.storybook == 'true'
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Build and deploy Storybook
id: storybook
@@ -379,8 +439,9 @@ jobs:
env:
SB_URL: ${{ steps.storybook.outputs.url }}
SB_FILES: ${{ steps.sb-changes.outputs.storybook_files }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = ${{ needs.check-pr.outputs.pr_number }};
@@ -401,7 +462,7 @@ jobs:
}
}
const deploymentUrl = `http://${{ secrets.NEW_VPS_HOST }}:${v2Port}`;
const deploymentUrl = `http://${process.env.NEW_VPS_HOST}:${v2Port}`;
// Only mention the portal when this image actually embeds it.
// Use the direct IP URL - the SSL hostname isn't supported yet.
@@ -447,6 +508,10 @@ jobs:
});
cleanup-v2-deployment:
# Tearing a preview down is not a deployment - no deployment object.
environment:
name: pr-preview
deployment: false
if: github.event.action == 'closed'
runs-on: ubuntu-latest
permissions:
@@ -456,26 +521,17 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Clean up V2 deployment comments
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { owner, repo } = context.repo;
const prNumber = ${{ github.event.pull_request.number }};
@@ -504,12 +560,14 @@ jobs:
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
echo "${NEW_VPS_SSH_KEY}" > ../private.key
sudo chmod 600 ../private.key
env:
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
- name: Cleanup V2 deployment
run: |
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << 'ENDSSH'
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << 'ENDSSH'
if [ -d "/stirling/V2-PR-${{ github.event.pull_request.number }}" ]; then
echo "Found V2 PR directory, proceeding with cleanup..."
@@ -542,8 +600,11 @@ jobs:
# Only remove PR-specific containers and directories
ENDSSH
env:
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
- name: Cleanup temporary files
if: always()
run: |
rm -f ../private.key
rm -f ../private.key docker-compose.yml storybook.tgz
continue-on-error: true
@@ -37,7 +37,8 @@ jobs:
check-comment:
runs-on: ubuntu-latest
permissions:
issues: write
contents: read # actions/checkout
issues: write # add reaction to the triggering issue comment
if: |
vars.CI_PROFILE != 'lite' && (
github.event_name == 'workflow_dispatch' ||
@@ -69,22 +70,13 @@ jobs:
enable_prototypes: ${{ steps.check-prototypes-flag.outputs.enable_prototypes }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get PR data
id: get-pr
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
@@ -155,7 +147,7 @@ jobs:
id: add-eyes-reaction
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
console.log(`Adding eyes reaction to comment ID: ${context.payload.comment.id}`);
try {
@@ -174,35 +166,38 @@ jobs:
}
deploy-pr:
environment: pr-preview
needs: check-comment
runs-on: ubuntu-latest
permissions:
issues: write
contents: read # actions/checkout, incl. the PR merge ref
issues: write # reactions, 'pr-deployed' label, deployment URL comment
pull-requests: write
packages: write # push PR image to ghcr.io
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Checkout PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: refs/pull/${{ needs.check-comment.outputs.pr_number }}/merge
token: ${{ steps.setup-bot.outputs.token }}
# untrusted tree gets built below - never leave credentials in .git/config
persist-credentials: false
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-deploy-pr-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
@@ -210,19 +205,8 @@ jobs:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Run Gradle Command
run: |
if [ "${{ needs.check-comment.outputs.disable_security }}" == "true" ]; then
@@ -238,13 +222,18 @@ jobs:
STIRLING_PDF_DESKTOP_UI: false
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Login to Docker Hub
- name: Login to GitHub Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Convert repository owner to lowercase
id: repoowner
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
- name: Build and push PR-specific image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
@@ -254,7 +243,7 @@ jobs:
push: true
cache-from: type=gha,scope=stirling-pdf-latest
cache-to: type=gha,mode=max,scope=stirling-pdf-latest
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ needs.check-comment.outputs.pr_number }}
tags: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:pr-${{ needs.check-comment.outputs.pr_number }}
build-args: |
VERSION_TAG=alpha
PROTOTYPES_BUILD=${{ needs.check-comment.outputs.enable_prototypes }}
@@ -269,15 +258,17 @@ jobs:
push: true
cache-from: type=gha,scope=stirling-pdf-engine
cache-to: type=gha,mode=max,scope=stirling-pdf-engine
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:engine-pr-${{ needs.check-comment.outputs.pr_number }}
tags: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:engine-pr-${{ needs.check-comment.outputs.pr_number }}
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
echo "${NEW_VPS_SSH_KEY}" > ../private.key
sudo chmod 600 ../private.key
env:
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
- name: Deploy to VPS
id: deploy
run: |
@@ -295,11 +286,11 @@ jobs:
# Set pro/enterprise settings (enterprise implies pro)
if [ "${{ needs.check-comment.outputs.enable_enterprise }}" == "true" ]; then
PREMIUM_ENABLED="true"
PREMIUM_KEY="${{ secrets.ENTERPRISE_KEY }}"
PREMIUM_KEY="${ENTERPRISE_KEY}"
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
elif [ "${{ needs.check-comment.outputs.enable_pro }}" == "true" ]; then
PREMIUM_ENABLED="true"
PREMIUM_KEY="${{ secrets.PREMIUM_KEY }}"
PREMIUM_KEY="${PRO_KEY}"
PREMIUM_PROFEATURES_AUDIT_ENABLED="true"
else
PREMIUM_ENABLED="false"
@@ -309,7 +300,6 @@ jobs:
ENABLE_PROTOTYPES="${{ needs.check-comment.outputs.enable_prototypes }}"
PR_NUMBER="${{ needs.check-comment.outputs.pr_number }}"
DOCKER_USER="${{ secrets.DOCKER_HUB_USERNAME }}"
# Build engine env vars for backend (only set when prototypes enabled)
if [ "$ENABLE_PROTOTYPES" == "true" ]; then
@@ -319,9 +309,9 @@ jobs:
ENGINE_SERVICE="
stirling-pdf-engine:
container_name: stirling-pdf-engine-pr-${PR_NUMBER}
image: ${DOCKER_USER}/test:engine-pr-${PR_NUMBER}
image: ${IMAGE_BASE}:engine-pr-${PR_NUMBER}
environment:
ANTHROPIC_API_KEY: \"${{ secrets.ANTHROPIC_API_KEY }}\"
ANTHROPIC_API_KEY: \"${ANTHROPIC_API_KEY}\"
networks:
- pr-network
restart: on-failure:5"
@@ -344,7 +334,7 @@ jobs:
services:
stirling-pdf:
container_name: stirling-pdf-pr-${PR_NUMBER}
image: ${DOCKER_USER}/test:pr-${PR_NUMBER}
image: ${IMAGE_BASE}:pr-${PR_NUMBER}
ports:
- "${PR_NUMBER}:8080"
volumes:
@@ -368,9 +358,9 @@ jobs:
EOF
# Then copy the file and execute commands
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/docker-compose.yml
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${NEW_VPS_USERNAME}@${NEW_VPS_HOST}:/tmp/docker-compose.yml
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << ENDSSH
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << ENDSSH
# Create PR-specific directories
mkdir -p /stirling/PR-${PR_NUMBER}/{data,config,logs}
@@ -386,11 +376,19 @@ jobs:
# Set output for use in PR comment
echo "security_status=${SECURITY_STATUS}" >> $GITHUB_ENV
env:
ENTERPRISE_KEY: ${{ secrets.ENTERPRISE_KEY }}
# named PRO_KEY, not PREMIUM_KEY, so the shell var it feeds is not self-referential
PRO_KEY: ${{ secrets.PREMIUM_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
- name: Add success reaction to comment
if: success() && github.event_name == 'issue_comment'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
console.log(`Adding rocket reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
try {
@@ -425,7 +423,7 @@ jobs:
if: failure() && github.event_name == 'issue_comment'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
console.log(`Adding -1 reaction to comment ID: ${{ needs.check-comment.outputs.comment_id }}`);
try {
@@ -444,15 +442,17 @@ jobs:
- name: Post deployment URL to PR
if: success()
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { GITHUB_REPOSITORY } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
const prNumber = ${{ needs.check-comment.outputs.pr_number }};
const securityStatus = process.env.security_status || "Security Disabled";
const deploymentUrl = `http://${{ secrets.NEW_VPS_HOST }}:${prNumber}`;
const deploymentUrl = `http://${process.env.NEW_VPS_HOST}:${prNumber}`;
const commentBody = `## 🚀 PR Test Deployment\n\n` +
`Your PR has been deployed for testing!\n\n` +
`🔗 **Test URL:** [${deploymentUrl}](${deploymentUrl})\n` +
@@ -477,26 +477,22 @@ jobs:
handle-label-commands:
if: ${{ github.event.issue.pull_request != null }}
runs-on: ubuntu-latest
permissions:
contents: read # actions/checkout, reads repo_devs.json and labels.yml
issues: write # add/remove labels, delete the command comment
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Check out the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Apply label commands
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const fs = require('fs');
const path = require('path');
+23 -20
View File
@@ -7,41 +7,33 @@ on:
permissions:
contents: read
env:
SERVER_IP: ${{ secrets.NEW_VPS_IP }} # Add this to your GitHub secrets
CLEANUP_PERFORMED: "false" # Add flag to track if cleanup occurred
jobs:
cleanup:
# Tearing a preview down is not a deployment - no deployment object.
environment:
name: pr-preview
deployment: false
if: github.event.action == 'closed'
runs-on: ubuntu-latest
permissions:
contents: read # actions/checkout
pull-requests: write
issues: write
issues: write # list/remove labels, list/delete comments
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout PR
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Remove 'pr-deployed' label if present
id: remove-label-comment
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const prNumber = ${{ github.event.pull_request.number }};
const owner = context.repo.owner;
@@ -100,14 +92,22 @@ jobs:
if: steps.remove-label-comment.outputs.present == 'true'
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
echo "${NEW_VPS_SSH_KEY}" > ../private.key
sudo chmod 600 ../private.key
env:
NEW_VPS_SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
- name: Convert repository owner to lowercase
id: repoowner
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
- name: Cleanup PR deployment
if: steps.remove-label-comment.outputs.present == 'true'
id: cleanup
# ENDSSH heredoc is quoted, so its body is sent literally: secrets inside it
# must stay as GitHub expressions, a shell var would be empty on the remote host.
run: |
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << 'ENDSSH'
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -T ${NEW_VPS_USERNAME}@${NEW_VPS_HOST} << 'ENDSSH'
if [ -d "/stirling/PR-${{ github.event.pull_request.number }}" ]; then
echo "Found PR directory, proceeding with cleanup..."
@@ -122,8 +122,8 @@ jobs:
rm -rf /stirling/PR-${{ github.event.pull_request.number }}
# Remove the Docker images
docker rmi --no-prune ${{ secrets.DOCKER_HUB_USERNAME }}/test:pr-${{ github.event.pull_request.number }} || true
docker rmi --no-prune ${{ secrets.DOCKER_HUB_USERNAME }}/test:engine-pr-${{ github.event.pull_request.number }} || true
docker rmi --no-prune ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:pr-${{ github.event.pull_request.number }} || true
docker rmi --no-prune ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:engine-pr-${{ github.event.pull_request.number }} || true
echo "PERFORMED_CLEANUP"
else
@@ -131,6 +131,9 @@ jobs:
echo "NO_CLEANUP_NEEDED"
fi
ENDSSH
env:
NEW_VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
NEW_VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
- name: Cleanup temporary files
if: always()
+246
View File
@@ -0,0 +1,246 @@
name: Auto SaaS Dev Deployment
on:
push:
branches:
- saas-prod
workflow_dispatch:
permissions:
contents: read
env:
FRONTEND_PORT: "901"
BACKEND_PORT: "902"
DEPLOY_DIR: /stirling/SAAS-DEV
jobs:
deploy-saas-dev:
runs-on: ubuntu-latest
environment: saas-dev
concurrency:
group: saas-dev-deploy
cancel-in-progress: true
permissions:
contents: read
packages: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Check SaaS configuration
id: config
env:
PROJECT_REF: ${{ secrets.SAAS_DB_PROJECT_REF }}
run: |
echo "supabase_url=https://${PROJECT_REF}.supabase.co" >> "$GITHUB_OUTPUT"
echo "meter_endpoint=https://${PROJECT_REF}.supabase.co/functions/v1/meter-payg-units" >> "$GITHUB_OUTPUT"
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Login to GitHub Container Registry
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
- name: Convert repository owner to lowercase
id: repoowner
run: echo "lowercase=$(echo ${{ github.repository_owner }} | awk '{print tolower($0)}')" >> $GITHUB_OUTPUT
- name: Get commit hash
id: commit-hash
run: echo "app_short=$(git rev-parse --short=8 HEAD)" >> $GITHUB_OUTPUT
- name: Build and push backend image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ./docker/backend/Dockerfile
push: true
cache-from: type=gha,scope=stirling-saas-backend
cache-to: type=gha,mode=max,scope=stirling-saas-backend
tags: |
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-backend-${{ steps.commit-hash.outputs.app_short }}
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-backend-latest
build-args: |
VERSION_TAG=v2-alpha
STIRLING_FLAVOR=saas
platforms: linux/amd64
- name: Build and push frontend image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ./docker/frontend/Dockerfile
push: true
cache-from: type=gha,scope=stirling-saas-frontend
cache-to: type=gha,mode=max,scope=stirling-saas-frontend
tags: |
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-frontend-${{ steps.commit-hash.outputs.app_short }}
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-frontend-latest
build-args: |
VERSION_TAG=v2-alpha
STIRLING_FLAVOR=saas
VITE_BUILD_MODE=development
VITE_SUPABASE_URL=${{ steps.config.outputs.supabase_url }}
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=${{ secrets.SAAS_SUPABASE_PUBLISHABLE_KEY }}
platforms: linux/amd64
- name: Build and push AI engine image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ./engine/Dockerfile
push: true
cache-from: type=gha,scope=stirling-saas-engine
cache-to: type=gha,mode=max,scope=stirling-saas-engine
tags: |
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-engine-${{ steps.commit-hash.outputs.app_short }}
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test:saas-engine-latest
platforms: linux/amd64
- name: Set up SSH
env:
SSH_KEY: ${{ secrets.NEW_VPS_SSH_KEY }}
run: |
mkdir -p ~/.ssh/
echo "$SSH_KEY" > ../private.key
sudo chmod 600 ../private.key
- name: Deploy to VPS
env:
IMAGE_BASE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf-test
IMAGE_TAG: ${{ steps.commit-hash.outputs.app_short }}
GHCR_USER: ${{ github.actor }}
GHCR_TOKEN: ${{ github.token }}
VPS_USERNAME: ${{ secrets.NEW_VPS_USERNAME }}
VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
SAAS_DB_URL: ${{ secrets.SAAS_DB_URL }}
SAAS_DB_USERNAME: ${{ secrets.SAAS_DB_USERNAME || 'postgres' }}
SAAS_DB_PASSWORD: ${{ secrets.SAAS_DB_PASSWORD }}
SAAS_DB_PROJECT_REF: ${{ secrets.SAAS_DB_PROJECT_REF }}
SUPABASE_EDGE_FUNCTION_SECRET: ${{ secrets.SUPABASE_EDGE_FUNCTION_SECRET }}
PAYG_METER_ENDPOINT: ${{ steps.config.outputs.meter_endpoint }}
STIRLING_KEYGEN_ENABLED: ${{ secrets.KEYGEN_ACCOUNT_ID != '' && secrets.KEYGEN_API_TOKEN != '' && secrets.KEYGEN_POLICY_ID != '' }}
KEYGEN_ACCOUNT_ID: ${{ secrets.KEYGEN_ACCOUNT_ID }}
KEYGEN_API_TOKEN: ${{ secrets.KEYGEN_API_TOKEN }}
KEYGEN_POLICY_ID: ${{ secrets.KEYGEN_POLICY_ID }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
VOYAGE_API_KEY: ${{ secrets.VOYAGE_API_KEY }}
run: |
set -euo pipefail
BASE_URL="http://${VPS_HOST}:${FRONTEND_PORT}"
yaml() {
printf "'%s'" "$(printf '%s' "$1" | sed -e "s/'/''/g" -e 's/\$/$$/g')"
}
ENGINE_SECRET="$(openssl rand -hex 32)"
AI_BACKEND_VARS="
SYSTEM_AIENGINE_ENABLED: \"true\"
SYSTEM_AIENGINE_URL: \"http://saas-engine:5001\"
APP_AI_SERVICEBASEURL: \"http://saas-engine:5001\"
STIRLING_ENGINE_SHARED_SECRET: $(yaml "$ENGINE_SECRET")"
AI_SERVICE="
saas-engine:
container_name: stirling-saas-dev-engine
image: ${IMAGE_BASE}:saas-engine-${IMAGE_TAG}
environment:
ANTHROPIC_API_KEY: $(yaml "$ANTHROPIC_API_KEY")
VOYAGE_API_KEY: $(yaml "$VOYAGE_API_KEY")
STIRLING_ENGINE_SHARED_SECRET: $(yaml "$ENGINE_SECRET")
restart: on-failure:5"
cat > docker-compose.yml << EOF
version: '3.3'
services:
saas-backend:
container_name: stirling-saas-dev-backend
image: ${IMAGE_BASE}:saas-backend-${IMAGE_TAG}
ports:
- "${BACKEND_PORT}:8080"
volumes:
- ${DEPLOY_DIR}/config:/configs:rw
- ${DEPLOY_DIR}/logs:/logs:rw
- ${DEPLOY_DIR}/storage:/storage:rw
environment:
SPRING_PROFILES_ACTIVE: "saas"
DISABLE_ADDITIONAL_FEATURES: "false"
SAAS_DB_URL: $(yaml "$SAAS_DB_URL")
SAAS_DB_USERNAME: $(yaml "$SAAS_DB_USERNAME")
SAAS_DB_PASSWORD: $(yaml "$SAAS_DB_PASSWORD")
SAAS_DB_PROJECT_REF: $(yaml "$SAAS_DB_PROJECT_REF")
SUPABASE_EDGE_FUNCTION_SECRET: $(yaml "$SUPABASE_EDGE_FUNCTION_SECRET")
PAYG_METER_ENDPOINT: $(yaml "$PAYG_METER_ENDPOINT")
STIRLING_KEYGEN_ENABLED: $(yaml "$STIRLING_KEYGEN_ENABLED")
KEYGEN_ACCOUNT_ID: $(yaml "$KEYGEN_ACCOUNT_ID")
KEYGEN_API_TOKEN: $(yaml "$KEYGEN_API_TOKEN")
KEYGEN_POLICY_ID: $(yaml "$KEYGEN_POLICY_ID")
SYSTEM_DEFAULTLOCALE: en-US
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
SWAGGER_SERVER_URL: "${BASE_URL}"
baseUrl: "${BASE_URL}"${AI_BACKEND_VARS}
restart: on-failure:5
saas-frontend:
container_name: stirling-saas-dev-frontend
image: ${IMAGE_BASE}:saas-frontend-${IMAGE_TAG}
ports:
- "${FRONTEND_PORT}:80"
environment:
VITE_API_BASE_URL: "http://saas-backend:8080"
depends_on:
- saas-backend
restart: on-failure:5${AI_SERVICE}
EOF
SSH_OPTS=(-i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null)
scp "${SSH_OPTS[@]}" docker-compose.yml "${VPS_USERNAME}@${VPS_HOST}:/tmp/saas-dev-docker-compose.yml"
ssh "${SSH_OPTS[@]}" -T "${VPS_USERNAME}@${VPS_HOST}" << ENDSSH
set -e
mkdir -p ${DEPLOY_DIR}/{config,logs,storage}
mv /tmp/saas-dev-docker-compose.yml ${DEPLOY_DIR}/docker-compose.yml
chmod 600 ${DEPLOY_DIR}/docker-compose.yml
cd ${DEPLOY_DIR}
printf '%s' "${GHCR_TOKEN}" | docker login ghcr.io -u "${GHCR_USER}" --password-stdin
docker-compose down --remove-orphans 2>/dev/null || true
docker-compose pull
docker-compose up -d
docker logout ghcr.io >/dev/null 2>&1 || true
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
ENDSSH
- name: Wait for the backend to answer
env:
VPS_HOST: ${{ secrets.NEW_VPS_HOST }}
run: |
URL="http://${VPS_HOST}:${BACKEND_PORT}/api/v1/info/status"
for i in $(seq 1 60); do
code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$URL" || true)
if [ "$code" = "200" ]; then echo "Healthy after $((i * 10))s"; exit 0; fi
sleep 10
done
echo "::error::SaaS dev backend did not become healthy within 10 minutes"
exit 1
- name: Cleanup temporary files
if: always()
run: rm -f ../private.key docker-compose.yml
continue-on-error: true
+1 -1
View File
@@ -38,7 +38,7 @@ jobs:
is_fork: ${{ steps.decide.outputs.is_fork }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
+3 -4
View File
@@ -20,7 +20,7 @@ jobs:
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -28,16 +28,15 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
cache-suffix: ai-engine
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Quality-check engine
id: engine-check
-228
View File
@@ -1,228 +0,0 @@
name: AI - PR Title Review
on:
pull_request:
types: [opened, edited]
branches: [main]
permissions: # required for secure-repo hardening
contents: read
jobs:
ai-title-review:
permissions:
contents: read
pull-requests: write
models: read
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
- name: Configure Git to suppress detached HEAD warning
run: git config --global advice.detachedHead false
- name: Setup GitHub App Bot
if: github.actor != 'dependabot[bot]'
id: setup-bot
uses: ./.github/actions/setup-bot
continue-on-error: true
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Check if actor is repo developer
id: actor
run: |
if [[ "${{ github.actor }}" == *"[bot]" ]]; then
echo "PR opened by a bot skipping AI title review."
echo "is_repo_dev=false" >> $GITHUB_OUTPUT
exit 0
fi
if [ ! -f .github/config/repo_devs.json ]; then
echo "Error: .github/config/repo_devs.json not found" >&2
exit 1
fi
# Validate JSON and extract repo_devs
REPO_DEVS=$(jq -r '.repo_devs[]' .github/config/repo_devs.json 2>/dev/null || { echo "Error: Invalid JSON in repo_devs.json" >&2; exit 1; })
# Convert developer list into Bash array
mapfile -t DEVS_ARRAY <<< "$REPO_DEVS"
if [[ " ${DEVS_ARRAY[*]} " == *" ${{ github.actor }} "* ]]; then
echo "is_repo_dev=true" >> $GITHUB_OUTPUT
else
echo "is_repo_dev=false" >> $GITHUB_OUTPUT
fi
- name: Get PR diff
if: steps.actor.outputs.is_repo_dev == 'true'
id: get_diff
run: |
git fetch origin ${{ github.base_ref }}
git diff origin/${{ github.base_ref }}...HEAD | head -n 10000 | grep -vP '[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x{202E}\x{200B}]' > pr.diff
echo "diff<<EOF" >> $GITHUB_OUTPUT
cat pr.diff >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Check and sanitize PR title
if: steps.actor.outputs.is_repo_dev == 'true'
id: sanitize_pr_title
env:
PR_TITLE_RAW: ${{ github.event.pull_request.title }}
run: |
# Sanitize PR title: max 72 characters, only printable characters
PR_TITLE=$(echo "$PR_TITLE_RAW" | tr -d '\n\r' | head -c 72 | sed 's/[^[:print:]]//g')
if [[ ${#PR_TITLE} -lt 5 ]]; then
echo "PR title is too short. Must be at least 5 characters." >&2
fi
echo "pr_title=$PR_TITLE" >> $GITHUB_OUTPUT
- name: AI PR Title Analysis
if: steps.actor.outputs.is_repo_dev == 'true'
id: ai-title-analysis
uses: actions/ai-inference@a7805884c80886efc241e94a5351df715968a0ad # v2.1.1
with:
model: openai/gpt-4o
system-prompt-file: ".github/config/system-prompt.txt"
prompt: |
Based on the following input data:
{
"diff": "${{ steps.get_diff.outputs.diff }}",
"pr_title": "${{ steps.sanitize_pr_title.outputs.pr_title }}"
}
Respond ONLY with valid JSON in the format:
{
"improved_rating": <0-10>,
"improved_ai_title_rating": <0-10>,
"improved_title": "<ai generated title>"
}
- name: Validate and set SCRIPT_OUTPUT
if: steps.actor.outputs.is_repo_dev == 'true'
run: |
cat <<EOF > ai_response.json
${{ steps.ai-title-analysis.outputs.response }}
EOF
# Validate JSON structure
jq -e '
(keys | sort) == ["improved_ai_title_rating", "improved_rating", "improved_title"] and
(.improved_rating | type == "number" and . >= 0 and . <= 10) and
(.improved_ai_title_rating | type == "number" and . >= 0 and . <= 10) and
(.improved_title | type == "string")
' ai_response.json
if [ $? -ne 0 ]; then
echo "Invalid AI response format" >&2
cat ai_response.json >&2
exit 1
fi
# Parse JSON fields
IMPROVED_RATING=$(jq -r '.improved_rating' ai_response.json)
IMPROVED_TITLE=$(jq -r '.improved_title' ai_response.json)
# Limit comment length to 1000 characters
COMMENT=$(cat <<EOF
## 🤖 AI PR Title Suggestion
**PR-Title Rating**: $IMPROVED_RATING/10
### ⬇️ Suggested Title (copy & paste):
\`\`\`
$IMPROVED_TITLE
\`\`\`
---
*Generated by GitHub Models AI*
EOF
)
echo "$COMMENT" > /tmp/ai-title-comment.md
# Log input and output to the GitHub Step Summary
echo "### 🤖 AI PR Title Analysis" >> $GITHUB_STEP_SUMMARY
echo "### Input PR Title" >> $GITHUB_STEP_SUMMARY
echo '```bash' >> $GITHUB_STEP_SUMMARY
echo "${{ steps.sanitize_pr_title.outputs.pr_title }}" >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
echo '### AI Response (raw JSON)' >> $GITHUB_STEP_SUMMARY
echo '```json' >> $GITHUB_STEP_SUMMARY
cat ai_response.json >> $GITHUB_STEP_SUMMARY
echo '```' >> $GITHUB_STEP_SUMMARY
- name: Post comment on PR if needed
if: steps.actor.outputs.is_repo_dev == 'true'
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
continue-on-error: true
with:
github-token: ${{ steps.setup-bot.outputs.token }}
script: |
const fs = require('fs');
const body = fs.readFileSync('/tmp/ai-title-comment.md', 'utf8');
const { GITHUB_REPOSITORY } = process.env;
const [owner, repo] = GITHUB_REPOSITORY.split('/');
const issue_number = context.issue.number;
const ratingMatch = body.match(/\*\*PR-Title Rating\*\*: (\d+)\/10/);
const rating = ratingMatch ? parseInt(ratingMatch[1], 10) : null;
const expectedActor = "${{ steps.setup-bot.outputs.app-slug }}[bot]";
const comments = await github.rest.issues.listComments({ owner, repo, issue_number });
const existing = comments.data.find(c =>
c.user?.login === expectedActor &&
c.body.includes("## 🤖 AI PR Title Suggestion")
);
if (rating === null) {
console.log("No rating found in AI response skipping.");
return;
}
if (rating <= 5) {
if (existing) {
await github.rest.issues.updateComment({
owner, repo,
comment_id: existing.id,
body
});
console.log("Updated existing suggestion comment.");
} else {
await github.rest.issues.createComment({
owner, repo, issue_number,
body
});
console.log("Created new suggestion comment.");
}
} else {
const praise = `## 🤖 AI PR Title Suggestion\n\nGreat job! The current PR title is clear and well-structured.\n\n✅ No suggestions needed.\n\n---\n*Generated by GitHub Models AI*`;
if (existing) {
await github.rest.issues.updateComment({
owner, repo,
comment_id: existing.id,
body: praise
});
console.log("Replaced suggestion with praise.");
} else {
console.log("Rating > 5 and no existing comment skipping comment.");
}
}
- name: is not repo dev
if: steps.actor.outputs.is_repo_dev != 'true'
run: |
exit 0 # Skip the AI title review for non-repo developers
- name: Clean up
if: always()
run: |
rm -f pr.diff ai_response.json /tmp/ai-title-comment.md
echo "Cleaned up temporary files."
continue-on-error: true # Ensure cleanup runs even if previous steps fail
+4 -3
View File
@@ -26,7 +26,7 @@ jobs:
jar_sha256: ${{ steps.hashes.outputs.jar_sha256 }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -66,11 +66,12 @@ jobs:
echo "jar_sha256=$JAR_SHA" >> "$GITHUB_OUTPUT"
publish-aur:
environment: package-publish
needs: get-release-info
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -106,7 +107,7 @@ jobs:
- name: Publish stirling-pdf-desktop to AUR
if: ${{ github.event_name == 'release' || inputs.dry_run == false }}
uses: KSXGitHub/github-actions-deploy-aur@da03e160361ce01bf087e790b6ffd196d7dccff7 # v4.1.3
uses: KSXGitHub/github-actions-deploy-aur@084b0d9b15415bf9cdb65d44dad1efe37a354050 # v4.2.0
with:
pkgname: stirling-pdf-desktop
pkgbuild: .github/aur/stirling-pdf-desktop/PKGBUILD
+5 -10
View File
@@ -13,26 +13,21 @@ jobs:
labeler:
runs-on: ubuntu-latest
permissions:
pull-requests: write
contents: read # checkout + labeler fetching its config from the repo
pull-requests: write # read changed files, apply labels to the PR
issues: write # labels are applied through the issues API
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- uses: srvaroa/labeler@bf262763a8a8e191f5847873aecc0f29df84f957 # v1.14.0
with:
config_path: .github/labeler-config-srvaroa.yml
use_local_config: false
fail_on_error: true
env:
GITHUB_TOKEN: "${{ steps.setup-bot.outputs.token }}"
GITHUB_TOKEN: "${{ github.token }}"
+17 -14
View File
@@ -20,6 +20,9 @@ permissions:
jobs:
build:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -28,31 +31,28 @@ jobs:
flavor: [core, proprietary, saas]
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-${{ matrix.jdk-version }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK ${{ matrix.jdk-version }}
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: ${{ matrix.jdk-version }}
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-${{ matrix.jdk-version }}-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-${{ matrix.jdk-version }}-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Check Java formatting (Spotless)
# Runs once per matrix combination - pick the cheapest leg
# (core - no proprietary, no saas) so we don't wait for the
@@ -153,6 +153,9 @@ jobs:
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
STIRLING_FLAVOR: ${{ matrix.flavor }}
# Configure the Gradle daemon explicitly; GRADLE_OPTS alone only
# configures the Gradle client JVM.
GRADLE_OPTS: "-Dorg.gradle.jvmargs=-Xmx4g -XX:+UseG1GC"
- name: Check Test Reports Exist
if: always()
@@ -194,7 +197,7 @@ jobs:
- name: Install uv
if: always() && matrix.flavor == 'saas'
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
+40 -13
View File
@@ -15,6 +15,11 @@ name: Enterprise E2E (Playwright)
on:
workflow_call:
inputs:
use_shared_cache:
required: false
type: boolean
default: false
push:
branches: ["main"]
schedule:
@@ -37,6 +42,9 @@ jobs:
uses: ./.github/workflows/_runner-pick.yml
playwright-e2e-enterprise:
environment:
name: ci-unsigned
deployment: false
needs: pick
# Skip on fork PRs / untrusted authors: they have no PREMIUM_KEY_ENTERPRISE,
# so the suite can't boot premium and would fail. See the header comment.
@@ -50,26 +58,36 @@ jobs:
SYSTEM_ENABLEANALYTICS: "false"
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
- name: Restore cache Gradle User Home
if: inputs.use_shared_cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Restore cache Gradle
if: inputs.use_shared_cache == false
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
key: gradle-playwright-e2e-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
@@ -77,7 +95,7 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Install Playwright (chromium only)
run: task e2e:install -- chromium
- name: Build frontend (needed for playwright's vite preview webServer)
@@ -306,9 +324,18 @@ jobs:
path: frontend/playwright-report/
retention-days: 7
- name: Cleanup temporary files
if: always()
run: |
rm -f /tmp/helpers.sh /tmp/backend.log /tmp/backend.pid
continue-on-error: true
# Multi-node regression: builds + seeds the clustered stack (testing/compose/docker-compose-multinode.yml)
# and runs behave features/multinode. Licence-gated, so it runs after the Playwright job (not in parallel).
multinode-e2e:
environment:
name: ci-unsigned
deployment: false
needs: [pick, playwright-e2e-enterprise]
# Nightly cron + manual dispatch only (heavy build), fork-gated for the licence secret.
if: >-
@@ -324,13 +351,13 @@ jobs:
MN_COMPOSE: docker-compose-multinode.yml
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
+38 -42
View File
@@ -37,6 +37,7 @@ jobs:
timeout-minutes: 3
outputs:
build: ${{ steps.changes.outputs.build }}
backend: ${{ steps.changes.outputs.backend }}
project: ${{ steps.changes.outputs.project }}
openapi: ${{ steps.changes.outputs.openapi }}
frontend: ${{ steps.changes.outputs.frontend }}
@@ -48,54 +49,25 @@ jobs:
proprietary: ${{ steps.changes.outputs.proprietary }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check for file changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: changes
with:
filters: .github/config/.files.yaml
gradle-cache-prime:
name: Prime shared Gradle cache
needs: [files-changed]
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Resolve backend dependencies
run: ./gradlew :stirling-pdf:classes -PnoSpotless --no-daemon
env:
STIRLING_FLAVOR: saas
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
uses: ./.github/workflows/gradle-cache-prime.yml
secrets: inherit
build:
if: needs.files-changed.outputs.backend == 'true'
needs: [files-changed, gradle-cache-prime]
permissions:
actions: read
@@ -134,9 +106,10 @@ jobs:
uses: ./.github/workflows/frontend-validation.yml
secrets: inherit
# Advisory: deliberately NOT in all-checks-passed. It reports on the stories a
# branch touches so a regression is visible in review, but a browser scan is
# too new here to block merges on. Promote it once its pass/fail proves stable.
# Required (in all-checks-passed). Scans the stories a branch touches in both
# light and dark; an axe violation in either theme blocks the merge. The
# whole-suite sweep (nightly.yml) still covers stories a change affects without
# touching them directly.
frontend-a11y:
if: needs.files-changed.outputs.frontend == 'true'
needs: [files-changed]
@@ -168,10 +141,12 @@ jobs:
contents: read
uses: ./.github/workflows/build-enterprise.yml
secrets: inherit
with:
use_shared_cache: true
check-licence:
if: needs.files-changed.outputs.build == 'true'
needs: [files-changed, build, gradle-cache-prime]
needs: [files-changed, gradle-cache-prime]
permissions:
contents: read
uses: ./.github/workflows/check-licence.yml
@@ -190,8 +165,22 @@ jobs:
docker-base-changed: ${{ needs.files-changed.outputs.docker-base }}
test-build-docker-images:
if: github.event_name == 'pull_request' && needs.files-changed.outputs.project == 'true'
needs: [files-changed, build, check-generateOpenApiDocs, check-licence, gradle-cache-prime]
if: |
always() &&
github.event_name == 'pull_request' &&
needs.files-changed.outputs.project == 'true' &&
contains(fromJSON('["success", "skipped"]'), needs.gradle-cache-prime.result) &&
contains(fromJSON('["success", "skipped"]'), needs.build.result) &&
contains(fromJSON('["success", "skipped"]'), needs.check-generateOpenApiDocs.result) &&
contains(fromJSON('["success", "skipped"]'), needs.check-licence.result)
needs:
[
files-changed,
build,
check-generateOpenApiDocs,
check-licence,
gradle-cache-prime,
]
permissions:
contents: read
packages: read
@@ -203,7 +192,7 @@ jobs:
tauri-build:
if: needs.files-changed.outputs.tauri == 'true'
needs: [files-changed]
needs: [files-changed, gradle-cache-prime]
permissions:
contents: read
pull-requests: write
@@ -217,6 +206,7 @@ jobs:
with:
platform: windows-macos
sign: true
use_shared_cache: true
ai-engine:
if: needs.files-changed.outputs.engine == 'true'
@@ -240,6 +230,8 @@ jobs:
pull-requests: write
uses: ./.github/workflows/check-generated-models.yml
secrets: inherit
with:
use_shared_cache: true
pre-commit:
needs: [files-changed]
@@ -286,10 +278,12 @@ jobs:
if: always()
needs:
- files-changed
- gradle-cache-prime
- build
- db-migration-test
- check-generateOpenApiDocs
- frontend-validation
- frontend-a11y
- playwright-e2e
- playwright-e2e-live
- playwright-e2e-enterprise
@@ -304,7 +298,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -312,10 +306,12 @@ jobs:
env:
RESULTS: |
files-changed=${{ needs.files-changed.result }}
gradle-cache-prime=${{ needs.gradle-cache-prime.result }}
build=${{ needs.build.result }}
db-migration-test=${{ needs.db-migration-test.result }}
check-generateOpenApiDocs=${{ needs.check-generateOpenApiDocs.result }}
frontend-validation=${{ needs.frontend-validation.result }}
frontend-a11y=${{ needs.frontend-a11y.result }}
playwright-e2e=${{ needs.playwright-e2e.result }}
playwright-e2e-live=${{ needs.playwright-e2e-live.result }}
playwright-e2e-enterprise=${{ needs.playwright-e2e-enterprise.result }}
+26 -15
View File
@@ -9,6 +9,11 @@ name: Check generated models
# post-merge safety net.
on:
workflow_call:
inputs:
use_shared_cache:
required: false
type: boolean
default: false
push:
branches: [main]
@@ -23,7 +28,7 @@ jobs:
pull-requests: write
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -31,13 +36,30 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
cache-suffix: generated-models
- name: Restore cache Gradle User Home
if: inputs.use_shared_cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Restore cache Gradle
if: inputs.use_shared_cache == false
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-generated-models-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
@@ -45,17 +67,6 @@ jobs:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
@@ -64,7 +75,7 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Verify generated models are up to date
id: models-check
+13 -13
View File
@@ -10,35 +10,35 @@ permissions:
jobs:
check-licence:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Check licenses for compatibility
run: task backend:licenses:check
env:
+13 -13
View File
@@ -11,35 +11,35 @@ permissions:
jobs:
check-generate-openapi-docs:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Generate OpenAPI documentation
run: task backend:swagger
env:
+34 -33
View File
@@ -23,29 +23,23 @@ jobs:
if: github.event_name == 'pull_request_target'
runs-on: ubuntu-latest
permissions:
contents: read # Checkout, and read translation files via the contents API
issues: write # Allow posting comments on issues/PRs
pull-requests: write # Allow writing to pull requests
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout main branch first
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Setup GitHub App Bot
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Get PR data
id: get-pr-data
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const prNumber = context.payload.pull_request.number;
const repoOwner = context.payload.repository.owner.login;
@@ -66,17 +60,18 @@ jobs:
- name: Fetch PR changed files
id: fetch-pr-changes
env:
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
GH_TOKEN: ${{ github.token }}
PR_NUMBER: ${{ steps.get-pr-data.outputs.pr_number }}
run: |
echo "Fetching PR changed files..."
echo "Getting list of changed files from PR..."
# Check if PR number exists
if [ -z "${{ steps.get-pr-data.outputs.pr_number }}" ]; then
if [ -z "${PR_NUMBER}" ]; then
echo "Error: PR number is empty"
exit 1
fi
# Get changed files and filter for TOML translation files
gh pr view ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^frontend/editor/public/locales/[a-zA-Z-]+/translation\.toml$' > changed_files.txt || echo "No matching TOML files found in PR"
gh pr view "${PR_NUMBER}" --json files -q ".files[].path" | grep -E '^frontend/editor/public/locales/[a-zA-Z-]+/translation\.toml$' > changed_files.txt || echo "No matching TOML files found in PR"
# Check if any files were found
if [ ! -s changed_files.txt ]; then
echo "No TOML translation files changed in this PR"
@@ -88,32 +83,36 @@ jobs:
- name: Determine reference file
id: determine-file
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
env:
# Untrusted, fork-controlled values are passed via env, never interpolated into the script
PR_NUMBER: ${{ steps.get-pr-data.outputs.pr_number }}
REPO_OWNER: ${{ steps.get-pr-data.outputs.repo_owner }}
REPO_NAME: ${{ steps.get-pr-data.outputs.repo_name }}
PR_REPO_OWNER: ${{ github.event.pull_request.head.repo.owner.login }}
PR_REPO_NAME: ${{ github.event.pull_request.head.repo.name }}
PR_BRANCH: ${{ steps.get-pr-data.outputs.branch }}
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const fs = require("fs");
const path = require("path");
const prNumber = ${{ steps.get-pr-data.outputs.pr_number }};
const repoOwner = "${{ steps.get-pr-data.outputs.repo_owner }}";
const repoName = "${{ steps.get-pr-data.outputs.repo_name }}";
const prRepoOwner = "${{ github.event.pull_request.head.repo.owner.login }}";
const prRepoName = "${{ github.event.pull_request.head.repo.name }}";
const branch = "${{ steps.get-pr-data.outputs.branch }}";
console.log(`Determining reference file for PR #${prNumber}`);
// Validate inputs
// Validate inputs before any use
const validateInput = (input, regex, name) => {
if (!regex.test(input)) {
if (typeof input !== "string" || !regex.test(input)) {
throw new Error(`Invalid ${name}: ${input}`);
}
return input;
};
validateInput(repoOwner, /^[a-zA-Z0-9_-]+$/, "repository owner");
validateInput(repoName, /^[a-zA-Z0-9._-]+$/, "repository name");
validateInput(branch, /^[a-zA-Z0-9._/-]+$/, "branch name");
const repoOwner = validateInput(process.env.REPO_OWNER, /^[a-zA-Z0-9_-]+$/, "repository owner");
const repoName = validateInput(process.env.REPO_NAME, /^[a-zA-Z0-9._-]+$/, "repository name");
const prRepoOwner = validateInput(process.env.PR_REPO_OWNER, /^[a-zA-Z0-9_-]+$/, "PR repository owner");
const prRepoName = validateInput(process.env.PR_REPO_NAME, /^[a-zA-Z0-9._-]+$/, "PR repository name");
const branch = validateInput(process.env.PR_BRANCH, /^[a-zA-Z0-9._/-]+$/, "branch name");
const prNumber = Number(validateInput(process.env.PR_NUMBER, /^[0-9]+$/, "PR number"));
console.log(`Determining reference file for PR #${prNumber}`);
// Get the list of changed files in the PR
const { data: files } = await github.rest.pulls.listFiles({
@@ -126,7 +125,7 @@ jobs:
const changedFiles = files
.filter(file =>
file.status !== "removed" &&
/^frontend\/public\/locales\/[a-zA-Z-]+\/translation\.toml$/.test(file.filename)
/^frontend\/editor\/public\/locales\/[a-zA-Z-]+\/translation\.toml$/.test(file.filename)
)
.map(file => file.filename);
@@ -196,7 +195,7 @@ jobs:
core.exportVariable("REFERENCE_FILE", referenceFilePath);
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
@@ -209,10 +208,12 @@ jobs:
- name: Run Python script to check files
id: run-check
env:
PR_ACTOR: ${{ github.event.pull_request.user.login }}
run: |
echo "Running Python script to check TOML files..."
uv run --project engine --locked --group tools python .github/scripts/check_language_toml.py \
--actor ${{ github.event.pull_request.user.login }} \
--actor "${PR_ACTOR}" \
--reference-file "${REFERENCE_FILE}" \
--branch "pr-branch" \
--files "${FILES_LIST[@]}" > result.txt
@@ -245,7 +246,7 @@ jobs:
if: env.SCRIPT_OUTPUT != ''
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const { GITHUB_REPOSITORY, SCRIPT_OUTPUT } = process.env;
const [repoOwner, repoName] = GITHUB_REPOSITORY.split('/');
@@ -261,7 +262,7 @@ jobs:
const comment = comments.data.find(c => c.body.includes("## 🌐 TOML Translation Verification Summary"));
// Only update or create comments by the action user
const expectedActor = "${{ steps.setup-bot.outputs.app-slug }}[bot]";
const expectedActor = "github-actions[bot]";
if (comment && comment.user.login === expectedActor) {
// Update existing comment
+10 -13
View File
@@ -34,31 +34,28 @@ jobs:
timeout-minutes: 15
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
+17 -12
View File
@@ -13,34 +13,34 @@ permissions:
jobs:
migration-test:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: 25
distribution: temurin
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
# Keep the normal formatting path here so this smoke test exercises the
# same Gradle configuration as the backend build.
- name: Build Stirling-PDF JAR
@@ -81,3 +81,8 @@ jobs:
path: /tmp/stirling-migration-failed-*/app.log
retention-days: 7
if-no-files-found: warn
- name: Cleanup temporary files
if: always()
run: rm -rf /tmp/stirling-migration-failed-*
continue-on-error: true
+1 -1
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
-189
View File
@@ -1,189 +0,0 @@
name: Auto V2 Deploy on Push
on:
push:
branches:
- V2
- deploy-on-v2-commit
permissions:
contents: read
jobs:
deploy-v2-on-push:
runs-on: ubuntu-latest
concurrency:
group: deploy-v2-push-V2
cancel-in-progress: true
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout code
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Get commit hashes for frontend and backend
id: commit-hashes
run: |
# Get last commit that touched the frontend folder, docker/frontend, or docker/compose
FRONTEND_HASH=$(git log -1 --format="%H" -- frontend/ docker/frontend/ docker/compose/ 2>/dev/null || echo "")
if [ -z "$FRONTEND_HASH" ]; then
FRONTEND_HASH="no-frontend-changes"
fi
# Get last commit that touched backend code, docker/backend, or docker/compose
BACKEND_HASH=$(git log -1 --format="%H" -- app/ docker/backend/ docker/compose/ 2>/dev/null || echo "")
if [ -z "$BACKEND_HASH" ]; then
BACKEND_HASH="no-backend-changes"
fi
echo "Frontend hash: $FRONTEND_HASH"
echo "Backend hash: $BACKEND_HASH"
echo "frontend_hash=$FRONTEND_HASH" >> $GITHUB_OUTPUT
echo "backend_hash=$BACKEND_HASH" >> $GITHUB_OUTPUT
# Short hashes for tags
if [ "$FRONTEND_HASH" = "no-frontend-changes" ]; then
echo "frontend_short=no-frontend" >> $GITHUB_OUTPUT
else
echo "frontend_short=${FRONTEND_HASH:0:8}" >> $GITHUB_OUTPUT
fi
if [ "$BACKEND_HASH" = "no-backend-changes" ]; then
echo "backend_short=no-backend" >> $GITHUB_OUTPUT
else
echo "backend_short=${BACKEND_HASH:0:8}" >> $GITHUB_OUTPUT
fi
- name: Check if frontend image exists
id: check-frontend
run: |
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }} >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Frontend image already exists, skipping build"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Frontend image needs to be built"
fi
- name: Check if backend image exists
id: check-backend
run: |
if docker manifest inspect ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }} >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
echo "Backend image already exists, skipping build"
else
echo "exists=false" >> $GITHUB_OUTPUT
echo "Backend image needs to be built"
fi
- name: Login to Docker Hub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push frontend image
if: steps.check-frontend.outputs.exists == 'false'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ./docker/frontend/Dockerfile
push: true
cache-from: type=gha,scope=stirling-v2-frontend
cache-to: type=gha,mode=max,scope=stirling-v2-frontend
tags: |
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-latest
build-args: VERSION_TAG=v2-alpha
platforms: linux/amd64
- name: Build and push backend image
if: steps.check-backend.outputs.exists == 'false'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ./docker/backend/Dockerfile
push: true
cache-from: type=gha,scope=stirling-v2-backend
cache-to: type=gha,mode=max,scope=stirling-v2-backend
tags: |
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-latest
build-args: VERSION_TAG=v2-alpha
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
chmod 600 ../private.key
- name: Deploy to VPS on port 3000
run: |
export UNIQUE_NAME=docker-compose-v2-$GITHUB_RUN_ID.yml
cat > $UNIQUE_NAME << EOF
version: '3.3'
services:
backend:
container_name: stirling-v2-backend
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-backend-${{ steps.commit-hashes.outputs.backend_short }}
ports:
- "13000:8080"
volumes:
- /stirling/V2/data:/usr/share/tessdata:rw
- /stirling/V2/config:/configs:rw
- /stirling/V2/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "true"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: "Stirling-PDF V2"
UI_HOMEDESCRIPTION: "V2 Frontend/Backend Split"
UI_APPNAMENAVBAR: "V2 Deployment"
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
SWAGGER_SERVER_URL: "https://demo.stirlingpdf.cloud"
baseUrl: "https://demo.stirlingpdf.cloud"
restart: on-failure:5
frontend:
container_name: stirling-v2-frontend
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:v2-frontend-${{ steps.commit-hashes.outputs.frontend_short }}
ports:
- "3000:80"
environment:
VITE_API_BASE_URL: "http://${{ secrets.NEW_VPS_HOST }}:13000"
depends_on:
- backend
restart: on-failure:5
EOF
# Copy to remote with unique name
scp -i ../private.key -o StrictHostKeyChecking=no $UNIQUE_NAME ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/$UNIQUE_NAME
# SSH and rename/move atomically to avoid interference
ssh -i ../private.key -o StrictHostKeyChecking=no ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << ENDSSH
mkdir -p /stirling/V2/{data,config,logs}
mv /tmp/$UNIQUE_NAME /stirling/V2/docker-compose.yml
cd /stirling/V2
docker-compose down || true
docker-compose pull
docker-compose up -d
docker system prune -af --volumes || true
docker image prune -af --filter "until=336h" --filter "label!=keep=true" || true
ENDSSH
- name: Cleanup temporary files
if: always()
run: |
rm -f ../private.key
+15 -15
View File
@@ -17,6 +17,9 @@ permissions:
jobs:
docker-compose-tests:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
permissions:
actions: write
@@ -25,30 +28,27 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
# When the PR changes the base image, test.sh builds it locally
# (stirling-pdf-base:local) into the daemon image store. A buildx
# container builder can't see that store, so skip it here and let
@@ -57,7 +57,7 @@ jobs:
# runtime token isn't exposed) since the docker driver can't use it.
- name: Set up Docker Buildx
if: inputs.docker-base-changed != 'true'
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
# Expose ACTIONS_RUNTIME_TOKEN / ACTIONS_RESULTS_URL for docker buildx type=gha cache backend.
- name: Expose GitHub runtime for Buildx cache
@@ -66,11 +66,11 @@ jobs:
- name: Install Docker Compose
run: |
sudo curl -SL "https://github.com/docker/compose/releases/download/v2.39.4/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo curl -SL "https://github.com/docker/compose/releases/download/v5.4.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
+16 -31
View File
@@ -11,48 +11,33 @@ permissions:
jobs:
playwright-e2e-live:
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
# Gradle does not retry 429s, and a cold cache resolving the buildscript
# classpath is exactly where Maven Central rate-limits us. Retry it here,
# where a failure is cheap, instead of inside the backgrounded bootRun.
- name: Prime Gradle dependencies
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
run: |
for attempt in 1 2 3; do
if ./gradlew --quiet -PnoSpotless :stirling-pdf:classes; then
exit 0
fi
echo "::warning::Gradle dependency resolution failed (attempt $attempt of 3)"
sleep $((attempt * 30))
done
echo "::error::Gradle could not resolve dependencies after 3 attempts"
exit 1
- name: Set up Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
@@ -60,7 +45,7 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Install Playwright (chromium only)
run: task e2e:install -- chromium
- name: Build frontend (production bundle for vite preview)
@@ -121,7 +106,7 @@ jobs:
fi
- name: Install uv
if: always()
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
+17 -4
View File
@@ -14,6 +14,11 @@ jobs:
playwright-e2e:
name: playwright-e2e (${{ matrix.browser }})
runs-on: ubuntu-latest
# The image already contains the Playwright browsers and all Linux
# dependencies. This keeps the matrix for per-browser reporting while
# avoiding three concurrent `playwright install --with-deps` runs.
container:
image: mcr.microsoft.com/playwright:v1.58.2-noble@sha256:6446946a1d9fd62d9ae501312a2d76a43ee688542b21622056a372959b65d63d
strategy:
# One browser breaking must not mask a failure in another - report all.
fail-fast: false
@@ -27,7 +32,7 @@ jobs:
project: stubbed-webkit
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
@@ -39,16 +44,24 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
- name: Install Playwright (${{ matrix.browser }})
run: task e2e:install -- ${{ matrix.browser }}
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Build frontend (production bundle for vite preview)
env:
VITE_BUILD_FOR_PREVIEW: "1"
run: task frontend:build
- name: Run stubbed E2E tests (${{ matrix.browser }})
env:
# The official Playwright image expects its browser runtime under
# the root home directory. Keep this scoped to Playwright and use a
# neutral Docker config path so Docker does not read /root/.docker.
HOME: /root
DOCKER_CONFIG: /tmp/playwright-docker-config
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json
NPM_CONFIG_PREFER_OFFLINE: "true"
NPM_CONFIG_FETCH_RETRIES: "5"
NPM_CONFIG_FETCH_RETRY_FACTOR: "2"
NPM_CONFIG_FETCH_RETRY_MINTIMEOUT: "1000"
NPM_CONFIG_FETCH_RETRY_MAXTIMEOUT: "120000"
run: task e2e:stubbed-project PROJECT=${{ matrix.project }} -- --workers=3
- name: Flag flaky tests
# Runs regardless of the test outcome: a flaky test (passed on retry)
+4 -9
View File
@@ -3,17 +3,12 @@ name: Frontend a11y regression gate
# Reusable workflow called from build.yml when frontend sources change.
#
# Scans the stories this branch touches in real Chromium and runs axe against
# each. Existing violations are grandfathered in .storybook/a11y-baseline.json;
# the check fails on a NEW violation — a story breaking a rule it wasn't already
# breaking — or on a story that fails to render at all.
# each; the check fails on any axe violation, or on a story that fails to render
# at all.
#
# Only changed stories, because a full sweep is ~30 minutes: far too slow to sit
# in front of every merge. The whole suite is scanned nightly instead
# (nightly.yml), which catches anything a branch didn't touch.
#
# Advisory for now: this is not in build.yml's all-checks-passed list, so a
# failure reports without blocking. Promote it once a few weeks of runs show the
# pass/fail is stable.
on:
workflow_call:
@@ -26,7 +21,7 @@ jobs:
timeout-minutes: 25
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
@@ -41,7 +36,7 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: a11y gate (changed stories)
run: task frontend:storybook:a11y:changed -- origin/${{ github.base_ref || 'main' }}
- name: Upload scan reports
@@ -28,7 +28,7 @@ jobs:
licenses-backend: ${{ steps.changes.outputs.licenses-backend }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -36,12 +36,16 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check for file changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: changes
with:
filters: .github/config/.files.yaml
generate-frontend-license-report:
# ci-bot, not bot-identity: this job runs on PRs too, and bot-identity is main-only.
environment:
name: ci-bot
deployment: false
if: needs.files-changed.outputs.licenses-frontend == 'true'
name: Generate Frontend License Report
needs: files-changed
@@ -52,7 +56,7 @@ jobs:
repository-projects: write # Required for enabling automerge
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -93,7 +97,7 @@ jobs:
run: npm ci --ignore-scripts --audit=false --fund=false
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Generate frontend license report (Push only)
if: github.event_name == 'push'
@@ -316,6 +320,10 @@ jobs:
GH_TOKEN: ${{ steps.setup-bot.outputs.token }}
generate-backend-license-report:
# ci-bot, not bot-identity: this job runs on PRs too, and bot-identity is main-only.
environment:
name: ci-bot
deployment: false
if: needs.files-changed.outputs.licenses-backend == 'true'
needs: files-changed
name: Generate Backend License Report
@@ -326,7 +334,7 @@ jobs:
repository-projects: write # Required for enabling automerge
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -344,25 +352,22 @@ jobs:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-license-report-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Check licenses and generate report
id: license-check
+3 -3
View File
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
@@ -27,7 +27,7 @@ jobs:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Quality-check frontend
id: frontend-check
run: task frontend:check:all
@@ -107,7 +107,7 @@ jobs:
}
- name: Install uv
if: always()
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
+66
View File
@@ -0,0 +1,66 @@
name: Prime Gradle Cache
on:
workflow_call:
push:
branches: ["main"]
permissions:
contents: read
jobs:
gradle-cache-prime:
environment:
name: ci-unsigned
deployment: false
name: Prime shared Gradle cache
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Calculate Gradle cache key
id: gradle-cache-key
shell: bash
run: |
echo "key=gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}" >> "$GITHUB_OUTPUT"
- name: Cache Gradle (lookup-only)
id: cache-gradle-restore
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ steps.gradle-cache-key.outputs.key }}
lookup-only: true
- name: Set up JDK 25
if: steps.cache-gradle-restore.outputs.cache-hit != 'true'
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Resolve backend dependencies
if: steps.cache-gradle-restore.outputs.cache-hit != 'true'
run: ./gradlew :stirling-pdf:classes --no-daemon
env:
STIRLING_FLAVOR: saas
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
- name: Save cache Gradle User Home
if: steps.cache-gradle-restore.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: ${{ steps.gradle-cache-key.outputs.key }}
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
issues: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
+78 -94
View File
@@ -38,6 +38,9 @@ permissions:
jobs:
determine-matrix:
environment:
name: ci-unsigned
deployment: false
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-latest
outputs:
@@ -45,31 +48,28 @@ jobs:
version: ${{ steps.versionNumber.outputs.versionNumber }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-tauri-releases-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Get version number
id: versionNumber
run: |
@@ -93,7 +93,7 @@ jobs:
ALL="$WINDOWS,$WINDOWS_ARM64,$MACOS,$LINUX"
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
case "${{ github.event.inputs.platform }}" in
case "${INPUT_PLATFORM}" in
"windows")
echo "matrix={\"include\":[$WINDOWS,$WINDOWS_ARM64]}" >> $GITHUB_OUTPUT
;;
@@ -115,7 +115,12 @@ jobs:
echo "matrix={\"include\":[$ALL]}" >> $GITHUB_OUTPUT
fi
env:
INPUT_PLATFORM: ${{ github.event.inputs.platform }}
build-jars:
environment:
name: ci-unsigned
deployment: false
needs: determine-matrix
runs-on: ubuntu-latest
strategy:
@@ -135,29 +140,26 @@ jobs:
file_suffix: "-server"
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-tauri-releases-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Setup Node.js
if: matrix.variant.build_frontend == true
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
@@ -167,7 +169,7 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Build JAR
run: ./gradlew build ${{ matrix.variant.build_frontend && '-PbuildWithFrontend=true' || '' }} -x spotlessApply -x spotlessCheck -x test -x sonarqube
@@ -194,6 +196,7 @@ jobs:
retention-days: 1
build:
environment: release-signing
needs: determine-matrix
strategy:
fail-fast: false
@@ -201,11 +204,10 @@ jobs:
runs-on: ${{ matrix.platform }}
env:
SM_API_KEY: ${{ secrets.SM_API_KEY }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
RELEASE_GPG_PRIVATE_KEY: ${{ secrets.RELEASE_GPG_PRIVATE_KEY }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
allowed-endpoints: >
@@ -234,6 +236,14 @@ jobs:
toolchain: stable
targets: ${{ matrix.platform == 'macos-15' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-tauri-releases-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
# x86_64 JDK is set up first so the aarch64 step below can leave its
# JAVA_HOME as the active one. The macOS universal JRE build needs
# jmods from both arches; the x64 path is captured into the env
@@ -257,19 +267,8 @@ jobs:
java-version: "25"
distribution: ${{ matrix.platform == 'windows-11-arm' && 'microsoft' || 'temurin' }}
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
# Build the universal JRE before desktop:prepare so the jlink:runtime
# task short-circuits on its `test -d runtime/jre` status check.
@@ -292,7 +291,7 @@ jobs:
# DigiCert KeyLocker Setup (Cloud HSM)
- name: Setup DigiCert KeyLocker
id: digicert-setup
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
uses: digicert/ssm-code-signing@1d820463733701cf1484c7eb5d7d24a15ca2c454 # v1.2.1
env:
SM_API_KEY: ${{ secrets.SM_API_KEY }}
@@ -302,22 +301,22 @@ jobs:
SM_HOST: ${{ secrets.SM_HOST }}
- name: Setup DigiCert KeyLocker Certificate
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
shell: pwsh
run: |
Write-Host "Setting up DigiCert KeyLocker environment..."
# Decode client certificate
$certBytes = [Convert]::FromBase64String("${{ secrets.SM_CLIENT_CERT_FILE_B64 }}")
$certBytes = [Convert]::FromBase64String("$env:SM_CLIENT_CERT_FILE_B64")
$certPath = "D:\Certificate_pkcs12.p12"
[IO.File]::WriteAllBytes($certPath, $certBytes)
# Set environment variables
echo "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" >> $env:GITHUB_ENV
echo "SM_HOST=${{ secrets.SM_HOST }}" >> $env:GITHUB_ENV
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> $env:GITHUB_ENV
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> $env:GITHUB_ENV
echo "SM_KEYPAIR_ALIAS=${{ secrets.SM_KEYPAIR_ALIAS }}" >> $env:GITHUB_ENV
echo "SM_HOST=$env:SM_HOST" >> $env:GITHUB_ENV
echo "SM_API_KEY=$env:SM_API_KEY" >> $env:GITHUB_ENV
echo "SM_CLIENT_CERT_PASSWORD=$env:SM_CLIENT_CERT_PASSWORD" >> $env:GITHUB_ENV
echo "SM_KEYPAIR_ALIAS=$env:SM_KEYPAIR_ALIAS" >> $env:GITHUB_ENV
# Get PKCS11 config path from DigiCert action
$pkcs11Config = $env:PKCS11_CONFIG
@@ -335,40 +334,14 @@ jobs:
}
}
# Traditional PFX Certificate Import (fallback if KeyLocker not configured)
- name: Import Windows Code Signing Certificate
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY == '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
env:
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
shell: powershell
run: |
if ($env:WINDOWS_CERTIFICATE) {
Write-Host "Importing Windows Code Signing Certificate..."
# Decode base64 certificate and save to file
$certBytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE)
$certPath = Join-Path $env:RUNNER_TEMP "certificate.pfx"
[IO.File]::WriteAllBytes($certPath, $certBytes)
# Import certificate to CurrentUser\My store
$cert = Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force)
# Extract and set thumbprint as environment variable
$thumbprint = $cert.Thumbprint
Write-Host "Certificate imported with thumbprint: $thumbprint"
echo "WINDOWS_CERTIFICATE_THUMBPRINT=$thumbprint" >> $env:GITHUB_ENV
# Clean up certificate file
Remove-Item $certPath
Write-Host "Windows certificate import completed."
} else {
Write-Host "⚠️ WINDOWS_CERTIFICATE secret not set - building unsigned binary"
}
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
SM_HOST: ${{ secrets.SM_HOST }}
SM_API_KEY: ${{ secrets.SM_API_KEY }}
SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
SM_KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
- name: Import Apple Developer Certificate
if: matrix.platform == 'macos-15' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')
if: matrix.platform == 'macos-15' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')
env:
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
@@ -389,7 +362,7 @@ jobs:
rm certificate.p12
- name: Verify Certificate
if: matrix.platform == 'macos-15' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')
if: matrix.platform == 'macos-15' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')
run: |
echo "Verifying Apple Developer Certificate..."
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
@@ -405,7 +378,7 @@ jobs:
# Without this, signCommand failures are opaque (Tauri captures but drops
# smctl's stderr) - running these loudly surfaces auth/env/keypair issues.
- name: Preflight smctl
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
shell: pwsh
env:
KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
@@ -436,7 +409,7 @@ jobs:
# smctl reads SM_HOST, SM_API_KEY, SM_CLIENT_CERT_FILE, SM_CLIENT_CERT_PASSWORD
# from env (set by prior DigiCert setup step). No --config-file needed.
- name: Configure Windows code signing
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
shell: bash
env:
KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
@@ -457,7 +430,7 @@ jobs:
sed "s/${KEYPAIR_ALIAS}/***/g" ./frontend/editor/src-tauri/tauri.windows.conf.json
- name: Import release GPG signing key (Linux)
if: matrix.platform == 'ubuntu-22.04' && env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')
if: matrix.platform == 'ubuntu-22.04' && env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')
run: |
echo "$RELEASE_GPG_PRIVATE_KEY" | gpg --batch --import
gpg --list-secret-keys --keyid-format=long
@@ -489,8 +462,8 @@ jobs:
# APPIMAGETOOL_SIGN_PASSPHRASE appimagetool uses this to unlock the GPG key non-interactively
# SIGN_KEY appimagetool picks the key matching this fingerprint
# Without SIGN=1, the other two are ignored and the AppImage is built unsigned even if a key is present.
# Mirror the Windows/macOS gate: only sign on a real release/dispatch+sign or V2-master, when secret is present.
SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')) && '1' || '0' }}
# Mirror the Windows/macOS gate: only sign on a real release/dispatch+sign or the release branch, when secret is present.
SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')) && '1' || '0' }}
APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.RELEASE_GPG_PASSPHRASE }}
SIGN_KEY: ${{ vars.RELEASE_GPG_FINGERPRINT }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
@@ -516,7 +489,7 @@ jobs:
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
GPG_SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')) && '1' || '0' }}
GPG_SIGN: ${{ (env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')) && '1' || '0' }}
SIGN_KEY: ${{ vars.RELEASE_GPG_FINGERPRINT }}
APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.RELEASE_GPG_PASSPHRASE }}
run: |
@@ -555,7 +528,7 @@ jobs:
echo "Stripped bundled libwayland from $(basename "$AI")"
- name: Clear release GPG key from runner keyring (Linux)
if: always() && matrix.platform == 'ubuntu-22.04' && env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master')
if: always() && matrix.platform == 'ubuntu-22.04' && env.RELEASE_GPG_PRIVATE_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release')
env:
RELEASE_GPG_FINGERPRINT: ${{ vars.RELEASE_GPG_FINGERPRINT }}
run: |
@@ -570,7 +543,7 @@ jobs:
# artifact. Tauri signs a COPY when bundling into the MSI and leaves the raw
# cargo output unsigned, so checking it produces false negatives.
- name: Verify Windows Code Signature
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }}
if: ${{ startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/release') }}
timeout-minutes: 15
shell: pwsh
run: |
@@ -724,6 +697,17 @@ jobs:
path: ./dist/*
retention-days: 1
- name: Cleanup temporary files
if: always()
shell: bash
run: |
rm -f certificate.p12
rm -rf "$RUNNER_TEMP/msi-verify"
if [ "${{ matrix.platform }}" = "macos-15" ]; then
security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true
fi
continue-on-error: true
collect-and-release:
needs: [determine-matrix, build, build-jars]
runs-on: ubuntu-latest
@@ -731,7 +715,7 @@ jobs:
contents: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -879,7 +863,7 @@ jobs:
PYEOF
- name: Upload merged artifacts for review
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-artifacts
path: ./artifacts/
@@ -888,7 +872,7 @@ jobs:
# Gate publish on valid updater sigs. Runs after the review upload (so
# artifacts survive for debugging) and before action-gh-release.
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
@@ -902,11 +886,11 @@ jobs:
# workflow_dispatch path requires platform=='all' so a single-platform
# dispatch can't overwrite an existing release's full latest.json with a
# partial one (action-gh-release defaults overwrite_files:true).
# release / V2-master always build the full matrix so no extra guard needed.
# release event / release branch always build the full matrix so no extra guard needed.
# fail_on_unmatched_files makes a missing latest.json or installer fail loudly
# instead of silently shipping a broken auto-update.
- name: Upload binaries to Release
if: (github.event_name == 'workflow_dispatch' && github.event.inputs.test_mode != 'true' && github.event.inputs.platform == 'all') || github.event_name == 'release' || github.ref == 'refs/heads/V2-master'
if: (github.event_name == 'workflow_dispatch' && github.event.inputs.test_mode != 'true' && github.event.inputs.platform == 'all') || github.event_name == 'release' || github.ref == 'refs/heads/release'
uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2
with:
tag_name: v${{ needs.determine-matrix.outputs.version }}
+96 -10
View File
@@ -4,6 +4,11 @@ on:
schedule:
- cron: "0 2 * * *" # 2 AM UTC every night
workflow_dispatch:
pull_request:
paths:
- .github/workflows/nightly.yml
- testing/cucumber/**
- docker/embedded/compose/test_cicd.yml
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -18,7 +23,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -33,7 +38,7 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Install all Playwright browsers
run: task e2e:install
@@ -59,13 +64,17 @@ jobs:
# the story itself — a shared component, a theme token — still surfaces within
# a day.
a11y-all-stories:
name: a11y (every story, light + dark)
name: a11y (every story)
strategy:
fail-fast: false
matrix:
theme: [light, dark]
runs-on: ubuntu-latest
# Two full sweeps (one per theme), each ~30 minutes of browser time.
timeout-minutes: 120
# One full sweep (~30 minutes of browser time).
timeout-minutes: 60
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -80,16 +89,16 @@ jobs:
cache-dependency-path: frontend/package-lock.json
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: a11y gate (every story, light + dark)
run: task frontend:storybook:a11y
- name: a11y gate (every story, ${{ matrix.theme }})
run: task frontend:storybook:a11y:${{ matrix.theme }}
- name: Upload scan reports
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: a11y-scan-nightly-${{ github.run_id }}
name: a11y-scan-nightly-${{ matrix.theme }}-${{ github.run_id }}
path: frontend/.a11y-scan/
retention-days: 14
if-no-files-found: ignore
@@ -99,8 +108,13 @@ jobs:
# Builds all desktop platforms on a schedule so the Rust dependency cache is
# written on main, where PR and merge-queue tauri builds can restore it.
#
# The only job here still pinned to schedule/main: it primes a cache rather than
# testing anything, and Actions scopes a cache written on a PR branch to that PR
# alone, so a PR run costs three platform builds and produces nothing reusable.
warm-tauri-cache:
name: Warm Tauri Rust cache
if: github.event_name == 'schedule' || github.ref == 'refs/heads/main'
permissions:
contents: read
pull-requests: write
@@ -109,3 +123,75 @@ jobs:
platform: all
sign: false
secrets: inherit
# Runs the @nightly tag (conversion scenarios) plus a 10-shard concurrency run
# of every other feature.
cucumber-nightly:
environment:
name: ci-unsigned
deployment: false
name: Cucumber (nightly scenarios + full concurrency)
runs-on: ubuntu-latest
# Fork pull requests get no MAVEN_* secrets, so the image build cannot work.
if: >-
github.event_name != 'pull_request' ||
github.event.pull_request.head.repo.full_name == github.repository
permissions:
contents: read
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Install uv
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
- name: Install Task
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Start the fat image with login and storage enabled
run: docker compose -f docker/embedded/compose/test_cicd.yml up -d --build
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
- name: Wait for the server
# Throwaway key from test_cicd.yml; out of the header literal for gitleaks.
env:
TEST_API_KEY: "123456789"
run: |
curl --retry 90 --retry-delay 3 --retry-connrefused --retry-all-errors \
-sf -H "X-API-KEY: $TEST_API_KEY" http://localhost:8080/api/v1/info/status
# Heavy LibreOffice/Calibre/Ghostscript conversions, excluded from the PR run.
# Both tasks install the behave deps themselves, so there is no separate uv sync step.
- name: Run @nightly scenarios
run: task cucumber:nightly
# Genuinely different payloads contending on one backend.
- name: Sharded concurrency validation
run: task cucumber:parallel SHARDS=10
- name: Container logs on failure
if: failure()
run: docker compose -f docker/embedded/compose/test_cicd.yml logs --tail 400
- name: Tear down
if: always()
run: docker compose -f docker/embedded/compose/test_cicd.yml down -v
+3 -2
View File
@@ -28,7 +28,7 @@ jobs:
jar_sha256: ${{ steps.hashes.outputs.jar_sha256 }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -73,13 +73,14 @@ jobs:
echo "jar_sha256=$JAR_SHA" >> "$GITHUB_OUTPUT"
update-homebrew-and-scoop:
environment: package-publish
needs: get-release-info
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
+5 -12
View File
@@ -27,29 +27,22 @@ jobs:
name: Label conflicted PRs
runs-on: ubuntu-latest
permissions:
contents: read
issues: write
pull-requests: read
contents: read # actions/checkout
issues: write # get/create the repo-level conflict label
pull-requests: write # pulls.get/list plus add/remove the label on PRs
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Check out the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up stirling-bot token
id: setup-bot
uses: ./.github/actions/setup-bot
with:
app-id: ${{ secrets.GH_APP_ID }}
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Apply conflict label
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
with:
github-token: ${{ steps.setup-bot.outputs.token }}
github-token: ${{ github.token }}
script: |
const conflictLabel = process.env.CONFLICT_LABEL;
const owner = context.repo.owner;
+8 -4
View File
@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -25,16 +25,20 @@ jobs:
persist-credentials: false
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
cache-suffix: pre-commit
- name: Install Task
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Run pre-commit checks
run: task pre-commit
# The fixture corpus checks the comment rules themselves, so it runs here
# rather than on every local commit.
- name: Check the comment-lint fixture corpus
run: task pre-commit:comment-lint:selftest
+8 -3
View File
@@ -17,6 +17,9 @@ permissions:
jobs:
push-base:
# Own environment: docker-publish is branch-locked to release/main,
# which excludes the baseDockerImage/accessIssueFix branches this runs on.
environment: docker-base-publish
if: ${{ vars.CI_PROFILE != 'lite' && github.actor == 'Frooodle' }}
runs-on: ubuntu-24.04-8core
permissions:
@@ -32,9 +35,11 @@ jobs:
- name: Set version
id: version
env:
INPUT_VERSION: ${{ github.event.inputs.version }}
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
VERSION="${{ github.event.inputs.version }}"
VERSION="${INPUT_VERSION}"
elif [ "${{ github.ref_name }}" == "accessIssueFix" ]; then
VERSION="1.0.3"
else
@@ -43,7 +48,7 @@ jobs:
echo "version=${VERSION}" >> $GITHUB_OUTPUT
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -64,7 +69,7 @@ jobs:
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Set up QEMU
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0
+153 -30
View File
@@ -18,12 +18,20 @@ on:
required: false
type: boolean
default: false
build_engine:
description: "Build & push the standalone stirling-engine image."
required: false
type: boolean
default: true
force_engine_rebuild:
description: "Rebuild stirling-engine even if its source hash is unchanged."
required: false
type: boolean
default: false
push:
branches:
- master
- release
- main
- V2-master
- testMain
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
@@ -42,6 +50,7 @@ permissions:
jobs:
push:
environment: docker-publish
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-24.04-8core
permissions:
@@ -51,37 +60,35 @@ jobs:
env:
RUN_MAIN_APP: ${{ github.event_name != 'workflow_dispatch' || inputs.build_main_app }}
RUN_UNOSERVER: ${{ github.event_name != 'workflow_dispatch' || inputs.build_unoserver }}
RUN_ENGINE: ${{ github.event_name != 'workflow_dispatch' || inputs.build_engine }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-push-docker-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
@@ -91,13 +98,13 @@ jobs:
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
- name: Install cosign
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master'
if: github.ref == 'refs/heads/release'
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: "v2.4.1"
- name: Install cosign
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master'
if: github.ref == 'refs/heads/release'
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
with:
cosign-release: "v2.4.1"
@@ -133,8 +140,8 @@ jobs:
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }},enable=${{ github.ref == 'refs/heads/release' }}
type=raw,value=latest,enable=${{ github.ref == 'refs/heads/release' }}
- name: Build and push Unified Dockerfile (latest variant)
id: build-push-latest
@@ -158,7 +165,7 @@ jobs:
sbom: true
- name: Sign regular images
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master') && steps.build-push-latest.outputs.digest != ''
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/release') && steps.build-push-latest.outputs.digest != ''
env:
DIGEST: ${{ steps.build-push-latest.outputs.digest }}
TAGS: ${{ steps.meta.outputs.tags }}
@@ -182,8 +189,8 @@ jobs:
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-fat,enable=${{ github.ref == 'refs/heads/release' }}
type=raw,value=latest-fat,enable=${{ github.ref == 'refs/heads/release' }}
- name: Build and push Unified Dockerfile (fat variant)
id: build-push-fat
@@ -204,7 +211,7 @@ jobs:
sbom: true
- name: Sign fat images
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master') && steps.build-push-fat.outputs.digest != ''
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/release') && steps.build-push-fat.outputs.digest != ''
env:
DIGEST: ${{ steps.build-push-fat.outputs.digest }}
TAGS: ${{ steps.meta-fat.outputs.tags }}
@@ -226,8 +233,8 @@ jobs:
ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-pdf
${{ secrets.DOCKER_HUB_ORG_USERNAME }}/stirling-pdf
tags: |
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master' }}
type=raw,value=${{ steps.versionNumber.outputs.versionNumber }}-ultra-lite,enable=${{ github.ref == 'refs/heads/release' }}
type=raw,value=latest-ultra-lite,enable=${{ github.ref == 'refs/heads/release' }}
- name: Build and push Unified Dockerfile (ultra-lite variant)
id: build-push-lite
@@ -248,7 +255,7 @@ jobs:
sbom: true
- name: Sign ultra-lite images
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/master' || github.ref == 'refs/heads/V2-master') && steps.build-push-lite.outputs.digest != ''
if: env.RUN_MAIN_APP == 'true' && (github.ref == 'refs/heads/release') && steps.build-push-lite.outputs.digest != ''
env:
DIGEST: ${{ steps.build-push-lite.outputs.digest }}
TAGS: ${{ steps.meta-lite.outputs.tags }}
@@ -260,7 +267,7 @@ jobs:
done
# Standalone unoserver image — versioned independently via
# docker/unoserver/VERSION. master/V2-master: publish <version>+latest
# docker/unoserver/VERSION. release: publish <version>+latest
# only when the version is new. main/testMain: republish :alpha only
# when the source hash differs from the published image's annotation.
- name: Read unoserver image version
@@ -319,7 +326,7 @@ jobs:
fi
case "$EFFECTIVE_REF" in
refs/heads/master|refs/heads/V2-master)
refs/heads/release)
if [ "${FORCE_REBUILD}" = "true" ]; then
echo "force_unoserver_rebuild=true — building stable regardless"
mode="stable"
@@ -391,3 +398,119 @@ jobs:
else
echo "Warning: COSIGN_PRIVATE_KEY not set, skipping unoserver image signing"
fi
# Standalone AI engine image, same shape as the unoserver image above.
- name: Compute engine image source hash
id: engineHash
if: env.RUN_ENGINE == 'true'
run: |
set -eu
hash=$( { cat engine/Dockerfile engine/pyproject.toml engine/uv.lock engine/.env; \
find engine/src -type f -print0 | sort -z | xargs -0 cat; } \
| sha256sum | cut -d' ' -f1)
echo "hash=${hash}" >> "$GITHUB_OUTPUT"
echo "Engine source hash: ${hash}"
- name: Decide whether to publish engine image
id: engineDecision
if: env.RUN_ENGINE == 'true'
env:
ENGINE_VERSION: ${{ steps.versionNumber.outputs.versionNumber }}
ENGINE_HASH: ${{ steps.engineHash.outputs.hash }}
ENGINE_IMAGE: ghcr.io/${{ steps.repoowner.outputs.lowercase }}/stirling-engine
ENGINE_HASH_ANNOTATION: org.stirlingpdf.engine-source-hash
FORCE_REBUILD: ${{ inputs.force_engine_rebuild }}
GH_REF: ${{ github.ref }}
EVENT_NAME: ${{ github.event_name }}
run: |
set -eu
mode="skip"
tags=""
read_published_hash() {
local ref="$1"
docker buildx imagetools inspect "$ref" --raw 2>/dev/null \
| jq -r --arg key "$ENGINE_HASH_ANNOTATION" \
'.annotations[$key] // empty' \
2>/dev/null || true
}
# Manual dispatch from any branch routes to the :alpha publish path.
EFFECTIVE_REF="$GH_REF"
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
EFFECTIVE_REF="refs/heads/testMain"
fi
case "$EFFECTIVE_REF" in
refs/heads/release)
if [ "${FORCE_REBUILD}" = "true" ]; then
echo "force_engine_rebuild=true — building stable regardless"
mode="stable"
tags="${ENGINE_IMAGE}:${ENGINE_VERSION},${ENGINE_IMAGE}:latest"
elif docker manifest inspect "${ENGINE_IMAGE}:${ENGINE_VERSION}" >/dev/null 2>&1; then
echo "stirling-engine:${ENGINE_VERSION} already on GHCR — skipping"
else
echo "stirling-engine:${ENGINE_VERSION} is new — will publish"
mode="stable"
tags="${ENGINE_IMAGE}:${ENGINE_VERSION},${ENGINE_IMAGE}:latest"
fi
;;
refs/heads/main|refs/heads/testMain)
published_hash=$(read_published_hash "${ENGINE_IMAGE}:alpha")
if [ "${FORCE_REBUILD}" = "true" ]; then
echo "force_engine_rebuild=true — rebuilding :alpha regardless"
mode="alpha"
tags="${ENGINE_IMAGE}:alpha"
elif [ -n "$published_hash" ] && [ "$published_hash" = "$ENGINE_HASH" ]; then
echo "Published :alpha source hash matches (${published_hash}) — skipping"
else
if [ -z "$published_hash" ]; then
echo ":alpha has no source-hash annotation (first publish) — will publish"
else
echo "Source hash changed (was ${published_hash}, now ${ENGINE_HASH}) — will publish"
fi
mode="alpha"
tags="${ENGINE_IMAGE}:alpha"
fi
;;
*)
echo "Branch ${GH_REF} does not publish engine image"
;;
esac
echo "mode=${mode}" >> "$GITHUB_OUTPUT"
echo "tags=${tags}" >> "$GITHUB_OUTPUT"
- name: Build and push engine image
id: build-push-engine
if: env.RUN_ENGINE == 'true' && steps.engineDecision.outputs.mode != 'skip'
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
builder: ${{ steps.buildx.outputs.name }}
context: .
file: ./engine/Dockerfile
push: true
cache-from: type=gha,scope=stirling-engine
cache-to: type=gha,mode=max,scope=stirling-engine
tags: ${{ steps.engineDecision.outputs.tags }}
# Manifest annotation read by the decision step above to detect drift.
annotations: |
index:org.stirlingpdf.engine-source-hash=${{ steps.engineHash.outputs.hash }}
platforms: linux/amd64,linux/arm64/v8
provenance: true
sbom: true
- name: Sign engine image
if: env.RUN_ENGINE == 'true' && steps.engineDecision.outputs.mode == 'stable'
env:
DIGEST: ${{ steps.build-push-engine.outputs.digest }}
TAGS: ${{ steps.engineDecision.outputs.tags }}
COSIGN_PRIVATE_KEY: ${{ secrets.COSIGN_PRIVATE_KEY }}
COSIGN_PASSWORD: ${{ secrets.COSIGN_PASSWORD }}
run: |
if [ -n "$COSIGN_PRIVATE_KEY" ]; then
echo "$TAGS" | tr ',' '\n' | while read -r tag; do
cosign sign --key env://COSIGN_PRIVATE_KEY --yes "${tag}@${DIGEST}"
done
else
echo "Warning: COSIGN_PRIVATE_KEY not set, skipping engine image signing"
fi
+2 -1
View File
@@ -13,12 +13,13 @@ permissions:
jobs:
rollback:
environment: docker-publish
runs-on: ubuntu-latest
permissions:
packages: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
+2 -2
View File
@@ -35,7 +35,7 @@ jobs:
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -75,6 +75,6 @@ jobs:
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@e4fba868fa4b1b91e1fdab776edc8cfbe6e9fb81 # v3.29.5
uses: github/codeql-action/upload-sarif@db488ddef3bf6cb639b32c2e9a7c0a7ea8271d28 # v4.37.8
with:
sarif_file: results.sarif
+2 -2
View File
@@ -17,12 +17,12 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: 30 days stale issues
uses: actions/stale@eb5cf3af3ac0a1aa4c9c45633dd1ae542a27a899 # v10.3.0
uses: actions/stale@4391f3da665fdf50b6810c1a66712fb9ba21aa93 # v11.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 30
+14 -14
View File
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
push:
branches:
- master
- release
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
@@ -23,33 +23,33 @@ permissions:
jobs:
push:
# package-publish holds SWAGGERHUB_API_KEY. It requires reviewer approval and
# is limited to main / release / v* tags, so every push to release waits on one.
environment: package-publish
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Cache Gradle
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-swagger-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Generate Swagger documentation
run: ./gradlew :stirling-pdf:generateOpenApiDocs
@@ -63,7 +63,7 @@ jobs:
SWAGGERHUB_USER: "Frooodle"
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Get version number
id: versionNumber
run: echo "versionNumber=$(./gradlew printVersion --quiet | tail -1)" >> $GITHUB_OUTPUT
+2 -1
View File
@@ -24,6 +24,7 @@ permissions:
jobs:
sync:
environment: bot-identity
name: Sync docs manifest
runs-on: ubuntu-latest
timeout-minutes: 10
@@ -32,7 +33,7 @@ jobs:
pull-requests: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
+4 -4
View File
@@ -33,10 +33,11 @@ permissions:
jobs:
sync-files:
environment: bot-identity
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -52,20 +53,19 @@ jobs:
private-key: ${{ secrets.GH_APP_PRIVATE_KEY }}
- name: Install uv
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
with:
enable-cache: true
cache-dependency-glob: |
engine/pyproject.toml
engine/uv.lock
cache-suffix: sync-files
- name: Install Python dependencies
run: |
uv sync --project engine --locked --group tools
- name: Install Task
uses: go-task/setup-task@3be4020d41929789a01026e0e427a4321ce0ad44 # v2.0.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Sync translation TOML files
run: |
+61 -55
View File
@@ -26,6 +26,10 @@ on:
required: false
type: boolean
default: false
use_shared_cache:
required: false
type: boolean
default: false
workflow_dispatch:
inputs:
platform:
@@ -57,13 +61,18 @@ permissions:
jobs:
determine-matrix:
# Only probes APPLE_CERTIFICATE for presence, so it stays on the unrestricted
# signing environment - release-signing would block every PR run.
environment:
name: ci-signing
deployment: false
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.set-matrix.outputs.matrix }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -103,6 +112,12 @@ jobs:
echo "matrix={\"include\":[$JOINED]}" >> $GITHUB_OUTPUT
build:
# Windows/GPG signing only runs on main (see the per-step gates below), so only
# that path needs the reviewer-gated release-signing environment. Everything else
# (PRs, merge queue, nightly) signs macOS only and uses ci-signing, which has no
# approval or branch restriction.
environment:
name: ${{ (inputs.sign && (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/tags/v'))) && 'release-signing' || 'ci-signing' }}
needs: determine-matrix
strategy:
fail-fast: false
@@ -110,7 +125,6 @@ jobs:
runs-on: ${{ matrix.platform }}
env:
SM_API_KEY: ${{ secrets.SM_API_KEY }}
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
RELEASE_GPG_PRIVATE_KEY: ${{ secrets.RELEASE_GPG_PRIVATE_KEY }}
# Per-platform sign gate. macOS signs on any run with the cert available,
@@ -120,7 +134,7 @@ jobs:
SIGN_BUNDLE: ${{ inputs.sign && (matrix.platform == 'macos-15' && secrets.APPLE_CERTIFICATE != '' || github.ref == 'refs/heads/main') }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -150,7 +164,7 @@ jobs:
# only recompiles the app crate. Written on main; PRs and the merge queue
# restore from it.
- name: Cache Rust build
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
with:
workspaces: frontend/editor/src-tauri
# Stable key shared across workflows so the nightly warmer.
@@ -160,6 +174,24 @@ jobs:
# Save the dependency cache even if a later step fails
cache-on-failure: true
- name: Restore cache Gradle User Home
if: inputs.use_shared_cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Restore cache Gradle
if: inputs.use_shared_cache == false
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-tauri-build-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up x86_64 JDK 25 (macOS universal JRE)
if: matrix.platform == 'macos-15'
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
@@ -179,19 +211,8 @@ jobs:
java-version: "25"
distribution: ${{ matrix.platform == 'windows-11-arm' && 'microsoft' || 'temurin' }}
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Setup Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Build universal macOS JRE
if: matrix.platform == 'macos-15'
@@ -227,20 +248,26 @@ jobs:
- name: Setup DigiCert KeyLocker Certificate
if: ${{ inputs.sign && startsWith(matrix.platform, 'windows') && env.SM_API_KEY != '' && github.ref == 'refs/heads/main' }}
shell: pwsh
env:
SM_CLIENT_CERT_FILE_B64: ${{ secrets.SM_CLIENT_CERT_FILE_B64 }}
SM_HOST: ${{ secrets.SM_HOST }}
SM_API_KEY: ${{ secrets.SM_API_KEY }}
SM_CLIENT_CERT_PASSWORD: ${{ secrets.SM_CLIENT_CERT_PASSWORD }}
SM_KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
run: |
Write-Host "Setting up DigiCert KeyLocker environment..."
# Decode client certificate
$certBytes = [Convert]::FromBase64String("${{ secrets.SM_CLIENT_CERT_FILE_B64 }}")
$certBytes = [Convert]::FromBase64String("$env:SM_CLIENT_CERT_FILE_B64")
$certPath = "D:\Certificate_pkcs12.p12"
[IO.File]::WriteAllBytes($certPath, $certBytes)
# Set environment variables
echo "SM_CLIENT_CERT_FILE=D:\Certificate_pkcs12.p12" >> $env:GITHUB_ENV
echo "SM_HOST=${{ secrets.SM_HOST }}" >> $env:GITHUB_ENV
echo "SM_API_KEY=${{ secrets.SM_API_KEY }}" >> $env:GITHUB_ENV
echo "SM_CLIENT_CERT_PASSWORD=${{ secrets.SM_CLIENT_CERT_PASSWORD }}" >> $env:GITHUB_ENV
echo "SM_KEYPAIR_ALIAS=${{ secrets.SM_KEYPAIR_ALIAS }}" >> $env:GITHUB_ENV
echo "SM_HOST=$env:SM_HOST" >> $env:GITHUB_ENV
echo "SM_API_KEY=$env:SM_API_KEY" >> $env:GITHUB_ENV
echo "SM_CLIENT_CERT_PASSWORD=$env:SM_CLIENT_CERT_PASSWORD" >> $env:GITHUB_ENV
echo "SM_KEYPAIR_ALIAS=$env:SM_KEYPAIR_ALIAS" >> $env:GITHUB_ENV
# Get PKCS11 config path from DigiCert action
$pkcs11Config = $env:PKCS11_CONFIG
@@ -258,38 +285,6 @@ jobs:
}
}
# Traditional PFX Certificate Import (fallback if KeyLocker not configured)
- name: Import Windows Code Signing Certificate
if: ${{ inputs.sign && startsWith(matrix.platform, 'windows') && env.SM_API_KEY == '' && github.ref == 'refs/heads/main' }}
env:
WINDOWS_CERTIFICATE: ${{ secrets.WINDOWS_CERTIFICATE }}
WINDOWS_CERTIFICATE_PASSWORD: ${{ secrets.WINDOWS_CERTIFICATE_PASSWORD }}
shell: powershell
run: |
if ($env:WINDOWS_CERTIFICATE) {
Write-Host "Importing Windows Code Signing Certificate..."
# Decode base64 certificate and save to file
$certBytes = [Convert]::FromBase64String($env:WINDOWS_CERTIFICATE)
$certPath = Join-Path $env:RUNNER_TEMP "certificate.pfx"
[IO.File]::WriteAllBytes($certPath, $certBytes)
# Import certificate to CurrentUser\My store
$cert = Import-PfxCertificate -FilePath $certPath -CertStoreLocation Cert:\CurrentUser\My -Password (ConvertTo-SecureString -String $env:WINDOWS_CERTIFICATE_PASSWORD -AsPlainText -Force)
# Extract and set thumbprint as environment variable
$thumbprint = $cert.Thumbprint
Write-Host "Certificate imported with thumbprint: $thumbprint"
echo "WINDOWS_CERTIFICATE_THUMBPRINT=$thumbprint" >> $env:GITHUB_ENV
# Clean up certificate file
Remove-Item $certPath
Write-Host "Windows certificate import completed."
} else {
Write-Host "⚠️ WINDOWS_CERTIFICATE secret not set - building unsigned binary"
}
- name: Import Apple Developer Certificate
if: env.SIGN_BUNDLE == 'true' && matrix.platform == 'macos-15'
env:
@@ -682,6 +677,17 @@ jobs:
fi
done
- name: Cleanup temporary files
if: always()
shell: bash
run: |
rm -f certificate.p12
rm -rf "$RUNNER_TEMP/msi-verify"
if [ "${{ matrix.platform }}" = "macos-15" ]; then
security delete-keychain "$RUNNER_TEMP/app-signing.keychain-db" 2>/dev/null || true
fi
continue-on-error: true
pr-comment:
needs: build
runs-on: ubuntu-latest
@@ -697,7 +703,7 @@ jobs:
pull-requests: write
steps:
- name: Harden the runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -789,7 +795,7 @@ jobs:
if: always()
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
+61 -35
View File
@@ -22,21 +22,45 @@ permissions:
contents: read
jobs:
# TODO: extract a pre-matrix `prepare` job that runs once and produces
# shared artifacts for the three matrix entries below to consume:
# 1. `task backend:build` — currently runs 3× in parallel with
# identical env (DISABLE_ADDITIONAL_FEATURES=true,
# STIRLING_PDF_DESKTOP_UI=false). Build once, upload the JAR as an
# artifact, matrix entries download.
# 2. The base-image `docker build` (gated on docker-base-changed) —
# currently runs 3× in parallel against the same Dockerfile and
# context. Build once, `docker save` to an artifact, matrix entries
# `docker load` before the embedded build.
# Saves ~2 full backend builds + 2 base-image builds per PR that touches
# docker. May also be reusable from backend-build.yml's jdk-25 +
# spring-security=true matrix entry if `task backend:build` and
# `task backend:build:ci` produce equivalent JARs (verify before wiring).
# A changed base image is shared by all three embedded-image builds. Build
# it once and transfer it as an artifact; the matrix jobs use the local
# Docker driver so the loaded image is visible to the build.
prepare-base-image:
if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true'
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout Repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Build base image locally
run: docker build --platform linux/amd64 -t stirling-pdf-base:pr-test -f docker/base/Dockerfile docker/base
- name: Export base image
run: docker save stirling-pdf-base:pr-test | gzip -1 > stirling-pdf-base-pr-test.tar.gz
- name: Upload base image
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: docker-base-pr-test
path: stirling-pdf-base-pr-test.tar.gz
retention-days: 1
if-no-files-found: error
test-build-docker-images:
if: always() && (needs.prepare-base-image.result == 'success' || needs.prepare-base-image.result == 'skipped')
needs: [prepare-base-image]
environment:
name: ci-unsigned
deployment: false
runs-on: ubuntu-latest
strategy:
fail-fast: false
@@ -53,7 +77,7 @@ jobs:
cache-scope: stirling-pdf-fat
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -78,25 +102,32 @@ jobs:
docker system prune -af || true
echo "Disk space after cleanup:" && df -h
- name: Download prepared base image
if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true'
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: docker-base-pr-test
- name: Load prepared base image
if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true'
run: gzip -dc stirling-pdf-base-pr-test.tar.gz | docker load
- name: Restore cache Gradle User Home
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-v1-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'buildSrc/**', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Install Task
uses: go-task/setup-task@01a4adf9db2d14c1de7a560f09170b6e0df736aa # v2.1.0
uses: go-task/setup-task@a00fbb05ce67b35648be3c78cbc9fd85354c757e # v2.2.0
- name: Build application
run: task backend:build
env:
@@ -111,12 +142,7 @@ jobs:
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Build base image locally (PR base change only)
if: github.event_name == 'pull_request' && inputs.docker-base-changed == 'true'
run: |
docker build -t stirling-pdf-base:pr-test -f docker/base/Dockerfile docker/base
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Set base image and platform for this build
id: build-params
@@ -191,7 +217,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
@@ -203,7 +229,7 @@ jobs:
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Build docker/unoserver/Dockerfile
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
-213
View File
@@ -1,213 +0,0 @@
name: UI test with TestDriverAI
on:
push:
branches: ["master", "UITest", "testdriver"]
# cancel in-progress jobs if a new job is triggered
# This is useful to avoid running multiple builds for the same branch if a new commit is pushed
# or a pull request is updated.
# It helps to save resources and time by ensuring that only the latest commit is built and tested
# This is particularly useful for long-running jobs that may take a while to complete.
# The `group` is set to a combination of the workflow name, event name, and branch name.
# This ensures that jobs are grouped by the workflow and branch, allowing for cancellation of
# in-progress jobs when a new commit is pushed to the same branch or a new pull request is opened.
concurrency:
group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref_name || github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
deploy:
if: ${{ vars.CI_PROFILE != 'lite' }}
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up JDK 25
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
java-version: "25"
distribution: "temurin"
- name: Cache Gradle User Home
uses: actions/cache@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
~/.gradle/caches
~/.gradle/wrapper
key: gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties', 'gradle/libs.versions.toml', 'settings.gradle', 'build.gradle', 'app/**/build.gradle', 'gradle/**/*.gradle') }}
restore-keys: |
gradle-${{ runner.os }}-${{ runner.arch }}-jdk-25-
gradle-${{ runner.os }}-${{ runner.arch }}-
- name: Build with Gradle
run: ./gradlew build
env:
MAVEN_USER: ${{ secrets.MAVEN_USER }}
MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }}
MAVEN_PUBLIC_URL: ${{ secrets.MAVEN_PUBLIC_URL }}
DISABLE_ADDITIONAL_FEATURES: true
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0
- name: Get version number
id: versionNumber
run: |
VERSION=$(grep "^version =" build.gradle | awk -F'"' '{print $2}')
echo "versionNumber=$VERSION" >> $GITHUB_OUTPUT
- name: Login to Docker Hub
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
username: ${{ secrets.DOCKER_HUB_USERNAME }}
password: ${{ secrets.DOCKER_HUB_API }}
- name: Build and push test image
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: ./docker/embedded/Dockerfile
push: true
cache-from: type=gha,scope=stirling-pdf-latest
cache-to: type=gha,mode=max,scope=stirling-pdf-latest
tags: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }}
build-args: VERSION_TAG=${{ steps.versionNumber.outputs.versionNumber }}
platforms: linux/amd64
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Deploy to VPS
run: |
cat > docker-compose.yml << EOF
version: '3.3'
services:
stirling-pdf:
container_name: stirling-pdf-test-${{ github.sha }}
image: ${{ secrets.DOCKER_HUB_USERNAME }}/test:test-${{ github.sha }}
ports:
- "1337:8080"
volumes:
- /stirling/test-${{ github.sha }}/data:/usr/share/tessdata:rw
- /stirling/test-${{ github.sha }}/config:/configs:rw
- /stirling/test-${{ github.sha }}/logs:/logs:rw
environment:
DISABLE_ADDITIONAL_FEATURES: "true"
SECURITY_ENABLELOGIN: "false"
SYSTEM_DEFAULTLOCALE: en-US
UI_APPNAME: "Stirling-PDF Test"
UI_HOMEDESCRIPTION: "Test Deployment"
UI_APPNAMENAVBAR: "Test"
SYSTEM_MAXFILESIZE: "100"
METRICS_ENABLED: "true"
SYSTEM_GOOGLEVISIBILITY: "false"
SYSTEM_ENABLEANALYTICS: "false"
restart: on-failure:5
EOF
scp -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null docker-compose.yml ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }}:/tmp/docker-compose.yml
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << EOF
mkdir -p /stirling/test-${{ github.sha }}/{data,config,logs}
mv /tmp/docker-compose.yml /stirling/test-${{ github.sha }}/docker-compose.yml
cd /stirling/test-${{ github.sha }}
docker-compose pull
docker-compose up -d
EOF
files-changed:
if: always()
name: detect what files changed
runs-on: ubuntu-latest
timeout-minutes: 3
outputs:
frontend: ${{ steps.changes.outputs.frontend }}
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Check for file changes
uses: dorny/paths-filter@7b450fff21473bca461d4b92ce414b9d0420d706 # v4.0.2
id: changes
with:
filters: ".github/config/.files.yaml"
test:
if: needs.files-changed.outputs.frontend == 'true'
needs: [deploy, files-changed]
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Node
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
cache: "npm"
cache-dependency-path: frontend/package-lock.json
- name: Run TestDriver.ai
uses: testdriverai/action@f0d0f45fdd684db628baa843fe9313f3ca3a8aa8 #1.1.3
with:
key: ${{secrets.TESTDRIVER_API_KEY}}
prerun: |
choco install go-task -y
task frontend:build
cd frontend
npm install dashcam-chrome --save
Start-Process "C:/Program Files/Google/Chrome/Application/chrome.exe" -ArgumentList "--start-maximized", "--load-extension=$(pwd)/node_modules/dashcam-chrome/build", "http://${{ secrets.NEW_VPS_HOST }}:1337"
Start-Sleep -Seconds 20
prompt: |
1. /run testing/testdriver/test.yml
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
FORCE_COLOR: "3"
cleanup:
needs: [deploy, test]
runs-on: ubuntu-latest
if: always()
steps:
- name: Harden Runner
uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0
with:
egress-policy: audit
- name: Set up SSH
run: |
mkdir -p ~/.ssh/
echo "${{ secrets.NEW_VPS_SSH_KEY }}" > ../private.key
sudo chmod 600 ../private.key
- name: Cleanup deployment
if: always()
run: |
ssh -i ../private.key -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ${{ secrets.NEW_VPS_USERNAME }}@${{ secrets.NEW_VPS_HOST }} << EOF
cd /stirling/test-${{ github.sha }}
docker-compose down
cd /stirling
rm -rf test-${{ github.sha }}
EOF
continue-on-error: true # Ensure cleanup runs even if previous steps fail
+112
View File
@@ -0,0 +1,112 @@
name: Update Gradle
on:
workflow_dispatch:
schedule:
- cron: "0 3 * * 1"
concurrency:
group: update-gradle
cancel-in-progress: true
jobs:
update-gradle:
name: Update Gradle and Docker images
permissions:
contents: write
pull-requests: write
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Harden runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Check out repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set up Java
uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5.7.0
with:
distribution: temurin
java-version: "25"
- name: Find latest Gradle release
id: gradle
shell: bash
run: |
set -euo pipefail
version=$(curl --fail --silent --show-error --retry 3 \
https://services.gradle.org/versions/current | jq -r '.version')
[[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "Could not determine a stable Gradle version: $version" >&2
exit 1
}
echo "version=$version" >> "$GITHUB_OUTPUT"
- name: Find matching Docker image digest
id: docker
env:
GRADLE_VERSION: ${{ steps.gradle.outputs.version }}
shell: bash
run: |
set -euo pipefail
tag="${GRADLE_VERSION}-jdk25"
digest=$(curl --fail --silent --show-error --retry 3 \
"https://hub.docker.com/v2/repositories/library/gradle/tags/${tag}" \
| jq -r '.digest // empty')
[[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || {
echo "Docker image gradle:${tag} was not found" >&2
exit 1
}
echo "tag=$tag" >> "$GITHUB_OUTPUT"
echo "digest=$digest" >> "$GITHUB_OUTPUT"
- name: Update Gradle wrapper
env:
GRADLE_VERSION: ${{ steps.gradle.outputs.version }}
run: ./gradlew wrapper --gradle-version "$GRADLE_VERSION" --distribution-type bin
- name: Update Gradle Docker images
env:
DOCKER_TAG: ${{ steps.docker.outputs.tag }}
DOCKER_DIGEST: ${{ steps.docker.outputs.digest }}
shell: bash
run: |
set -euo pipefail
find docker -type f -name 'Dockerfile*' -print0 |
xargs -0 sed -E -i \
"s#gradle:[^@[:space:]]+-jdk25(@sha256:[^[:space:]]+)?#gradle:${DOCKER_TAG}@${DOCKER_DIGEST}#g"
- name: Verify Gradle update
env:
EXPECTED_VERSION: ${{ steps.gradle.outputs.version }}
shell: bash
run: |
set -euo pipefail
actual=$(./gradlew --version | sed -n 's/^Gradle \([0-9.]*\)$/\1/p')
[[ "$actual" == "$EXPECTED_VERSION" ]] || {
echo "Wrapper resolved Gradle $actual, expected $EXPECTED_VERSION" >&2
exit 1
}
if git diff --quiet; then
echo "Gradle is already up to date."
exit 0
fi
git diff --check
- name: Create pull request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.GITHUB_TOKEN }}
branch: automation/update-gradle
delete-branch: true
commit-message: "chore: update Gradle"
title: "chore: update Gradle to ${{ steps.gradle.outputs.version }}"
body: |
Automated update of the Gradle wrapper and Gradle Docker build images.
Gradle version: `${{ steps.gradle.outputs.version }}`
Docker image: `gradle:${{ steps.docker.outputs.tag }}`
labels: dependencies
+22 -2
View File
@@ -26,6 +26,7 @@ watchedFolders/
# also matches this frontend source component dir; keep the source tracked.
!frontend/editor/src/proprietary/components/watchedFolders/
clientWebUI/
policy-webhook-spool/
# Scratch dir used by local fixture-regeneration runs (see
# app/proprietary/src/test/resources/db-migration-fixtures/README.md).
# Holds downloaded JARs and disposable workdirs. Never committed.
@@ -38,6 +39,7 @@ exampleYmlFiles/stirling/
/testing/file_snapshots
/testing/cucumber/junit/
/testing/cucumber/report.html
/testing/cucumber/.parallel/
/testing/.failed_tests
/.test-state/
SwaggerDoc.json
@@ -63,13 +65,23 @@ app/core/src/main/resources/static/og_images/
app/core/src/main/resources/static/samples/
app/core/src/main/resources/static/manifest-classic.json
app/core/src/main/resources/static/og-metadata.json
app/core/src/main/resources/static/og-metadata.saas.json
app/core/src/main/resources/static/sw-folder-retry.js
app/core/src/main/resources/static/robots.txt
app/core/src/main/resources/static/android-chrome-*.png
app/core/src/main/resources/static/mstile-*.png
app/core/src/main/resources/static/favicon.png
app/core/src/main/resources/static/safari-pinned-tab.svg
app/core/src/main/resources/static/pdfium/
app/core/src/main/resources/static/pdfjs/
app/core/src/main/resources/static/vendor/
app/core/src/main/resources/static/**/*.gz
app/core/src/main/resources/static/**/*.br
app/core/src/main/resources/static/css/cookieconsent.css
app/core/src/main/resources/static/css/cookieconsentCustomisation.css
app/core/src/main/resources/static/mockServiceWorker.js
app/core/src/main/resources/static/js/thirdParty/cookieconsent.umd.js
app/core/src/main/resources/static/images/google-drive.svg
# Note: Keep backend-managed files like fonts/, css/, js/, pdfjs/, etc.
# Gradle
@@ -164,6 +176,8 @@ app/core/src/main/resources/static/**/*.br
*.nar
*.ear
*.zip
# Real backend archives the form-bundle reader is tested against.
!frontend/editor/src/core/tools/formFill/__fixtures__/*.zip
*.tar.gz
*.rar
*.db
@@ -284,8 +298,13 @@ docs/type3/signatures/
**/application-dev-local.properties
# Claude
.claude/
# Claude. Contents are ignored so personal config stays local, with the two
# shared pieces re-included: settings.json (the comment-lint hook) and skills/.
# The directory itself cannot be ignored or git will not look inside it.
.claude/*
!.claude/settings.json
!.claude/skills/
.claude/settings.local.json
# Playwright MCP screenshots / traces
.playwright-mcp/
@@ -293,3 +312,4 @@ docs/type3/signatures/
# Local screenshot artifacts from *-screenshots.spec.ts
frontend/editor/screenshots/
frontend/editor/src-tauri/libs/.variant
+5
View File
@@ -27,3 +27,8 @@ app/core/src/main/java/stirling/software/SPDF/pdf/signature/CreateSignatureBase.
# Supabase publishable key (public by design, RLS-protected) used as a CI fallback
# default in the tauri-build workflow when the GitHub secret is unset - not a real secret.
.github/workflows/tauri-build.yml:generic-api-key:402
# Staging Supabase publishable key (public by design). Ignored here rather than with an
# inline gitleaks:allow because a trailing comment in a .properties file is part of the
# value, so the pragma would end up inside the key.
app/saas/src/main/resources/application-staging.properties:generic-api-key:16
+3 -1
View File
@@ -1,5 +1,7 @@
{
"ignoredFiles": [
"frontend/editor/src-tauri/icons/icon.png"
"frontend/editor/src-tauri/icons/macos/*",
"frontend/editor/src-tauri/icons/linux/*",
"frontend/editor/src-tauri/icons/windows/*"
]
}
+110 -8
View File
@@ -40,12 +40,15 @@ tasks:
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED | default "false"}}'
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS | default "120"}}'
SECURITY_ENABLELOGIN: '{{.SECURITY_ENABLELOGIN | default ""}}'
# Set by dev:linked. Inline rather than in `env:` so an empty value emits nothing
# and cannot blank the committed default.
ACCOUNT_LINK_SAAS_BASE_URL: '{{.ACCOUNT_LINK_SAAS_BASE_URL | default ""}}'
env:
SERVER_PORT: '{{.PORT}}'
cmds:
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED={{.AIENGINE_ENABLED}} AIENGINE_TIMEOUTSECONDS={{.AIENGINE_TIMEOUTSECONDS}} {{end}}{{if .SECURITY_ENABLELOGIN}}SECURITY_ENABLELOGIN={{.SECURITY_ENABLELOGIN}} {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED={{.AIENGINE_ENABLED}} AIENGINE_TIMEOUTSECONDS={{.AIENGINE_TIMEOUTSECONDS}} {{end}}{{if .SECURITY_ENABLELOGIN}}SECURITY_ENABLELOGIN={{.SECURITY_ENABLELOGIN}} {{end}}{{if .ACCOUNT_LINK_SAAS_BASE_URL}}STIRLING_BILLING_ACCOUNT_LINK_ENABLED=true STIRLING_BILLING_ACCOUNT_LINK_SAAS_BASE_URL={{.ACCOUNT_LINK_SAAS_BASE_URL}} {{end}}cmd /c ".\gradlew.bat :stirling-pdf:bootRun"'
platforms: [windows]
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED={{.AIENGINE_ENABLED}} AIENGINE_TIMEOUTSECONDS={{.AIENGINE_TIMEOUTSECONDS}} {{end}}{{if .SECURITY_ENABLELOGIN}}SECURITY_ENABLELOGIN={{.SECURITY_ENABLELOGIN}} {{end}}./gradlew :stirling-pdf:bootRun'
- cmd: '{{if .AIENGINE_URL}}AIENGINE_URL={{.AIENGINE_URL}} AIENGINE_ENABLED={{.AIENGINE_ENABLED}} AIENGINE_TIMEOUTSECONDS={{.AIENGINE_TIMEOUTSECONDS}} {{end}}{{if .SECURITY_ENABLELOGIN}}SECURITY_ENABLELOGIN={{.SECURITY_ENABLELOGIN}} {{end}}{{if .ACCOUNT_LINK_SAAS_BASE_URL}}STIRLING_BILLING_ACCOUNT_LINK_ENABLED=true STIRLING_BILLING_ACCOUNT_LINK_SAAS_BASE_URL={{.ACCOUNT_LINK_SAAS_BASE_URL}} {{end}}./gradlew :stirling-pdf:bootRun'
platforms: [linux, darwin]
dev:bundled:
@@ -57,29 +60,128 @@ tasks:
- cmd: ./gradlew clean bootRun -PbuildWithFrontend=true
platforms: [linux, darwin]
# SaaS backend. dev:saas -> the PR's preview branch, staging:saas -> shared v3,
# PROFILES=none -> production against your own SAAS_DB_*. Production has no named
# task on purpose. Use `none`, not an empty value: Go template `default` treats ""
# as absent and would resolve back to dev.
dev:saas:
desc: "Start backend in SaaS flavor against Supabase"
# `dotenv:` reads from the root Taskfile's directory (".") because this
# subtaskfile is included with `dir: .`.
desc: "Start SaaS backend against the current PR's Supabase preview branch"
dotenv: ['app/.env.saas.local', 'app/.env.saas']
vars:
PROFILES: '{{.PROFILES | default "dev"}}'
cmds:
# Don't move this check into a `sh:` var: dotenv is visible in cmds but not
# during var evaluation, so the test would always see an empty value.
- cmd: |
if [ "{{.PROFILES}}" = "dev" ] && [ -z "${SAAS_DEV_PROJECT_REF:-}" ]; then
echo ">> SAAS_DEV_PROJECT_REF is not set."
echo ">> Testing a SaaS PR? Put its ref, DB password and publishable key in app/.env.saas.local."
echo ">> Wanted the shared v3 project? Use 'task backend:staging:saas' instead."
exit 1
fi
- task: _run:saas
vars:
PORT: '{{.PORT}}'
PROFILES: '{{.PROFILES}}'
AIENGINE_URL: '{{.AIENGINE_URL}}'
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
APP_BASE_URL: '{{.APP_BASE_URL}}'
BASE_PATH: '{{.BASE_PATH}}'
staging:saas:
desc: "Start SaaS backend against the shared v3 staging project"
cmds:
- task: _run:saas
vars:
PORT: '{{.PORT}}'
PROFILES: staging
AIENGINE_URL: '{{.AIENGINE_URL}}'
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
APP_BASE_URL: '{{.APP_BASE_URL}}'
BASE_PATH: '{{.BASE_PATH}}'
dev:linked:
desc: "Self-hosted backend linked to a locally running SaaS backend (see task linked:*)"
ignore_error: true
vars:
PORT: '{{.PORT | default "8080"}}'
SAAS_BASE_URL: '{{.SAAS_BASE_URL | default "http://localhost:8081"}}'
cmds:
- 'echo ">> self-hosted :{{.PORT}} linking to SaaS at {{.SAAS_BASE_URL}}"'
# The two backends run different STIRLING_FLAVOURs, which are different Gradle
# project graphs sharing one build/ tree. Waiting avoids overlapping builds; it
# does not make the sharing safe, so avoid rebuilding one while the other runs.
- cmd: |
n=0
while [ "$n" -lt 150 ]; do
if curl -s -m 2 "{{.SAAS_BASE_URL}}" >/dev/null 2>&1; then
echo ">> SaaS backend is up, starting self-hosted"
break
fi
n=$((n + 1))
{{if eq OS "windows"}}powershell -NoProfile -Command "Start-Sleep -Seconds 2"{{else}}sleep 2{{end}}
done
if [ "$n" -ge 150 ]; then
echo ">> SaaS backend never answered; starting anyway"
fi
- task: dev:proprietary
vars:
PORT: '{{.PORT}}'
ACCOUNT_LINK_SAAS_BASE_URL: '{{.SAAS_BASE_URL}}'
_run:saas:
internal: true
# The frontend files are here only for RUN_SUBPATH, which the authorize URL needs.
# Last, because dotenv is set-if-absent: app/* still decides everything else.
dotenv:
- 'app/.env.saas.local'
- 'app/.env.saas'
- 'frontend/editor/.env.saas.local'
- 'frontend/editor/.env.saas'
ignore_error: true
vars:
PORT: '{{.PORT | default "8080"}}'
# Override to "" to run the pure `saas` profile against your own SAAS_DB_*.
PROFILES: '{{.PROFILES | default "dev"}}'
# Built here rather than inline in the cmds below: the Windows line is an
# unquoted YAML scalar wrapping a cmd.exe string, so a nested {{if ne .X
# "none"}} needs escaped quotes that reach the Go template as literal
# backslashes and fail with `unexpected "\" in operand`.
PROFILE_ARGS: '{{if ne .PROFILES "none"}}--spring.profiles.include={{.PROFILES}}{{end}}'
AIENGINE_URL: '{{.AIENGINE_URL | default ""}}'
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED | default "false"}}'
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS | default "120"}}'
# Empty is the same as unset: the property defaults to empty and is blank-checked.
APP_BASE_URL: '{{.APP_BASE_URL | default ""}}'
# Relocates configs/pipeline/logs, for a second backend in the same directory.
# Empty is the same as unset: the reader blank-checks it.
BASE_PATH: '{{.BASE_PATH | default ""}}'
env:
SERVER_PORT: '{{.PORT}}'
STIRLING_FLAVOR: saas
STIRLING_BASE_PATH: '{{.BASE_PATH}}'
AIENGINE_URL: '{{.AIENGINE_URL}}'
AIENGINE_ENABLED: '{{.AIENGINE_ENABLED}}'
AIENGINE_TIMEOUTSECONDS: '{{.AIENGINE_TIMEOUTSECONDS}}'
# Appends RUN_SUBPATH: the approval page is at <base>/link, so a subpath build
# serves it at <base>/app/link. An explicit value still wins.
SYSTEM_FRONTENDURL:
sh: |
if [ -n "${SYSTEM_FRONTENDURL:-}" ]; then
echo "${SYSTEM_FRONTENDURL}"
elif [ -n "{{.APP_BASE_URL}}" ] && [ -n "${RUN_SUBPATH:-}" ]; then
echo "{{.APP_BASE_URL}}/${RUN_SUBPATH}"
else
echo "{{.APP_BASE_URL}}"
fi
cmds:
- cmd: cmd /c ".\gradlew.bat :stirling-pdf:bootRun {{if .PROFILES}}--args=\"--spring.profiles.include={{.PROFILES}}\"{{end}}"
# PROFILE_ARGS is empty when PROFILES=none, i.e. the bare `saas` profile
# against SAAS_DB_* (production).
- cmd: cmd /c ".\gradlew.bat :stirling-pdf:bootRun {{if .PROFILE_ARGS}}--args=\"{{.PROFILE_ARGS}}\"{{end}}"
platforms: [windows]
- cmd: ./gradlew :stirling-pdf:bootRun {{if .PROFILES}}--args='--spring.profiles.include={{.PROFILES}}'{{end}}
- cmd: ./gradlew :stirling-pdf:bootRun {{if .PROFILE_ARGS}}--args='{{.PROFILE_ARGS}}'{{end}}
platforms: [linux, darwin]
build:
+44
View File
@@ -0,0 +1,44 @@
version: '3'
tasks:
install:
desc: "Sync the Python environment with the cucumber test dependencies"
run: once
# Deliberately no sources/status fingerprint: the engine venv is shared, so it can
# already exist while synced to a different dependency group. uv no-ops when correct.
cmds:
- uv sync --project ../../engine --locked --group cucumber
run:
desc: "Run the cucumber suite against a running server (BASE_URL, default localhost:8080)"
deps: [install]
cmds:
- uv run --project ../../engine --locked --group cucumber python -m behave --no-capture -f plain {{.CLI_ARGS}}
nightly:
desc: "Run the @nightly cucumber scenarios, excluded from the default run"
summary: |
Heavy LibreOffice/Calibre/Ghostscript conversions. behave.ini excludes @nightly,
so this opts back in explicitly.
Pass extra behave flags via -- :
task cucumber:nightly -- --tags=@convert
deps: [install]
cmds:
- uv run --project ../../engine --locked --group cucumber python -m behave --tags=@nightly --no-capture -f plain {{.CLI_ARGS}}
parallel:
desc: "Run the cucumber suite as concurrent shards against one server (SHARDS, default 10)"
summary: |
Splits the feature files across SHARDS concurrent behave processes hitting a single
backend, to shake out cross-request interference. Auth-coupled features are pinned
to one shard because they change the admin password mid-scenario.
task cucumber:parallel
task cucumber:parallel SHARDS=4
BASE_URL=http://localhost:8081 task cucumber:parallel
deps: [install]
vars:
SHARDS: '{{.SHARDS | default "10"}}'
cmds:
- bash run-parallel.sh {{.SHARDS}} {{if .CLI_ARGS}}-- {{.CLI_ARGS}}{{end}}
+5 -1
View File
@@ -195,7 +195,11 @@ tasks:
# `desktop:build` run `jlink:clean` first to force a fresh build.
- cmd: chmod -R u+w runtime/jre
platforms: [linux, darwin]
- cmd: powershell -NoProfile -Command "Get-ChildItem -Recurse runtime/jre | ForEach-Object { $_.IsReadOnly = $false }"
# Single-quoted so Task's shell leaves `$_` and `$false` alone. Double
# quotes let it expand them as its own variables, and since neither is
# set the command PowerShell actually received was
# `ForEach-Object { .IsReadOnly = }`, which fails on every file.
- cmd: powershell -NoProfile -Command 'Get-ChildItem -Recurse -File runtime/jre | ForEach-Object { $_.IsReadOnly = $false }'
platforms: [windows]
status:
- test -f runtime/jre/release
+99 -24
View File
@@ -5,6 +5,14 @@ version: '3'
# mode flag) or use `--project editor/...` for tsc — so the editor lives
# under frontend/editor/ without each task needing a cd.
vars:
# Dev-only browser-tab label so concurrent worktrees are distinguishable. Only
# the worktree folder basename (e.g. "wt1") is exposed — never the full path,
# hostname, or user. Dropped from production builds.
DEV_LABEL:
sh: >-
{{if eq OS "windows"}}powershell -NoProfile -Command '$root = git rev-parse --show-toplevel 2>$null; if (-not $root) { $root = (Get-Location).Path }; Split-Path -Leaf $root'{{else}}basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"{{end}}
tasks:
install:
desc: "Install dependencies"
@@ -15,7 +23,7 @@ tasks:
- package-lock.json
- package.json
status:
- test -d node_modules
- npm ls --depth=0
env:
CI: '{{ .CI | default "false" }}'
@@ -80,16 +88,52 @@ tasks:
OPEN: '{{.OPEN | default ""}}'
env:
BACKEND_URL: '{{.BACKEND_URL}}'
# Dev-only browser-tab label so concurrent worktrees are distinguishable.
# Only the worktree folder basename (e.g. "wt1") is exposed — never the
# full path, hostname, or user. Consumed at dev-serve time by vite.config
# and dropped from production builds.
STIRLING_DEV_LABEL:
sh: >-
{{if eq OS "windows"}}powershell -NoProfile -Command '$root = git rev-parse --show-toplevel 2>$null; if (-not $root) { $root = (Get-Location).Path }; Split-Path -Leaf $root'{{else}}basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"{{end}}
STIRLING_DEV_LABEL: '{{.DEV_LABEL}}'
cmds:
- npx vite editor --mode {{.MODE}} --port {{.PORT}}{{if .OPEN}} --open{{end}}
# Separate from dev:_run rather than a flag on it: Task sets an `env:` key even
# when its value resolves to empty, and Vite treats an empty process.env VITE_* as
# authoritative over the committed editor/.env, so folding these in blanks Supabase
# config for the core, proprietary and desktop dev servers.
dev:_run:saas:
internal: true
ignore_error: true
# The backend's own env files, so both halves target one project. Paths are
# relative to this taskfile's dir, `frontend`.
dotenv: ['../app/.env.saas.local', '../app/.env.saas']
vars:
PORT: '{{.PORT | default "5173"}}'
BACKEND_URL: '{{.BACKEND_URL | default "http://localhost:8080"}}'
OPEN: '{{.OPEN | default ""}}'
SAAS_ENV: '{{.SAAS_ENV | default "dev"}}'
env:
BACKEND_URL: '{{.BACKEND_URL}}'
STIRLING_DEV_LABEL: '{{.DEV_LABEL}}'
SAAS_ENV: '{{.SAAS_ENV}}'
# A real process.env VITE_* beats a committed .env in Vite (loadEnv applies
# process.env last), which is what lets this override editor/.env.
#
# These must stay `sh:`, not Go templates: dotenv values are visible to Task's
# embedded shell but not to templates, where {{.SAAS_DEV_PROJECT_REF}} is
# always empty.
VITE_SUPABASE_URL:
sh: |
case "${SAAS_ENV:-dev}" in
staging) ref="${SAAS_STAGING_PROJECT_REF:?set it in app/.env.saas.local}" ;;
*) ref="${SAAS_DEV_PROJECT_REF:?set it in app/.env.saas.local, or pass SAAS_ENV=staging}" ;;
esac
echo "https://${ref}.supabase.co"
VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY:
sh: |
case "${SAAS_ENV:-dev}" in
staging) echo "${SAAS_STAGING_PUBLISHABLE_KEY:?set it in app/.env.saas.local}" ;;
*) echo "${SAAS_DEV_PUBLISHABLE_KEY:?set it in app/.env.saas.local, or pass SAAS_ENV=staging}" ;;
esac
cmds:
- 'echo ">> frontend {{.SAAS_ENV}}: Supabase $VITE_SUPABASE_URL, backend $BACKEND_URL"'
- npx vite editor --mode saas --port {{.PORT}}{{if .OPEN}} --open{{end}}
dev:
desc: "Start frontend dev server"
cmds:
@@ -111,13 +155,33 @@ tasks:
vars: { MODE: proprietary, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
dev:saas:
desc: "Start frontend dev server in SaaS mode"
desc: "Start frontend dev server in SaaS mode (SAAS_ENV=dev|staging|prod)"
deps:
- task: prepare
vars: { MODE: saas }
vars:
SAAS_ENV: '{{.SAAS_ENV | default "dev"}}'
# prod routes to the plain runner, which sets no VITE_SUPABASE_* and so leaves
# the committed editor/.env alone.
RUNNER: '{{if eq .SAAS_ENV "prod"}}dev:_run{{else}}dev:_run:saas{{end}}'
cmds:
- task: dev:_run
vars: { MODE: saas, PORT: '{{.PORT}}', BACKEND_URL: '{{.BACKEND_URL}}', OPEN: '{{.OPEN}}' }
- task: '{{.RUNNER}}'
vars:
MODE: saas
PORT: '{{.PORT}}'
BACKEND_URL: '{{.BACKEND_URL}}'
OPEN: '{{.OPEN}}'
SAAS_ENV: '{{.SAAS_ENV}}'
staging:saas:
desc: "Start frontend dev server against the shared v3 staging project"
cmds:
- task: dev:saas
vars:
SAAS_ENV: staging
PORT: '{{.PORT}}'
BACKEND_URL: '{{.BACKEND_URL}}'
OPEN: '{{.OPEN}}'
dev:desktop:
desc: "Start frontend dev server in desktop mode"
@@ -210,15 +274,26 @@ tasks:
# task frontend:storybook:test -- Button
- npx vitest run --config .storybook/vitest.config.ts {{.CLI_ARGS}}
storybook:a11y:
desc: "a11y regression gate over every story, light and dark: fail only on NEW axe violations"
storybook:a11y:light:
desc: "a11y gate over every story in light mode"
deps: [prepare, storybook:browser]
cmds:
- node .storybook/a11y-scan.mjs
- node .storybook/a11y-scan.mjs {{.CLI_ARGS}}
- node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt
- SCAN_THEME=dark node .storybook/a11y-scan.mjs
storybook:a11y:dark:
desc: "a11y gate over every story in dark mode"
deps: [prepare, storybook:browser]
cmds:
- SCAN_THEME=dark node .storybook/a11y-scan.mjs {{.CLI_ARGS}}
- node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt --baseline .storybook/a11y-baseline.dark.json
storybook:a11y:
desc: "a11y gate over every story, light and dark"
cmds:
- task: storybook:a11y:light
- task: storybook:a11y:dark
storybook:a11y:changed:
desc: "a11y gate over the stories this branch affects (default base origin/main)"
summary: |
@@ -233,7 +308,6 @@ tasks:
Pass a base ref through CLI_ARGS, e.g.
task frontend:storybook:a11y:changed -- origin/release
deps: [prepare, storybook:browser]
vars:
BASE: '{{.CLI_ARGS | default "origin/main"}}'
CHANGED:
@@ -244,10 +318,10 @@ tasks:
echo "a11y: no story files affected vs {{.BASE}} — nothing to check"
exit 0
fi
node .storybook/a11y-scan.mjs {{.CHANGED}}
node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt
SCAN_THEME=dark node .storybook/a11y-scan.mjs {{.CHANGED}}
node .storybook/a11y-check.mjs --in .a11y-scan --manifest .a11y-scan/manifest.txt --baseline .storybook/a11y-baseline.dark.json
rc=0
task frontend:storybook:a11y:light -- {{.CHANGED}} || rc=1
task frontend:storybook:a11y:dark -- {{.CHANGED}} || rc=1
exit $rc
storybook:a11y:record:
desc: "Re-record both a11y baselines (run after intentionally fixing/adding violations)"
@@ -311,13 +385,13 @@ tasks:
desc: "Auto-fix code formatting"
deps: [install]
cmds:
- npx prettier --write .
- npx oxfmt --write .
format:check:
desc: "Check code formatting"
deps: [install]
cmds:
- npx prettier --check .
- npx oxfmt --check .
fix:
desc: "Auto-fix lint and format"
@@ -490,6 +564,7 @@ tasks:
deps: [install, ":backend:swagger"]
cmds:
- npx tsx editor/scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output editor/src/core/types/toolApiTypes.ts --io-output editor/src/core/types/toolIO.ts
- task: format
sources:
- editor/scripts/generate-tool-api-types.mts
- ../SwaggerDoc.json
@@ -499,9 +574,9 @@ tasks:
tool-models:check:
desc: "Fail if committed tool API types are out of date"
deps: [install, ":backend:swagger"]
cmds:
- npx tsx editor/scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output editor/src/core/types/toolApiTypes.ts --io-output editor/src/core/types/toolIO.ts --check
- task: tool-models
- git diff --exit-code -- editor/src/core/types/toolApiTypes.ts editor/src/core/types/toolIO.ts
licenses:generate:
desc: "Generate frontend license report"
+82
View File
@@ -11,6 +11,7 @@ vars:
'.github/scripts/*.py'
'app/core/src/main/resources/static/python/*.py'
':(exclude)*split_photos.py'
':(exclude)scripts/lint/fixtures/*'
SPELL_FILES: >-
'*.html'
'*.css'
@@ -59,6 +60,7 @@ tasks:
- task: gitleaks
- task: whitespace
- task: toml-sort
- task: comment-lint
fix:
desc: "Auto-fix formatting, spelling, and secrets issues across the repo"
@@ -75,6 +77,7 @@ tasks:
vars: { FIX: '1' }
- task: codespell
- task: gitleaks
- task: comment-lint
install:
desc: "Install the pinned pre-commit Python tools"
@@ -130,6 +133,85 @@ tasks:
cmds:
- "{{.GITLEAKS_BIN}} git --pre-commit --redact --staged --verbose"
comment-lint:
desc: "Check comment quality on the lines this branch adds"
summary: |
Blocks a comment that restates the code below it, a section banner, or a
block of commented-out code. Everything else it reports is advisory.
Scoped to added lines, so touching an old file never surfaces the standing
backlog. The standard is devGuide/CODE_COMMENTS.md.
With no arguments it diffs the working tree against HEAD, which is what a
pre-commit run wants: the lines you are about to commit. On a CI pull request
it diffs against the target branch instead, via GITHUB_BASE_REF.
To ask what a whole branch adds instead, use the branch variant, which
needs no argument passing:
task comment-lint:branch
Full tree (report only): task pre-commit:comment-lint:all
Fixture corpus: task pre-commit:comment-lint:selftest
# Depends on the frontend install because the .ts/.tsx half of the rule set
# runs as an oxlint plugin. Without it the TS engine warns and skips, which
# would leave the frontend silently unchecked on CI.
deps: [":frontend:install"]
cmds:
- node scripts/lint/comment-lint.mjs {{.CLI_ARGS}}
comment-lint:branch:
desc: "Check comment quality on everything this branch adds over its base"
summary: |
Like `task comment-lint`, but scoped to the whole branch rather than to
uncommitted work, so it still reports after you commit.
Exists as its own task because passing `-- --since origin/main` through Task
is not portable: with the npm build of Task the launcher is a PowerShell
script, and PowerShell strips the `--` before Task sees it, leaving Task to
print its own usage.
Override the base with BASE=<ref>.
vars:
BASE: '{{.BASE | default "origin/main"}}'
deps: [":frontend:install"]
cmds:
- node scripts/lint/comment-lint.mjs --since {{.BASE}}
comment-lint:ci:
desc: "Comment gate as CI runs it: fixture corpus, then the diff"
summary: |
The corpus checks the rules themselves rather than the code under review, so
it belongs on CI and not on every local commit. Run this before changing a
rule, and let CI run it on every pull request.
deps: [":frontend:install"]
cmds:
- node scripts/lint/comment-lint.mjs --selftest
- node scripts/lint/comment-lint.mjs {{.CLI_ARGS}}
comment-lint:hook:
desc: "Comment gate for the editor hook: everything this turn changed"
summary: |
Same scope as `task comment-lint`, kept as its own name so the hook has a
stable entry point and the taskfile shows every way the linter is invoked.
Not in the frontend-install dependency chain on purpose: this runs at the end
of every turn, so it stays as short as it can be. If oxlint is missing the TS
half warns and skips.
cmds:
- node scripts/lint/comment-lint.mjs
comment-lint:all:
desc: "Report every comment finding in the tree (never fails)"
deps: [":frontend:install"]
cmds:
- node scripts/lint/comment-lint.mjs --all
comment-lint:selftest:
desc: "Check both comment-lint engines against the fixture corpus"
deps: [":frontend:install"]
cmds:
- node scripts/lint/comment-lint.mjs --selftest
gitleaks-bin:
internal: true
desc: "Ensure the pinned, checksum-verified gitleaks binary is cached in .task/bin"
+76 -20
View File
@@ -73,40 +73,52 @@
"stirling",
],
"java.project.resourceFilters": [
".cache/",
".claude/",
".devcontainer/",
".git/",
".git-blame-ignore-revs",
".gitattributes",
".github/",
".gitignore",
".gradle/",
".pre-commit-config.yaml",
".task/",
".taskfiles/",
".venv/",
".venv*/",
".vscode/",
"bin/",
"app/core/bin/",
"app/.gitignore",
"app/build/",
"app/common/.gitignore",
"app/common/bin/",
"app/proprietary/bin/",
"build/",
"app/core/build/",
"app/common/build/",
"app/proprietary/build/",
"configs/",
"app/core/.gitignore",
"app/core/bin/",
"app/core/configs/",
"customFiles/",
"app/core/customFiles/",
"app/core/LOCAL_APPDATA_FONTCONFIG_CACHE/",
"app/core/logs/",
"app/core/pipeline/",
"app/core/storage/",
"app/proprietary/.gitignore",
"app/proprietary/bin/",
"app/proprietary/storage/",
"app/saas/.gitignore",
"app/saas/bin/",
"app/saas/build/",
"bin/",
"build/",
"devGuide/",
"devTools/",
"docker/",
"docs/",
"exampleYmlFiles",
"engine/",
"frontend/",
"gradle/",
"images/",
"logs/",
"pipeline/",
"scripts/",
"testings/",
".git-blame-ignore-revs",
".gitattributes",
".gitignore",
"app/core/.gitignore",
"app/common/.gitignore",
"app/proprietary/.gitignore",
".pre-commit-config.yaml",
],
// Enables signature help in Java.
"java.signatureHelp.enabled": true,
@@ -135,13 +147,57 @@
"html.format.indentHandlebars": true,
"html.format.preserveNewLines": true,
"html.format.maxPreserveNewLines": 2,
"stylelint.configFile": "devTools/.stylelintrc.json",
"stylelint.configFile": "${workspaceFolder}/devTools/.stylelintrc.json",
"css.lint.unknownAtRules": "ignore",
"scss.lint.unknownAtRules": "ignore",
"less.lint.unknownAtRules": "ignore",
"java.project.sourcePaths": [
"app/core/src/main/java",
"app/common/src/main/java",
"app/proprietary/src/main/java"
],
"[javascript]": {
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
},
"[javascriptreact]": {
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
},
"[typescript]": {
"editor.defaultFormatter": "vscode.typescript-language-features"
"editor.defaultFormatter": "vscode.typescript-language-features",
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
},
"[typescriptreact]": {
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
}
},
"oxc.enable.oxlint": true,
"oxc.enable.oxfmt": false,
"oxc.configPath": "frontend/oxlint.config.ts",
"oxc.requireConfig": true,
"oxc.lint.run": "onType",
"oxc.fixKind": "safe_fix",
"[toml]": {
"editor.defaultFormatter": "tamasfe.even-better-toml",
// Keep TOML formatting compatible with .editorconfig and the pre-commit
// locale sorter. Key ordering itself is handled by task pre-commit:toml-sort.
"editor.insertSpaces": true,
"editor.tabSize": 4,
"editor.rulers": [127],
"evenBetterToml.formatter.alignEntries": false,
"evenBetterToml.formatter.alignComments": false,
"evenBetterToml.formatter.indentString": " ",
"evenBetterToml.formatter.columnWidth": 127,
"evenBetterToml.formatter.reorderKeys": false,
"evenBetterToml.formatter.reorderArrays": false,
"evenBetterToml.formatter.reorderInlineTables": false,
"evenBetterToml.formatter.trailingNewline": true,
"evenBetterToml.formatter.crlf": false
}
}
+38 -1
View File
@@ -21,6 +21,43 @@ Task `desc:` fields should describe **what** the task does, not **how** it does
- `task docker:build` — build standard Docker image
- `task docker:up` — start Docker compose stack
## Comments
A comment must carry information the code cannot. If a reader could derive it from the code in front of them, delete it.
Comment the current state. Not what the code used to do, not what changed, not why it changed: git holds that. Where history explains the shape, state the reason instead, so "this used to reimplement the modal internals" becomes "thin wrapper over the shared Modal: duplicating its portal and focus trap is how dialogs drift apart". Future state goes in a TODO with an issue.
Write a comment when it does one of these four jobs:
- **Contract.** What a caller must know that the signature cannot say: preconditions, invariants, units, ownership and lifetime, thread-safety, error semantics, side effects. Document the contract of everything a caller outside the file can reach, and nothing else. Goes on the type/method/module as Javadoc, JSDoc, or a docstring.
- **Why.** The constraint the code satisfies, the bug it avoids, the alternative rejected and the reason.
- **Hazard.** "Must stay in sync with X", "order matters because Y", "do not remove, it prevents Z".
- **Map.** A short orientation at the top of a genuinely complex file: what it owns, and what it deliberately does not.
Never write:
- A comment that restates the next line. `// Handle drag start` above `handleDragStart` is noise.
- Section banners or position markers: `// --- Types ---`, `// Helpers`, `// =====`.
- Step narration in a function body (`// Step 1:`, `// Then we`). If the steps need labels they need names: extract functions. Numbering a genuinely numbered thing, like a wizard step, is fine.
- Commented-out code. Delete it.
- Doc tags that restate the signature. `@param blob - The blob` says nothing; omit the tag rather than pad it.
- Docs on self-explanatory members with no constraint to state.
Two tests before keeping a comment:
- **Delete it.** Is any information lost? If not, it stays deleted.
- **Could a name carry it instead?** A better identifier, an extracted function, or a named constant beats a comment. Prefer the code change.
A comment at the end of a line usually decodes that line, and that is worth keeping: `{0x25, 0x50} // "%PDF"`, `50L * 1024 * 1024 // 50 MB`. The rules that compare a comment against the code below it do not apply there, but a trailing TODO or a trailing bit of history is judged like any other.
A reference is supplementary, never load-bearing: the comment must survive deleting it. `// See #1234` is a dead end; `// saving first loses every annotation (#6865)` is not. Prefer a spec (`RFC 3161`) or CVE where one applies.
A TODO needs an issue, not an owner: `// TODO(#1234): re-enable the gate once account syncing lands`. If it is not worth an issue, it is not worth a TODO. A question is not a TODO.
A comment block over ~12 lines outside a file or type header usually means the code needs restructuring, or that the prose is product documentation and belongs in the docs repo.
`task comment-lint` checks the mechanical part of this on the lines you add, and runs inside `task pre-commit`. Reasoning, worked examples and the linter's own rules: @devGuide/CODE_COMMENTS.md
## Common Development Commands
### Build and Test
@@ -70,7 +107,7 @@ The project structure is defined in `engine/pyproject.toml`. Any new dependencie
- Avoid nested functions and nested classes unless the language construct requires them.
- Prefer composition to inheritance when combining concepts.
- Avoid speculative abstractions. Add a layer only when it removes real duplication or clarifies lifecycle.
- Add comments sparingly and only when they explain non-obvious intent.
- Comments follow the repo-wide rules in the "Comments" section above.
#### Python Typing and Models
- Deserialize into Pydantic models as early as possible.
+8 -4
View File
@@ -2,6 +2,13 @@
Thank you for your interest in contributing to Stirling-PDF! There are many ways to contribute other than writing code. For example, reporting bugs, creating suggestions, and adding or modifying translations.
## License
By contributing to this project, you agree that your contributions will be licensed under the project [license](LICENSE), which follows an open-core model.
The codebase is a mix of MIT and source-available code, so your contribution is licensed according to the directory it is committed to.
PRs are welcome in any directory by any user, just be aware of which license applies to the code you change.
## Issue Guidelines
Issues can be used to report bugs, request features, or ask questions. If you have a question, you could also ask us in our [Discord](https://discord.gg/FJUSXUSYec).
@@ -35,6 +42,7 @@ Please make sure your Pull Request adheres to the following guidelines:
- Keep commits atomic. One commit should contain one change. If you want to make multiple changes, submit multiple Pull Requests.
- Commits should be clear, concise, and easy to understand.
- References to the Issue number in the Pull Request and/or Commit message.
- Every comment in the diff should say something the code does not. See [Code comments](devGuide/CODE_COMMENTS.md); `task comment-lint` checks the mechanical part.
## Translations
@@ -63,7 +71,3 @@ For technical guides, setup instructions, and development resources:
For configuration and usage guides, see:
- [Database Guide](DATABASE.md) - Database setup and configuration
- [OCR Guide](HowToUseOCR.md) - OCR setup and configuration
## License
By contributing to this project, you agree that your contributions will be licensed under the [MIT License](LICENSE).
+2 -2
View File
@@ -46,8 +46,8 @@ This guide focuses on developing for Stirling 2.0, including both the React fron
- Docker
- Git
- Java JDK 25
- Node.js 18+ and npm (required for frontend development)
- Gradle 7.0 or later (Included within the repo)
- Node.js 22+ and npm (required for frontend development)
- Gradle 9.0 or later (Included within the repo)
- [uv](https://docs.astral.sh/uv/) — Python package manager (required for engine development)
- Rust and Cargo (required for Tauri desktop app development)
- Tauri CLI (install with `cargo install tauri-cli`)
+121 -3
View File
@@ -25,6 +25,9 @@ includes:
e2e:
taskfile: .taskfiles/e2e.yml
dir: .
cucumber:
taskfile: .taskfiles/cucumber.yml
dir: testing/cucumber
pre-commit:
taskfile: .taskfiles/pre-commit.yml
dir: .
@@ -96,22 +99,122 @@ tasks:
BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}'
OPEN: "true"
# Set SAAS_DEV_PROJECT_REF in app/.env.saas.local to pick the PR.
dev:saas:
desc: "Start SaaS backend + frontend concurrently on free ports"
desc: "Start SaaS backend + frontend + engine against the current PR's preview branch"
cmds:
- task: dev:_all
vars: { FRONTEND: saas, BACKEND: saas }
vars: { FRONTEND: saas, BACKEND: saas, SAAS_ENV: dev }
staging:saas:
desc: "Start SaaS backend + frontend + engine against the shared v3 staging project"
cmds:
- task: dev:_all
vars:
FRONTEND: saas
BACKEND: saas
BACKEND_TASK: backend:staging:saas
SAAS_ENV: staging
dev:all:
desc: "Start backend + frontend + engine concurrently on free ports"
cmds:
- task: dev:_all
# No engine: linking never calls it.
linked:staging:
desc: "SaaS on the shared v3 project + a self-hosted instance linked to it"
cmds:
- task: linked:_all
vars: { SAAS_ENV: staging }
linked:dev:
desc: "SaaS on the current PR's preview branch + a self-hosted instance linked to it"
cmds:
- task: linked:_all
vars: { SAAS_ENV: dev }
linked:_all:
internal: true
vars:
SAAS_ENV: '{{.SAAS_ENV | default "staging"}}'
PORTS:
sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8081 5174 8080 5173{{else}}{{.FIND_FREE_PORT_SH}} 8081 5174 8080 5173{{end}}'
SAAS_BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}'
SAAS_FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 1}}'
APP_BACKEND_PORT: '{{index (splitList "\n" .PORTS) 2}}'
APP_FRONTEND_PORT: '{{index (splitList "\n" .PORTS) 3}}'
deps:
# APP_BASE_URL is the SaaS *frontend*: the approval page is served by vite, not
# by the API. BASE_PATH moves this backend's configs/pipeline aside so it does not
# race the self-hosted one, which keeps ./configs and its existing database.
- task: 'backend:{{.SAAS_ENV}}:saas'
vars:
PORT: '{{.SAAS_BACKEND_PORT}}'
APP_BASE_URL: 'http://localhost:{{.SAAS_FRONTEND_PORT}}'
BASE_PATH: 'tmp/linked-saas'
- task: frontend:dev:saas
vars:
PORT: '{{.SAAS_FRONTEND_PORT}}'
BACKEND_URL: 'http://localhost:{{.SAAS_BACKEND_PORT}}'
SAAS_ENV: '{{.SAAS_ENV}}'
- task: backend:dev:linked
vars:
PORT: '{{.APP_BACKEND_PORT}}'
SAAS_BASE_URL: 'http://localhost:{{.SAAS_BACKEND_PORT}}'
- task: frontend:dev:proprietary
vars:
PORT: '{{.APP_FRONTEND_PORT}}'
BACKEND_URL: 'http://localhost:{{.APP_BACKEND_PORT}}'
OPEN: "true"
- task: linked:_ready
vars:
SAAS_BACKEND_PORT: '{{.SAAS_BACKEND_PORT}}'
SAAS_FRONTEND_PORT: '{{.SAAS_FRONTEND_PORT}}'
APP_BACKEND_PORT: '{{.APP_BACKEND_PORT}}'
APP_FRONTEND_PORT: '{{.APP_FRONTEND_PORT}}'
# Waits for all four to answer, then prints where they landed.
linked:_ready:
internal: true
cmds:
- cmd: |
n=0
ok=0
while [ "$n" -lt 150 ]; do
ok=1
for u in "http://localhost:{{.SAAS_BACKEND_PORT}}" \
"http://localhost:{{.SAAS_FRONTEND_PORT}}" \
"http://localhost:{{.APP_BACKEND_PORT}}" \
"http://localhost:{{.APP_FRONTEND_PORT}}"; do
# Not -o /dev/null: Windows curl.exe treats it as a real path and exits 23.
curl -s -m 2 "$u" >/dev/null 2>&1 || ok=0
done
if [ "$ok" = 1 ]; then break; fi
n=$((n + 1))
# `sleep` is a binary, not a builtin, and Windows has none.
{{if eq OS "windows"}}powershell -NoProfile -Command "Start-Sleep -Seconds 2"{{else}}sleep 2{{end}}
done
echo ""
if [ "$ok" = 1 ]; then
echo ">> all four answering"
else
echo ">> still waiting on one or more after 5 minutes; addresses below anyway"
fi
echo ">> self-hosted UI http://localhost:{{.APP_FRONTEND_PORT}}/processor"
echo ">> self-hosted api http://localhost:{{.APP_BACKEND_PORT}}"
echo ">> saas UI http://localhost:{{.SAAS_FRONTEND_PORT}}"
echo ">> saas api http://localhost:{{.SAAS_BACKEND_PORT}}"
echo ""
dev:_all:
internal: true
vars:
FRONTEND: '{{.FRONTEND | default "proprietary"}}'
BACKEND: '{{.BACKEND | default "proprietary"}}'
BACKEND_TASK: '{{.BACKEND_TASK | default (printf "backend:dev:%s" .BACKEND)}}'
# Only meaningful to the saas frontend; every other flavor ignores it.
SAAS_ENV: '{{.SAAS_ENV | default ""}}'
PORTS:
sh: '{{if eq OS "windows"}}{{.FIND_FREE_PORT_PS}} 8080 5173 5001{{else}}{{.FIND_FREE_PORT_SH}} 8080 5173 5001{{end}}'
BACKEND_PORT: '{{index (splitList "\n" .PORTS) 0}}'
@@ -121,7 +224,7 @@ tasks:
- task: engine:dev
vars:
PORT: '{{.ENGINE_PORT}}'
- task: 'backend:dev:{{.BACKEND}}'
- task: '{{.BACKEND_TASK}}'
vars:
PORT: '{{.BACKEND_PORT}}'
AIENGINE_URL: 'http://localhost:{{.ENGINE_PORT}}'
@@ -131,6 +234,7 @@ tasks:
PORT: '{{.FRONTEND_PORT}}'
BACKEND_URL: 'http://localhost:{{.BACKEND_PORT}}'
OPEN: "true"
SAAS_ENV: '{{.SAAS_ENV}}'
# ============================================================
# Build
@@ -162,6 +266,20 @@ tasks:
cmds:
- task: frontend:lint
- task: engine:lint
- task: comment-lint
comment-lint:
desc: "Check comment quality on the lines this branch adds"
aliases: [comments]
cmds:
- task: pre-commit:comment-lint
vars: { CLI_ARGS: '{{.CLI_ARGS}}' }
comment-lint:branch:
desc: "Check comment quality on everything this branch adds over its base"
cmds:
- task: pre-commit:comment-lint:branch
vars: { BASE: '{{.BASE}}' }
fix:
desc: "Auto-fix all components"
+33 -38
View File
@@ -4,6 +4,11 @@ This guide explains how to set up Windows code signing for Stirling-PDF desktop
## Overview
Releases are signed with **DigiCert KeyLocker**, a cloud HSM: the private key never
leaves DigiCert, and the runner signs through a PKCS#11 provider. The older approach
of uploading a base64 `.pfx` to a repository secret has been removed from the
workflows - the sections below describe KeyLocker, which is what actually runs.
Windows code signing is essential for:
- Preventing Windows SmartScreen warnings
- Building trust with users
@@ -49,29 +54,19 @@ openssl pkcs12 -export -out certificate.pfx -inkey private-key.key -in certifica
### Required Secrets
Navigate to your GitHub repository → Settings → Secrets and variables → Actions
Navigate to your GitHub repository → Settings → Environments → `release-signing`.
Add the following secrets:
These live in the `release-signing` environment, not at repository scope. That
environment requires reviewer approval and is limited to `main`, `release`,
`hotfix/*` and `v*` tags. All five come from the DigiCert ONE console.
#### 1. `WINDOWS_CERTIFICATE`
- **Description**: Base64-encoded .pfx certificate file
- **How to create**:
**On macOS/Linux:**
```bash
base64 -i certificate.pfx | pbcopy # Copies to clipboard
```
**On Windows (PowerShell):**
```powershell
[Convert]::ToBase64String([IO.File]::ReadAllBytes("certificate.pfx")) | Set-Clipboard
```
Paste the entire base64 string into the GitHub secret.
#### 2. `WINDOWS_CERTIFICATE_PASSWORD`
- **Description**: Password for the .pfx certificate
- **Value**: The password you set when creating/exporting the .pfx file
| Secret | Description |
| --- | --- |
| `SM_API_KEY` | KeyLocker API key. Also acts as the on/off switch: signing steps are gated on it being non-empty. |
| `SM_CLIENT_CERT_FILE_B64` | Base64-encoded PKCS#12 client authentication certificate. |
| `SM_CLIENT_CERT_PASSWORD` | Password for that client certificate. |
| `SM_KEYPAIR_ALIAS` | Alias of the signing keypair to use. |
| `SM_HOST` | DigiCert ONE host, e.g. `https://clientauth.one.digicert.com`. |
### Optional Secrets for Tauri Updater
@@ -110,23 +105,23 @@ The Windows signing configuration is already set up:
### 2. GitHub Workflow (.github/workflows/tauri-build.yml)
The workflow includes three Windows signing steps:
The workflow includes four Windows signing steps, all gated on `SM_API_KEY` being
set and the ref being the release branch:
1. **Import Certificate**: Decodes and imports the .pfx certificate into Windows certificate store
2. **Build Tauri App**: Builds and signs the application using the imported certificate
3. **Verify Signature**: Validates that both .exe and .msi files are properly signed
1. **Setup DigiCert KeyLocker**: Installs the DigiCert signing tools via `digicert/ssm-code-signing`
2. **Setup DigiCert KeyLocker Certificate**: Writes the client cert and exports the PKCS#11 config
3. **Configure Windows code signing / Build Tauri app**: Signs through the PKCS#11 provider
4. **Verify Windows Code Signature**: Validates that the .exe and .msi are properly signed
## Testing the Setup
### 1. Local Testing (Windows Only)
Before pushing to GitHub, test locally:
KeyLocker is CI-only. To check signing locally, install your own certificate into
the Windows store and point Tauri at it; the build no longer reads any certificate
from an environment variable.
```powershell
# Set environment variables
$env:WINDOWS_CERTIFICATE = [Convert]::ToBase64String([IO.File]::ReadAllBytes("certificate.pfx"))
$env:WINDOWS_CERTIFICATE_PASSWORD = "your-certificate-password"
# Build the application
cd frontend
npm run tauri build
@@ -191,9 +186,10 @@ Look for:
- Consider EV certificate for immediate reputation
### Certificate Not Found During Build
- Verify `WINDOWS_CERTIFICATE` secret is set
- Check base64 encoding is correct (no extra whitespace)
- Ensure password is correct
- Verify `SM_API_KEY` is present in the `release-signing` environment. If it is empty
the signing steps skip silently and the build succeeds unsigned.
- Check `SM_CLIENT_CERT_FILE_B64` base64 encoding is correct (no extra whitespace)
- Ensure `SM_CLIENT_CERT_PASSWORD` and `SM_KEYPAIR_ALIAS` match the DigiCert keypair
## Security Best Practices
@@ -220,11 +216,10 @@ Look for:
## Certificate Lifecycle
### Before Expiration
1. Obtain new certificate from CA (typically annual renewal)
2. Convert to .pfx format if needed
3. Update `WINDOWS_CERTIFICATE` secret with new base64-encoded certificate
4. Update `WINDOWS_CERTIFICATE_PASSWORD` if password changed
5. Test build to verify new certificate works
1. Renew the certificate in the DigiCert ONE console (typically annual)
2. If the keypair alias changed, update `SM_KEYPAIR_ALIAS` in the `release-signing` environment
3. If the client authentication certificate was reissued, update `SM_CLIENT_CERT_FILE_B64` and `SM_CLIENT_CERT_PASSWORD`
4. Test build to verify the new certificate works
### Expired Certificates
- Signed binaries remain valid (timestamp proves signing time)
+35 -17
View File
@@ -1,15 +1,16 @@
###############################################################################
# Stirling-PDF SaaS environment defaults.
# Stirling-PDF SaaS environment defaults. Committed, non-secret. Real values for secrets go in
# .env.saas.local, which is loaded first and wins. Do not commit that file.
#
# This file is committed and provides non-secret defaults loaded by
# `task backend:dev:saas`. Put real values for secrets (passwords, project
# refs, edge function secrets) in `.env.saas.local` - any variable set there
# takes precedence over what's defined here.
# Three environments, each deriving its Supabase URLs, JWT issuer and JWKS from one project ref:
#
# DO NOT commit `.env.saas.local`. Only `.env.saas` is checked in.
###############################################################################
# prod PROFILES=none SAAS_DB_* the live project
# staging PROFILES=staging SAAS_STAGING_* pinned to v3, always there
# dev PROFILES=dev SAAS_DEV_* follows a SaaS PR's preview branch
#
# dev is the default for `task backend:dev:saas`. Use staging for somewhere stable; use dev when
# testing an open SaaS PR, since its preview branch is the only place those migrations are applied.
# ---------- Supabase project ----------
# ---------- Supabase project (prod / no-profile) ----------
# Project reference (the subdomain part of <ref>.supabase.co). Required.
# Set in .env.saas.local.
SAAS_DB_PROJECT_REF=
@@ -17,18 +18,35 @@ SAAS_DB_PROJECT_REF=
# Edge function secret used by billing/license rollup calls. Set in .env.saas.local.
SUPABASE_EDGE_FUNCTION_SECRET=
# ---------- Database (saas profile) ----------
# Direct JDBC URL to the Supabase Postgres. Required when running the plain
# `saas` profile (i.e. without `--spring.profiles.include=dev`).
# ---------- Database (no profile) ----------
# Direct JDBC URL to the Supabase Postgres. Required when running without
# `--spring.profiles.include=...`.
# Example: jdbc:postgresql://db.<project-ref>.supabase.co:5432/postgres
SAAS_DB_URL=
SAAS_DB_USERNAME=postgres
SAAS_DB_PASSWORD=
# ---------- Database (dev profile overrides) ----------
# Used when `--spring.profiles.include=dev` is active. The dev profile
# defaults the URL/username to the shared dev Supabase project, but the
# password must still be provided in .env.saas.local.
SAAS_DEV_DB_URL=
# ---------- staging profile ----------
# The shared long-lived v3 project. application-staging.properties defaults the ref,
# URL, database host and meter endpoint, so staging needs only the password, in
# .env.saas.local. Set SAAS_STAGING_PROJECT_REF to repoint it; everything derives.
#
# The ref and publishable key are duplicated here because the task derives the
# frontend's VITE_SUPABASE_* from them and a shell cannot read a Spring default.
# Neither is secret: the ref is a public subdomain, the key ships in the bundle.
SAAS_STAGING_PROJECT_REF=qacaivhsjtftfwtgjvva
SAAS_STAGING_PUBLISHABLE_KEY=sb_publishable_nIM8y-9ARPE7EzQwAQHKMg_40fCN6kY # gitleaks:allow
SAAS_STAGING_DB_USERNAME=postgres
SAAS_STAGING_DB_PASSWORD=
# ---------- dev profile ----------
# The SaaS PR's Supabase preview branch. Take the ref from that PR's "Supabase
# Preview" check; the profile derives URL, JWT issuer, JWKS, meter endpoint and
# database host from it, so this one value follows a different PR.
#
# A preview branch has its own password and keys; the parent project's will not
# authenticate. Both go in .env.saas.local, along with the ref.
SAAS_DEV_PROJECT_REF=
SAAS_DEV_PUBLISHABLE_KEY=
SAAS_DEV_DB_USERNAME=postgres
SAAS_DEV_DB_PASSWORD=
+34 -9
View File
@@ -3,6 +3,10 @@ bootRun {
enabled = false
}
dependencies {
// Security-hardening utilities (zip-slip, SSRF, filename sanitization, command injection).
// Declared as api here so core + proprietary (which depend on common) get it transitively,
// keeping it off modules that don't need it (e.g. saas).
api 'io.github.pixee:java-security-toolkit:1.2.3'
api "com.google.guava:guava:${guavaVersion}"
api 'org.springframework.boot:spring-boot-starter-webmvc'
api 'org.springframework.boot:spring-boot-starter-aspectj'
@@ -18,11 +22,14 @@ dependencies {
api "org.apache.pdfbox:preflight:$pdfboxVersion"
api 'com.github.junrar:junrar:8.0.0' // RAR archive support for CBR files
api 'jakarta.servlet:jakarta.servlet-api:6.1.0'
api 'org.snakeyaml:snakeyaml-engine:3.0.1'
api 'org.snakeyaml:snakeyaml-engine:3.1.1'
api "org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.3"
// Simple Java Mail for EML/MSG parsing (replaces direct Angus Mail usage)
api 'org.simplejavamail:simple-java-mail:9.2.0'
api 'org.simplejavamail:outlook-module:9.2.0' // MSG file support
api 'org.simplejavamail:simple-java-mail:9.3.2'
// MSG file support; exclude commons-math3 (only HSSF/formula needs it, MSG parsing doesn't)
api('org.simplejavamail:outlook-module:9.3.2') {
exclude group: 'org.apache.commons', module: 'commons-math3'
}
api 'jakarta.mail:jakarta.mail-api:2.1.5'
runtimeOnly 'org.eclipse.angus:angus-mail:2.0.5'
@@ -36,12 +43,30 @@ dependencies {
api "com.stirling:jpdfium:${jpdfiumVersion}"
// -PjpdfiumPlatforms=all|none|<csv of linux-x64,linux-arm64,darwin-x64,darwin-arm64,windows-x64>
// 'none' skips natives entirely (windows-arm64 builds, until JPDFium ships that platform).
def jpdfiumPlatformsProp = (project.findProperty('jpdfiumPlatforms') ?: 'all').toString().trim()
def jpdfiumAllPlatforms = ['linux-x64', 'linux-arm64', 'darwin-x64', 'darwin-arm64', 'windows-x64']
// -PjpdfiumPlatforms=auto|all|none|<csv of linux-x64,linux-arm64,linux-musl-x64,linux-musl-arm64,darwin-x64,darwin-arm64,windows-x64> (windows-arm64 natives not published yet)
def jpdfiumPlatformsProp = (project.findProperty('jpdfiumPlatforms') ?: 'auto').toString().trim()
def jpdfiumAllPlatforms = ['linux-x64', 'linux-arm64', 'linux-musl-x64', 'linux-musl-arm64', 'darwin-x64', 'darwin-arm64', 'windows-x64']
def jpdfiumPlatforms
if (jpdfiumPlatformsProp == 'all') {
if (jpdfiumPlatformsProp == 'auto') {
def osName = System.getProperty('os.name').toLowerCase()
def osArch = System.getProperty('os.arch').toLowerCase()
def isArm64 = osArch.contains('aarch64') || osArch.contains('arm64')
if (osName.contains('linux')) {
jpdfiumPlatforms = isArm64 ? ['linux-arm64'] : ['linux-x64']
} else if (osName.contains('mac')) {
jpdfiumPlatforms = isArm64 ? ['darwin-arm64'] : ['darwin-x64']
} else if (osName.contains('win')) {
if (isArm64) {
logger.lifecycle("JPDFium natives are not available for windows-arm64; set -PjpdfiumPlatforms=none to skip bundling natives.")
jpdfiumPlatforms = []
} else {
jpdfiumPlatforms = ['windows-x64']
}
} else {
// Fallback: bundle all platforms when host can't be determined
jpdfiumPlatforms = jpdfiumAllPlatforms
}
} else if (jpdfiumPlatformsProp == 'all') {
jpdfiumPlatforms = jpdfiumAllPlatforms
} else if (jpdfiumPlatformsProp == 'none') {
jpdfiumPlatforms = []
@@ -51,7 +76,7 @@ dependencies {
def jpdfiumInvalid = jpdfiumPlatforms.findAll { !jpdfiumAllPlatforms.contains(it) }
if (jpdfiumInvalid) {
throw new GradleException("Unknown jpdfiumPlatforms value(s): ${jpdfiumInvalid.join(', ')}. " +
"Valid: ${jpdfiumAllPlatforms.join(', ')}, 'all' or 'none'.")
"Valid: ${jpdfiumAllPlatforms.join(', ')}, 'auto', 'all' or 'none'.")
}
logger.lifecycle("JPDFium native platforms: ${jpdfiumPlatforms ? jpdfiumPlatforms.join(', ') : 'none'}")
jpdfiumPlatforms.each { platform ->
@@ -6,6 +6,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
@@ -13,6 +14,7 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.PdfaLevelAServiceInterface;
@Service
@Slf4j
@@ -46,17 +48,21 @@ public class EndpointConfiguration {
private final ApplicationProperties applicationProperties;
@Getter private Map<String, Boolean> endpointStatuses = new ConcurrentHashMap<>();
private Map<String, Set<String>> endpointGroups = new ConcurrentHashMap<>();
private Set<String> disabledGroups = new HashSet<>();
private Set<String> disabledGroups = ConcurrentHashMap.newKeySet();
private Map<String, DisableReason> endpointDisableReasons = new ConcurrentHashMap<>();
private Map<String, DisableReason> groupDisableReasons = new ConcurrentHashMap<>();
private Map<String, Set<String>> endpointAlternatives = new ConcurrentHashMap<>();
private final boolean runningProOrHigher;
private final boolean pdfUaAvailable;
public EndpointConfiguration(
ApplicationProperties applicationProperties,
@Qualifier("runningProOrHigher") boolean runningProOrHigher) {
@Qualifier("runningProOrHigher") boolean runningProOrHigher,
@Autowired(required = false) PdfaLevelAServiceInterface pdfaLevelAService) {
this.applicationProperties = applicationProperties;
this.runningProOrHigher = runningProOrHigher;
// The PDF/UA tagger ships in the proprietary module, and so do its endpoints.
this.pdfUaAvailable = pdfaLevelAService != null;
init();
processEnvironmentConfigs();
}
@@ -356,6 +362,7 @@ public class EndpointConfiguration {
addEndpointToGroup("Convert", "pdf-to-img");
addEndpointToGroup("Convert", "img-to-pdf");
addEndpointToGroup("Convert", "pdf-to-pdfa");
addEndpointToGroup("Convert", "pdf-to-ua");
addEndpointToGroup("Convert", "file-to-pdf");
addEndpointToGroup("Convert", "pdf-to-word");
addEndpointToGroup("Convert", "pdf-to-presentation");
@@ -395,6 +402,7 @@ public class EndpointConfiguration {
// Backend-only endpoints (not in frontend tool registry endpoints)
addEndpointToGroup("Security", "redact");
addEndpointToGroup("Security", "verify-pdf");
addEndpointToGroup("Security", "accessibility-report");
addEndpointToGroup("Security", "sign");
// Adding endpoints to "Other" group
@@ -529,6 +537,8 @@ public class EndpointConfiguration {
addEndpointToGroup("Java", "json-to-pdf");
addEndpointToGroup("Java", "pdf-to-video");
addEndpointToGroup("Java", "verify-pdf");
addEndpointToGroup("Java", "pdf-to-ua");
addEndpointToGroup("Java", "accessibility-report");
addEndpointToGroup("Java", "flatten");
addEndpointToGroup("Java", "unlock-pdf-forms");
addEndpointToGroup("Java", "validate-signature");
@@ -600,6 +610,8 @@ public class EndpointConfiguration {
// veraPDF dependent endpoints
addEndpointToGroup("veraPDF", "verify-pdf");
addEndpointToGroup("veraPDF", "pdf-to-ua");
addEndpointToGroup("veraPDF", "accessibility-report");
// Pdftohtml dependent endpoints
addEndpointToGroup("Pdftohtml", "pdf-to-html");
@@ -630,6 +642,11 @@ public class EndpointConfiguration {
disableGroup("enterprise");
}
if (!pdfUaAvailable) {
disableEndpoint("pdf-to-ua");
disableEndpoint("accessibility-report");
}
if (!applicationProperties.getSystem().isEnableUrlToPDF()) {
disableEndpoint("url-to-pdf");
}
@@ -237,7 +237,7 @@ public class TabulaTableParser implements TableParser {
score -= 0.3f;
}
return Math.max(0f, Math.min(1f, score));
return Math.clamp(score, 0f, 1f);
}
private Bounds tableBounds(Table table) {
@@ -2,7 +2,9 @@ package stirling.software.common.aop;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
@@ -273,6 +275,7 @@ public class AutoJobAspect {
// Store the fileId for later reference
pdfFile.setFileId(fileId);
recordPendingInputFile(fileId);
// Replace the original MultipartFile with our persistent copy
MultipartFile persistentFile = fileStorage.retrieveFile(fileId);
@@ -290,6 +293,29 @@ public class AutoJobAspect {
return originalArgs;
}
/**
* Queue an input copy for attribution to the job. The job id does not exist yet at this point,
* so {@link JobExecutorService} drains this list once it mints one.
*/
@SuppressWarnings("unchecked")
private void recordPendingInputFile(String fileId) {
try {
Object existing = request.getAttribute(JobExecutorService.PENDING_INPUT_FILE_IDS_ATTR);
List<String> ids;
if (existing instanceof List<?> list) {
ids = (List<String>) list;
} else {
ids = new ArrayList<>();
request.setAttribute(JobExecutorService.PENDING_INPUT_FILE_IDS_ATTR, ids);
}
ids.add(fileId);
} catch (RuntimeException ex) {
// Without a bound request the copy cannot be attributed; the periodic sweep is the
// only backstop, so make the miss visible rather than silently leaking the file.
log.warn("Could not record input copy {} for cleanup: {}", fileId, ex.getMessage());
}
}
private String getJobIdFromContext() {
try {
return (String) request.getAttribute("jobId");
@@ -73,7 +73,7 @@ public class RuntimePathConfig {
defaultWatchedFolders,
watchedFoldersDirs,
pipeline != null ? pipeline.getWatchedFoldersDir() : null);
this.pipelineWatchedFoldersPath = this.pipelineWatchedFoldersPaths.get(0);
this.pipelineWatchedFoldersPath = this.pipelineWatchedFoldersPaths.getFirst();
this.pipelineFinishedFoldersPath =
resolvePath(
defaultFinishedFolders,
@@ -1316,7 +1316,7 @@ public class ApplicationProperties {
public static class Ui {
private String appNameNavbar;
private List<String> languages;
private String logoStyle = "classic"; // Options: "classic" (default) or "modern"
private String logoStyle = "modern"; // Options: "modern" (default) or "classic"
private boolean defaultHideUnavailableTools = false;
private boolean defaultHideUnavailableConversions = false;
private HideDisabledTools hideDisabledTools = new HideDisabledTools();
@@ -1327,10 +1327,10 @@ public class ApplicationProperties {
public String getLogoStyle() {
// Validate and return either "modern" or "classic"
if ("modern".equalsIgnoreCase(logoStyle)) {
return "modern";
if ("classic".equalsIgnoreCase(logoStyle)) {
return "classic";
}
return "classic"; // default
return "modern"; // default
}
@Data
@@ -62,6 +62,15 @@ public class FormFieldWithCoordinates {
@Schema(description = "Widget coordinates on each page (fields can have multiple widgets)")
private List<WidgetCoordinates> widgets;
@Schema(description = "Maximum character count for a text field (/MaxLen); null when unset")
private Integer maxLength;
@Schema(
description =
"Push button activation action as a spec string:"
+ " 'reset', 'print', 'uri:<url>' or 'submit:<url>'")
private String buttonActionSpec;
/**
* Coordinates for a single widget annotation (visual representation of the field). A field can
* have multiple widgets if it appears on multiple pages.
@@ -94,5 +103,12 @@ public class FormFieldWithCoordinates {
@Schema(description = "Font size in PDF points")
private Float fontSize;
@Schema(
description =
"CropBox height in PDF points. Lets the frontend reverse the backend's"
+ " Y-flip when sending new widget coordinates back for"
+ " create/modify operations.")
private Float cropBoxHeight;
}
}
@@ -52,6 +52,13 @@ public class JobResult {
/** Key/value metadata that survives the write-through into the shared job store. */
private final Map<String, String> metadata = new ConcurrentHashMap<>();
/**
* File ids of the persistent input copies made for this job. An async submit copies the upload
* into FileStorage so the job can still read it after the request returns; without tracking
* them here nothing would ever delete those copies.
*/
@JsonIgnore private final List<String> inputFileIds = new CopyOnWriteArrayList<>();
/**
* Create a new JobResult with the given job ID
*
@@ -167,6 +174,22 @@ public class JobResult {
return Collections.unmodifiableList(notes);
}
/** Record a persistent input copy so job cleanup deletes it alongside the results. */
public void addInputFileId(String fileId) {
if (fileId != null && !fileId.isBlank() && !inputFileIds.contains(fileId)) {
this.inputFileIds.add(fileId);
}
}
/**
* File ids of this job's persistent input copies.
*
* @return An unmodifiable view of the input file ids
*/
public List<String> getInputFileIds() {
return Collections.unmodifiableList(inputFileIds);
}
/** Attach a metadata value, e.g. a policy id so cluster peers can identify a policy run. */
public void putMetadata(String key, String value) {
if (key != null && value != null) {
@@ -60,54 +60,40 @@ public class Provider {
}
private UsernameAttribute validateUsernameAttribute(UsernameAttribute usernameAttribute) {
switch (name) {
case "google" -> {
return validateGoogleUsernameAttribute(usernameAttribute);
}
case "github" -> {
return validateGitHubUsernameAttribute(usernameAttribute);
}
case "keycloak" -> {
return validateKeycloakUsernameAttribute(usernameAttribute);
}
default -> {
return usernameAttribute;
}
}
return switch (name) {
case "google" -> validateGoogleUsernameAttribute(usernameAttribute);
case "github" -> validateGitHubUsernameAttribute(usernameAttribute);
case "keycloak" -> validateKeycloakUsernameAttribute(usernameAttribute);
default -> usernameAttribute;
};
}
private UsernameAttribute validateKeycloakUsernameAttribute(
UsernameAttribute usernameAttribute) {
switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME, PREFERRED_USERNAME -> {
return usernameAttribute;
}
return switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME, PREFERRED_USERNAME -> usernameAttribute;
default ->
throw new UnsupportedClaimException(
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
}
};
}
private UsernameAttribute validateGoogleUsernameAttribute(UsernameAttribute usernameAttribute) {
switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -> {
return usernameAttribute;
}
return switch (usernameAttribute) {
case EMAIL, NAME, GIVEN_NAME, FAMILY_NAME -> usernameAttribute;
default ->
throw new UnsupportedClaimException(
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
}
};
}
private UsernameAttribute validateGitHubUsernameAttribute(UsernameAttribute usernameAttribute) {
switch (usernameAttribute) {
case LOGIN, EMAIL, NAME -> {
return usernameAttribute;
}
return switch (usernameAttribute) {
case LOGIN, EMAIL, NAME -> usernameAttribute;
default ->
throw new UnsupportedClaimException(
String.format(EXCEPTION_MESSAGE, usernameAttribute, clientName));
}
};
}
@Override
@@ -361,8 +361,8 @@ public class PdfMarkdownConverter {
if (xs.isEmpty()) {
return List.of(lines);
}
float minX = xs.get(0);
float maxX = xs.get(xs.size() - 1);
float minX = xs.getFirst();
float maxX = xs.getLast();
float splitAt = (minX + maxX) / 2f;
float biggestGap = 0;
for (int i = 1; i < xs.size(); i++) {
@@ -492,7 +492,7 @@ public class PdfMarkdownConverter {
List<List<Line>> anchorGroups = new ArrayList<>();
List<Line> current = new ArrayList<>();
current.add(cands.get(0));
current.add(cands.getFirst());
for (int i = 1; i < cands.size(); i++) {
float gap = cands.get(i - 1).y - cands.get(i).y;
if (gap > splitThreshold) {
@@ -513,8 +513,8 @@ public class PdfMarkdownConverter {
if (anchors.size() < 2) {
continue;
}
float top = anchors.get(0).y;
float bottom = anchors.get(anchors.size() - 1).y;
float top = anchors.getFirst().y;
float bottom = anchors.getLast().y;
// Each anchor seeds a row; absorb wrapped continuation lines (non-anchors within the
// run's vertical span, with a little slack below the last row) into the anchor above.
@@ -674,8 +674,8 @@ public class PdfMarkdownConverter {
float minGutter = Math.max(10f, charWidth * 2.5f);
List<float[]> merged = new ArrayList<>();
for (float[] band : columns) {
if (!merged.isEmpty() && band[0] - merged.get(merged.size() - 1)[1] < minGutter) {
merged.get(merged.size() - 1)[1] = band[1];
if (!merged.isEmpty() && band[0] - merged.getLast()[1] < minGutter) {
merged.getLast()[1] = band[1];
} else {
merged.add(new float[] {band[0], band[1]});
}
@@ -734,7 +734,7 @@ public class PdfMarkdownConverter {
}
}
StringBuilder sb = new StringBuilder();
sb.append(buildGfmRow(rows.get(0), widths, cols)).append('\n');
sb.append(buildGfmRow(rows.getFirst(), widths, cols)).append('\n');
sb.append('|');
for (int c = 0; c < cols; c++) {
sb.append('-').append("-".repeat(widths[c])).append('-').append('|');
@@ -910,8 +910,8 @@ public class PdfMarkdownConverter {
}
// Only merge a sentence continuation between two text paragraphs, never into/out of a
// table.
if (!(output.get(output.size() - 1) instanceof String last)
|| !(pageItems.get(0) instanceof String first)) {
if (!(output.getLast() instanceof String last)
|| !(pageItems.getFirst() instanceof String first)) {
return;
}
if (!first.isEmpty()
@@ -932,13 +932,13 @@ public class PdfMarkdownConverter {
for (Object e : elements) {
if (e instanceof TableBlock tb
&& !out.isEmpty()
&& out.get(out.size() - 1) instanceof TableBlock prev
&& out.getLast() instanceof TableBlock prev
&& columnsMatch(flatten(prev.rows()), flatten(tb.rows()))) {
List<List<Line>> merged = new ArrayList<>(prev.rows());
List<List<Line>> tail = tb.rows();
if (!tail.isEmpty()
&& !prev.rows().isEmpty()
&& rowText(tail.get(0)).equals(rowText(prev.rows().get(0)))) {
&& rowText(tail.getFirst()).equals(rowText(prev.rows().getFirst()))) {
tail = tail.subList(1, tail.size());
}
merged.addAll(tail);
@@ -971,7 +971,7 @@ public class PdfMarkdownConverter {
continue;
}
if (e instanceof TableBlock tb && !tb.rows().isEmpty()) {
return rowText(tb.rows().get(0));
return rowText(tb.rows().getFirst());
}
return null;
}
@@ -729,4 +729,32 @@ public class CustomPDFDocumentFactory {
p.toFile().deleteOnExit();
return p;
}
/** A custom RandomAccessRead implementation that deletes the file when closed */
private static class DeletingRandomAccessFile extends RandomAccessReadBufferedFile {
private final Path tempFilePath;
public DeletingRandomAccessFile(File file) throws IOException {
super(file);
this.tempFilePath = file.toPath();
}
@Override
public void close() throws IOException {
try {
super.close();
} finally {
try {
boolean deleted = Files.deleteIfExists(tempFilePath);
if (deleted) {
log.info("Successfully deleted temp file: {}", tempFilePath);
} else {
log.warn("Failed to delete temp file (may not exist): {}", tempFilePath);
}
} catch (IOException e) {
log.error("Error deleting temp file: {}", tempFilePath, e);
}
}
}
}
}
@@ -179,6 +179,21 @@ public class FileStorage {
return fileStore.delete(fileId);
}
/**
* Delete a stored file without the per-file ownership check.
*
* <p>Job cleanup authorises at the job level and then deletes that job's own files, so the
* deleter is legitimately not their owner - an admin sweeping every user's jobs, or the
* unauthenticated scheduled task. Routing those through {@link #deleteFile(String)} makes the
* ownership check throw and silently orphans the files on disk.
*
* <p>Only ever pass file ids read back off a job that the caller has already been authorised
* for; never a caller-supplied id.
*/
public boolean deleteFileAsSystem(String fileId) {
return fileStore.delete(fileId);
}
public boolean fileExists(String fileId) {
enforceOwnership(fileId);
return fileStore.exists(fileId);
@@ -157,7 +157,7 @@ public class InternalApiClient {
boolean hasFilePart =
body.values().stream()
.flatMap(java.util.List::stream)
.anyMatch(v -> v instanceof Resource);
.anyMatch(Resource.class::isInstance);
if (isAiTool && !hasFilePart) {
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
}
@@ -1,6 +1,7 @@
package stirling.software.common.service;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@@ -33,6 +34,14 @@ import stirling.software.common.util.RegexPatternUtils;
@Slf4j
public class JobExecutorService {
/**
* Request attribute holding the FileStorage ids of persistent input copies made for the job
* about to be created. Populated before the job id exists (the aspect copies the upload while
* processing arguments), drained onto the JobResult as soon as the task is created so cleanup
* can delete them.
*/
public static final String PENDING_INPUT_FILE_IDS_ATTR = "autoJobPendingInputFileIds";
private final TaskManager taskManager;
private final FileStorage fileStorage;
private final HttpServletRequest request;
@@ -133,6 +142,7 @@ public class JobExecutorService {
resourceWeight);
taskManager.createTask(jobId);
registerPendingInputFiles(jobId);
final String capturedJobIdForQueue = jobId;
Supplier<Object> wrappedWork =
@@ -163,6 +173,7 @@ public class JobExecutorService {
return ResponseEntity.ok().body(new JobResponse<>(true, jobId, null));
} else if (async) {
taskManager.createTask(jobId);
registerPendingInputFiles(jobId);
final String capturedJobId = jobId;
@@ -484,4 +495,30 @@ public class JobExecutorService {
}
return baseJobId;
}
/**
* Hand the input copies made while processing arguments to the freshly created job, so job
* cleanup deletes them. Drains the attribute so a retry cannot attribute the same ids twice.
*/
@SuppressWarnings("unchecked")
private void registerPendingInputFiles(String jobId) {
if (request == null) {
return;
}
Object pending;
try {
pending = request.getAttribute(PENDING_INPUT_FILE_IDS_ATTR);
request.removeAttribute(PENDING_INPUT_FILE_IDS_ATTR);
} catch (RuntimeException ex) {
// No request bound to this thread (e.g. an internally dispatched job).
log.debug("Could not read pending input file ids: {}", ex.getMessage());
return;
}
if (!(pending instanceof List<?> ids)) {
return;
}
for (String fileId : (List<String>) ids) {
taskManager.registerInputFile(jobId, fileId);
}
}
}
@@ -0,0 +1,22 @@
package stirling.software.common.service;
import java.util.List;
/**
* Raises a converted PDF/A file from conformance level B to level A, which needs the tagging the
* PDF/UA tagger does. Implemented only in the proprietary module; core builds convert at level B.
*/
public interface PdfaLevelAServiceInterface {
/**
* @param levelA true only when the file was tagged and validated, so the claim is never a guess
*/
record Result(byte[] pdfBytes, boolean levelA, List<String> warnings) {}
/**
* @param part PDF/A part, 1 to 3; part 1 keeps its PDF 1.4 version
* @param alsoDeclareUa additionally claim PDF/UA, but only if it validates
*/
Result upgradeToLevelA(
byte[] pdfBytes, int part, String language, String title, boolean alsoDeclareUa);
}
@@ -17,6 +17,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Predicate;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
@@ -234,6 +235,24 @@ public class TaskManager {
return false;
}
/**
* Record a persistent input copy against a job so cleanup deletes it with the results.
*
* @param jobId The job ID
* @param fileId The FileStorage id of the input copy
* @return true if the job exists and the id was recorded
*/
public boolean registerInputFile(String jobId, String fileId) {
JobResult jobResult = jobResults.get(jobId);
if (jobResult == null) {
log.warn("Attempted to register an input file against non-existent job ID: {}", jobId);
return false;
}
jobResult.addInputFileId(fileId);
log.debug("Registered input file {} for job {}", fileId, jobId);
return true;
}
/** Attach metadata to a job and write it through to the shared store for cluster peers. */
public boolean putMetadata(String jobId, String key, String value) {
JobResult jobResult = jobResults.get(jobId);
@@ -329,25 +348,59 @@ public class TaskManager {
return jobResults.computeIfAbsent(jobId, JobResult::createNew);
}
/**
* What a cleanup pass removed. Returned by the on-demand cleanup so callers can assert on it.
*/
public record CleanupSummary(int jobsRemoved, int filesDeleted, int jobsRetained) {}
/** Clean up old completed job results. No-op in cluster mode; the backplane TTL owns expiry. */
public void cleanupOldJobs() {
public CleanupSummary cleanupOldJobs() {
if (clusterBackplane != null && !clusterBackplane.shouldRunLocalCleanup()) {
return;
return new CleanupSummary(0, 0, jobResults.size());
}
return cleanupJobs(false, jobId -> true);
}
/**
* Force-expire this node's finished jobs now, ignoring the age threshold. Jobs still running
* are left alone - deleting their files mid-flight would break them - and are reported as
* retained.
*
* <p>Unlike {@link #cleanupOldJobs()} this always runs locally: it is an explicit request to
* release this node's storage, not the scheduled sweep the backplane TTL owns.
*
* @param jobIdFilter Only jobs whose id passes this predicate are considered, so a caller can
* restrict the sweep to jobs the requester is allowed to touch
* @return What was removed
*/
public CleanupSummary cleanupFinishedJobsNow(Predicate<String> jobIdFilter) {
return cleanupJobs(true, jobIdFilter);
}
private CleanupSummary cleanupJobs(boolean force, Predicate<String> filter) {
LocalDateTime expiryThreshold =
LocalDateTime.now().minus(jobResultExpiryMinutes, ChronoUnit.MINUTES);
LocalDateTime pendingExpiryThreshold =
LocalDateTime.now().minus(pendingJobExpiryMinutes, ChronoUnit.MINUTES);
int removedCount = 0;
int filesDeleted = 0;
int retainedCount = 0;
try {
for (Map.Entry<String, JobResult> entry : jobResults.entrySet()) {
JobResult result = entry.getValue();
if (!filter.test(entry.getKey())) {
retainedCount++;
continue;
}
boolean expiredCompletedJob =
result.isComplete()
&& result.getCompletedAt() != null
&& result.getCompletedAt().isBefore(expiryThreshold);
&& (force
|| (result.getCompletedAt() != null
&& result.getCompletedAt()
.isBefore(expiryThreshold)));
boolean abandonedPendingJob =
!result.isComplete()
&& result.getCreatedAt() != null
@@ -360,7 +413,7 @@ public class TaskManager {
// Clean up file results
if (expiredCompletedJob) {
cleanupJobFiles(result, entry.getKey());
filesDeleted += cleanupJobFiles(result, entry.getKey());
}
// Remove the job result
@@ -369,15 +422,22 @@ public class TaskManager {
jobStore.delete(entry.getKey());
}
removedCount++;
} else {
retainedCount++;
}
}
if (removedCount > 0) {
log.info("Cleaned up {} expired job results", removedCount);
log.info(
"Cleaned up {} {} job results ({} files deleted)",
removedCount,
force ? "finished" : "expired",
filesDeleted);
}
} catch (Exception e) {
log.error("Error during job cleanup: {}", e.getMessage(), e);
}
return new CleanupSummary(removedCount, filesDeleted, retainedCount);
}
/** Mirror the in-memory {@code JobResult} into the cluster-visible {@link JobStore}. */
@@ -525,22 +585,43 @@ public class TaskManager {
}
}
/** Clean up files associated with a job result */
private void cleanupJobFiles(JobResult result, String jobId) {
/**
* Clean up files associated with a job result: both the results and the persistent input copy
* an async submit made of the upload.
*
* @return The number of files actually deleted
*/
private int cleanupJobFiles(JobResult result, String jobId) {
int deleted = 0;
// Clean up all result files
if (result.hasFiles()) {
for (ResultFile resultFile : result.getAllResultFiles()) {
try {
fileStorage.deleteFile(resultFile.getFileId());
} catch (Exception e) {
log.warn(
"Failed to delete file {} for job {}: {}",
resultFile.getFileId(),
jobId,
e.getMessage());
if (deleteJobFile(resultFile.getFileId(), jobId)) {
deleted++;
}
}
}
for (String inputFileId : result.getInputFileIds()) {
if (deleteJobFile(inputFileId, jobId)) {
deleted++;
}
}
return deleted;
}
/**
* Deletes as the system, not as the caller: an admin sweeping another user's jobs, or the
* scheduled task running with no security context, is not the file's owner, and the
* ownership-checked delete would throw and leave the file orphaned on disk. The job itself is
* already authorised by the time we get here, and these ids come off that job, not the request.
*/
private boolean deleteJobFile(String fileId, String jobId) {
try {
return fileStorage.deleteFileAsSystem(fileId);
} catch (Exception e) {
log.warn("Failed to delete file {} for job {}: {}", fileId, jobId, e.getMessage());
return false;
}
}
/** Find the ResultFile metadata for a given file ID by searching through all job results */
@@ -140,20 +140,25 @@ public class ChecksumUtils {
for (String algorithm : algorithms) {
String key = algorithm; // keep original key for output
switch (algorithm.toUpperCase(Locale.ROOT)) {
case "CRC32":
checksums.put(key, new CRC32());
break;
case "ADLER32":
checksums.put(key, new Adler32());
break;
default:
try {
// For MessageDigest, pass the original name (case-insensitive per JCA)
digests.put(key, MessageDigest.getInstance(algorithm));
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException("Unsupported algorithm: " + algorithm, e);
}
Object digestOrChecksum =
switch (algorithm.toUpperCase(Locale.ROOT)) {
case "CRC32" -> new CRC32();
case "ADLER32" -> new Adler32();
default -> {
try {
// For MessageDigest, pass the original name (case-insensitive
// per JCA)
yield MessageDigest.getInstance(algorithm);
} catch (NoSuchAlgorithmException e) {
throw new IllegalStateException(
"Unsupported algorithm: " + algorithm, e);
}
}
};
if (digestOrChecksum instanceof Checksum checksum) {
checksums.put(key, checksum);
} else {
digests.put(key, (MessageDigest) digestOrChecksum);
}
}
@@ -751,7 +751,7 @@ public class ExceptionUtils {
String targetDescription;
if (errorInfo.affectedPages() != null && !errorInfo.affectedPages().isEmpty()) {
if (errorInfo.affectedPages().size() == 1) {
targetDescription = "page " + errorInfo.affectedPages().get(0);
targetDescription = "page " + errorInfo.affectedPages().getFirst();
} else {
targetDescription =
"pages "
@@ -848,7 +848,7 @@ public class ExceptionUtils {
}
// Use the first page number, or null if none found
Integer pageNumber = affectedPages.isEmpty() ? null : affectedPages.get(0);
Integer pageNumber = affectedPages.isEmpty() ? null : affectedPages.getFirst();
return new GhostscriptErrorInfo(
ErrorCode.GHOSTSCRIPT_PAGE_DRAWING,
@@ -3,14 +3,20 @@ package stirling.software.common.util;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.pdfbox.cos.COSName;
import org.apache.pdfbox.pdmodel.graphics.color.PDColor;
import org.apache.pdfbox.pdmodel.graphics.color.PDDeviceRGB;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionNamed;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionResetForm;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionSubmitForm;
import org.apache.pdfbox.pdmodel.interactive.action.PDActionURI;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAnnotationWidget;
import org.apache.pdfbox.pdmodel.interactive.annotation.PDAppearanceCharacteristicsDictionary;
import org.apache.pdfbox.pdmodel.interactive.form.PDAcroForm;
@@ -59,6 +65,24 @@ public enum FormFieldTypeSupport {
List<String> options)
throws IOException {
PDTextField textField = (PDTextField) field;
if (definition.fontSize() != null && definition.fontSize() > 0) {
textField.setDefaultAppearance("/Helv " + definition.fontSize() + " Tf 0 g");
}
if (Boolean.TRUE.equals(definition.multiline())) {
textField.setMultiline(true);
}
// Comb field: evenly spaced character cells (e.g. SSN, phone). Requires
// a positive MaxLen and is mutually exclusive with multiline.
if (definition.maxLength() != null && definition.maxLength() > 0) {
textField.setMaxLen(definition.maxLength());
if (!Boolean.TRUE.equals(definition.multiline())) {
try {
textField.setComb(true);
} catch (Exception e) {
log.debug("Unable to set comb flag: {}", e.getMessage());
}
}
}
String defaultValue = Optional.ofNullable(definition.defaultValue()).orElse("");
if (!defaultValue.isBlank()) {
FormUtils.setTextValue(textField, defaultValue);
@@ -114,7 +138,7 @@ public enum FormFieldTypeSupport {
return;
}
PDAnnotationWidget widget = checkBox.getWidgets().get(0);
PDAnnotationWidget widget = checkBox.getWidgets().getFirst();
PDAppearanceCharacteristicsDictionary appearanceChars =
widget.getAppearanceCharacteristics();
@@ -272,14 +296,108 @@ public enum FormFieldTypeSupport {
PDTerminalField createField(PDAcroForm acroForm) {
return new PDSignatureField(acroForm);
}
@Override
boolean doesNotsupportsDefinitionCreation() {
return false;
}
// Empty signature placeholder: no value to apply (signed later by a sign tool).
},
BUTTON("button", "pushButton", PDPushButton.class) {
@Override
PDTerminalField createField(PDAcroForm acroForm) {
return new PDPushButton(acroForm);
}
@Override
boolean doesNotsupportsDefinitionCreation() {
return false;
}
@Override
void applyNewFieldDefinition(
PDTerminalField field,
FormUtils.NewFormFieldDefinition definition,
List<String> options)
throws IOException {
if (field.getWidgets().isEmpty()) {
return;
}
PDAnnotationWidget widget = field.getWidgets().get(0);
// Visible caption (/MK /CA).
String caption = definition.label();
if (caption == null || caption.isBlank()) {
caption = definition.name();
}
if (caption != null && !caption.isBlank()) {
PDAppearanceCharacteristicsDictionary mk = widget.getAppearanceCharacteristics();
if (mk == null) {
mk = new PDAppearanceCharacteristicsDictionary(widget.getCOSObject());
widget.setAppearanceCharacteristics(mk);
}
mk.setNormalCaption(caption);
}
widget.setPrinted(true);
applyButtonAction(widget, definition.buttonAction());
}
};
/**
* Writes a push button's activation action from a "reset"/"print"/"uri:"/"submit:" spec,
* returning why it could not, or null on success. A blank spec clears the action.
*/
public static String applyButtonAction(PDAnnotationWidget widget, String action) {
if (action == null) {
return null;
}
if (action.isBlank()) {
// An explicit blank clears the action rather than leaving the old one behind.
widget.getCOSObject().removeItem(COSName.A);
return null;
}
String spec = action.trim();
if (!ACTION_SPEC.matcher(spec).matches()) {
return "'" + action + "' is not a button action this editor understands";
}
// The editor emits "uri:" the moment that kind is picked, before a URL is typed; an
// empty target is not yet an action, so clear rather than write an inert one.
int colon = spec.indexOf(':');
if (colon >= 0 && spec.substring(colon + 1).isBlank()) {
widget.getCOSObject().removeItem(COSName.A);
return null;
}
try {
String lower = spec.toLowerCase(Locale.ROOT);
if (lower.equals("reset")) {
widget.getCOSObject().setItem(COSName.A, new PDActionResetForm().getCOSObject());
} else if (lower.equals("print")) {
PDActionNamed named = new PDActionNamed();
named.setN("Print");
widget.getCOSObject().setItem(COSName.A, named.getCOSObject());
} else if (lower.startsWith("uri:")) {
PDActionURI uri = new PDActionURI();
uri.setURI(spec.substring(4));
widget.getCOSObject().setItem(COSName.A, uri.getCOSObject());
} else if (lower.startsWith("submit:")) {
PDActionSubmitForm submit = new PDActionSubmitForm();
// Store the target URL on the action dictionary's /F entry.
submit.getCOSObject().setString(COSName.F, spec.substring(7));
widget.getCOSObject().setItem(COSName.A, submit.getCOSObject());
}
return null;
} catch (Exception e) {
log.debug("Unable to apply button action '{}': {}", action, e.getMessage());
return e.getMessage();
}
}
/** The spec forms applyButtonAction understands; anything else is reported, not dropped. */
private static final Pattern ACTION_SPEC =
Pattern.compile(
"^(reset|print|uri:.*|submit:.*)$", Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
private static final Map<String, FormFieldTypeSupport> BY_TYPE =
Arrays.stream(values())
.collect(
File diff suppressed because it is too large Load Diff
@@ -12,6 +12,7 @@ import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.MessageDigest;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -54,6 +55,10 @@ public class GeneralUtils {
private final String DEFAULT_WEBUI_CONFIGS_DIR = "defaultWebUIConfigs";
private final String PYTHON_SCRIPTS_DIR = "python";
// Extracted once per run. Rewriting a script while another request is exec-ing it
// races wherever rename is not atomic, such as 9p or NFS bind mounts.
private final Map<String, Path> EXTRACTED_SCRIPTS = new ConcurrentHashMap<>();
private final RegexPatternUtils patternCache = RegexPatternUtils.getInstance();
// Valid size units used for convertSizeToBytes validation and parsing
private final Set<String> VALID_SIZE_UNITS = Set.of("B", "KB", "MB", "GB", "TB");
@@ -1025,17 +1030,30 @@ public class GeneralUtils {
}
Path scriptsDir = Path.of(InstallationPathConfig.getScriptsPath(), PYTHON_SCRIPTS_DIR);
Files.createDirectories(scriptsDir);
Path target = scriptsDir.resolve(scriptName);
ClassPathResource res =
new ClassPathResource("static/" + PYTHON_SCRIPTS_DIR + "/" + scriptName);
if (!res.exists()) {
log.error("Resource not found: {}", res.getPath());
throw new IOException("Resource not found: " + res.getPath());
Path cached = EXTRACTED_SCRIPTS.get(scriptName);
if (cached != null && Files.isRegularFile(cached)) {
return cached;
}
synchronized (EXTRACTED_SCRIPTS) {
cached = EXTRACTED_SCRIPTS.get(scriptName);
if (cached != null && Files.isRegularFile(cached)) {
return cached;
}
Files.createDirectories(scriptsDir);
ClassPathResource res =
new ClassPathResource("static/" + PYTHON_SCRIPTS_DIR + "/" + scriptName);
if (!res.exists()) {
log.error("Resource not found: {}", res.getPath());
throw new IOException("Resource not found: " + res.getPath());
}
copyResourceToFile(res, target);
EXTRACTED_SCRIPTS.put(scriptName, target);
return target;
}
copyResourceToFile(res, target);
return target;
}
/*
@@ -26,29 +26,27 @@ import lombok.extern.slf4j.Slf4j;
public class ImageProcessingUtils {
static BufferedImage convertColorType(BufferedImage sourceImage, String colorType) {
BufferedImage convertedImage;
switch (colorType) {
case "greyscale":
convertedImage =
return switch (colorType) {
case "greyscale" -> {
BufferedImage convertedImage =
new BufferedImage(
sourceImage.getWidth(),
sourceImage.getHeight(),
BufferedImage.TYPE_BYTE_GRAY);
convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
break;
case "blackwhite":
convertedImage =
yield convertedImage;
}
case "blackwhite" -> {
BufferedImage convertedImage =
new BufferedImage(
sourceImage.getWidth(),
sourceImage.getHeight(),
BufferedImage.TYPE_BYTE_BINARY);
convertedImage.getGraphics().drawImage(sourceImage, 0, 0, null);
break;
default: // full color
convertedImage = sourceImage;
break;
}
return convertedImage;
yield convertedImage;
}
default -> sourceImage;
};
}
public static byte[] getImageData(BufferedImage image) {
@@ -330,7 +330,7 @@ public class PDFToFile {
if (outputFiles.size() == 1) {
// Return single output file
File outputFile = outputFiles.get(0);
File outputFile = outputFiles.getFirst();
if ("txt:Text".equals(outputFormat)) {
outputFormat = "txt";
}
@@ -307,7 +307,7 @@ public class ProcessExecutor {
boolean isQpdf =
commandToRun != null
&& !commandToRun.isEmpty()
&& commandToRun.get(0).contains("qpdf");
&& commandToRun.getFirst().contains("qpdf");
if (!outputLines.isEmpty()) {
String outputMessage = String.join("\n", outputLines);
@@ -370,7 +370,7 @@ public class ProcessExecutor {
}
// Check if this is a UNO conversion by looking for unoconvert executable
String executable = command.get(0);
String executable = command.getFirst();
if (executable != null) {
// Extract basename from path for matching
String basename = executable;
@@ -504,7 +504,7 @@ public class ProcessExecutor {
}
// Validate executable (first argument)
String executable = command.get(0);
String executable = command.getFirst();
if (executable == null || executable.isBlank()) {
throw new IllegalArgumentException("Command executable must not be empty");
}
@@ -4,7 +4,14 @@ import java.util.regex.Pattern;
public class RequestUriUtils {
private static final Pattern SHARE_LINK_PATTERN = Pattern.compile("^/share/[^/]+/?$");
// Share tokens are 36-char lowercase UUIDs (UUID.randomUUID().toString()); match exactly
private static final Pattern SHARE_LINK_PATTERN =
Pattern.compile(
"^/share/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/?$");
// Invite tokens are 36-char lowercase UUIDs (UUID.randomUUID().toString()); match exactly
private static final Pattern INVITE_LINK_PATTERN =
Pattern.compile(
"^/invite/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/?$");
public static boolean isStaticResource(String requestURI) {
return isStaticResource("", requestURI);
@@ -69,7 +76,7 @@ public class RequestUriUtils {
// cookie, so the server can't authenticate the navigation itself). The
// portal gates access via its own auth gate + RequirePortalAccess, and its
// data APIs stay protected, so serving the shell pre-auth is safe.
if (normalizedUri.equals("/processor") || normalizedUri.startsWith("/processor/")) {
if ("/processor".equals(normalizedUri) || normalizedUri.startsWith("/processor/")) {
return true;
}
@@ -209,7 +216,9 @@ public class RequestUriUtils {
// Workflow participant endpoints - access controlled by share tokens, not login
|| trimmedUri.startsWith("/api/v1/workflow/participant/")
// Share-link SPA bootstrap; data APIs remain protected
|| SHARE_LINK_PATTERN.matcher(trimmedUri).matches();
|| SHARE_LINK_PATTERN.matcher(trimmedUri).matches()
// Invite-accept SPA bootstrap; data APIs remain protected
|| INVITE_LINK_PATTERN.matcher(trimmedUri).matches();
}
private static String stripContextPath(String contextPath, String requestURI) {
@@ -114,7 +114,7 @@ public class YamlHelper {
for (NodeTuple tuple : mappingNode.getValue()) {
ScalarNode keyNode = (tuple.getKeyNode() instanceof ScalarNode sk) ? sk : null;
if (keyNode == null || !keyNode.getValue().equals(keys.get(0))) {
if (keyNode == null || !keyNode.getValue().equals(keys.getFirst())) {
updatedTuples.add(tuple);
continue;
}
@@ -15,7 +15,8 @@ public class StringToMapPropertyEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
try {
TypeReference<HashMap<String, String>> typeRef = new TypeReference<>() {};
TypeReference<HashMap<String, String>> typeRef =
new TypeReference<HashMap<String, String>>() {};
Map<String, String> map = objectMapper.readValue(text, typeRef);
setValue(map);
} catch (Exception e) {
@@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test;
import stirling.software.SPDF.config.EndpointConfiguration.DisableReason;
import stirling.software.SPDF.config.EndpointConfiguration.EndpointAvailability;
import stirling.software.common.model.ApplicationProperties;
import stirling.software.common.service.PdfaLevelAServiceInterface;
/**
* Unit tests for {@link EndpointConfiguration}. The class wires up its endpoint/group registry in
@@ -32,7 +33,14 @@ class EndpointConfigurationGapTest {
* Construct an EndpointConfiguration with the given pro flag and current applicationProperties.
*/
private EndpointConfiguration build(boolean runningProOrHigher) {
return new EndpointConfiguration(applicationProperties, runningProOrHigher);
return build(runningProOrHigher, null);
}
/** The PDF/UA service is only present in proprietary builds, so it is injected separately. */
private EndpointConfiguration build(
boolean runningProOrHigher, PdfaLevelAServiceInterface pdfaLevelAService) {
return new EndpointConfiguration(
applicationProperties, runningProOrHigher, pdfaLevelAService);
}
/** Default config: not pro, no removals, url-to-pdf disabled (default System flag is false). */
@@ -177,6 +185,28 @@ class EndpointConfigurationGapTest {
}
}
@Nested
@DisplayName("PDF/UA availability")
class PdfUaTests {
@Test
@DisplayName("the PDF/UA endpoints are off when the proprietary tagger is absent")
void disabledWithoutTagger() {
EndpointConfiguration config = build(false, null);
assertFalse(config.isEndpointEnabled("pdf-to-ua"));
assertFalse(config.isEndpointEnabled("accessibility-report"));
}
@Test
@DisplayName("they are on once the tagger is on the classpath")
void enabledWithTagger() {
EndpointConfiguration config =
build(false, (pdfBytes, part, language, title, alsoDeclareUa) -> null);
assertTrue(config.isEndpointEnabled("pdf-to-ua"));
assertTrue(config.isEndpointEnabled("accessibility-report"));
}
}
@Nested
@DisplayName("group enable / disable")
class GroupTests {
@@ -2,6 +2,7 @@ package stirling.software.common.service;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -65,6 +66,24 @@ class FileStorageOwnershipTest {
assertThrows(SecurityException.class, () -> fs.deleteFile(id));
}
@Test
void systemDeleteOfAnotherUsersFile_allowed_soJobCleanupDoesNotOrphanIt(@TempDir Path tempDir)
throws IOException {
// An admin sweeping every user's finished jobs is not the owner of their files. The
// ownership-checked delete throws there, which used to drop the job record and leave the
// files stranded on disk with nothing left able to reference them.
AtomicReference<String> user = new AtomicReference<>("alice");
FileStorage fs = newStorageWithCurrentUser(tempDir, user);
String id = fs.storeBytes("alice's file".getBytes(), "x.bin");
user.set("admin");
assertThrows(SecurityException.class, () -> fs.deleteFile(id));
assertTrue(fs.deleteFileAsSystem(id), "System delete must not be blocked by ownership");
user.set("alice");
assertThrows(IOException.class, () -> fs.retrieveBytes(id), "File should really be gone");
}
@Test
void anonymousRetrieveOfOwnedFile_allowed_noCurrentUserMeansNoCompare(@TempDir Path tempDir)
throws IOException {

Some files were not shown because too many files have changed in this diff Show More