Compare commits

...
3306 changed files with 11114 additions and 10442 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ vs `main`. Flags: `--html` (also emit a rendered HTML twin), `--no-screens`.
- Read the PR description / commit messages for stated intent. Do **not** invent
history or motivation that isn't evidenced (state current behavior in present tense).
- Classify touched files by layer:
- **Frontend**: tools (`frontend/editor/src/core/components/tools/*` or `.../core/tools/*`),
- **Frontend**: tools (`frontend/src/editor/core/components/tools/*` or `.../core/tools/*`),
hooks (`core/hooks/tools/*`, `useToolOperation`), contexts, routes, i18n
(`public/locales/en-US`).
- **Java backend**: controllers (`.../controller/api/...`), services, models, config.
+5 -5
View File
@@ -53,11 +53,11 @@ gh pr diff <pr> --name-only # or: git diff --name-only <base>...HEAD
Map changed frontend files to URLs generically:
- **Tools**: a changed `components/tools/<toolDir>/…` or `hooks/tools/<tool>/…`
toolId → URL via the repo's own rule `getToolUrlPath` in
[toolsTaxonomy.ts:200](frontend/editor/src/core/data/toolsTaxonomy.ts): `/` + the
[toolsTaxonomy.ts:200](frontend/src/editor/core/data/toolsTaxonomy.ts): `/` + the
id kebab-cased (`addPageNumbers``/add-page-numbers`).
- **Pages/routes**: changed `filesPage/*``/files`, etc.
- `--all`: enumerate every tool in the registry instead of just changed ones.
Write `frontend/editor/screenshots/ui-diff/targets.json` =
Write `frontend/screenshots/ui-diff/targets.json` =
`[{ "id":"compress", "url":"/compress", "name":"Compress" }]`. This is what makes
it generic - the spec never names a tool.
@@ -69,12 +69,12 @@ the full viewport - or the `--scope` container if given). Ensure the harness is
(node_modules + icons).
```
# after = current head
cd frontend/editor && PR_SHOT_SIDE=after PR_SHOT_THEME=light \
cd frontend && PR_SHOT_SIDE=after PR_SHOT_THEME=light \
npx playwright test --project=stubbed ui-before-after.spec.ts
# before = base, in an isolated worktree (copy the spec + targets.json in)
git worktree add ../ba-base origin/<baseRefName> # or the merge-base
# set up its frontend, copy spec + screenshots/ui-diff/targets.json across, then:
cd ../ba-base/frontend/editor && PR_SHOT_SIDE=before PR_SHOT_THEME=light \
cd ../ba-base/frontend && PR_SHOT_SIDE=before PR_SHOT_THEME=light \
npx playwright test --project=stubbed ui-before-after.spec.ts
# copy its screenshots/ui-diff/before/ back next to after/. Repeat with
# PR_SHOT_THEME=dark if --theme includes dark. Remove worktree when done.
@@ -82,7 +82,7 @@ cd ../ba-base/frontend/editor && PR_SHOT_SIDE=before PR_SHOT_THEME=light \
### 4. Auto-diff (surface what changed)
```
cd frontend/editor && node <skill>/diff-shots.mjs \
cd frontend && node <skill>/diff-shots.mjs \
screenshots/ui-diff/before screenshots/ui-diff/after screenshots/ui-diff
```
Produces `diff-report.json` classifying each view `unchanged | changed | added |
@@ -2,7 +2,7 @@
// unchanged | changed | added | removed, and CROP each changed pair to the
// affected region (bounding box of differing pixels + padding) - unless the
// change spans most of the page, in which case the full frame is kept.
// Run from frontend/editor (so deps resolve):
// Run from frontend (so deps resolve):
// node <skill>/diff-shots.mjs <beforeDir> <afterDir> [outDir]
// Env:
// DIFF_THRESHOLD min fraction of differing pixels to count as changed (default 0.001)
@@ -1,5 +1,5 @@
// Render each .tab-section of a montage HTML into its own PNG (the PR-ready image).
// Run from frontend/editor (so @playwright/test resolves):
// Run from frontend (so @playwright/test resolves):
// node <skill>/shoot-sections.mjs <montage.html> <outDir>
import path from "node:path";
import { pathToFileURL } from "node:url";
+6 -6
View File
@@ -28,13 +28,13 @@ current branch diff. Flags: `--fix`, `--theme light|dark|both` (default both),
## What this repo gives you (use it, don't reinvent)
- **Stubbed Playwright project** = backend-free screenshots via `page.route()` mocks.
Reference implementation: `frontend/editor/src/core/tests/stubbed/files-page-screenshots.spec.ts`.
Reference implementation: `frontend/src/editor/core/tests/stubbed/files-page-screenshots.spec.ts`.
It already shows the light / **dark** / **RTL** passes, JWT seeding, IndexedDB
seeding, and dumping PNGs to a `screenshots/<area>/` folder. Copy its shape.
- Helpers: `frontend/editor/src/core/tests/helpers/ui-helpers.ts`
- Helpers: `frontend/src/editor/core/tests/helpers/ui-helpers.ts`
(`uploadFiles`, `openSettings`, `waitForModalOpen`, `dismissTourTooltip`, …)
and the `stub-test-base` fixtures (`autoGoto`, `seedJwt`, `viewport`).
- Config: `frontend/editor/playwright.config.ts` (run from `frontend/editor/`).
- Config: `frontend/playwright.config.ts` (run from `frontend/`).
- Report template: [report-template.html](report-template.html) - self-contained,
one big image at a time, a global light/dark slider that flips every shot,
thumbnail rail, prev/next + arrow keys, and a Findings tab.
@@ -54,13 +54,13 @@ current branch diff. Flags: `--fix`, `--theme light|dark|both` (default both),
Worktrees have no `node_modules` and no generated icons. From repo root:
```
cd frontend && npm ci # or junction main's node_modules (see memory)
cd frontend/editor && node scripts/generate-icons.js
cd frontend && node scripts/generate-icons.js
```
Kill any stale dev server first (it serves old modules):
`Get-NetTCPConnection -LocalPort 5173 -State Listen | %{ Stop-Process -Id $_.OwningProcess -Force }`
### 3. Write the capture spec
Create `frontend/editor/src/core/tests/stubbed/<feature>-walkthrough.spec.ts`,
Create `frontend/src/editor/core/tests/stubbed/<feature>-walkthrough.spec.ts`,
modeled on `files-page-screenshots.spec.ts`. For each enumerated view:
- stub the APIs it needs, drive the UI to that state, wait on a real locator
(not a fixed sleep), `await settle(page)` for Mantine portals, then
@@ -71,7 +71,7 @@ modeled on `files-page-screenshots.spec.ts`. For each enumerated view:
- Name shots `NN_<view>_<theme>.png` so light/dark pair up by suffix.
- Prefer **stable test-ids** over translated accessible names (RTL/i18n breaks text locators).
Run it: `cd frontend/editor && npx playwright test --project=stubbed <feature>-walkthrough.spec.ts`.
Run it: `cd frontend && npx playwright test --project=stubbed <feature>-walkthrough.spec.ts`.
Add `--project=stubbed-firefox`/`-webkit` only if cross-browser layout matters.
### 4. Build the report
+4 -4
View File
@@ -26,16 +26,16 @@ version_builds/
node_modules/
**/node_modules/
frontend/node_modules/
frontend/editor/dist/
frontend/editor/playwright-report/
frontend/dist/
frontend/playwright-report/
.npm/
.yarn/
# Tauri/desktop builds
src-tauri/target/
src-tauri/dist/
frontend/editor/src-tauri/target/
frontend/editor/src-tauri/dist/
frontend/src-tauri/target/
frontend/src-tauri/dist/
# IDE and editor
.idea/
+10 -10
View File
@@ -92,12 +92,12 @@ frontend: &frontend
# job on changes to any of these.
tauri: &tauri
- *ci
- frontend/editor/src-tauri/**
- frontend/editor/src/desktop/**
- frontend/editor/tsconfig.desktop.vite.json
- frontend/src-tauri/**
- frontend/src/editor/desktop/**
- frontend/tsconfig.desktop.vite.json
- frontend/package.json
- frontend/package-lock.json
- frontend/editor/vite.config.ts
- frontend/vite.config.ts
- .github/workflows/tauri-build.yml
- Taskfile.yml
- .taskfiles/desktop.yml
@@ -121,9 +121,9 @@ engine: &engine
generated-models: &generated-models
- *ci
- *openapi
- frontend/editor/scripts/generate-tool-api-types.mts
- frontend/editor/src/core/types/toolApiTypes.ts
- frontend/editor/src/core/types/toolIO.ts
- frontend/scripts/generate-tool-api-types.mts
- frontend/src/editor/core/types/toolApiTypes.ts
- frontend/src/editor/core/types/toolIO.ts
- engine/scripts/generate_tool_models.py
- engine/src/stirling/models/tool_models.py
- engine/src/stirling/models/tool_io.py
@@ -135,7 +135,7 @@ licenses-frontend: &licenses-frontend
- ".github/workflows/frontend-backend-licenses-update.yml"
- "frontend/package.json"
- "frontend/package-lock.json"
- "frontend/editor/scripts/generate-licenses.js"
- "frontend/scripts/generate-licenses.js"
licenses-backend: &licenses-backend
- ".github/workflows/frontend-backend-licenses-update.yml"
@@ -146,8 +146,8 @@ licenses-backend: &licenses-backend
proprietary: &proprietary
- *ci
- app/proprietary/**
- frontend/editor/src/proprietary/**
- frontend/editor/src/core/tests/enterprise/**
- frontend/src/editor/proprietary/**
- frontend/src/editor/core/tests/enterprise/**
- testing/compose/docker-compose-keycloak-oauth.yml
- testing/compose/docker-compose-keycloak-saml.yml
- testing/compose/keycloak-realm-oauth.json
+3 -3
View File
@@ -128,9 +128,9 @@ updates:
- package-ecosystem: cargo
directories:
- /frontend/editor/src-tauri
- /frontend/editor/src-tauri/thumbnail-handler
- /frontend/editor/src-tauri/provisioner
- /frontend/src-tauri
- /frontend/src-tauri/thumbnail-handler
- /frontend/src-tauri/provisioner
schedule:
interval: "weekly"
cooldown:
+3 -3
View File
@@ -51,7 +51,7 @@ labels:
- label: 'Translation'
files:
- 'frontend/editor/public/locales/[a-zA-Z]{2}-[a-zA-Z\-]{2,7}/translation.toml'
- 'frontend/public/locales/[a-zA-Z]{2}-[a-zA-Z\-]{2,7}/translation.toml'
- 'scripts/ignore_translation.toml'
- 'scripts/remove_translation_keys.sh'
- 'scripts/replace_translation_line.sh'
@@ -70,8 +70,8 @@ labels:
- label: 'Tauri'
files:
- 'frontend/editor/src-tauri/**'
- 'frontend/editor/src-tauri/.*'
- 'frontend/src-tauri/**'
- 'frontend/src-tauri/.*'
- label: 'engine'
files:
+2 -2
View File
@@ -13,7 +13,7 @@ Usage:
"""
# Sample for Windows:
# python .github/scripts/check_language_toml.py --reference-file frontend/editor/public/locales/en-US/translation.toml --branch "" --files frontend/editor/public/locales/de-DE/translation.toml frontend/editor/public/locales/fr-FR/translation.toml
# python .github/scripts/check_language_toml.py --reference-file frontend/public/locales/en-US/translation.toml --branch "" --files frontend/public/locales/de-DE/translation.toml frontend/public/locales/fr-FR/translation.toml
import argparse
import glob
@@ -308,7 +308,7 @@ def check_for_differences(reference_file, file_list, branch, actor):
report.append("## ❌ Overall Check Status: **_Failed_**")
report.append("")
report.append(
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [en-US/translation.toml](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/frontend/editor/public/locales/en-US/translation.toml)"
f"@{actor} please check your translation if it conforms to the standard. Follow the format of [en-US/translation.toml](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/frontend/public/locales/en-US/translation.toml)"
)
else:
report.append("## ✅ Overall Check Status: **_Success_**")
+1 -3
View File
@@ -14,9 +14,7 @@ from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
from cryptography.exceptions import InvalidSignature
ART_ROOT = Path(sys.argv[1])
CONF = Path(
sys.argv[2] if len(sys.argv) > 2 else "frontend/editor/src-tauri/tauri.conf.json"
)
CONF = Path(sys.argv[2] if len(sys.argv) > 2 else "frontend/src-tauri/tauri.conf.json")
def load_pubkey():
+1 -1
View File
@@ -294,7 +294,7 @@ jobs:
if: always()
working-directory: frontend
run: >
npx tsx editor/scripts/report-flaky-tests.mts
npx tsx scripts/report-flaky-tests.mts
"${{ github.workspace }}/frontend/playwright-report/results-oauth.json"
"${{ github.workspace }}/frontend/playwright-report/results-saml.json"
"${{ github.workspace }}/frontend/playwright-report/results-feature.json"
+5 -5
View File
@@ -6,7 +6,7 @@ on:
pull_request_target:
types: [opened, synchronize, reopened]
paths:
- "frontend/editor/public/locales/*/translation.toml"
- "frontend/public/locales/*/translation.toml"
- ".github/scripts/check_language_toml.py"
- ".github/workflows/check_toml.yml"
@@ -76,7 +76,7 @@ jobs:
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 ${{ steps.get-pr-data.outputs.pr_number }} --json files -q ".files[].path" | grep -E '^frontend/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"
@@ -166,12 +166,12 @@ jobs:
// Determine reference file
let referenceFilePath;
if (changedFiles.includes("frontend/editor/public/locales/en-US/translation.toml")) {
if (changedFiles.includes("frontend/public/locales/en-US/translation.toml")) {
console.log("Using PR branch reference file.");
const { data: fileContent } = await github.rest.repos.getContent({
owner: prRepoOwner,
repo: prRepoName,
path: "frontend/editor/public/locales/en-US/translation.toml",
path: "frontend/public/locales/en-US/translation.toml",
ref: branch,
});
@@ -183,7 +183,7 @@ jobs:
const { data: fileContent } = await github.rest.repos.getContent({
owner: repoOwner,
repo: repoName,
path: "frontend/editor/public/locales/en-US/translation.toml",
path: "frontend/public/locales/en-US/translation.toml",
ref: "main",
});
+4 -4
View File
@@ -93,7 +93,7 @@ jobs:
# ::warning:: annotations + a job summary; never fails the job.
if: always()
working-directory: frontend
run: npx tsx editor/scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE"
run: npx tsx scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE"
env:
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json
- name: Generate JaCoCo report from e2e:live .exec
@@ -230,8 +230,8 @@ jobs:
run: |
echo "::group::Playwright output dirs"
# Playwright anchors its default outputDir + HTML report to the
# nearest package.json, which is frontend/ (frontend/editor has
# none), so artifacts land under frontend/, not frontend/editor/.
# nearest package.json, which is frontend/ (frontend has
# none), so artifacts land under frontend/, not frontend/.
ls -la frontend/playwright-report 2>/dev/null \
|| echo "no playwright-report at frontend/"
ls -la frontend/test-results 2>/dev/null \
@@ -246,7 +246,7 @@ jobs:
# test-results/ holds the per-test trace.zip (with browser console
# logs) + screenshots/video; playwright-report/ is the HTML report.
# Both live under frontend/ (Playwright anchors them to the nearest
# package.json, which is frontend/; frontend/editor has none).
# package.json, which is frontend/; frontend has none).
path: |
frontend/playwright-report/
frontend/test-results/
+1 -1
View File
@@ -43,7 +43,7 @@ jobs:
# ::warning:: annotations + a job summary; never fails the job.
if: always()
working-directory: frontend
run: npx tsx editor/scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE"
run: npx tsx scripts/report-flaky-tests.mts "$PLAYWRIGHT_JSON_OUTPUT_FILE"
env:
PLAYWRIGHT_JSON_OUTPUT_FILE: ${{ github.workspace }}/frontend/playwright-report/results.json
- name: Upload Playwright report
@@ -113,31 +113,31 @@ jobs:
NPM_CONFIG_IGNORE_SCRIPTS: "true"
working-directory: frontend
run: |
mkdir -p editor/src/assets
npx --yes license-report --only=prod --output=json > editor/src/assets/3rdPartyLicenses.json
mkdir -p src/assets
npx --yes license-report --only=prod --output=json > src/assets/3rdPartyLicenses.json
- name: Postprocess with project script (BASE version)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
env:
PR_IS_FORK: "true"
run: |
node base/frontend/editor/scripts/generate-licenses.js \
--input frontend/editor/src/assets/3rdPartyLicenses.json
node base/frontend/scripts/generate-licenses.js \
--input frontend/src/assets/3rdPartyLicenses.json
- name: Copy postprocessed artifacts back (fork PRs)
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork == true
run: |
mkdir -p frontend/editor/src/assets
if [ -f "base/frontend/editor/src/assets/3rdPartyLicenses.json" ]; then
cp base/frontend/editor/src/assets/3rdPartyLicenses.json frontend/editor/src/assets/3rdPartyLicenses.json
mkdir -p frontend/src/assets
if [ -f "base/frontend/src/assets/3rdPartyLicenses.json" ]; then
cp base/frontend/src/assets/3rdPartyLicenses.json frontend/src/assets/3rdPartyLicenses.json
fi
if [ -f "base/frontend/editor/src/assets/license-warnings.json" ]; then
cp base/frontend/editor/src/assets/license-warnings.json frontend/editor/src/assets/license-warnings.json
if [ -f "base/frontend/src/assets/license-warnings.json" ]; then
cp base/frontend/src/assets/license-warnings.json frontend/src/assets/license-warnings.json
fi
- name: Check for license warnings
run: |
if [ -f "frontend/editor/src/assets/license-warnings.json" ]; then
if [ -f "frontend/src/assets/license-warnings.json" ]; then
echo "LICENSE_WARNINGS_EXIST=true" >> $GITHUB_ENV
else
echo "LICENSE_WARNINGS_EXIST=false" >> $GITHUB_ENV
@@ -185,10 +185,10 @@ jobs:
echo ""
if [ "${LICENSE_WARNINGS_EXIST}" = "true" ]; then
echo "❌ **Failed** incompatible or unknown licenses found."
if [ -f "frontend/editor/src/assets/license-warnings.json" ]; then
if [ -f "frontend/src/assets/license-warnings.json" ]; then
echo ""
echo "### Warnings"
jq -r '.warnings[] | "- \(.message)"' frontend/editor/src/assets/license-warnings.json || true
jq -r '.warnings[] | "- \(.message)"' frontend/src/assets/license-warnings.json || true
fi
else
echo "✅ **Passed** no license warnings detected."
@@ -214,7 +214,7 @@ jobs:
const fs = require('fs');
let warningDetails = '';
try {
const warnings = JSON.parse(fs.readFileSync('frontend/editor/src/assets/license-warnings.json', 'utf8'));
const warnings = JSON.parse(fs.readFileSync('frontend/src/assets/license-warnings.json', 'utf8'));
warningDetails = warnings.warnings.map(w => `- ${w.message}`).join('\n');
} catch (e) {
warningDetails = 'Unable to read warning details';
@@ -254,7 +254,7 @@ jobs:
- name: Commit changes (Push only)
if: github.event_name == 'push'
run: |
git add frontend/editor/src/assets/3rdPartyLicenses.json
git add frontend/src/assets/3rdPartyLicenses.json
# Note: Do NOT commit license-warnings.json - it's only for PR review
git diff --staged --quiet || echo "CHANGES_DETECTED=true" >> $GITHUB_ENV
@@ -272,7 +272,7 @@ jobs:
The following licenses may require review for corporate compatibility:
$(cat frontend/editor/src/assets/license-warnings.json | jq -r '.warnings[].message')
$(cat frontend/src/assets/license-warnings.json | jq -r '.warnings[].message')
Please review these licenses to ensure they are acceptable for your use case."
fi
+3 -3
View File
@@ -130,19 +130,19 @@ jobs:
run: |
python scripts/coverage-summary.py \
--title "Frontend Vitest coverage" \
--vitest frontend/editor/coverage/coverage-summary.json \
--vitest frontend/coverage/coverage-summary.json \
--github-step-summary
- name: Upload vitest coverage report
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: frontend-coverage
path: frontend/editor/coverage/
path: frontend/coverage/
retention-days: 7
if-no-files-found: warn
- name: Upload frontend build artifacts
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: frontend-build
path: frontend/editor/dist/
path: frontend/dist/
retention-days: 3
+9 -9
View File
@@ -441,7 +441,7 @@ jobs:
env:
KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
run: |
cat > ./frontend/editor/src-tauri/tauri.windows.conf.json <<EOF
cat > ./frontend/src-tauri/tauri.windows.conf.json <<EOF
{
"bundle": {
"windows": {
@@ -454,7 +454,7 @@ jobs:
}
EOF
echo "Generated tauri.windows.conf.json (alias masked):"
sed "s/${KEYPAIR_ALIAS}/***/g" ./frontend/editor/src-tauri/tauri.windows.conf.json
sed "s/${KEYPAIR_ALIAS}/***/g" ./frontend/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')
@@ -502,7 +502,7 @@ jobs:
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}
CI: true
with:
projectPath: ./frontend/editor
projectPath: ./frontend
tauriScript: npx tauri
args: ${{ matrix.args }}
updaterJsonKeepUniversal: true
@@ -522,7 +522,7 @@ jobs:
APPIMAGETOOL_SIGN_PASSPHRASE: ${{ secrets.RELEASE_GPG_PASSPHRASE }}
run: |
set -euo pipefail
AI=$(find "$PWD/frontend/editor/src-tauri/target" -name "*.AppImage" | head -1)
AI=$(find "$PWD/frontend/src-tauri/target" -name "*.AppImage" | head -1)
if [ -z "$AI" ]; then echo "No AppImage found - skipping"; exit 0; fi
chmod +x "$AI"
WORK=$(mktemp -d)
@@ -579,7 +579,7 @@ jobs:
# inner exe before packing and the setup exe after, so verifying the setup
# exe is the arm64 equivalent of the MSI + inner-exe check below.
if ("${{ matrix.platform }}" -eq "windows-11-arm") {
$setupExes = Get-ChildItem -Path "./frontend/editor/src-tauri/target" -Filter "*-setup.exe" -Recurse -File
$setupExes = Get-ChildItem -Path "./frontend/src-tauri/target" -Filter "*-setup.exe" -Recurse -File
if ($setupExes.Count -eq 0) {
Write-Host "[ERROR] No NSIS installer found under target/"
exit 1
@@ -599,7 +599,7 @@ jobs:
$allSigned = $true
# Check MSI installer (outer wrapper - what users download)
$msiFiles = Get-ChildItem -Path "./frontend/editor/src-tauri/target" -Filter "*.msi" -Recurse -File
$msiFiles = Get-ChildItem -Path "./frontend/src-tauri/target" -Filter "*.msi" -Recurse -File
if ($msiFiles.Count -eq 0) {
Write-Host "[ERROR] No MSI found under target/"
exit 1
@@ -682,7 +682,7 @@ jobs:
# Absolute dist path so the cd below can't break the copy targets.
DIST="$GITHUB_WORKSPACE/dist"
mkdir -p "$DIST"
cd ./frontend/editor/src-tauri/target
cd ./frontend/src-tauri/target
echo "=== tauri bundle artifacts ==="
find . -path "*/bundle/*" \( -name "*.msi" -o -name "*.deb" \
@@ -743,7 +743,7 @@ jobs:
with:
sparse-checkout: |
.github/scripts/verify-updater-signatures.py
frontend/editor/src-tauri/tauri.conf.json
frontend/src-tauri/tauri.conf.json
sparse-checkout-cone-mode: false
- name: Download all Tauri artifacts
@@ -892,7 +892,7 @@ jobs:
run: |
python3 -m pip install --quiet 'cryptography==44.0.0'
python3 .github/scripts/verify-updater-signatures.py \
./artifacts/tauri frontend/editor/src-tauri/tauri.conf.json
./artifacts/tauri frontend/src-tauri/tauri.conf.json
# workflow_dispatch path requires platform=='all' so a single-platform
# dispatch can't overwrite an existing release's full latest.json with a
+2 -2
View File
@@ -84,12 +84,12 @@ jobs:
body: |
Auto-generated by ${{ steps.setup-bot.outputs.app-slug }}[bot].
Regenerates `frontend/editor/src/portal/generated/docsManifest.json`
Regenerates `frontend/src/processor/proprietary/generated/docsManifest.json`
from the Stirling docs repo via `npm run docs:sync`.
labels: |
Documentation
github-actions
Front End
add-paths: frontend/editor/src/portal/generated/docsManifest.json
add-paths: frontend/src/processor/proprietary/generated/docsManifest.json
delete-branch: true
sign-commits: true
+5 -5
View File
@@ -12,7 +12,7 @@ on:
- "app/proprietary/build.gradle"
- "gradle/spotless.gradle"
- "README.md"
- "frontend/editor/public/locales/*/translation.toml"
- "frontend/public/locales/*/translation.toml"
- "app/core/src/main/resources/static/3rdPartyLicenses.json"
- "scripts/ignore_translation.toml"
@@ -72,7 +72,7 @@ jobs:
- name: Sync translation TOML files
run: |
python .github/scripts/check_language_toml.py --reference-file "frontend/editor/public/locales/en-US/translation.toml" --branch main
python .github/scripts/check_language_toml.py --reference-file "frontend/public/locales/en-US/translation.toml" --branch main
- name: Sort translation TOML files
run: |
@@ -80,7 +80,7 @@ jobs:
- name: Commit translation files
run: |
git add frontend/editor/public/locales/*/translation.toml
git add frontend/public/locales/*/translation.toml
git diff --staged --quiet || git commit -m ":memo: Sync translation files (TOML)" || echo "No changes detected"
- name: Sync README.md
@@ -110,7 +110,7 @@ jobs:
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`.
- Updated translation files (`frontend/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
@@ -135,5 +135,5 @@ jobs:
sign-commits: true
add-paths: |
README.md
frontend/editor/public/locales/*/translation.toml
frontend/public/locales/*/translation.toml
scripts/ignore_translation.toml
+10 -10
View File
@@ -152,7 +152,7 @@ jobs:
- name: Cache Rust build
uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
workspaces: frontend/editor/src-tauri
workspaces: frontend/src-tauri
# Stable key shared across workflows so the nightly warmer.
# rust-cache still appends OS + rustc + Cargo.lock.
shared-key: tauri-${{ matrix.name }}
@@ -353,7 +353,7 @@ jobs:
env:
KEYPAIR_ALIAS: ${{ secrets.SM_KEYPAIR_ALIAS }}
run: |
cat > ./frontend/editor/src-tauri/tauri.windows.conf.json <<EOF
cat > ./frontend/src-tauri/tauri.windows.conf.json <<EOF
{
"bundle": {
"windows": {
@@ -412,7 +412,7 @@ jobs:
SM_CODE_SIGNING_CERT_SHA1_HASH: ${{ secrets.SM_CODE_SIGNING_CERT_SHA1_HASH }}
CI: true
with:
projectPath: ./frontend/editor
projectPath: ./frontend
tauriScript: npx tauri
# Linux: build deb+rpm only here (deb-only on minimal smoke builds).
# AppImage runs in its own continue-on-error step below so its
@@ -432,7 +432,7 @@ jobs:
VITE_SAAS_BACKEND_API_URL: ${{ secrets.VITE_SAAS_BACKEND_API_URL || 'https://api.stirlingpdf.com' }}
CI: true
with:
projectPath: ./frontend/editor
projectPath: ./frontend
tauriScript: npx tauri
# Linux: build deb+rpm only here (deb-only on minimal smoke builds).
# AppImage runs in its own continue-on-error step below so its
@@ -460,7 +460,7 @@ jobs:
VITE_SAAS_BACKEND_API_URL: ${{ secrets.VITE_SAAS_BACKEND_API_URL || 'https://api.stirlingpdf.com' }}
CI: true
with:
projectPath: ./frontend/editor
projectPath: ./frontend
tauriScript: npx tauri
args: --bundles appimage
@@ -472,7 +472,7 @@ jobs:
continue-on-error: true
run: |
set -euo pipefail
AI=$(find "$PWD/frontend/editor/src-tauri/target" -name "*.AppImage" | head -1)
AI=$(find "$PWD/frontend/src-tauri/target" -name "*.AppImage" | head -1)
if [ -z "$AI" ]; then echo "No AppImage found - skipping"; exit 0; fi
chmod +x "$AI"
WORK=$(mktemp -d)
@@ -509,7 +509,7 @@ jobs:
if: env.SIGN_BUNDLE == 'true' && matrix.platform == 'macos-15'
run: |
echo "🔍 Verifying notarization status..."
cd ./frontend/editor/src-tauri/target
cd ./frontend/src-tauri/target
DMG_FILE=$(find . -name "*.dmg" | head -1)
if [ -n "$DMG_FILE" ]; then
echo "Found DMG: $DMG_FILE"
@@ -526,7 +526,7 @@ jobs:
# Absolute dist path so the cd below can't break the copy targets.
DIST="$GITHUB_WORKSPACE/dist"
mkdir -p "$DIST"
cd ./frontend/editor/src-tauri/target
cd ./frontend/src-tauri/target
# Find and rename artifacts based on platform
if [ "${{ matrix.platform }}" = "windows-latest" ]; then
@@ -638,7 +638,7 @@ jobs:
- name: Verify build artifacts
shell: bash
run: |
cd ./frontend/editor/src-tauri/target
cd ./frontend/src-tauri/target
# Check for expected artifacts based on platform
if [ "${{ matrix.platform }}" = "windows-latest" ] || [ "${{ matrix.platform }}" = "windows-11-arm" ]; then
@@ -669,7 +669,7 @@ jobs:
- name: Test artifact sizes
shell: bash
run: |
cd ./frontend/editor/src-tauri/target
cd ./frontend/src-tauri/target
echo "Artifact sizes for ${{ matrix.name }}:"
find . -name "*.exe" -o -name "*.dmg" -o -name "*.deb" -o -name "*.rpm" -o -name "*.AppImage" -o -name "*.msi" | while read file; do
if [ -f "$file" ]; then
+7 -4
View File
@@ -22,6 +22,9 @@ pipeline/
customFiles/
configs/
watchedFolders/
# The rule above targets the app's runtime watched-folders working dir, but it
# also matches this frontend source component dir; keep the source tracked.
!frontend/src/editor/proprietary/components/watchedFolders/
clientWebUI/
# Scratch dir used by local fixture-regeneration runs (see
# app/proprietary/src/test/resources/db-migration-fixtures/README.md).
@@ -171,7 +174,7 @@ app/proprietary/build
common/build
proprietary/build
stirling-pdf/build
frontend/editor/src-tauri/provisioner/target
frontend/src-tauri/provisioner/target
# Byte-compiled / optimized / DLL files
__pycache__/
@@ -222,7 +225,7 @@ out/
*.asc
# Allow test fixture certificates (synthetic, no real credentials)
!frontend/editor/src/core/tests/test-fixtures/certs/**
!frontend/src/editor/core/tests/test-fixtures/certs/**
# SSH Keys
*.pub
@@ -264,7 +267,7 @@ node_modules/
*compact*.json
test_batch.json
*.backup.*.json
frontend/editor/public/locales/*/translation.backup*.json
frontend/public/locales/*/translation.backup*.json
# Development/build artifacts
.gradle-cache/
@@ -289,4 +292,4 @@ docs/type3/signatures/
*.playwright-mcp.png
# Local screenshot artifacts from *-screenshots.spec.ts
frontend/editor/screenshots/
frontend/screenshots/
+2 -2
View File
@@ -15,10 +15,10 @@ testing/compose/validate-mcp-test.sh:curl-auth-header:92
testing/compose/validate-mcp-test.sh:curl-auth-header:116
# Storybook example showing curl with a fake Bearer token placeholder (sk_live_a3f8...).
frontend/editor/src/proprietary/ui/CodeBlock.stories.tsx:curl-auth-header:5
frontend/src/editor/proprietary/ui/CodeBlock.stories.tsx:curl-auth-header:5
# Truncated placeholder API key in portal docs example (sk_live_8f2c...e10) - not a real secret.
frontend/editor/src/portal/components/docs/GettingStartedSection.tsx:generic-api-key:30
frontend/src/processor/proprietary/components/docs/GettingStartedSection.tsx:generic-api-key:30
# False positive: generic-api-key matches the Java type name "X509Certificate"
# in a method signature (CreateSignatureBase.resolveSignatureAlgorithm) - not a secret.
+1 -1
View File
@@ -1,5 +1,5 @@
{
"ignoredFiles": [
"frontend/editor/src-tauri/icons/icon.png"
"frontend/src-tauri/icons/icon.png"
]
}
+6 -16
View File
@@ -39,7 +39,6 @@ tasks:
provisioner:
desc: "Build installer provisioner"
platforms: [windows]
dir: editor
cmds:
- node scripts/build-provisioner.mjs
@@ -47,55 +46,48 @@ tasks:
desc: "Start Tauri desktop dev mode"
deps: [prepare]
ignore_error: true
dir: editor
cmds:
- npx tauri dev --no-watch
build:
desc: "Build Tauri desktop app (production)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build
build:dev:
desc: "Build Tauri desktop app (dev, no bundling)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build --no-bundle
build:dev:mac:
desc: "Build Tauri desktop .app bundle (macOS)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build --bundles app --config '{"bundle":{"createUpdaterArtifacts":false}}'
build:dev:windows:
desc: "Build Tauri desktop NSIS installer (Windows)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build --bundles nsis --config '{"bundle":{"createUpdaterArtifacts":false}}'
build:dev:linux:
desc: "Build Tauri desktop AppImage (Linux)"
deps: [prepare]
dir: editor
cmds:
- npx tauri build --bundles appimage --config '{"bundle":{"createUpdaterArtifacts":false}}'
test:
desc: "Run Tauri/Cargo tests"
deps: [prepare]
dir: editor/src-tauri
dir: src-tauri
cmds:
- cargo test
clean:
desc: "Clean Tauri/Cargo build artifacts"
dir: editor
cmds:
- task: jlink:clean
- cd src-tauri && cargo clean
@@ -117,7 +109,6 @@ tasks:
jlink:verify:
desc: "Fail the build if the bundled JRE is older than the app JAR requires"
dir: editor
env:
REQUIRED_JAVA: "{{.REQUIRED_JAVA}}"
cmds:
@@ -135,15 +126,15 @@ tasks:
platforms: [windows]
- cmd: ./gradlew bootJar --no-daemon -PjpdfiumPlatforms={{.JPDFIUM_PLATFORMS}}
platforms: [linux, darwin]
- mkdir -p frontend/editor/src-tauri/libs
- cp app/core/build/libs/stirling-pdf-*.jar frontend/editor/src-tauri/libs/
- mkdir -p frontend/src-tauri/libs
- cp app/core/build/libs/stirling-pdf-*.jar frontend/src-tauri/libs/
status:
- test -f frontend/editor/src-tauri/libs/stirling-pdf-*.jar
- test -f frontend/src-tauri/libs/stirling-pdf-*.jar
jlink:runtime:
desc: "Create custom JRE with jlink"
deps: [jlink:jar]
dir: editor/src-tauri
dir: src-tauri
cmds:
- rm -rf runtime/jre
- mkdir -p runtime
@@ -202,7 +193,7 @@ tasks:
jlink:clean:
desc: "Remove JLink runtime and bundled JARs"
dir: editor/src-tauri
dir: src-tauri
cmds:
- rm -rf libs runtime
@@ -215,7 +206,6 @@ tasks:
desc: "Create universal (arm64+x86_64) JRE for the macOS Tauri build"
deps: [jlink:jar]
platforms: [darwin]
dir: editor
env:
JLINK_MODULES: "{{.JLINK_MODULES}}"
OUTPUT_DIR: src-tauri/runtime/jre
+5 -5
View File
@@ -3,14 +3,14 @@ version: '3'
tasks:
install:
desc: "Install Playwright browsers"
dir: frontend/editor
dir: frontend
deps: [ ':frontend:install' ]
cmds:
- npx playwright install {{.CLI_ARGS}} --with-deps
stubbed:
desc: "Run stubbed E2E tests"
dir: frontend/editor
dir: frontend
deps: [ ':frontend:prepare' ]
cmds:
- npx playwright test --project=stubbed {{.CLI_ARGS}}
@@ -82,7 +82,7 @@ tasks:
live:runner:
internal: true
deps: [ ':frontend:prepare' ]
dir: frontend/editor
dir: frontend
vars:
BASE_DIR: '{{.ROOT_DIR}}/.test-state/playwright'
cmds:
@@ -151,14 +151,14 @@ tasks:
up first with:
task e2e:oauth:up
task e2e:saml:up
dir: frontend/editor
dir: frontend
deps: [ ':frontend:prepare' ]
cmds:
- npx playwright test --project=enterprise {{.CLI_ARGS}}
cross-browser:
desc: "Run stubbed E2E tests on Chromium, Firefox, and WebKit"
dir: frontend/editor
dir: frontend
deps: [ ':frontend:prepare' ]
cmds:
- npx playwright test --project=stubbed --project=stubbed-firefox --project=stubbed-webkit {{.CLI_ARGS}}
+49 -49
View File
@@ -3,7 +3,7 @@ version: '3'
# Tasks operate from the workspace root (frontend/). Editor commands pass
# `editor` as the vite project root (positional after `build` / before the
# mode flag) or use `--project editor/...` for tsc — so the editor lives
# under frontend/editor/ without each task needing a cd.
# under frontend/ without each task needing a cd.
tasks:
install:
@@ -26,34 +26,34 @@ tasks:
vars:
MODE: '{{.MODE | default ""}}'
cmds:
- npx tsx editor/scripts/setup-env.mts{{if .MODE}} --{{.MODE}}{{end}}
- npx tsx scripts/setup-env.mts{{if .MODE}} --{{.MODE}}{{end}}
sources:
- editor/scripts/setup-env.mts
- scripts/setup-env.mts
generates:
- editor/.env.local
- editor/.env{{if .MODE}}.{{.MODE}}{{end}}.local
- .env.local
- .env{{if .MODE}}.{{.MODE}}{{end}}.local
prepare:icons:
internal: true
run: once
deps: [install]
cmds:
- node editor/scripts/generate-icons.js
- node scripts/generate-icons.js
prepare:og:
internal: true
run: when_changed
desc: "Regenerate OG/social-preview metadata from the tool registry"
cmds:
- node editor/scripts/generate-og-metadata.mjs
- node scripts/generate-og-metadata.mjs
sources:
- editor/src/core/types/toolId.ts
- editor/src/core/utils/urlMapping.ts
- editor/src/core/data/useTranslatedToolRegistry.tsx
- editor/public/og_images/*.png
- src/editor/core/types/toolId.ts
- src/editor/core/utils/urlMapping.ts
- src/editor/core/data/useTranslatedToolRegistry.tsx
- public/og_images/*.png
generates:
- editor/src/core/data/ogImageMap.json
- editor/public/og-metadata.json
- src/editor/core/data/ogImageMap.json
- public/og-metadata.json
prepare:
desc: "Set up dev environment"
@@ -88,7 +88,7 @@ tasks:
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}}
cmds:
- npx vite editor --mode {{.MODE}} --port {{.PORT}}{{if .OPEN}} --open{{end}}
- npx vite --mode {{.MODE}} --port {{.PORT}}{{if .OPEN}} --open{{end}}
dev:
desc: "Start frontend dev server"
@@ -143,13 +143,13 @@ tasks:
desc: "Production build (default mode)"
deps: [prepare]
cmds:
- npx vite build editor
- npx vite build
build:core:
desc: "Build for core mode"
deps: [prepare]
cmds:
- npx vite build editor --mode core
- npx vite build --mode core
build:proprietary:
desc: "Build for proprietary mode"
@@ -157,7 +157,7 @@ tasks:
vars:
PREVIEW: '{{.PREVIEW | default ""}}'
cmds:
- '{{if .PREVIEW}}VITE_BUILD_FOR_PREVIEW=1 {{end}}npx vite build editor --mode proprietary'
- '{{if .PREVIEW}}VITE_BUILD_FOR_PREVIEW=1 {{end}}npx vite build --mode proprietary'
build:saas:
desc: "Build for SaaS mode"
@@ -165,7 +165,7 @@ tasks:
- task: prepare
vars: { MODE: saas }
cmds:
- npx vite build editor --mode saas
- npx vite build --mode saas
build:desktop:
desc: "Build for desktop mode"
@@ -173,13 +173,13 @@ tasks:
- task: prepare
vars: { MODE: desktop }
cmds:
- npx vite build editor --mode desktop
- npx vite build --mode desktop
build:prototypes:
desc: "Build for prototypes mode"
deps: [prepare]
cmds:
- npx vite build editor --mode prototypes
- npx vite build --mode prototypes
storybook:
@@ -272,23 +272,23 @@ tasks:
# Covers the whole editor tree, including the portal/processor layer and
# public/css. Vendored CSS and build output are excluded via ignoreFiles
# in stylelint.config.mjs.
- npx stylelint "editor/**/*.css"
- npx stylelint "src/**/*.css"
lint:colors:
desc: "Enforce theme tokens — no hardcoded colours or raw primitives in components"
aliases: [lint:colours]
deps: [install]
cmds:
- node editor/scripts/lint/theme-lint.mjs
- node editor/scripts/lint/theme-lint.mjs css-colors
- node editor/scripts/lint/theme-lint.mjs code-colors
- node editor/scripts/lint/theme-lint.mjs no-primitives
- node scripts/lint/theme-lint.mjs
- node scripts/lint/theme-lint.mjs css-colors
- node scripts/lint/theme-lint.mjs code-colors
- node scripts/lint/theme-lint.mjs no-primitives
contrast:
desc: "Report low-contrast theme token pairs (warning only, never blocks)"
deps: [install]
cmds:
- node editor/scripts/lint/theme-lint.mjs contrast
- node scripts/lint/theme-lint.mjs contrast
lint:eslint:
desc: "Run ESLint linting"
@@ -302,7 +302,7 @@ tasks:
cmds:
# Globs so dpdm walks the whole tree. dpdm expands the braces itself, so this is
# shell-agnostic. Covers the whole editor tree, including the portal layer.
- npx dpdm "editor/src/**/*.{ts,tsx}" --circular --no-warning --no-tree --exit-code circular:1
- npx dpdm "src/**/*.{ts,tsx}" --circular --no-warning --no-tree --exit-code circular:1
lint:fix:
desc: "Auto-fix lint issues"
@@ -343,14 +343,14 @@ tasks:
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: editor/src/core/tsconfig.json }
vars: { PROJECT: src/editor/core/tsconfig.json }
typecheck:proprietary:
desc: "Typecheck proprietary build variant"
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: editor/src/proprietary/tsconfig.json }
vars: { PROJECT: src/editor/proprietary/tsconfig.json }
typecheck:saas:
desc: "Typecheck SaaS build variant"
@@ -359,7 +359,7 @@ tasks:
vars: { MODE: saas }
cmds:
- task: typecheck:_run
vars: { PROJECT: editor/src/saas/tsconfig.json }
vars: { PROJECT: src/editor/saas/tsconfig.json }
typecheck:desktop:
desc: "Typecheck desktop build variant"
@@ -368,35 +368,35 @@ tasks:
vars: { MODE: desktop }
cmds:
- task: typecheck:_run
vars: { PROJECT: editor/src/desktop/tsconfig.json }
vars: { PROJECT: src/editor/desktop/tsconfig.json }
typecheck:cloud:
desc: "Typecheck cloud shared layer (standalone)"
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: editor/src/cloud/tsconfig.json }
vars: { PROJECT: src/editor/cloud/tsconfig.json }
typecheck:scripts:
desc: "Typecheck scripts"
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: editor/scripts/tsconfig.json }
vars: { PROJECT: scripts/tsconfig.json }
typecheck:prototypes:
desc: "Typecheck prototypes build variant"
deps: [prepare]
cmds:
- task: typecheck:_run
vars: { PROJECT: editor/src/prototypes/tsconfig.json }
vars: { PROJECT: src/editor/prototypes/tsconfig.json }
typecheck:portal:
desc: "Typecheck developer portal build variant"
deps: [install]
cmds:
- task: typecheck:_run
vars: { PROJECT: editor/src/portal/tsconfig.json }
vars: { PROJECT: src/processor/proprietary/tsconfig.json }
typecheck:storybook:
desc: "Typecheck Storybook config and stories"
@@ -433,7 +433,7 @@ tasks:
og:check:
desc: "Fail if committed OG/social-preview metadata is out of date"
cmds:
- node editor/scripts/generate-og-metadata.mjs --check
- node scripts/generate-og-metadata.mjs --check
check:all:
desc: "Full CI quality gate"
@@ -461,13 +461,13 @@ tasks:
desc: "Run editor tests"
deps: [prepare]
cmds:
- npx vitest run --root editor
- npx vitest run --root .
test:watch:
desc: "Run tests in watch mode"
deps: [prepare]
cmds:
- npx vitest --watch --root editor
- npx vitest --watch --root .
test:coverage:
desc: "Run tests with coverage (one-shot; CI-friendly)."
@@ -478,12 +478,12 @@ tasks:
# coverage-summary.py helper consumes; html/text are kept for humans.
#
# reportsDirectory is pinned to ./coverage relative to vitest's root
# (--root editor), so output lands at frontend/editor/coverage/. The
# (--root .), so output lands at frontend/coverage/. The
# CI upload step reads from that path. An earlier attempt with
# `./editor/coverage` double-nested into frontend/editor/editor/coverage;
# `./editor/coverage` double-nested into frontend/coverage;
# pinning future-proofs against vitest changing the default.
- >
npx vitest run --root editor --coverage
npx vitest run --root . --coverage
--coverage.provider=v8
--coverage.reporter=text-summary
--coverage.reporter=json-summary
@@ -498,25 +498,25 @@ tasks:
desc: "Generate tool API types from the Java OpenAPI spec"
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
- npx tsx scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output src/editor/core/types/toolApiTypes.ts --io-output src/editor/core/types/toolIO.ts
sources:
- editor/scripts/generate-tool-api-types.mts
- scripts/generate-tool-api-types.mts
- ../SwaggerDoc.json
generates:
- editor/src/core/types/toolApiTypes.ts
- editor/src/core/types/toolIO.ts
- src/editor/core/types/toolApiTypes.ts
- src/editor/core/types/toolIO.ts
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
- npx tsx scripts/generate-tool-api-types.mts --spec ../SwaggerDoc.json --output src/editor/core/types/toolApiTypes.ts --io-output src/editor/core/types/toolIO.ts --check
licenses:generate:
desc: "Generate frontend license report"
deps: [install]
cmds:
- node editor/scripts/generate-licenses.js
- node scripts/generate-licenses.js
# ============================================================
# Clean
@@ -525,7 +525,7 @@ tasks:
clean:
desc: "Clean build artifacts and caches"
cmds:
- cmd: powershell rm -Recurse -Force -ErrorAction SilentlyContinue node_modules/.vite, editor/dist, dist
- cmd: powershell rm -Recurse -Force -ErrorAction SilentlyContinue node_modules/.vite, dist, dist
platforms: [windows]
- cmd: rm -rf node_modules/.vite editor/dist dist
- cmd: rm -rf node_modules/.vite dist dist
platforms: [linux, darwin]
+2 -2
View File
@@ -21,7 +21,7 @@ vars:
':(exclude).devcontainer/*'
':(exclude)app/core/src/main/resources/*'
':(exclude)app/proprietary/src/main/resources/*'
':(exclude)frontend/editor/public/vendor/*'
':(exclude)frontend/public/vendor/*'
':(exclude)*Dockerfile*'
':(exclude)*pdfjs*'
':(exclude)*thirdParty*'
@@ -39,7 +39,7 @@ vars:
':(exclude)*.min.*'
':(exclude)*diff.js'
':(exclude).github/workflows/*'
LOCALE_TOML: 'frontend/editor/public/locales/*/translation.toml'
LOCALE_TOML: 'frontend/public/locales/*/translation.toml'
# gitleaks is pinned + checksum-verified by scripts/pre-commit/install_gitleaks.py,
# which owns the version and caches the binary here.
+6 -6
View File
@@ -10,14 +10,14 @@ When adding tools, follow this systematic approach using the established pattern
Create these files in the correct directories:
```
frontend/editor/src/hooks/tools/[toolName]/
frontend/src/hooks/tools/[toolName]/
├── use[ToolName]Parameters.ts # Parameter definitions and validation
└── use[ToolName]Operation.ts # Tool operation logic using useToolOperation
frontend/editor/src/components/tools/[toolName]/
frontend/src/components/tools/[toolName]/
└── [ToolName]Settings.tsx # Settings UI component (if needed)
frontend/editor/src/tools/
frontend/src/tools/
└── [ToolName].tsx # Main tool component
```
@@ -128,7 +128,7 @@ export default [ToolName] as ToolComponent;
## 3. Register Tool in System
Update these files to register your new tool:
**Tool Registry** (`frontend/editor/src/data/useTranslatedToolRegistry.tsx`):
**Tool Registry** (`frontend/src/data/useTranslatedToolRegistry.tsx`):
1. Add imports at the top:
```typescript
import [ToolName] from "../tools/[ToolName]";
@@ -155,7 +155,7 @@ import [ToolName]Settings from "../components/tools/[toolName]/[ToolName]Setting
## 4. Add Tooltips (Optional but Recommended)
Create user-friendly tooltips to help non-technical users understand your tool. **Use simple, clear language - avoid technical jargon:**
**Tooltip Hook** (`frontend/editor/src/components/tooltips/use[ToolName]Tips.ts`):
**Tooltip Hook** (`frontend/src/components/tooltips/use[ToolName]Tips.ts`):
```typescript
import { useTranslation } from 'react-i18next';
import { TooltipContent } from '../../types/tips';
@@ -202,7 +202,7 @@ const [ToolName] = (props: BaseToolProps) => {
## 5. Add Translations
Update translation files. **Important: Only update `en-US` files** - other languages are handled separately.
**File to update:** `frontend/editor/public/locales/en-US/translation.toml`
**File to update:** `frontend/public/locales/en-US/translation.toml`
**Required Translation Keys**:
```toml
+17 -17
View File
@@ -139,10 +139,10 @@ The project structure is defined in `engine/pyproject.toml`. Any new dependencie
#### Environment Variables
- All `VITE_*` variables must be declared in the appropriate committed env file:
- `frontend/editor/.env` — core and shared vars (base, loaded in every mode)
- `frontend/editor/.env.proprietary` — proprietary-only vars, e.g. the admin portal's SaaS/account-link keys (layered on top of `.env` in proprietary mode)
- `frontend/editor/.env.saas` — SaaS-only vars (layered on top of `.env` in SaaS mode)
- `frontend/editor/.env.desktop` — desktop (Tauri)-only vars (layered on top of `.env` in desktop mode)
- `frontend/.env` — core and shared vars (base, loaded in every mode)
- `frontend/.env.proprietary` — proprietary-only vars, e.g. the admin portal's SaaS/account-link keys (layered on top of `.env` in proprietary mode)
- `frontend/.env.saas` — SaaS-only vars (layered on top of `.env` in SaaS mode)
- `frontend/.env.desktop` — desktop (Tauri)-only vars (layered on top of `.env` in desktop mode)
- These files are committed to Git and must not contain private keys
- Local overrides (API keys, machine-specific settings) go in uncommitted sibling `.env.local` / `.env.saas.local` / `.env.desktop.local` files — Vite automatically layers them on top
- Never use `|| 'hardcoded-fallback'` inline — put defaults in the committed env files
@@ -153,9 +153,9 @@ The project structure is defined in `engine/pyproject.toml`. Any new dependencie
#### Import Paths - CRITICAL
**ALWAYS use `@app/*` for imports.** Do not use `@core/*` or `@proprietary/*` unless explicitly wrapping/extending a lower layer implementation.
For a broader explanation of the frontend layering and override architecture, read @frontend/editor/DeveloperGuide.md
For a broader explanation of the frontend layering and override architecture, read @frontend/DeveloperGuide.md
Before touching colours or theming (tokens, dark mode, accent colours), read @frontend/editor/src/core/theme/README.md — it explains the palette/`--c-*` token system and the rule that literal colours live only in `primitives.css`.
Before touching colours or theming (tokens, dark mode, accent colours), read @frontend/src/editor/core/theme/README.md — it explains the palette/`--c-*` token system and the rule that literal colours live only in `primitives.css`.
```typescript
// ✅ CORRECT - Use @app/* for all imports
@@ -270,7 +270,7 @@ Frontend designed for **stateful document processing**:
- No file reloading between tools - performance critical for large PDFs (up to 100GB+)
#### FileContext - Central State Management
**Location**: `frontend/editor/src/core/contexts/FileContext.tsx`
**Location**: `frontend/src/editor/core/contexts/FileContext.tsx`
- **Active files**: Currently loaded PDFs and their variants
- **Tool navigation**: Current mode (viewer/pageEditor/fileEditor/toolName)
- **Memory management**: PDF document cleanup, blob URL lifecycle, Web Worker management
@@ -295,7 +295,7 @@ Without cleanup: browser crashes with memory leaks.
**Architecture**: Modular hook-based system with clear separation of concerns:
- **useToolOperation** (`frontend/editor/src/core/hooks/tools/shared/useToolOperation.ts`): Main orchestrator hook
- **useToolOperation** (`frontend/src/editor/core/hooks/tools/shared/useToolOperation.ts`): Main orchestrator hook
- Coordinates all tool operations with consistent interface
- Integrates with FileContext for operation tracking
- Handles validation, error handling, and UI state management
@@ -383,7 +383,7 @@ return useToolOperation({
### Frontend Directory Structure
The frontend is organized with a clear separation of concerns:
- **`frontend/editor/src/core/`**: Main application code (shared, production-ready components)
- **`frontend/src/editor/core/`**: Main application code (shared, production-ready components)
- **`core/components/`**: React components organized by feature
- `core/components/tools/`: Individual PDF tool implementations
- `core/components/viewer/`: PDF viewer components
@@ -401,17 +401,17 @@ The frontend is organized with a clear separation of concerns:
- **`core/data/`**: Static data (tool taxonomy, etc.)
- **`core/services/`**: Business logic services (PDF processing, storage, etc.)
- **`frontend/editor/src/desktop/`**: Desktop-specific (Tauri) code
- **`frontend/editor/src/proprietary/`**: Proprietary/licensed features
- **`frontend/editor/src-tauri/`**: Tauri (Rust) native desktop application code
- **`frontend/editor/public/`**: Static assets served directly
- **`frontend/src/editor/desktop/`**: Desktop-specific (Tauri) code
- **`frontend/src/editor/proprietary/`**: Proprietary/licensed features
- **`frontend/src-tauri/`**: Tauri (Rust) native desktop application code
- **`frontend/public/`**: Static assets served directly
- `public/locales/`: Translation JSON files
### Component Architecture
- **Static Assets**: CSS, JS, and resources in `src/main/resources/static/` (legacy) + `frontend/editor/public/` (modern)
- **Static Assets**: CSS, JS, and resources in `src/main/resources/static/` (legacy) + `frontend/public/` (modern)
- **Internationalization**:
- Backend: `messages_*.properties` files
- Frontend: JSON files in `frontend/editor/public/locales/` (converted from .properties)
- Frontend: JSON files in `frontend/public/locales/` (converted from .properties)
- Conversion Script: `scripts/convert_properties_to_json.py`
### Configuration Modes
@@ -436,7 +436,7 @@ The frontend is organized with a clear separation of concerns:
4. **Code Style**: Spotless enforces Google Java Format automatically (`task backend:format`)
5. **Translations**:
- Backend: Use helper scripts in `/scripts` for multi-language updates
- Frontend: Update JSON files in `frontend/editor/public/locales/` or use conversion script
- Frontend: Update JSON files in `frontend/public/locales/` or use conversion script
6. **Documentation**: API docs auto-generated and available at `/swagger-ui/index.html`
## Frontend Architecture Status
@@ -454,7 +454,7 @@ The frontend is organized with a clear separation of concerns:
## Translation Rules
- **CRITICAL**: Always update translations in `en-US` only - all other languages (including `en-GB`) are handled separately
- Translation files are located in `frontend/editor/public/locales/`
- Translation files are located in `frontend/public/locales/`
- After changing any translation file, run `task pre-commit:fix`
## Important Notes
+1 -1
View File
@@ -458,7 +458,7 @@ For Stirling 2.0, new features are built as React components:
1. **Create the React Component:**
```typescript
// frontend/editor/src/tools/NewTool.tsx
// frontend/src/tools/NewTool.tsx
import { useState } from 'react';
import { Button, FileInput, Container } from '@mantine/core';
+12 -14
View File
@@ -10,20 +10,18 @@ if that directory exists, is licensed under the license defined in "app/propriet
if that directory exists, is licensed under the license defined in "app/saas/LICENSE".
* All content that resides under the "engine/" directory of this repository,
if that directory exists, is licensed under the license defined in "engine/LICENSE".
* All content that resides under the "frontend/editor/src/proprietary/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/proprietary/LICENSE".
* All content that resides under the "frontend/editor/src/desktop/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/desktop/LICENSE".
* All content that resides under the "frontend/editor/src/saas/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/saas/LICENSE".
* All content that resides under the "frontend/editor/src/cloud/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/cloud/LICENSE".
* All content that resides under the "frontend/editor/src/prototypes/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/prototypes/LICENSE".
* All content that resides under the "frontend/editor/src/portal/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/portal/LICENSE".
* All content that resides under the "frontend/editor/src/portal-saas/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/editor/src/portal-saas/LICENSE".
* All content that resides under the "frontend/src/editor/proprietary/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/proprietary/LICENSE".
* All content that resides under the "frontend/src/editor/desktop/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/desktop/LICENSE".
* All content that resides under the "frontend/src/editor/saas/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/saas/LICENSE".
* All content that resides under the "frontend/src/editor/cloud/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/cloud/LICENSE".
* All content that resides under the "frontend/src/editor/prototypes/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/editor/prototypes/LICENSE".
* All content that resides under the "frontend/src/processor/" directory of this repository,
if that directory exists, is licensed under the license defined in "frontend/src/processor/LICENSE".
* Content outside of the above mentioned directories or restrictions above is
available under the MIT License as defined below.
+1 -1
View File
@@ -86,7 +86,7 @@ If you're using Tauri's built-in updater feature:
## Configuration Files
### 1. Tauri Configuration (frontend/editor/src-tauri/tauri.conf.json)
### 1. Tauri Configuration (frontend/src-tauri/tauri.conf.json)
The Windows signing configuration is already set up:
+4 -4
View File
@@ -156,7 +156,7 @@ if (buildWithPortal) {
}
// Workspace root holds package.json and node_modules (shared across editor /
// future portal). Editor-specific paths (src, public, dist, tauri) live one
// level deeper under frontend/editor/.
// level deeper under frontend/.
// Vite mode: -PprototypesMode > -PfrontendMode > enableSaas > disableAdditional > proprietary.
def frontendModeOverride = project.findProperty('frontendMode')?.toString()?.toLowerCase()
@@ -176,11 +176,11 @@ def frontendBuildTask = "frontend:build:${frontendMode}"
// Workspace root holds package.json and node_modules (shared across editor /
// future portal). Editor-specific paths (src, public, dist, tauri) live one
// level deeper under frontend/editor/. When the portal lands as an embedded
// level deeper under frontend/. When the portal lands as an embedded
// app, add a sibling frontendPortalDir / frontendPortalDistDir alongside.
def frontendDir = file('../../frontend')
def frontendEditorDir = file('../../frontend/editor')
def frontendEditorDistDir = file('../../frontend/editor/dist')
def frontendEditorDir = file('../../frontend')
def frontendEditorDistDir = file('../../frontend/dist')
def resourcesStaticDir = file('src/main/resources/static')
def generatedFrontendPaths = [
'assets',
@@ -2,7 +2,7 @@ package stirling.software.proprietary.access.model;
/** Types of resources whose access can be gated by {@link ResourceGrant}. */
public enum ResourceType {
// The admin portal / processor (frontend/editor/src/portal). Singleton resource (empty
// The admin portal / processor (frontend/src/processor/proprietary). Singleton resource (empty
// resourceId).
PORTAL,
// A stored S3/MCP/API integration configuration.
+3 -3
View File
@@ -110,9 +110,9 @@ allprojects {
}
def appVersionStr = project.version.toString()
def tauriConfigPath = layout.projectDirectory.file('frontend/editor/src-tauri/tauri.conf.json').asFile.path
def sim1Path = layout.projectDirectory.file('frontend/editor/src/core/testing/serverExperienceSimulations.ts').asFile.path
def sim2Path = layout.projectDirectory.file('frontend/editor/src/proprietary/testing/serverExperienceSimulations.ts').asFile.path
def tauriConfigPath = layout.projectDirectory.file('frontend/src-tauri/tauri.conf.json').asFile.path
def sim1Path = layout.projectDirectory.file('frontend/src/editor/core/testing/serverExperienceSimulations.ts').asFile.path
def sim2Path = layout.projectDirectory.file('frontend/src/editor/proprietary/testing/serverExperienceSimulations.ts').asFile.path
def aurDesktopPkgbuildPath = layout.projectDirectory.file('.github/aur/stirling-pdf-desktop/PKGBUILD').asFile.path
def aurServerPkgbuildPath = layout.projectDirectory.file('.github/aur/stirling-pdf-server-bin/PKGBUILD').asFile.path
+4 -4
View File
@@ -12,12 +12,12 @@ Fork Stirling-PDF and create a new branch out of `main`.
### Add Language Directory and Translation File
1. Create a new language directory in `frontend/editor/public/locales/`
1. Create a new language directory in `frontend/public/locales/`
- Use hyphenated format: `pl-PL` (not underscore)
2. Copy the reference translation file:
- Source: `frontend/editor/public/locales/en-US/translation.toml`
- Destination: `frontend/editor/public/locales/pl-PL/translation.toml`
- Source: `frontend/public/locales/en-US/translation.toml`
- Destination: `frontend/public/locales/pl-PL/translation.toml`
3. Translate all entries in the TOML file
- Keep the TOML structure intact
@@ -49,7 +49,7 @@ ignore = [
> [!IMPORTANT]
> If you add any new translation tags, they must first be added to the `en-US/translation.toml` file. This ensures consistency across all language files.
- New translation tags **must be added** to `frontend/editor/public/locales/en-US/translation.toml` to maintain a reference for other languages.
- New translation tags **must be added** to `frontend/public/locales/en-US/translation.toml` to maintain a reference for other languages.
- After adding the new tags to `en-US/translation.toml`, add and translate them in the respective language file (e.g., `pl-PL/translation.toml`).
- Use the scripts in `scripts/translations/` to validate and manage translations (see `scripts/translations/README.md`)
+3 -3
View File
@@ -12,7 +12,7 @@ RUN npm ci
COPY frontend .
# Generate material-symbols icon subset (normally done by task prepare:icons).
RUN node editor/scripts/generate-icons.js
RUN node scripts/generate-icons.js
# Defaults match prior behaviour. Supabase values are client-publishable build args.
ARG STIRLING_FLAVOR=proprietary
@@ -21,7 +21,7 @@ ARG VITE_SUPABASE_URL=""
# pragma: allowlist secret
ARG VITE_SUPABASE_PUBLISHABLE_DEFAULT_KEY=""
# Build vite from editor/, output lands in editor/dist/.
# Build vite from editor/, output lands in dist/.
RUN set -eu; \
export STIRLING_FLAVOR="${STIRLING_FLAVOR}"; \
if [ -n "${VITE_SUPABASE_URL}" ]; then export VITE_SUPABASE_URL="${VITE_SUPABASE_URL}"; fi; \
@@ -31,7 +31,7 @@ RUN set -eu; \
# Stage 2: nginx
FROM nginx:alpine@sha256:b0f7830b6bfaa1258f45d94c240ab668ced1b3651c8a222aefe6683447c7bf55
COPY --from=build /app/editor/dist /usr/share/nginx/html
COPY --from=build /app/dist /usr/share/nginx/html
COPY docker/frontend/nginx.conf /etc/nginx/nginx.conf
COPY docker/frontend/entrypoint.sh /entrypoint.sh
+4 -4
View File
@@ -15,7 +15,7 @@
/storybook-static
/editor/build
/editor/dist
/dist
# misc
.DS_Store
@@ -40,12 +40,12 @@ playwright-report
test-results
# auto-generated files
/editor/src/assets/material-symbols-icons.json
/editor/src/assets/material-symbols-icons.d.ts
/src/assets/material-symbols-icons.json
/src/assets/material-symbols-icons.d.ts
# dev update testing - keys, built bundles, screenshots, and generated config override
/scripts/dev-update-test/.keys/
/scripts/dev-update-test/.update-dist/
/scripts/dev-update-test/screenshots/
/editor/src-tauri/tauri.conf.dev-update.json
/src-tauri/tauri.conf.dev-update.json
.a11y-scan/
+15 -15
View File
@@ -1,30 +1,30 @@
dist/
editor/dist/
dist/
# Tauri/Cargo build output (binary assets named *.js etc. confuse Prettier).
# Match nested target/ dirs too - provisioner/ and thumbnail-handler/ each
# have their own Cargo workspace under src-tauri/.
editor/src-tauri/**/target/
editor/src-tauri/gen/
src-tauri/**/target/
src-tauri/gen/
node_modules/
editor/public/vendor/
public/vendor/
# Auto-generated by MSW (`msw init`); regenerated verbatim, not hand-formatted.
editor/public/mockServiceWorker.js
public/mockServiceWorker.js
# Auto-generated OG/social-preview metadata (scripts/generate-og-metadata.mjs); regenerated verbatim.
editor/public/og-metadata.json
editor/public/og-metadata.saas.json
editor/src/core/data/ogImageMap.json
public/og-metadata.json
public/og-metadata.saas.json
src/editor/core/data/ogImageMap.json
# Auto-generated portal docs manifest (scripts/sync-portal-docs.mts); regenerated verbatim.
editor/src/portal/generated/docsManifest.json
editor/public/pdfjs*/
editor/public/js/thirdParty/
editor/public/css/cookieconsent.css
src/processor/proprietary/generated/docsManifest.json
public/pdfjs*/
public/js/thirdParty/
public/css/cookieconsent.css
# Build / test artifacts that may exist locally even though they're gitignored
storybook-static/
playwright-report/
editor/playwright-report/
playwright-report/
test-results/
test-results/
editor/test-results/
*.min.*
*.md
*.wxs
editor/src/output.css
src/output.css
File diff suppressed because it is too large Load Diff
+3 -8
View File
@@ -59,19 +59,14 @@ if (args.length > 0) {
// checked before it is added to the index.
files = [
...new Set([
...git(
"ls-files",
"--",
"editor/src/**/*.stories.ts",
"editor/src/**/*.stories.tsx",
),
...git("ls-files", "--", "src/**/*.stories.ts", "src/**/*.stories.tsx"),
...git(
"ls-files",
"--others",
"--exclude-standard",
"--",
"editor/src/**/*.stories.ts",
"editor/src/**/*.stories.tsx",
"src/**/*.stories.ts",
"src/**/*.stories.tsx",
),
]),
].sort();
+25 -15
View File
@@ -6,14 +6,14 @@ import tsconfigPaths from "vite-tsconfig-paths";
* Storybook 9 ships essentials, interactions, and docs as built-ins, so the
* addon list is just the extras we want: theme switching + a11y auditing.
*
* Story files live next to their components under editor/src/ (which includes
* the portal layer at editor/src/portal/). MDX docs pages live in
* editor/src/portal/docs/.
* Story files live next to their components under src/editor/ (which includes
* the portal layer at src/processor/proprietary/). MDX docs pages live in
* src/processor/proprietary/docs/.
*/
const config: StorybookConfig = {
stories: [
"../editor/src/portal/**/*.mdx",
"../editor/src/**/*.stories.@(ts|tsx)",
"../src/processor/proprietary/**/*.mdx",
"../src/**/*.stories.@(ts|tsx)",
],
addons: [
"@storybook/addon-themes",
@@ -29,35 +29,45 @@ const config: StorybookConfig = {
},
// Serve the MSW worker file from the portal's public dir so Storybook can
// intercept network calls the same way the dev portal does.
staticDirs: ["../editor/public"],
staticDirs: ["../public"],
viteFinal: async (config) => {
// Wire the @portal/* alias directly on the Storybook bundler so portal
// Wire the @processor/* alias directly on the Storybook bundler so portal
// story imports resolve without needing the portal's vite config.
config.resolve = config.resolve ?? {};
config.resolve.alias = {
...(config.resolve.alias ?? {}),
"@portal": resolve(__dirname, "../editor/src/portal"),
"@processor": resolve(__dirname, "../src/processor/proprietary"),
// Direct layer aliases so .storybook config files (preview.tsx), which sit
// outside src/ and so aren't covered by tsconfigPaths, can import layer
// modules (e.g. the auth supabase client that moved into proprietary).
"@proprietary": resolve(__dirname, "../editor/src/proprietary"),
"@core": resolve(__dirname, "../editor/src/core"),
"@proprietary": resolve(__dirname, "../src/editor/proprietary"),
"@core": resolve(__dirname, "../src/editor/core"),
// Public assets (e.g. the en-US translation TOML loaded ?raw by preview.tsx).
// No src alias covers public/, so this lets the config use an alias rather
// than a relative path.
"@public": resolve(__dirname, "../editor/public"),
"@public": resolve(__dirname, "../public"),
};
// Editor stories import via @app/* (proprietary→core fallback), @core/* and
// Editor stories import via @editor/* (proprietary→core fallback), @core/* and
// @proprietary/*. Resolve them exactly the way the editor's own build does —
// through vite-tsconfig-paths against the proprietary vite tsconfig — so the
// shared Storybook can host editor components without duplicating the alias
// map here.
config.plugins = config.plugins ?? [];
// Storybook's builder-vite auto-loads the app's vite.config.ts and merges in
// its plugins, but a Storybook build is not an app build. Drop the app's
// build-only plugins that emit deploy artifacts against a dist/ that a
// Storybook build never produces (prerender-og would otherwise throw ENOENT).
const APP_BUILD_ONLY = new Set(["prerender-og", "compress-static-copy"]);
config.plugins = config.plugins.filter((p) => {
const name =
p && typeof p === "object" && "name" in p
? (p as { name?: unknown }).name
: undefined;
return typeof name !== "string" || !APP_BUILD_ONLY.has(name);
});
config.plugins.push(
tsconfigPaths({
projects: [
resolve(__dirname, "../editor/tsconfig.proprietary.vite.json"),
],
projects: [resolve(__dirname, "../tsconfig.proprietary.vite.json")],
}),
);
// Point apiClient.saas at a mock origin so the SaaS-backed billing stories
+7 -7
View File
@@ -14,12 +14,12 @@ import { withThemeByDataAttribute } from "@storybook/addon-themes";
void React;
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { TierProvider, type Tier } from "@portal/contexts/TierContext";
import { LinkProvider, type LinkState } from "@portal/contexts/LinkContext";
import { ThemeProvider, useTheme } from "@portal/contexts/ThemeContext";
import { UIProvider } from "@portal/contexts/UIContext";
import { SuiProvider } from "@portal/theme/SuiProvider";
import { handlers } from "@portal/mocks/handlers";
import { TierProvider, type Tier } from "@processor/contexts/TierContext";
import { LinkProvider, type LinkState } from "@processor/contexts/LinkContext";
import { ThemeProvider, useTheme } from "@processor/contexts/ThemeContext";
import { UIProvider } from "@processor/contexts/UIContext";
import { SuiProvider } from "@processor/theme/SuiProvider";
import { handlers } from "@processor/mocks/handlers";
import { configureSupabase } from "@proprietary/auth/supabase/supabaseClient";
import i18next from "i18next";
import { initReactI18next } from "react-i18next";
@@ -36,7 +36,7 @@ import "@core/tokens/base.css";
// async fetch (Storybook has no backend to serve /locales/). t(key) then renders
// the shipped copy (e.g. "No sources connected yet") rather than the raw key.
const localeModules = import.meta.glob<string>(
"../editor/public/locales/*/translation.toml",
"../public/locales/*/translation.toml",
{ query: "?raw", import: "default", eager: true },
);
+6 -6
View File
@@ -1,15 +1,15 @@
{
// Extends the same tsconfig main.ts feeds to vite-tsconfig-paths, so the
// alias map can't drift from the one Storybook actually bundles with, and
// stories are checked under that resolution (@app/* = proprietary -> core).
// stories are checked under that resolution (@editor/* = proprietary -> core).
// The per-layer typecheck projects also cover story files, but each resolves
// @app/* its own way - not the way the single Storybook build renders them.
"extends": "../editor/tsconfig.proprietary.vite.json",
// @editor/* its own way - not the way the single Storybook build renders them.
"extends": "../tsconfig.proprietary.vite.json",
"exclude": [],
"include": [
"./**/*",
"../editor/src/global.d.ts",
"../editor/src/**/*.stories.ts",
"../editor/src/**/*.stories.tsx"
"../src/global.d.ts",
"../src/**/*.stories.ts",
"../src/**/*.stories.tsx"
]
}
+2 -2
View File
@@ -6,7 +6,7 @@ import { storybookTest } from "@storybook/addon-vitest/vitest-plugin";
* Dedicated Vitest config that turns every story into a browser test: it mounts
* the story in real Chromium and runs axe against it, so a story fails if it
* throws on mount or trips an accessibility rule. Kept separate from
* editor/vitest.config.ts (the jsdom unit tests) so the two suites don't collide.
* vitest.config.ts (the jsdom unit tests) so the two suites don't collide.
*
* The storybook test must live in a `test.projects[]` entry (not a flat config)
* so Vitest wires up the browser test runner correctly.
@@ -17,7 +17,7 @@ export default defineConfig({
optimizeDeps: {
// Pre-scan every story + the preview so Vite discovers the story set's large
// dep surface (embedpdf plugins, @mui icons, …) in one pass up front.
entries: ["editor/src/**/*.stories.@(ts|tsx)", ".storybook/preview.tsx"],
entries: ["src/**/*.stories.@(ts|tsx)", ".storybook/preview.tsx"],
// `entries` alone does not catch deps reached through a transformed JSX
// runtime import, nor the preview's own dependency graph (the test plugin
// injects the preview in a way the entry scanner doesn't crawl). Vite then
@@ -10,7 +10,7 @@ Refer to the various `tsconfig.*.json` files to see the specific path alias orde
The vast majority of the code is in the `src/core` folder, which is the open-source app.
Other builds, such as the desktop app, use `src/core` as the base layer, and then override various files to change behaviour.
If an import is `from '@app/a/b'`, this will refer to `src/core/a/b.ts` in the core build of the app, but may refer to `src/desktop/a/b.ts` in the desktop app if that file exists.
If an import is `from '@editor/a/b'`, this will refer to `src/editor/core/a/b.ts` in the core build of the app, but may refer to `src/editor/desktop/a/b.ts` in the desktop app if that file exists.
It is important to try to minimise the amount of overridden code in the app.
Often, just one function needs to behave differently in a specific mode.
@@ -27,7 +27,7 @@ In cases like this, instead of duplicating the entire file, create a new extensi
```ts
// core/file1.ts
import { f2 } from '@app/file1Extensions';
import { f2 } from '@editor/file1Extensions';
function f1() { /* ... */ }
function f3() { /* ... */ }
@@ -102,7 +102,7 @@ export function getToolbarButtons(): ToolbarButton[] {
```tsx
// core/Toolbar.tsx
import { getToolbarButtons } from '@app/toolbarExtensions';
import { getToolbarButtons } from '@editor/toolbarExtensions';
export function Toolbar() {
return (
@@ -123,7 +123,7 @@ This pattern works well for things like menu items or toolbar actions - anything
### Import aliases
In general, all imports for app code should come via `@app` because it allows for other builds of the app to override behaviour if necessary.
In general, all imports for app code should come via `@editor` because it allows for other builds of the app to override behaviour if necessary.
The only time that it is beneficial to import via a specific folder (e.g. `@core`) is when you want to reduce duplication **in the file you are overriding**. For example:
```ts
+2 -2
View File
@@ -16,7 +16,7 @@ For desktop app development, see the [Tauri](#tauri) section below.
## Layout
`frontend/` is a workspace containing one or more apps. Today it holds the
PDF editor under `frontend/editor/`; new apps (the developer portal, etc.)
PDF editor under `frontend/`; new apps (the developer portal, etc.)
will sit alongside it as siblings. Shared tooling — `package.json`, `node_modules`,
`.storybook/`, ESLint, Prettier — lives at `frontend/` so every app installs
once and lints with the same config.
@@ -24,7 +24,7 @@ once and lints with the same config.
## Environment Variables
The editor's environment variables live in committed `.env` files at
`frontend/editor/`:
`frontend/`:
- `.env` — used by all builds (core, proprietary, and as the base for desktop/SaaS)
- `.env.desktop` — additional vars loaded in desktop (Tauri) mode
-24
View File
@@ -1,24 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": [
"../../src/cloud/*",
"../../src/proprietary/*",
"../../src/core/*"
],
"@portal/*": ["../../src/portal/*"],
"@cloud/*": ["../../src/cloud/*"],
"@proprietary/*": ["../../src/proprietary/*"],
"@core/*": ["../../src/core/*"]
}
},
"include": [
"../global.d.ts",
"../*.js",
"../*.ts",
"../*.tsx",
"../core/setupTests.ts",
"."
]
}
@@ -1,24 +0,0 @@
export {
FormFillProvider,
useFormFill,
useFieldValue,
useAllFormValues,
} from "@app/tools/formFill/FormFillContext";
export { FormFieldSidebar } from "@app/tools/formFill/FormFieldSidebar";
export { FormFieldOverlay } from "@app/tools/formFill/FormFieldOverlay";
export { FormSaveBar } from "@app/tools/formFill/FormSaveBar";
export { default as FormFill } from "@app/tools/formFill/FormFill";
export { FieldInput } from "@app/tools/formFill/FieldInput";
export {
FIELD_TYPE_ICON,
FIELD_TYPE_COLOR,
} from "@app/tools/formFill/fieldMeta";
export type {
FormField,
FormFieldType,
FormFillState,
WidgetCoordinates,
} from "@app/tools/formFill/types";
export type { IFormDataProvider } from "@app/tools/formFill/providers/types";
export { PdfiumFormProvider } from "@app/tools/formFill/providers/PdfiumFormProvider";
export { PdfBoxFormProvider } from "@app/tools/formFill/providers/PdfBoxFormProvider";
@@ -1,3 +0,0 @@
export type { IFormDataProvider } from "@app/tools/formFill/providers/types";
export { PdfiumFormProvider } from "@app/tools/formFill/providers/PdfiumFormProvider";
export { PdfBoxFormProvider } from "@app/tools/formFill/providers/PdfBoxFormProvider";
-9
View File
@@ -1,9 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": ["../../src/core/*"]
}
},
"include": ["../global.d.ts", "../*.js", "../*.ts", "../*.tsx", "."]
}
-54
View File
@@ -1,54 +0,0 @@
export * from "@app/ui/Button";
export * from "@app/ui/ActionIcon";
export * from "@app/ui/Logo";
export * from "@app/ui/FilePicker";
export * from "@app/ui/SegmentedControl";
export * from "@app/ui/StatusBadge";
export * from "@app/ui/MethodBadge";
export * from "@app/ui/ToggleSwitch";
export * from "@app/ui/ProgressBar";
export * from "@app/ui/MetricCard";
export * from "@app/ui/NavItem";
export * from "@app/ui/NavSurface";
export * from "@app/ui/PanelHeader";
export * from "@app/ui/CodeBlock";
export * from "@app/ui/SectionDivider";
export * from "@app/ui/CarouselDots";
export * from "@app/ui/Card";
export * from "@app/ui/Modal";
export * from "@app/ui/SettingsShell";
// Layout
export * from "@app/ui/Stack";
export * from "@app/ui/Inline";
export * from "@app/ui/MetricStrip";
export * from "@app/ui/StatTile";
// Feedback
export * from "@app/ui/Spinner";
export * from "@app/ui/Skeleton";
export * from "@app/ui/Avatar";
export * from "@app/ui/Chip";
export * from "@app/ui/EmptyState";
export * from "@app/ui/Banner";
export * from "@app/ui/Toast";
// Compound
export * from "@app/ui/Collapsible";
export * from "@app/ui/Tabs";
export * from "@app/ui/Dropdown";
export * from "@app/ui/Drawer";
export * from "@app/ui/Table";
// Forms
export * from "@app/ui/FormField";
export * from "@app/ui/Input";
export * from "@app/ui/Select";
export * from "@app/ui/Checkbox";
export * from "@app/ui/Radio";
export * from "@app/ui/Slider";
// Mantine-backed form elements (SUI-styled)
export * from "@app/ui/MultiSelect";
export * from "@app/ui/NumberInput";
export * from "@app/ui/ColorInput";
-24
View File
@@ -1,24 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": [
"../../src/desktop/*",
"../../src/cloud/*",
"../../src/proprietary/*",
"../../src/core/*"
],
"@cloud/*": ["../../src/cloud/*"],
"@proprietary/*": ["../../src/proprietary/*"],
"@core/*": ["../../src/core/*"]
}
},
"include": [
"../global.d.ts",
"../*.js",
"../*.ts",
"../*.tsx",
"../core/setupTests.ts",
"."
]
}
@@ -1,40 +0,0 @@
import { assistantHandlers } from "@portal/mocks/handlers/assistant";
import { authHandlers } from "@portal/mocks/handlers/auth";
import { notificationsHandlers } from "@portal/mocks/handlers/notifications";
import { searchHandlers } from "@portal/mocks/handlers/search";
import { pipelinesHandlers } from "@portal/mocks/handlers/pipelines";
import { sourcesHandlers } from "@portal/mocks/handlers/sources";
import { infrastructureHandlers } from "@portal/mocks/handlers/infrastructure";
import { procurementSaasHandlers } from "@portal/mocks/handlers/procurementSaas";
import { docsHandlers } from "@portal/mocks/handlers/docs";
import { usersHandlers } from "@portal/mocks/handlers/users";
import { teamSaasHandlers } from "@portal/mocks/handlers/teamSaas";
import { policiesHandlers } from "@portal/mocks/handlers/policies";
import { classificationHandlers } from "@portal/mocks/handlers/classification";
import { documentsHandlers } from "@portal/mocks/handlers/documents";
import { editorDeployHandlers } from "@portal/mocks/handlers/editorDeploy";
import { linkHandlers } from "@portal/mocks/handlers/link";
import { integrationsHandlers } from "@portal/mocks/handlers/integrations";
export const handlers = [
...authHandlers,
...notificationsHandlers,
...assistantHandlers,
...searchHandlers,
...pipelinesHandlers,
...sourcesHandlers,
...infrastructureHandlers,
...docsHandlers,
...procurementSaasHandlers,
...usersHandlers,
...teamSaasHandlers,
...policiesHandlers,
...classificationHandlers,
...documentsHandlers,
...editorDeployHandlers,
...linkHandlers,
...integrationsHandlers,
];
export { resetNotificationsStore } from "@portal/mocks/handlers/notifications";
export { resetTeamSaasStore } from "@portal/mocks/handlers/teamSaas";
-17
View File
@@ -1,17 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": [
"../../src/cloud/*",
"../../src/proprietary/*",
"../../src/core/*"
],
"@portal/*": ["../../src/portal/*"],
"@cloud/*": ["../../src/cloud/*"],
"@proprietary/*": ["../../src/proprietary/*"],
"@core/*": ["../../src/core/*"]
}
},
"include": ["../global.d.ts", "."]
}
@@ -1,8 +0,0 @@
export { default as StripeCheckout } from "@app/components/shared/stripeCheckout/StripeCheckout";
export type {
StripeCheckoutProps,
CheckoutStage,
CheckoutState,
PollingStatus,
SavingsCalculation,
} from "@app/components/shared/stripeCheckout/types/checkout";
@@ -1,5 +0,0 @@
import { useServerExperienceContext } from "@app/contexts/ServerExperienceContext";
export function useServerExperience() {
return useServerExperienceContext();
}
@@ -1,18 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": ["../../src/proprietary/*", "../../src/core/*"],
"@portal/*": ["../../src/portal/*"],
"@core/*": ["../../src/core/*"]
}
},
"include": [
"../global.d.ts",
"../*.js",
"../*.ts",
"../*.tsx",
"../core/setupTests.ts",
"."
]
}
@@ -1,22 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": [
"../../src/prototypes/*",
"../../src/proprietary/*",
"../../src/core/*"
],
"@proprietary/*": ["../../src/proprietary/*"],
"@core/*": ["../../src/core/*"]
}
},
"include": [
"../global.d.ts",
"../*.js",
"../*.ts",
"../*.tsx",
"../core/setupTests.ts",
"."
]
}
-51
View File
@@ -1,51 +0,0 @@
Stirling PDF User License
Copyright (c) 2025 Stirling PDF Inc.
License Scope & Usage Rights
Production use of the Stirling PDF Software is only permitted with a valid Stirling PDF User License.
For purposes of this license, “the Software” refers to the Stirling PDF application and any associated documentation files
provided by Stirling PDF Inc. You or your organization may not use the Software in production, at scale, or for business-critical
processes unless you have agreed to, and remain in compliance with, the Stirling PDF Subscription Terms of Service
(https://www.stirlingpdf.com/terms) or another valid agreement with Stirling PDF, and hold an active User License subscription
covering the appropriate number of licensed users.
Trial and Minimal Use
You may use the Software without a paid subscription for the sole purposes of internal trial, evaluation, or minimal use, provided that:
* Use is limited to the capabilities and restrictions defined by the Software itself;
* You do not copy, distribute, sublicense, reverse-engineer, or use the Software in client-facing or commercial contexts.
Continued use beyond this scope requires a valid Stirling PDF User License.
Modifications and Derivative Works
You may modify the Software only for development or internal testing purposes. Any such modifications or derivative works:
* May not be deployed in production environments without a valid User License;
* May not be distributed or sublicensed;
* Remain the intellectual property of Stirling PDF and/or its licensors;
* May only be used, copied, or exploited in accordance with the terms of a valid Stirling PDF User License subscription.
Prohibited Actions
Unless explicitly permitted by a paid license or separate agreement, you may not:
* Use the Software in production environments;
* Copy, merge, distribute, sublicense, or sell the Software;
* Remove or alter any licensing or copyright notices;
* Circumvent access restrictions or licensing requirements.
Third-Party Components
The Stirling PDF Software may include components subject to separate open source licenses. Such components remain governed by
their original license terms as provided by their respective owners.
Disclaimer
THE SOFTWARE IS PROVIDED “AS IS,” WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR NON-INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-26
View File
@@ -1,26 +0,0 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": [
"../../src/saas/*",
"../../src/cloud/*",
"../../src/proprietary/*",
"../../src/core/*"
],
"@portal/*": ["../../src/portal-saas/*", "../../src/portal/*"],
"@portal-proprietary/*": ["../../src/portal/*"],
"@cloud/*": ["../../src/cloud/*"],
"@proprietary/*": ["../../src/proprietary/*"],
"@core/*": ["../../src/core/*"]
}
},
"include": [
"../global.d.ts",
"../*.js",
"../*.ts",
"../*.tsx",
".",
"../portal-saas"
]
}
-9
View File
@@ -1,9 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": ["./src/core/*"]
}
},
"exclude": ["src/proprietary", "src/desktop"]
}
@@ -1,24 +0,0 @@
{
"extends": "./tsconfig.proprietary.vite.json",
"compilerOptions": {
"paths": {
"@app/*": [
"./src/desktop/*",
"./src/cloud/*",
"./src/proprietary/*",
"./src/core/*"
],
"@cloud/*": ["./src/cloud/*"],
"@proprietary/*": ["./src/proprietary/*"],
"@core/*": ["./src/core/*"]
}
},
"exclude": [
"src/core/**/*.test.ts*",
"src/core/**/*.spec.ts*",
"src/proprietary/**/*.test.ts*",
"src/proprietary/**/*.spec.ts*",
"src/cloud/**/*.test.ts*",
"src/cloud/**/*.spec.ts*"
]
}
-13
View File
@@ -1,13 +0,0 @@
{
"extends": "./tsconfig.json",
"comment": "Path resolution for the portal's vitest project (referenced by vitest.config.ts). Broad include ('src', via the base) so vite-tsconfig-paths rewrites @app/* in every editor/src file the portal tests pull in (e.g. core/ui), not just the portal layer. The portal's own typecheck uses src/portal/tsconfig.json.",
"compilerOptions": {
"paths": {
"@app/*": ["./src/cloud/*", "./src/proprietary/*", "./src/core/*"],
"@portal/*": ["./src/portal/*"],
"@cloud/*": ["./src/cloud/*"],
"@proprietary/*": ["./src/proprietary/*"],
"@core/*": ["./src/core/*"]
}
}
}
@@ -1,12 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": ["./src/proprietary/*", "./src/core/*"],
"@portal/*": ["./src/portal/*"],
"@proprietary/*": ["./src/proprietary/*"],
"@core/*": ["./src/core/*"]
}
},
"exclude": ["src/core/**/*.test.ts*", "src/core/**/*.spec.ts*", "src/desktop"]
}
@@ -1,18 +0,0 @@
{
"extends": "./tsconfig.proprietary.vite.json",
"compilerOptions": {
"paths": {
"@app/*": ["./src/prototypes/*", "./src/proprietary/*", "./src/core/*"],
"@proprietary/*": ["./src/proprietary/*"],
"@core/*": ["./src/core/*"]
}
},
"exclude": [
"src/core/**/*.test.ts*",
"src/core/**/*.spec.ts*",
"src/proprietary/**/*.test.ts*",
"src/proprietary/**/*.spec.ts*",
"src/desktop",
"src/saas"
]
}
-27
View File
@@ -1,27 +0,0 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"paths": {
"@app/*": [
"./src/saas/*",
"./src/cloud/*",
"./src/proprietary/*",
"./src/core/*"
],
"@portal/*": ["./src/portal-saas/*", "./src/portal/*"],
"@portal-proprietary/*": ["./src/portal/*"],
"@cloud/*": ["./src/cloud/*"],
"@proprietary/*": ["./src/proprietary/*"],
"@core/*": ["./src/core/*"]
}
},
"exclude": [
"src/core/**/*.test.ts*",
"src/core/**/*.spec.ts*",
"src/proprietary/**/*.test.ts*",
"src/proprietary/**/*.spec.ts*",
"src/cloud/**/*.test.ts*",
"src/cloud/**/*.spec.ts*",
"src/desktop"
]
}
-151
View File
@@ -1,151 +0,0 @@
import { defineConfig } from "vitest/config";
import react from "@vitejs/plugin-react-swc";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
test: {
globals: true,
environment: "jsdom",
setupFiles: ["./src/core/setupTests.ts"],
css: false,
exclude: [
"node_modules/",
"src/**/*.spec.ts", // Exclude Playwright E2E tests
"src/tests/test-fixtures/**",
],
testTimeout: 10000,
hookTimeout: 10000,
coverage: {
reporter: ["text", "json", "html"],
exclude: [
"node_modules/",
"src/core/setupTests.ts",
"src/proprietary/setupTests.ts",
"src/saas/setupTests.ts",
"**/*.d.ts",
"src/tests/test-fixtures/**",
"src/**/*.spec.ts",
],
},
projects: [
{
test: {
name: "core",
include: ["src/core/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/core/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.core.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "portal",
include: ["src/portal/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/portal/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
// Broad project so @app/@portal resolve in every editor file the
// portal tests pull in (core/ui, core, ...).
projects: ["./tsconfig.portal.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "proprietary",
include: ["src/proprietary/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/core/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.proprietary.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "desktop",
include: ["src/desktop/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/core/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.desktop.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "saas",
// src/saas = editor-saas layer; src/portal-saas = the portal's saas
// overrides (sibling to src/portal). Both build under the saas flavor,
// so both resolve @portal via the saas cascade (tsconfig.saas.vite.json).
include: [
"src/saas/**/*.test.{ts,tsx}",
"src/portal-saas/**/*.test.{ts,tsx}",
],
environment: "jsdom",
globals: true,
setupFiles: ["./src/saas/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.saas.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
{
test: {
name: "prototypes",
include: ["src/prototypes/**/*.test.{ts,tsx}"],
environment: "jsdom",
globals: true,
setupFiles: ["./src/core/setupTests.ts"],
},
plugins: [
react(),
tsconfigPaths({
projects: ["./tsconfig.prototypes.vite.json"],
}),
],
esbuild: {
target: "es2020",
},
},
],
},
esbuild: {
target: "es2020",
},
});
+72 -72
View File
@@ -6,15 +6,15 @@ import { defineConfig } from "eslint/config";
import tseslint from "typescript-eslint";
const srcGlobs = [
// The portal layers live under editor/src/portal (base) and
// editor/src/portal-saas (saas override), so editor/src/** covers them.
"editor/src/**/*.{js,mjs,jsx,ts,tsx}",
// The portal layers live under src/processor/proprietary (base) and
// src/processor/saas (saas override), so src/** covers them.
"src/**/*.{js,mjs,jsx,ts,tsx}",
];
const nodeGlobs = [
"scripts/**/*.{js,ts,mjs,mts}",
"editor/scripts/**/*.{js,ts,mjs,mts}",
"scripts/**/*.{js,ts,mjs,mts}",
// Covers editor/vite.config.ts and editor/vitest.config.ts.
"editor/*.config.{js,ts,mjs}",
"*.config.{js,ts,mjs}",
"*.config.{js,ts,mjs}",
".storybook/*.{js,ts,mjs,mts,tsx}",
];
@@ -23,7 +23,7 @@ const baseRestrictedImportPatterns = [
{
regex: "^\\.",
message:
"Use a workspace alias (@app/* for editor, @portal/* for portal) instead of relative imports.",
"Use a workspace alias (@editor/* for editor, @processor/* for portal) instead of relative imports.",
},
{
regex: "^src/",
@@ -31,26 +31,26 @@ const baseRestrictedImportPatterns = [
},
];
// Button/SegmentedControl/Chip must come from the shared DS (@app/ui), not Mantine.
// If no variant fits, extend @app/ui — that layer (editor/src/core/ui) is exempt below.
// Button/SegmentedControl/Chip must come from the shared DS (@editor/ui), not Mantine.
// If no variant fits, extend @editor/ui — that layer (src/editor/core/ui) is exempt below.
const mantineComponentImportRestrictions = [
{
selector:
"ImportDeclaration[source.value='@mantine/core'] > ImportSpecifier[imported.name=/^(Button|ActionIcon|UnstyledButton|CloseButton|FileButton)$/]",
message:
'Use the shared Button (@app/ui/Button) instead of the Mantine button family. variant=primary|secondary|tertiary, accent=default|neutral|brand|ai|premium|danger|success|warning; an icon-only button is `<Button leftSection={…} aria-label="…" />`. If no variant fits, extend the shared Button rather than importing Mantine.',
'Use the shared Button (@editor/ui/Button) instead of the Mantine button family. variant=primary|secondary|tertiary, accent=default|neutral|brand|ai|premium|danger|success|warning; an icon-only button is `<Button leftSection={…} aria-label="…" />`. If no variant fits, extend the shared Button rather than importing Mantine.',
},
{
selector:
"ImportDeclaration[source.value='@mantine/core'] > ImportSpecifier[imported.name='SegmentedControl']",
message:
"Use the shared SegmentedControl (@app/ui/SegmentedControl) instead of Mantine's.",
"Use the shared SegmentedControl (@editor/ui/SegmentedControl) instead of Mantine's.",
},
{
selector:
"ImportDeclaration[source.value='@mantine/core'] > ImportSpecifier[imported.name=/^(Chip|Pill)$/]",
message:
"Use the shared Chip (@app/ui/Chip) instead of Mantine's Chip/Pill.",
"Use the shared Chip (@editor/ui/Chip) instead of Mantine's Chip/Pill.",
},
];
@@ -59,7 +59,7 @@ const mantineComponentImportRestrictions = [
const rawButtonSyntaxRestriction = {
selector: "JSXOpeningElement[name.name='button']",
message:
"Use the shared Button (@app/ui/Button) instead of a raw <button> element. If no variant fits, extend the shared Button.",
"Use the shared Button (@editor/ui/Button) instead of a raw <button> element. If no variant fits, extend the shared Button.",
};
const sharedComponentSyntaxRestrictions = [
@@ -77,11 +77,11 @@ export default defineConfig(
"playwright-report",
"storybook-static",
"test-results",
"editor/dist",
"editor/public",
"editor/src-tauri",
"editor/playwright-report",
"editor/test-results",
"dist",
"public",
"src-tauri",
"playwright-report",
"test-results",
],
},
eslint.configs.recommended,
@@ -117,10 +117,10 @@ export default defineConfig(
},
},
// Desktop-only packages must not be imported from core or proprietary code.
// Use the stub/shadow pattern instead: define a stub in editor/src/core/ and override in editor/src/desktop/.
// Use the stub/shadow pattern instead: define a stub in src/editor/core/ and override in src/editor/desktop/.
{
files: srcGlobs,
ignores: ["editor/src/desktop/**"],
ignores: ["src/editor/desktop/**"],
rules: {
"no-restricted-imports": [
"error",
@@ -130,7 +130,7 @@ export default defineConfig(
{
regex: "^@tauri-apps/",
message:
"Tauri APIs are desktop-only. Review frontend/editor/DeveloperGuide.md for structure advice.",
"Tauri APIs are desktop-only. Review frontend/DeveloperGuide.md for structure advice.",
},
],
},
@@ -141,10 +141,10 @@ export default defineConfig(
// saas and desktop leaves, so it must stay platform-portable. It must not
// reach platform-specific things directly (Supabase, Tauri, raw fetch,
// window.location, web storage, or import.meta.env.VITE_*) — those arrive via
// @app/* seams (services/apiClient, auth/session, platform/openExternal, ...)
// @editor/* seams (services/apiClient, auth/session, platform/openExternal, ...)
// that each leaf provides for its own platform.
{
files: ["editor/src/cloud/**/*.{js,mjs,jsx,ts,tsx}"],
files: ["src/editor/cloud/**/*.{js,mjs,jsx,ts,tsx}"],
rules: {
"no-restricted-imports": [
"error",
@@ -154,12 +154,12 @@ export default defineConfig(
{
regex: "^@supabase/",
message:
"cloud/ must stay platform-portable. Reach Supabase via an @app/* seam (e.g. @app/auth/supabase, @app/auth/session) provided per-platform in saas/ and desktop/.",
"cloud/ must stay platform-portable. Reach Supabase via an @editor/* seam (e.g. @editor/auth/supabase, @editor/auth/session) provided per-platform in saas/ and desktop/.",
},
{
regex: "^@tauri-apps/",
message:
"cloud/ must stay platform-portable. Tauri APIs are desktop-only — reach native features via an @app/* seam (e.g. @app/platform/openExternal).",
"cloud/ must stay platform-portable. Tauri APIs are desktop-only — reach native features via an @editor/* seam (e.g. @editor/platform/openExternal).",
},
],
},
@@ -169,17 +169,17 @@ export default defineConfig(
{
name: "fetch",
message:
"cloud/ must not call raw fetch — use @app/services/apiClient so each platform supplies its own transport.",
"cloud/ must not call raw fetch — use @editor/services/apiClient so each platform supplies its own transport.",
},
{
name: "localStorage",
message:
"cloud/ must not touch localStorage — use an @app/* storage seam so desktop/web can differ.",
"cloud/ must not touch localStorage — use an @editor/* storage seam so desktop/web can differ.",
},
{
name: "sessionStorage",
message:
"cloud/ must not touch sessionStorage — use an @app/* storage seam so desktop/web can differ.",
"cloud/ must not touch sessionStorage — use an @editor/* storage seam so desktop/web can differ.",
},
],
"no-restricted-syntax": [
@@ -189,26 +189,26 @@ export default defineConfig(
selector:
"MemberExpression[object.name='window'][property.name='location']",
message:
"cloud/ must not touch window.location — use an @app/* seam (e.g. @app/platform/openExternal) so desktop/web can differ.",
"cloud/ must not touch window.location — use an @editor/* seam (e.g. @editor/platform/openExternal) so desktop/web can differ.",
},
{
selector:
"MemberExpression[property.name='env'][object.type='MetaProperty'][object.meta.name='import'][object.property.name='meta']",
message:
"cloud/ must not read import.meta.env — use @app/constants/app / @app/platform seams so config is supplied per-platform.",
"cloud/ must not read import.meta.env — use @editor/constants/app / @editor/platform seams so config is supplied per-platform.",
},
],
},
},
// app code must use shared DS Button/SegmentedControl/Chip; cloud/ covered above.
{
files: ["editor/src/**/*.{js,mjs,jsx,ts,tsx}"],
files: ["src/**/*.{js,mjs,jsx,ts,tsx}"],
ignores: [
"editor/src/cloud/**/*.{js,mjs,jsx,ts,tsx}", // covered by cloud/ block above
"editor/src/core/ui/**/*.{js,mjs,jsx,ts,tsx}", // the shared DS itself — wraps Mantine/raw elements
"src/editor/cloud/**/*.{js,mjs,jsx,ts,tsx}", // covered by cloud/ block above
"src/editor/core/ui/**/*.{js,mjs,jsx,ts,tsx}", // the shared DS itself — wraps Mantine/raw elements
"**/*.stories.{js,mjs,jsx,ts,tsx}", // stories may demo Mantine directly
"**/*.test.{js,mjs,jsx,ts,tsx}", // tests may use raw elements as fixtures
"editor/src/prototypes/**/*.{js,mjs,jsx,ts,tsx}", // not shipped
"src/editor/prototypes/**/*.{js,mjs,jsx,ts,tsx}", // not shipped
],
rules: {
"no-restricted-syntax": ["error", ...sharedComponentSyntaxRestrictions],
@@ -219,9 +219,9 @@ export default defineConfig(
// Do NOT add ordinary buttons here.
{
files: [
"editor/src/core/components/shared/FileSelectorPicker.tsx",
"editor/src/core/components/filesPage/FileManagerView.tsx",
"editor/src/core/pages/HomePage.tsx",
"src/editor/core/components/shared/FileSelectorPicker.tsx",
"src/editor/core/components/filesPage/FileManagerView.tsx",
"src/editor/core/pages/HomePage.tsx",
],
rules: {
"no-restricted-syntax": "off",
@@ -234,7 +234,7 @@ export default defineConfig(
// Button in a follow-up PR. Do NOT add other folders to this block.
{
files: [
"editor/src/portal/components/procurement/**/*.{js,mjs,jsx,ts,tsx}",
"src/processor/proprietary/components/procurement/**/*.{js,mjs,jsx,ts,tsx}",
],
rules: {
"no-restricted-syntax": ["error", ...mantineComponentImportRestrictions],
@@ -246,7 +246,7 @@ export default defineConfig(
// without heavy overrides. Exempt ONLY the raw-<button> rule — the Mantine
// import bans stay in force — and migrate these in a follow-up PR.
{
files: ["editor/src/portal/components/users/UsersDirectory.tsx"],
files: ["src/processor/proprietary/components/users/UsersDirectory.tsx"],
rules: {
"no-restricted-syntax": ["error", ...mantineComponentImportRestrictions],
},
@@ -258,7 +258,7 @@ export default defineConfig(
// can't represent. Exempt ONLY the raw-<button> rule; the Mantine import bans
// stay. Migrate these alongside the procurement buttons.
{
files: ["editor/src/portal/components/DownloadEditorModal.tsx"],
files: ["src/processor/proprietary/components/DownloadEditorModal.tsx"],
rules: {
"no-restricted-syntax": ["error", ...mantineComponentImportRestrictions],
},
@@ -269,10 +269,10 @@ export default defineConfig(
// Raw-<button> rule only; migrate later.
{
files: [
"editor/src/portal/components/sources/ConnectionTypePicker.tsx",
"editor/src/portal/components/sources/SourceModal.tsx",
"editor/src/portal/components/policies/PolicyExternalApiConfig.tsx",
"editor/src/portal/views/Integrations.tsx",
"src/processor/proprietary/components/sources/ConnectionTypePicker.tsx",
"src/processor/proprietary/components/sources/SourceModal.tsx",
"src/processor/proprietary/components/policies/PolicyExternalApiConfig.tsx",
"src/processor/proprietary/views/Integrations.tsx",
],
rules: {
"no-restricted-syntax": ["error", ...mantineComponentImportRestrictions],
@@ -282,35 +282,35 @@ export default defineConfig(
{
files: srcGlobs,
ignores: [
"editor/src/core/components/annotation/**/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/pageEditor/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/pageEditor/commands/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/pageEditor/hooks/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/shared/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/shared/config/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/shared/config/configSections/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/shared/pageEditor/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/addStamp/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/automate/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/bookletImposition/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/certSign/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/pdfTextEditor/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/tools/shared/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/components/viewer/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/contexts/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/contexts/file/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/contexts/viewer/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/hooks/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/hooks/signing/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/hooks/tools/adjustContrast/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/hooks/tools/convert/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/hooks/tools/removePassword/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/hooks/tools/shared/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/services/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/tools/annotate/useAnnotationSelection.ts",
"editor/src/core/types/*.{js,mjs,jsx,ts,tsx}",
"editor/src/core/utils/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/annotation/**/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/pageEditor/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/pageEditor/commands/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/pageEditor/hooks/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/shared/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/shared/config/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/shared/config/configSections/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/shared/pageEditor/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/tools/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/tools/addStamp/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/tools/automate/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/tools/bookletImposition/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/tools/certSign/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/tools/pdfTextEditor/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/tools/shared/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/components/viewer/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/contexts/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/contexts/file/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/contexts/viewer/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/hooks/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/hooks/signing/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/hooks/tools/adjustContrast/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/hooks/tools/convert/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/hooks/tools/removePassword/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/hooks/tools/shared/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/services/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/tools/annotate/useAnnotationSelection.ts",
"src/editor/core/types/*.{js,mjs,jsx,ts,tsx}",
"src/editor/core/utils/*.{js,mjs,jsx,ts,tsx}",
],
rules: {
"@typescript-eslint/no-explicit-any": "error",
+3 -3
View File
@@ -83,7 +83,7 @@
"web-vitals": "^5.1.0"
},
"scripts": {
"docs:sync": "tsx editor/scripts/sync-portal-docs.mts",
"docs:sync": "tsx scripts/sync-portal-docs.mts",
"update:minor": "node scripts/update-minor.js",
"update:major": "npx npm-check-updates -u && npm install",
"update:interactive": "npx npm-check-updates -i",
@@ -93,7 +93,7 @@
"tauri:serve-dev-update": "bash scripts/dev-update-test/serve-update.sh",
"tauri:test-update-e2e": "bash scripts/dev-update-test/test-update-e2e.sh",
"tauri:test-update-e2e:install": "bash scripts/dev-update-test/test-update-e2e.sh --install",
"tauri:dev-with-update": "cd editor && npx tsx scripts/setup-env.mts --desktop && node scripts/generate-icons.js && npx tauri dev --config src-tauri/tauri.conf.dev-update.json"
"tauri:dev-with-update": "npx tsx scripts/setup-env.mts --desktop && node scripts/generate-icons.js && npx tauri dev --config src-tauri/tauri.conf.dev-update.json"
},
"browserslist": {
"production": [
@@ -182,7 +182,7 @@
},
"msw": {
"workerDirectory": [
"editor/public"
"public"
]
}
}
@@ -6,10 +6,10 @@ import { defineConfig, devices } from "@playwright/test";
* The suite is split into two projects:
* - `stubbed` - backend-free specs that mock `/api/v1/*` via `page.route()`.
* Safe to run in CI without the Spring Boot server. Lives in
* `src/core/tests/stubbed/**`.
* `src/editor/core/tests/stubbed/**`.
* - `live` - specs that require a real backend on `localhost:8080`
* (auth, admin mutation, real tool round-trips). Lives in
* `src/core/tests/live/**`.
* `src/editor/core/tests/live/**`.
*
* Run one:
* npx playwright test --project=stubbed
@@ -23,7 +23,7 @@ const chromiumViewport = {
};
export default defineConfig({
testDir: "./src/core/tests",
testDir: "./src/editor/core/tests",
testMatch: "**/*.spec.ts",
fullyParallel: true,
@@ -58,7 +58,7 @@ export default defineConfig({
// Stubbed - no backend required, chromium-only for CI speed
{
name: "stubbed",
testDir: "./src/core/tests/stubbed",
testDir: "./src/editor/core/tests/stubbed",
use: chromiumViewport,
},
@@ -67,7 +67,7 @@ export default defineConfig({
// backend. The live project depends on it.
{
name: "live-setup",
testDir: "./src/core/tests/live-setup",
testDir: "./src/editor/core/tests/live-setup",
testMatch: /.*\.setup\.ts$/,
use: chromiumViewport,
},
@@ -75,7 +75,7 @@ export default defineConfig({
// Live backend - auth + admin-mutation + real-tool smoke
{
name: "live",
testDir: "./src/core/tests/live",
testDir: "./src/editor/core/tests/live",
use: chromiumViewport,
dependencies: ["live-setup"],
},
@@ -86,7 +86,7 @@ export default defineConfig({
// because the OAuth/SAML callback URLs are registered against 8080.
{
name: "enterprise",
testDir: "./src/core/tests/enterprise",
testDir: "./src/editor/core/tests/enterprise",
use: {
...chromiumViewport,
baseURL: "http://localhost:8080",
@@ -96,12 +96,12 @@ export default defineConfig({
// Cross-browser coverage for the stubbed suite (opt-in locally)
{
name: "stubbed-firefox",
testDir: "./src/core/tests/stubbed",
testDir: "./src/editor/core/tests/stubbed",
use: { ...devices["Desktop Firefox"] },
},
{
name: "stubbed-webkit",
testDir: "./src/core/tests/stubbed",
testDir: "./src/editor/core/tests/stubbed",
use: { ...devices["Desktop Safari"] },
},
],

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

Some files were not shown because too many files have changed in this diff Show More