Commit Graph
147 Commits
Author SHA1 Message Date
Anthony StirlingandGitHub fe7a2a5ac7 Fix Multi Tool page rotation lost on save (#6733)
# Description of Changes

Rotating a page in the Multi Tool and saving could leave the page at its
original rotation (the change appeared lost), with inconsistent results
across pages.

- Page rotation is now always written on export, including 0°, so
rotating a page that already had a non-zero rotation in the source PDF
(e.g. a 270° page rotated back to upright) is no longer dropped.
- Per-page rotation is always read when building the Multi Tool
document, so pages keep their true orientation regardless of file size.
- Rotation is only applied after a page imports successfully, avoiding a
misaligned or failed export when an import fails.

---

## 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)
- [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
- [ ] 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-06-19 14:19:51 +00:00
James BruntonandGitHub 6a9876a067 Fix more any typing usage in the frontend (#6664)
# Description of Changes
Continued effort to remove the remaining uses of the `any` type from our
TS code. The vast majority of these uses that it cleans up was just
catching errors as `any`, which are pretty simple to fix. I couldn't
completely remove the `any` type usage from `core/tools` because there
were cascading issues from a couple of the files in there (most notably
Automate) but still, moving in the right direction.
2026-06-19 13:37:53 +00:00
James BruntonandGitHub 3793a6df52 Fix bad frontend architecture (#6730)
# Description of Changes
#6727 introduced frontend code which goes against the architecture, so
this PR re-implements it in the architecture properly, along with
another bad Tauri check that I found in the source. I also updated the
`AGENTS.md` file to use Claude's "read this file" syntax to try and
force AI to actually read the file instead of just suggesting that it
does it.
2026-06-19 12:34:13 +00:00
LudyandGitHub f8ceca0c3f feat(i18n): sync editor translations with pluralization support and new UI strings (#6565)
# Description of Changes

## What was changed

- Updated editor translation files across multiple locales.
- Migrated numerous count-based translation keys from legacy
`{{plural}}` handling to ICU-style plural forms using `_one`, `_other`,
and where applicable `_zero` variants.
- Added translations and localization keys for newly introduced features
and UI areas, including:
  - Stirling Agents
  - Chat interface and quick actions
  - Files management and folder organization
  - Desktop update workflow
  - Folder scanning warnings
  - Team and workspace management
  - Sharing and upload dialogs
  - Comparison status messages
  - Relative time formatting
  - Additional tool panel and update UI strings
- Added missing translation entries required by recently introduced
frontend functionality.
- Reorganized some translation sections to maintain consistency and key
ordering.

## Why the change was made

- To align locale files with the current frontend feature set.
- To support proper pluralization behavior across languages.
- To prevent missing translation keys and fallback text in newly added
UI components.
- To improve localization consistency and maintainability as the
application grows.

---

## 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-06-19 10:34:55 +00:00
Anthony StirlingandGitHub e6d476297d Clean up update dialog UI and fix desktop external links (#6727) 2026-06-18 22:20:02 +01:00
c3795c1a3c fix(viewer): wire Ctrl+A to select all text in the PDF (#6517)
# Description of Changes

Allow Ctrl A support in viewer and fix select text to copy issues via a
hovering copy button

---

## 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: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2026-06-18 13:50:08 +00:00
c8925acee7 add prerendered Open Graph previews and OG card generator (#6661)
# Description of Changes

add prerendered Open Graph previews and OG card generator
so that /compresss etc shows a pre generated static html file (Since
google etc doenst render javascript)


---

## 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: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
2026-06-18 13:49:16 +00:00
EthanHealy01andGitHub 18da914bf9 fix theme issues, remove dead rainbow mode code, standardize theme us… (#6668)
Fix issues with the theme of the app that caused some things to persist
in light mode/dark mode whilst the rest of the app was the opposite
theme.

Removed dead rainbow mode code.

Added system theme option to settings.
2026-06-18 12:42:19 +00:00
Reece BrowneandGitHub 8f46ca0d92 feat(policies): lock policies to the SaaS build + profile (#6702)
## What & why

Policies (automation-backed enforcement) execute and bill through the
cloud backend, so the feature should only be available in the hosted
**SaaS** product — not in self-hosted proprietary or core builds. Today
it's enabled in the proprietary build (and the API is exposed in any
proprietary backend), so this locks it to SaaS on both layers.

## Frontend (build-flavor gate)

`POLICIES_ENABLED` is the single gate `usePoliciesEnabled` uses (rail +
auto-run controller).

- `proprietary` flag → **`false`** (self-hosted web no longer shows
policies)
- new `src/saas/constants/featureFlags.ts` → re-exports proprietary
flags, overrides `POLICIES_ENABLED = true`
- new `src/desktop/constants/featureFlags.ts` → same `true` override —
**required**: desktop's `@app` alias has no saas layer, and desktop
already gates policies on `POLICIES_ENABLED && useConfirmedSaaSMode()`,
so without a `true` here that runtime gate could never be satisfied.
Behaviour unchanged: desktop shows policies only when connected to SaaS.
- `PoliciesSidebar.test` mocks the flag on (it tests the component, not
the build gate — same pattern the existing `usePolicyAutoRun.retry.test`
uses).

## Backend (`@Profile("saas")` gate)

The saas backend runs under the `saas` Spring profile (as
`EntitlementGuard`, the AI controllers, etc. already do). The policy
beans are now `@Profile("saas")`, so `/api/v1/policies/*` and the
auto-run triggers exist **only** in the saas backend:

`PolicyController`, `PolicyEngine`, `PolicyRunner`, `PolicyRunRegistry`,
`PolicyValidator`, `JpaPolicyStore`, `FolderInputSource`,
`FolderOutputSink`, `InlineOutputSink`, `PolicyAccessGuard`,
`FolderAccessGuard`, `FolderWatchTrigger`, `ScheduleTrigger`,
`PolicyTriggerManager`.

**Deliberately *not* gated:** `PolicyExecutor` — `AiWorkflowService`
(always-on) injects it to run ad-hoc pipelines, so it stays
profile-free. It only depends on shared infra (`InternalApiClient`,
`ToolMetadataService`, `TempFileManager`, `ObjectMapper`), so leaving it
on is safe. Gating the engine/store/triggers as a set keeps wiring
consistent (nothing un-gated depends on a gated bean).

The saas `PolicyManagementAuthority` impl
(`TeamLeaderPolicyManagementAuthority`, `@Profile("saas")`) satisfies
`PolicyAccessGuard` in the saas context.
`AdminPolicyManagementAuthority` (`@Profile("!saas")`) becomes an unused
orphan in non-saas builds — harmless; left as-is rather than expanding
this PR's scope.

## Testing
- Frontend: full suite **869 pass**; typecheck clean on
proprietary/saas/core.
- Backend: `:proprietary` compiles, spotless clean, policy tests pass,
and the proprietary (non-saas) Spring context still boots with the
policy beans gated out (verified via the MCP `@SpringBootTest`
integration tests — no missing-bean failures).

Net: SaaS web build + desktop-in-SaaS-mode get policies (UI + API);
self-hosted proprietary and core get neither the UI nor the
`/api/v1/policies` endpoints.
2026-06-18 11:05:55 +00:00
Anthony StirlingandGitHub 9a3bc6b47f Add cloud-aware delete and version history to My Files (#6704)
## /files
- Cloud-aware delete: choose device / cloud / both
- `/files` left rail always collapsed
- Details panel: pinned buttons, collapsible info, smaller preview
- Removed redundant Quick view
- New i18n keys added to en-US/en-GB

## Sidebar 
- Sidebar kebab: upload to server, delete, version history (+ cloud
badge)
- Version history modal everywhere files appear
2026-06-18 10:33:18 +00:00
James BruntonandGitHub 0c503cc41d Fix all top-level dev tasks treating engine as enabled (#6705)
# Description of Changes
Currently, `task dev` explicitly calls the backend with
`AIENGINE_ENABLED=true` even though it isn't being spawned, so you just
get a dead FAB in the UI. This PR fixes it so that the engine will only
be enabled for tasks that will actually spawn the engine.

It also fixes a bug with the chat which makes it unusable locally. The
API path was not going through `apiClient` so for local dev you end up
with `//api/v1/...` which is not a valid path, so you get CORS errors
when trying to connect to the AI engine.
2026-06-18 10:08:50 +00:00
albanobattistellaandGitHub b1fef4c647 Update Italian translations (#6713) 2026-06-18 08:35:31 +00:00
EthanHealy01andGitHub 06254853af allow drag and drop onto left files section and make top bar slightly smaller (#6711)
<img width="1261" height="984" alt="Screenshot 2026-06-17 at 5 59 30 PM"
src="https://github.com/user-attachments/assets/849dee17-1927-4336-81fc-dff7e91e55e7"
/>
2026-06-18 08:28:11 +00:00
Anthony StirlingandGitHub d9e6041a75 set z-index on config dropdowns so they render above the modal (#6674) 2026-06-18 09:08:50 +01:00
James BruntonandGitHub 13af10a6d1 Redesign policy running (#6609)
# Description of Changes
Redesign policy running so the server is in charge of policy IDs and
running, to make it impossible to have the frontend miss the results.
This solves a minor bug that we currently have in policies, where if you
load a file and then refresh while the policy is running, you'll never
receive the outputted file.
2026-06-17 16:18:50 +00:00
EthanHealy01andGitHub 3750111ffc fix agent overlay chat position when workbench size changes (#6682)
<img width="2056" height="1047" alt="Screenshot 2026-06-16 at 12 42
05 AM"
src="https://github.com/user-attachments/assets/74a38b93-f31f-4263-bb62-24c2334a22e8"
/>
<img width="1443" height="1051" alt="Screenshot 2026-06-16 at 12 42
33 AM"
src="https://github.com/user-attachments/assets/adb3ba47-f3e6-44a7-bbc3-2097e15843b6"
/>
2026-06-17 15:54:09 +00:00
ConnorYohandGitHub 20c88feabb refactor(saas): remove the legacy credits engine (FE + Java) (#6687)
Complete legacy-credits teardown ("Group 3"). The per-user/per-team
credit model is fully superseded by PAYG (`wallet_ledger`) — confirmed
no PAYG code references it. Authorized to also remove the `TeamCredit`
pool + its monthly reset.

## Frontend (saas)
- Deleted `saas/hooks/useCredits.ts`, `apiKeys/hooks/useCredits.ts`,
`types/credits.ts`, `apiKeys/UsageSection.tsx`.
- `UseSession.tsx`: removed credit members (`creditBalance`,
`creditSummary`, `hasSufficientCredits`, `updateCredits`,
`refreshCredits`, `fetchCredits`) + the credit types + global
credit-update callback. **Kept** `isPro`/`refreshProStatus` and the
Supabase auth subscription listener.
- `services/apiClient.ts`: removed the dead `x-credits-remaining`
handler + low-credit plumbing (token-refresh / PAYG / 401 logic
untouched).
- Credit refs removed from `ApiKeys.tsx`, `AppConfigModal.tsx`,
`auth/teamSession.ts`.

## Java (:saas)
**Deleted (15):** `UserCredit`(+repo),
`TeamCredit`(+repo)+`TeamCreditService`, `CreditService`,
`CreditHeaderUtils`, `CreditResetScheduler`, `CreditController`,
`CreditInterceptorConfig`, `UnifiedCreditInterceptor`,
`CreditSuccessAdvice`, `CreditErrorAdvice`, `CreditConsumptionResult` (+
the CreditController test).

**Edited — stripped legacy credit side-effects, preserved
auth/role/AI/PAYG logic:**
- `AiCreate`/`AiProxyController`: dropped the
`X-Credits-Remaining`/`X-Credit-Source` response header (its only
consumer, the desktop credit system, was already removed).
- `SaasTeamService`: dropped UserCredit/TeamCredit init on team-create +
seat-update.
- `SupabaseAuthenticationFilter` / `SupabaseSecurityConfig`: dropped
`getOrCreateUserCredits` on signup + the credit field/CORS header.
- `UserRoleService`: dropped `resetCycleAllocationForRoleChange`;
`ROLE_PRO_USER` grant/revoke preserved.
- proprietary `UserRepository`: dropped
`findUsersWithApiKeyButNoCredits()`.
- Tests updated to drop credit mocks/refs.

## Kept / scope
- `isPro` / `is_pro` RPC / `ROLE_PRO_USER` (that's the separate Group-4
/ EE effort) and **all PAYG** are untouched.
- **No DB tables dropped.** `user_credits`/`team_credits` stay until a
later **gated** migration — which this PR unblocks (the JPA entities
that pinned them are gone).

## Verify
`:saas:compileJava` + `:saas:compileTestJava` pass; FE `tsc --noEmit`
(saas) + eslint clean; 0 stray artifacts; no residual source refs to the
deleted classes.

## Follow-up (not in this PR)
`ErrorTrackingService` (+
`UserErrorTracker`/`ProcessingErrorType`/`CreditsProperties`) is now a
dead island — its only callers were the deleted interceptors. Safe to
delete, but it cascades beyond the credit scope, so it's a separate
tidy-up.

Targets `feat/desktop-cloud-saas-reuse`.
2026-06-17 14:11:06 +00:00
ConnorYohandGitHub 4f26fdeb5c feat(desktop): show the AI assistant in SaaS mode via the cloud kill switch (#6666)
## What & why

Chained on top of #6649 (the `cloud/` refactor). The AI assistant was
effectively dead on desktop:

1. **Hidden** — `ChatFAB` gates on `aiEngineEnabled`, which desktop
reads from the **local** bundled backend's `/api/v1/config/app-config`.
The local backend has no AI engine, so the flag is always `false` and
the FAB never renders.
2. **Mis-routed** — even if shown, AI calls used `getApiBaseUrl()`,
which is empty/local on desktop, so the orchestrate stream and AI
result-file download missed the engine (which only runs in the cloud).

This PR wires AI properly **without hardcoding it on**, so the cloud
keeps the kill switch: flip `aiEngineEnabled` server-side and the
desktop FAB disappears on the next load — no desktop release required.
(Deliberately *not* assume-on, so a future "turn AI off" doesn't strand
shipped versions.)

## Changes

**General SaaS app-config service** (reusable for any cloud flag, not
just AI):
- `desktop/services/saasAppConfigService.ts` — SaaS-mode-only fetch +
5-min cache of the **public** `/api/v1/config/app-config` from the
**SaaS** backend over native HTTP (`@tauri-apps/plugin-http`, no CORS).
Returns `null` outside SaaS mode.
- `desktop/hooks/useSaasAppConfig.ts` — hook over it; reloads on
connection-mode change.

**AI gating + routing seams:**
- `useAiEngineEnabled()` — core reads `useAppConfig()` (web), desktop
reads `useSaasAppConfig()`. `ChatFAB` consumes it.
- `getAiBaseUrl()` — core uses the normal API base (web), desktop points
AI calls at the SaaS backend. `ChatContext` uses it for the orchestrate
stream + result-file download.
- `operationRouter` — route `/api/v1/ai/*` to the SaaS backend
(cloud-only prefix).

**Docs:** AGENTS.md gains a short "cloud feature flags on desktop" note
so the pattern is maintained.

## Verification
- `tsc --noEmit` green for saas / desktop / cloud flavors
- `eslint --max-warnings=0` clean (cloud-layer guardrail respected — the
platform-coupled bits live in `desktop/`)
- New `saasAppConfigService.test.ts` (3 tests) + existing
`operationRouter` / `tauriHttpClient` / `httpErrorHandler` suites green
- 0 stray compiled artifacts

## Not headlessly verifiable — needs a live Tauri smoke
The orchestrate **SSE stream** uses the webview's global `fetch` (native
HTTP can't stream the body the same way), so it's subject to browser
CORS to the SaaS backend. The `SupabaseSecurityConfig` tauri-origin
allowance (from #6649) covers it, but please confirm on a real build:
open the FAB in SaaS mode, run an agent task, watch the stream + a
result-file download succeed.
2026-06-17 14:10:35 +00:00
cd7264a76a refactor(fe): share the SaaS PAYG experience with desktop via a cloud/ layer (#6649)
Co-authored-by: James Brunton <jbrunton96@gmail.com>
2026-06-17 11:12:05 +01:00
James BruntonandGitHub ef0deef4f2 Skip flaky Playwright test (#6698)
# Description of Changes
One of the Playwright tests is flaky, despite several attempts to fix it
before it made it into main. This disables the test for now so a
followup PR can try to fix it again.
2026-06-17 09:12:09 +00:00
65fcc036fe Fix inverted link toolbar in rotated PDFs (#6518) (#6684)
Closes #6518 

# Cause of the bug 
This is a fix to the #6518 issue. The bug happened because the link
toolbar was rendered inside the PDF page layer. That layer can be
affected by the viewer/page rotation transform, so the toolbar was laid
out using local page coordinates and then visually transformed together
with the page.

As a result, the placement logic could calculate a position that was
correct in the page’s local coordinate space, such as above or below the
link, but the parent transform would rotate or shift that result after
layout. On rotated pages, this could make the toolbar appear on the
wrong side, inverted, or misaligned relative to the link.

More specifically, in the PDF that exposed the bug, the page content
appears to have been authored upside down and then corrected with a
180-degree page/viewer rotation so it looks normal to the user.

Because the toolbar was rendered inside the same transformed page layer,
it inherited that 180-degree rotation as well. The PDF content looked
upright because the rotation was part of how the page was displayed, but
the toolbar is viewer UI and should not be rotated with the page. As a
result, the tooltip appeared upside down even though the PDF itself
looked correct.


# Description of Changes

Fixes the inverted link tooltip/toolbar positioning in rotated PDF
viewer pages.

The link toolbar is now rendered through a body portal and positioned
from the link element’s real viewport bounds, so page rotation
transforms no longer flip or misalign it.

The update also keeps the toolbar within the viewport during scroll,
resize, zoom, and rotation changes, preserves the hover delay between
the link and toolbar, centralizes the z-index in a shared constant, and
improves label sizing to avoid clipped text.

Note: The link hover styling was also changed from an underline to a
subtle rectangular highlight based on the PDF link annotation bounds.

<!--
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)

UI behaviour before the changes :

<img width="1256" height="868" alt="Captura de tela de 2026-06-16
00-12-10"
src="https://github.com/user-attachments/assets/321edbb3-42a2-4bc3-96ad-3ccc70a355b8"
/>

<img width="762" height="496" alt="Captura de tela de 2026-06-16
00-11-46"
src="https://github.com/user-attachments/assets/be4c1af4-5488-4a54-9b6f-675e3bea73b8"
/>

<img width="1256" height="868" alt="Captura de tela de 2026-06-16
00-12-57"
src="https://github.com/user-attachments/assets/60f44cd5-c772-44a8-97c8-bde135764e53"
/>


UI behaviour after the changes :
 
<img width="1256" height="868" alt="Captura de tela de 2026-06-16
00-22-02"
src="https://github.com/user-attachments/assets/dda77bda-0780-4807-a70d-3bbc60683e5a"
/>

<img width="1256" height="868" alt="Captura de tela de 2026-06-16
00-23-07"
src="https://github.com/user-attachments/assets/5745c37e-438a-4bbe-ba1e-c6f2098421de"
/>

<img width="1256" height="868" alt="Captura de tela de 2026-06-16
00-23-24"
src="https://github.com/user-attachments/assets/85932541-4a6f-48e4-879f-41f34a6d79e6"
/>


### 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: James Brunton <jbrunton96@gmail.com>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
2026-06-17 08:15:31 +00:00
Reece BrowneandGitHub f127d4f575 fix(policies): poll runs to completion with progress, soft-retry when queue is full (#6690)
## What & why

Production reports of policy enforcement "hanging" traced to
large/many-page documents: the watermark step's flatten-to-image
(`convertPDFToImage`) on a 500+ page PDF takes minutes, exceeding both
the client poll cap and the backend per-step timeout. This makes the
slow case graceful instead of looking broken, and makes load-shedding
non-fatal.

### Poll runs to completion (no false "hang")
The client poll loop used a flat ~150s cap that was **shorter than the
backend's 300s per-step timeout**, so it abandoned long-but-healthy runs
mid-flight. The budget is now sized to the backend's real worst case —
`stepCount × per-step timeout + grace`, learned from the first status
report — so the client always polls long enough to surface the run's
**actual** terminal state (success or the backend's real error) rather
than a misleading client-side timeout.

### Per-step progress
The activity feed now shows `Enforcing… · step n/m` (from
`currentStep`/`stepCount`), so a slow step shows movement instead of a
dead spinner.

### Soft-retry on queue rejection
Under load the shared `JobQueue` rejects runs ("queue full"), which
previously surfaced as a hard failure needing a manual Retry. The
backend now tags that rejection with a stable `POLICY_QUEUE_FULL`
errorCode; the client treats it as transient backpressure and
**auto-retries the file in place** with exponential backoff (≈4s→64s, ~2
min), shown as a soft "Busy — retrying…" row, falling back to the manual
Retry only once the retry budget is spent.

## Testing
- **Frontend unit tests** (30 pass across the policies suite), including
a new `usePolicyAutoRun.retry.test.tsx` that drives the real controller
orchestration (poll → `POLICY_QUEUE_FULL` → relabel → backoff → in-place
re-dispatch), plus poll-budget, step-progress, and activity-feed relabel
cases.
- **Backend** `PolicyEngineTest` case asserting a queue-rejected run
carries the `POLICY_QUEUE_FULL` code.
- Typecheck clean on all three flavors (proprietary/saas/core); prettier
+ spotless clean.
- Poll-budget + progress + real-error surfacing were also verified live
end-to-end against a 599-page run (survived past the old cap, showed
step progress, reported the backend's real 300s-timeout failure,
recovered after a simulated network drop).

## Not included (follow-ups)
- The underlying flatten-to-image cost itself (bounded-memory/streaming
flatten, revisiting `convertPDFToImage` default and the 300s timeout) —
the real perf fix, deliberately out of scope here.
2026-06-16 17:32:12 +00:00
7e67bfc459 Fix SaaS issues (#6694)
# Description of Changes

Fixes several SaaS issues,  was integration branch for saas release

---

## 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 4.8 <noreply@anthropic.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
Co-authored-by: Reece <reece@stirlingpdf.com>
Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
2026-06-16 17:16:07 +01:00
Anthony StirlingandGitHub 5389e39cfc Revert "SaaS fixes (#6578)"
This reverts commit ddf78d11ae.
2026-06-16 16:48:30 +01:00
ddf78d11ae SaaS fixes (#6578)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: James Brunton <jbrunton96@gmail.com>
Co-authored-by: Reece Browne <74901996+reecebrowne@users.noreply.github.com>
Co-authored-by: ConnorYoh <40631091+ConnorYoh@users.noreply.github.com>
Co-authored-by: Reece <reece@stirlingpdf.com>
Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
Co-authored-by: Ludy <Ludy87@users.noreply.github.com>
2026-06-16 16:41:25 +01:00
James BruntonandGitHub 10b4551449 Fix failing Playwright test in SaaS (#6688)
# Description of Changes
Fix Playwright failing test in SaaS (I think this is my third attempt
now so who knows if this will actually fix it for real this time, but
hopefully it does)
2026-06-16 15:56:30 +01:00
James BruntonandGitHub 9a883be697 Cleanup of SaaS code (#6669)
# Description of Changes
De-AI comments and fix ridiculously indented code
2026-06-16 11:49:13 +01:00
Anthony StirlingandGitHub d18caf6116 Fix PDF text selection locked out on touch devices (#6656) 2026-06-15 23:04:43 +01:00
James Brunton 04d68c650a Merge remote-tracking branch 'origin/main' into saas-update
# Conflicts:
#	engine/src/stirling/documents/pgvector_store.py
2026-06-15 14:10:21 +01:00
James BruntonandGitHub c1a637d764 Fix CI errors in SaaS (#6662)
# Description of Changes
Fix CI errors in #6578 to make SaaS branch ready for merge into main
2026-06-15 11:26:29 +01:00
LudyandGitHub cb13102117 chore(frontend): make Vite allowed hosts configurable (#6354) 2026-06-14 15:49:45 +01:00
LudyandGitHub 1fa1293b39 chore(build): upgrade Gradle wrapper and Docker build images to 9.5.1 (#6501) 2026-06-14 15:48:44 +01:00
Anthony StirlingandGitHub eefa8eff61 Route mobile scanner API and vendor loads through the app base path (#6648) 2026-06-12 16:00:11 +01:00
Reece BrowneandGitHub 63ecbe3b6d Policies: centre the collapsed-rail policy button between its dividers (#6646) 2026-06-12 13:39:03 +01:00
Anthony StirlingandGitHub f1ed850a73 Fix SaaS mobile scanner being auth-gated under /app base path (#6642) 2026-06-12 13:13:49 +01:00
Anthony StirlingandGitHub b11c272e87 Feature/v2/guest action gating (#6643) 2026-06-12 13:13:38 +01:00
Reece BrowneandGitHub 4e880c7510 Policies: summon the guest sign-up banner when a guest clicks a policy (#6644)
Guests (anonymous users on a login-enabled deployment) could open a
policy's setup/detail. Policies are an account feature, so a guest
clicking a policy should be nudged to sign up rather than opening it.

## Behaviour
A guest clicking a policy row — or a collapsed-rail icon — now
**re-summons the existing guest sign-up banner** ("You're using Stirling
PDF as a guest!…") and does **not** open the policy.

- `GuestUserBanner` listens for a `stirling:show-guest-banner` window
event and re-shows (even if previously dismissed; the render guard still
hides it for non-anonymous users).
- The policy sidebar dispatches that event on a guest click (cross-layer
via `CustomEvent`, same pattern as `payg:signupRequired`; a no-op on
builds without the banner).
- `usePolicyGuestBlocked()` gates it: `config.enableLogin === true &&
user.is_anonymous === true`.
- **Login-disabled single-user** deployments have an anonymous local
operator with full access → not gated.

## Verification
- Typecheck clean (proprietary + saas); eslint clean; sidebar tests
pass.

## Note
No dedicated guest unit test — the suite mocks `useAppConfig` at module
scope, and making it per-test controllable needs `vi.hoisted` plumbing
that risked the existing tests. Easy follow-up.
2026-06-12 13:10:37 +01:00
James BruntonandGitHub 511b92b321 De-AI the onboarding prose (#6641)
# Description of Changes
De-AI the onboarding prose.
2026-06-12 11:39:19 +01:00
ConnorYohandGitHub 87723d3ce2 fix(payg): fire the usage-limit modal when an AI agent run hits the limit (#6638)
## Problem

We're getting 402s when an AI **agent** (chat) run hits the free
allowance / spending cap, but the frontend handles them poorly and never
pops the usage-limit modal.

The agent runs its tool calls **server-side** (loopback HTTP via
`PolicyExecutor`), so the 402 never reaches the `apiClient` interceptor
that pops the modal for direct calls. It was caught by the generic
tool-failure handler and flattened into a `CANNOT_CONTINUE` reason
string (`"The /api/v1/… tool failed: 402…"`), streamed as a `result`
event, and rendered as a scary chat bubble. This is the same gap the
policy auto-run path bridges (#6626) — one layer up.

## Fix

**Backend** (`proprietary`)
- `AiWorkflowResponse` gains `errorCode` + `errorSubscribed`.
- `AiWorkflowService` detects a downstream 401/402 entitlement sentinel
in its three tool-exec catch sites (`onToolCall`, `runPlan`,
`onConvertMarkdown`) and surfaces the structured code (+ `subscribed`)
on the terminal response instead of the raw failure text.
- Factored the 401/402 body extraction `PolicyEngine` already had into a
shared `DownstreamEntitlementError` util so the two server-side paths
can't drift.

**Frontend**
- New `usageLimitBridge` (`PAYG_LIMIT_REACHED_EVENT` +
`dispatchPaygLimitReached`) generalises the previously policy-only
bridge. Proprietary can't import the saas modal API (layering), so
server-side limit hits broadcast a window event the saas
`UsageLimitModalHost` opens the modal from. Migrated the policy path
onto it.
- `ChatContext` fires the matching modal (free → subscribe, subscribed →
raise cap) on the limit result **and** on a direct 402, replacing the
raw reason with a brief friendly line
(`chat.responses.usage_limit_reached`).

No Python engine changes — the charge/402 happens on the Java tool
endpoint that Java itself calls.

## Test plan

- [x] `:proprietary:compileJava` + `spotlessCheck` clean
- [x] `AiWorkflowServiceTest` + `PolicyEngineTest` green
- [x] eslint, proprietary + saas typechecks clean
- [ ] Manual: drive an agent run over the limit → brief line in chat +
the right modal (free vs cap)

> Note: proprietary test compilation is currently blocked on the
pre-existing `InitialSecuritySetupTest` 6-arg ctor break (unrelated,
tracked separately); verified locally by temporarily patching it.
2026-06-12 11:38:07 +01:00
EthanHealy01andGitHub eb2527fc7f Properly sync US and GB translation files (#6635)
add en-US changes to SaaS, previously merged into main. So this is
effectively a main -> SaaS PR also. It seems to be all additive.

Also take the 230 ish missing translations from en-GB over to en-US
using a script, and also make and english spellings American when adding
them to the en-US file, and fix any existing American spellings in the
en-GB file.
2026-06-12 11:18:25 +01:00
James BruntonandGitHub d363a1e957 Improve search logic (#6637)
# Description of Changes
Search has got significantly worse since #6581, where I added all the
missing tags for tools that should have been there for months. Turns out
that the fuzzy matching search logic has always been way too permissive
to match words with Levenshtein distances way too far away from the
target word, so long searches include way too much stuff. The new tags
just exposed that underlying logic issue. This PR makes the Levenshtein
logic much stricter, so it is still tolerant to minor typos in tool
names, but doesn't match completely inappropriate strings.
2026-06-12 10:34:11 +01:00
James Brunton d995471a55 Merge remote-tracking branch 'origin/main' into SaaS-update
# Conflicts:
#	frontend/editor/src/proprietary/components/chat/ChatContext.tsx
#	frontend/editor/src/saas/components/shared/TrialStatusBanner.tsx
2026-06-12 09:58:40 +01:00
Reece BrowneandGitHub e3e49c07ae Policies: let team leaders configure policies in the UI (#6634)
Frontend follow-up to #6632 (team-scoped policies, editing gated to team
leaders on the backend). Brings the UI's edit gate in line.

## Problem
The policy config UI gated editing to `config.isAdmin`. On SaaS, org
users are never the single global admin, so **no one could open the
policy editor** — the same lockout #6632 fixed on the backend.

## Fix
`usePolicies` now allows a **team leader** to configure, falling back to
a global admin self-hosted:

```ts
canConfigure =
  config != null && (!config.enableLogin || isTeamLeader || config.isAdmin === true);
```

- SaaS → `isTeamLeader` (from `useSaaSTeam()`) — team leaders can
configure; members get the read-only surface.
- Self-hosted → `config.isAdmin` (the core `useSaaSTeam` stub returns
`false`, so admins aren't locked out).
- Login disabled (single-user) → always allowed.
- The `config != null` guard keeps the gate closed until app-config
resolves, so edit controls never flash for users who can't use them.

The two locked-policy banners now read "Contact a team leader to change
this policy" (updated in the `t()` defaults and the `en-GB`
translations).

## Verification
- Typecheck clean (proprietary + saas); eslint clean.
- Tests pass: `usePolicies`, `PoliciesSidebar`.
2026-06-11 23:51:26 +01:00
EthanHealy01andGitHub 962119e14f UI ux/add ai warning and change style (#6633)
<img width="394" height="426" alt="Screenshot 2026-06-11 at 11 39 50 PM"
src="https://github.com/user-attachments/assets/15805931-73fd-416b-841b-99a556468433"
/>
bottom text input is sticky even when shrunk down
2026-06-11 23:49:52 +01:00
cc1235bbf2 i18n(policies): route policy UI strings through i18n (English only) (#6628)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 23:37:43 +01:00
EthanHealy01andGitHub b756b5befb add agent warning and update style (#6629) 2026-06-11 21:55:51 +01:00
ConnorYohandGitHub eddc54c6c0 fix(payg): land usage-limit modal CTAs on the Plan section (#6630) 2026-06-11 21:42:59 +01:00
ConnorYohandGitHub 22379fd5ab fe(payg): show the usage-limit modal when the limit is hit (direct + policy) (#6626) 2026-06-11 21:28:44 +01:00
Reece BrowneandGitHub 6f1c19c179 Policies: enforce input on uploads only; badge follows edited files (#6627) 2026-06-11 21:27:44 +01:00
ef65e6b015 feat(policies): org-wide policies with admin-only editing (#6625)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 21:23:06 +01:00