Removes the stubbed E2E tests that exercised UI deleted in this branch:
the file-sidebar 'Rail Search' row (files-page.spec) and the right-rail
tool-search (tool-search.spec), both superseded by the global super search.
Adds the super-search / files-filter / toolbar i18n keys to en-GB and en-US
(superSearch.*, filesPage.search.*, filesPage.empty.noResults.*,
workbenchBar.show/hideToolbar) and removes the now-unused keys left behind by
dropping the sidebar and files-page searches (fileSidebar.search[Placeholder],
filesPage.search[Placeholder], filesPage.clearSearch). Also applies Prettier
formatting flagged by frontend validation.
The global super search now covers tool lookup, so the tool-panel header
search is removed. With it gone the header can be empty (no active tool), so
the collapse chevron floats into the corner and the tool list starts at the
top. In My Files list view, a filter with no matches now keeps the column
headers and shows the no-results message beneath them instead of replacing
the whole table.
Reconciles the super-search work with main's refactors:
- WorkbenchBar keeps the super-search grid layout + viewer retract handle;
main's policy-aware download import is preserved.
- AppConfigModal keeps main's no-flash switchSection plus the settings
deep-link focus effect.
- RightSidebar takes main's Policies panel; the agents-era 'empty header
gap' fix is dropped as obsolete (the header now hosts a tool search).
Drops the in-header tool search (now handled by global search) and, when
the header would carry only the collapse chevron, floats it into the corner
so the tool list starts at the top instead of below a blank 52px band.
Reconnects the existing (orphaned) name-filter pipeline with an input next
to the sort controls. When a filter returns nothing, the page shows a
dedicated 'no matching files' empty state instead of the generic per-tab
message (e.g. 'Nothing modified yet').
A chevron at the right of the viewer's tool row hides it to give the
document more height; a small tab on the bar's bottom-right edge brings it
back. Scoped to the viewer - other views keep their tool row visible. Also
swaps the in-document search icon to find-in-page.
The global super search now covers file lookup, so the per-sidebar search
row and its /files focus bridge are removed. The super search input also
grows to fill the bar up to its max width.
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md">js-yaml's
changelog</a>.</em></p>
<blockquote>
<h2>[4.2.0] - 2026-06-01</h2>
<h3>Added</h3>
<ul>
<li>Added <code>docs/safety.md</code> with notes about processing
untrusted YAML.</li>
<li>Added <code>maxDepth</code> (100) loader option. Not a problem, but
gives a better
exception instead of RangeError on stack overflow.</li>
<li>Added <code>maxMergeSeqLength</code> (20) loader option. Not a
problem after <code>merge</code> fix,
but an additional restriction for safety.</li>
<li>Added sourcemaps to <code>dist/</code> builds.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Stop resolving numbers with underscores as numeric scalars, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/627">#627</a>.</li>
<li>Switched dev toolchains to Vite / neostandard.</li>
<li>Updated demo.</li>
<li>Reorganized tests.</li>
<li><code>dist/</code> files are no longer kept in the repository.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fix parsing of properties on the first implicit block mapping key,
<a
href="https://redirect.github.com/nodeca/js-yaml/issues/62">#62</a>.</li>
<li>Fix trailing whitespace handling when folding flow scalar lines, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/307">#307</a>.</li>
<li>Reject top-level block scalars without content indentation, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/280">#280</a>.</li>
<li>Ensure numbers survive round-trip, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/737">#737</a>.</li>
<li>Fix test coverage for issue <a
href="https://redirect.github.com/nodeca/js-yaml/issues/221">#221</a>.</li>
<li>Fix flow scalar trailing whitespace folding, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/307">#307</a>.</li>
<li>Fix digits in YAML named tag handles.</li>
</ul>
<h3>Security</h3>
<ul>
<li>Fix potential DoS via quadratic complexity in merge - deduplicate
repeated
elements (makes sense for malformed files > 10K).</li>
</ul>
<h2>[3.14.2] - 2025-11-15</h2>
<h3>Security</h3>
<ul>
<li>Backported v4.1.1 fix to v3</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/nodeca/js-yaml/commits">compare view</a></li>
</ul>
</details>
<br />
[](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>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md">js-yaml's
changelog</a>.</em></p>
<blockquote>
<h2>[4.2.0] - 2026-06-01</h2>
<h3>Added</h3>
<ul>
<li>Added <code>docs/safety.md</code> with notes about processing
untrusted YAML.</li>
<li>Added <code>maxDepth</code> (100) loader option. Not a problem, but
gives a better
exception instead of RangeError on stack overflow.</li>
<li>Added <code>maxMergeSeqLength</code> (20) loader option. Not a
problem after <code>merge</code> fix,
but an additional restriction for safety.</li>
<li>Added sourcemaps to <code>dist/</code> builds.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>Stop resolving numbers with underscores as numeric scalars, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/627">#627</a>.</li>
<li>Switched dev toolchains to Vite / neostandard.</li>
<li>Updated demo.</li>
<li>Reorganized tests.</li>
<li><code>dist/</code> files are no longer kept in the repository.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Fix parsing of properties on the first implicit block mapping key,
<a
href="https://redirect.github.com/nodeca/js-yaml/issues/62">#62</a>.</li>
<li>Fix trailing whitespace handling when folding flow scalar lines, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/307">#307</a>.</li>
<li>Reject top-level block scalars without content indentation, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/280">#280</a>.</li>
<li>Ensure numbers survive round-trip, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/737">#737</a>.</li>
<li>Fix test coverage for issue <a
href="https://redirect.github.com/nodeca/js-yaml/issues/221">#221</a>.</li>
<li>Fix flow scalar trailing whitespace folding, <a
href="https://redirect.github.com/nodeca/js-yaml/issues/307">#307</a>.</li>
<li>Fix digits in YAML named tag handles.</li>
</ul>
<h3>Security</h3>
<ul>
<li>Fix potential DoS via quadratic complexity in merge - deduplicate
repeated
elements (makes sense for malformed files > 10K).</li>
</ul>
<h2>[3.14.2] - 2025-11-15</h2>
<h3>Security</h3>
<ul>
<li>Backported v4.1.1 fix to v3</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/nodeca/js-yaml/commits">compare view</a></li>
</ul>
</details>
<br />
[](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>
Co-authored-by: Anthony Stirling <77850077+Frooodle@users.noreply.github.com>
# Description of Changes
Add message describing the most common tasks when running `task` with no
arguments. I think this should help newcomers because `task --list` is
massive at this point and nobody's going to read through it all. Let me
know if you think any other commands should be in the default message.
# 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.
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>
## What
Residual dead-code cleanup following the credits engine teardown
(#6687).
- **Delete the `ErrorTrackingService` dead island** (6 files): the
service, `UserErrorTrackerRepository`, `UserErrorTracker`,
`ProcessingErrorType`, `CreditsProperties`, and
`ErrorTrackingServiceTest`. These formed a self-referential cluster with
**zero external callers** once the credit machinery was removed.
- **Remove `/api/v1/credits/**`** from both `excludePathPatterns` blocks
in `PaygWebMvcConfig` — the credits controller no longer exists, so the
exclusion is defunct. (spotless collapsed the lists to one line.)
## Verification
- `./gradlew :saas:compileJava :saas:compileTestJava` → **BUILD
SUCCESSFUL**
- grep confirms zero dangling references to the deleted types
## Not in scope (deliberately deferred)
Destructive DB drops
(`user_credits`/`team_credits`/`user_subscription_plans` tables, dead
`payg_shadow_charge` columns, `user_error_tracker` table) are gated
behind the post-release soak (`live_ratio==1.0 ≥7d`) and tracked in a
separate bundle.
Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com>
# Description of Changes
The `pre-commit` commands in this repo are inconsistent with the rest of
the dev workflow, as they are impossible to run through Task and they
can cause CI to fail with no way for a developer to run the `pre-commit`
scripts after they've failed. This PR adds `task pre-commit` (and `task
pre-commit:fix`) and then hooks up the existing `pre-commit` hooks and
CI to call the Task rule, so if developers are using pre-commit hooks
then they should still work, but they're also runnable without using
pre-commit at all.
I think it'd be worth reviewing what we're actually running at
pre-commit in the future because I'm not entirely convinced by all of
the scripts that we are running, but this should at least make what we
have properly enforced and usable by all devs.
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.
## 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.
# 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.
# 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.
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`.
## 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.
- Surface the real reason an MCP token is rejected: the 401's
WWW-Authenticate header now includes error_description
(audience/issuer/expiry), and a present-but-rejected token logs the
concrete OAuth2 reason. Tokenless 401s (the normal discovery handshake)
stay at debug.
- Add McpConfigValidator that sanity-checks MCP config at startup and
logs actionable warnings (missing issuer-uri/resource-id, unrecognized
auth mode, sub + require-existing-account, open access, scopes,
allow/block overlap) so misconfig shows up in the logs before a client
ever connects.
- Align the audience-rejection message to mention both resource-id and
accepted-audiences.
- Harden audit writes: hash JWT-shaped or over-long principals
(token:<sha256-prefix>) so the insert fits the column and never stores a
raw bearer token, and stop logging the raw principal on persist failure.
---
## 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.
# 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.
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>
## 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.
# 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)