Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
6fab845
Add native Windows and Linux desktop builds
max06bayer Jul 18, 2026
8ef8e2e
Bundle Linux LibreOffice symlink targets
max06bayer Jul 18, 2026
5c35136
Generate the Tauri desktop shell during staging
max06bayer Jul 18, 2026
df2e639
Normalize legacy Windows pdf2htmlEX arguments
max06bayer Jul 18, 2026
16441ac
Preserve bundled Linux runtime binaries
max06bayer Jul 18, 2026
52e469d
Use the Windows LibreOffice console launcher
max06bayer Jul 18, 2026
60127e6
Expose the bundled JVM to Linux packaging
max06bayer Jul 18, 2026
64c16e4
Fix AppImage service startup on rolling Linux
max06bayer Jul 18, 2026
34e4b1b
Use compatible WebKit rendering on Linux
max06bayer Jul 18, 2026
09e7794
Set WebKit compatibility flags in Linux launcher
max06bayer Jul 18, 2026
370ca9f
Replace Linux shell launcher with native executable
max06bayer Jul 18, 2026
0628837
Build native Arch and CachyOS package
max06bayer Jul 18, 2026
55d1ceb
Upload native Arch package artifact
max06bayer Jul 18, 2026
565ee40
Fix Linux editor viewport and Windows startup
max06bayer Jul 18, 2026
e45dff9
Retry verified Windows runtime downloads
max06bayer Jul 18, 2026
da2a44a
Test packaged Windows document tools
max06bayer Jul 18, 2026
3831b7f
Bundle Windows document tool data
max06bayer Jul 18, 2026
28187b6
Normalize packaged native tool paths
max06bayer Jul 18, 2026
9101205
Polish native PDF desktop integration
max06bayer Jul 19, 2026
2958dbb
Gate macOS file-open events
max06bayer Jul 19, 2026
b02b593
Capture packaged Linux tool logs
max06bayer Jul 19, 2026
3d866f5
Run bundled Linux document tools directly
max06bayer Jul 19, 2026
42b8b0b
Use native Arch document tools
max06bayer Jul 19, 2026
24e474a
Isolate Arch pdf2htmlEX compatibility library
max06bayer Jul 19, 2026
7b7a8e3
Target installed Arch runtime resources
max06bayer Jul 19, 2026
886f965
Resolve Arch pdf2htmlEX compatibility closure
max06bayer Jul 19, 2026
26adcdd
Fix Linux window drag and OCR rendering
max06bayer Jul 19, 2026
6a0ed5c
Use native Linux window drag and OCR wrapper
max06bayer Jul 19, 2026
86c642b
Fix Linux native drag event and OCR reload bytes
max06bayer Jul 19, 2026
610141b
Fix authorized Linux drag regions and verify OCR pixels
max06bayer Jul 19, 2026
0bb17f3
Link download button to latest release
max06bayer Jul 20, 2026
4843100
landing page update
max06bayer Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
300 changes: 300 additions & 0 deletions .github/workflows/desktop-native-builds.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
name: Desktop native builds

on:
workflow_dispatch:
pull_request:
paths:
- '.github/workflows/desktop-native-builds.yml'
- 'backend/**'
- 'desktop/**'
- 'package-lock.json'
- 'package.json'
- 'public/**'
- 'scripts/**'
- 'src/**'

permissions:
contents: read

jobs:
windows-x64:
name: Windows x64 offline installer
runs-on: windows-2025
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24.18.0
cache: npm
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: desktop/src-tauri -> target
- name: Install JavaScript dependencies
shell: pwsh
run: |
npm ci
npm ci --prefix desktop
- name: Install bundled Python conversion packages
shell: pwsh
run: python -m pip install --disable-pip-version-check pillow python-docx lxml openpyxl python-pptx xlsxwriter
- name: Prepare native Windows runtimes
shell: pwsh
run: ./desktop/scripts/prepare-windows-runtimes.ps1 -RuntimeRoot "$pwd/desktop/.native-runtime/windows"
- name: Build and smoke-test Windows package
shell: pwsh
working-directory: desktop
env:
DOCUFLEX_PDF2HTMLEX_RUNTIME: ${{ github.workspace }}/desktop/.native-runtime/windows/pdf2htmlEX
DOCUFLEX_OCR_RUNTIME: ${{ github.workspace }}/desktop/.native-runtime/windows/ocr
DOCUFLEX_OFFICE_RUNTIME: ${{ github.workspace }}/desktop/.native-runtime/windows/office
run: |
$env:DOCUFLEX_PYTHON_RUNTIME = $env:pythonLocation
npm run build
- name: Launch-test packaged Windows runtime
shell: pwsh
working-directory: desktop
run: |
$installer = Resolve-Path 'src-tauri/target/release/bundle/nsis/*.exe'
$install = Start-Process -FilePath $installer -ArgumentList '/S' -PassThru -Wait
if ($install.ExitCode -ne 0) {
throw "NSIS installation failed with exit code $($install.ExitCode)."
}
$entry = Get-ItemProperty 'HKCU:\Software\Microsoft\Windows\CurrentVersion\Uninstall\*' |
Where-Object { $_.DisplayName -eq 'Docuflex' } |
Select-Object -First 1
$candidates = @()
if ($entry.DisplayIcon) {
$candidates += ($entry.DisplayIcon -replace ',\d+$', '').Trim('"')
}
if ($entry.InstallLocation -and (Test-Path $entry.InstallLocation)) {
$candidates += Get-ChildItem $entry.InstallLocation -Filter '*.exe' -File |
Where-Object { $_.Name -notmatch 'uninstall' } |
Select-Object -ExpandProperty FullName
}
$appPath = $candidates | Where-Object { Test-Path $_ } | Select-Object -First 1
if (-not $appPath) {
throw 'Could not locate the Docuflex executable after NSIS installation.'
}
$marker = Join-Path $env:RUNNER_TEMP 'docuflex-windows-page-loaded'
Remove-Item -LiteralPath $marker -Force -ErrorAction SilentlyContinue
$env:DOCUFLEX_PAGE_LOAD_MARKER = $marker
$process = Start-Process -FilePath $appPath -PassThru
try {
foreach ($attempt in 1..60) {
$process.Refresh()
if ($process.HasExited) {
throw "Packaged Windows app exited with code $($process.ExitCode) before startup completed."
}
$backendReady = $false
$frontendReady = $false
try {
$backendReady = (Invoke-WebRequest -UseBasicParsing 'http://127.0.0.1:43128/health' -TimeoutSec 2).StatusCode -eq 200
$frontendReady = (Invoke-WebRequest -UseBasicParsing 'http://127.0.0.1:43127/editor' -TimeoutSec 2).StatusCode -eq 200
} catch {}
if ($backendReady -and $frontendReady -and (Test-Path $marker) -and ((Get-Content $marker -Raw).Trim() -eq 'editor-loaded')) {
Write-Host 'Packaged Windows services and WebView page-load test passed.'
node scripts/smoke-live-document-tools.mjs
if ($LASTEXITCODE -ne 0) { throw 'Packaged document-tool API tests failed.' }
exit 0
}
Start-Sleep -Seconds 1
}
throw 'Packaged Windows app did not become ready within 60 seconds.'
} finally {
if (-not $process.HasExited) {
& taskkill.exe /PID $process.Id /T /F | Out-Null
}
$logRoot = Join-Path $env:LOCALAPPDATA 'com.docuflex.editor/logs'
if (Test-Path $logRoot) {
Get-ChildItem $logRoot -Filter '*.log' | ForEach-Object {
Write-Host "[$($_.Name)]"
Get-Content $_.FullName
}
}
}
- name: Upload Windows installer
uses: actions/upload-artifact@v4
with:
name: Docuflex-Windows-x64
if-no-files-found: error
path: desktop/src-tauri/target/release/bundle/nsis/*.exe

linux-x64:
name: Linux x64 AppImage and Debian package
runs-on: ubuntu-22.04
timeout-minutes: 90
steps:
- uses: actions/checkout@v4
- name: Install Tauri Linux build dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf libfuse2 xvfb
- uses: actions/setup-node@v4
with:
node-version: 24.18.0
cache: npm
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '17'
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
with:
workspaces: desktop/src-tauri -> target
- name: Install JavaScript dependencies
run: |
npm ci
npm ci --prefix desktop
- name: Install bundled Python conversion packages
run: python -m pip install --disable-pip-version-check pillow python-docx lxml openpyxl python-pptx xlsxwriter
- name: Prepare native Linux runtimes
run: |
chmod +x desktop/scripts/prepare-linux-runtimes.sh desktop/runtime/*.sh
desktop/scripts/prepare-linux-runtimes.sh "$GITHUB_WORKSPACE/desktop/.native-runtime/linux"
- name: Build and smoke-test Linux packages
working-directory: desktop
env:
DOCUFLEX_PDF2HTMLEX_RUNTIME: ${{ github.workspace }}/desktop/.native-runtime/linux/pdf2htmlEX
DOCUFLEX_OCR_RUNTIME: ${{ github.workspace }}/desktop/.native-runtime/linux/ocr
DOCUFLEX_OFFICE_RUNTIME: ${{ github.workspace }}/desktop/.native-runtime/linux/office
DOCUFLEX_PYTHON_RUNTIME: ${{ env.pythonLocation }}
run: npm run build
- name: Launch-test packaged AppImage
working-directory: desktop
run: |
appimage=$(find src-tauri/target/release/bundle/appimage -maxdepth 1 -name '*.AppImage' -print -quit)
test -n "$appimage"
chmod +x "$appimage"
rm -f /tmp/docuflex-launcher-env /tmp/docuflex-page-loaded
DOCUFLEX_LAUNCHER_MARKER=/tmp/docuflex-launcher-env \
DOCUFLEX_PAGE_LOAD_MARKER=/tmp/docuflex-page-loaded \
timeout 75s xvfb-run -a "$appimage" > /tmp/docuflex-appimage.log 2>&1 &
launcher_pid=$!
cleanup() {
kill "$launcher_pid" 2>/dev/null || true
wait "$launcher_pid" 2>/dev/null || true
}
trap cleanup EXIT
for attempt in $(seq 1 60); do
if ! kill -0 "$launcher_pid" 2>/dev/null; then
cat /tmp/docuflex-appimage.log
echo "Packaged AppImage exited before its local services became ready." >&2
exit 1
fi
if curl --fail --silent http://127.0.0.1:43128/health >/dev/null \
&& curl --fail --silent http://127.0.0.1:43127/editor >/dev/null \
&& grep --quiet '^dmabuf=1$' /tmp/docuflex-launcher-env 2>/dev/null \
&& grep --quiet '^compositing=1$' /tmp/docuflex-launcher-env 2>/dev/null \
&& grep --quiet '^editor-loaded$' /tmp/docuflex-page-loaded 2>/dev/null; then
echo "Packaged AppImage service and WebKit page-load test passed."
if ! node scripts/smoke-live-document-tools.mjs; then
find "$HOME/.local/share/com.docuflex.editor/logs" -type f -name '*.log' \
-exec sh -c 'echo "[$1]"; cat "$1"' _ {} \; 2>/dev/null || true
exit 1
fi
exit 0
fi
sleep 1
done
cat /tmp/docuflex-appimage.log
echo "Packaged AppImage local services were not ready within 60 seconds." >&2
exit 1
- name: Upload Linux packages
uses: actions/upload-artifact@v4
with:
name: Docuflex-Linux-x64
if-no-files-found: error
path: |
desktop/src-tauri/target/release/bundle/appimage/*.AppImage
desktop/src-tauri/target/release/bundle/deb/*.deb

arch-linux-x64:
name: Arch and CachyOS native x64 package
needs: linux-x64
runs-on: ubuntu-22.04
container: archlinux:latest
timeout-minutes: 90
steps:
- name: Install current Arch build and runtime dependencies
run: |
pacman -Syu --noconfirm
pacman -S --noconfirm --needed \
base-devel binutils curl dbus git gtk3 libayatana-appindicator \
librsvg nodejs npm poppler rust tesseract webkit2gtk-4.1 xorg-server-xvfb
- uses: actions/checkout@v4
- uses: actions/download-artifact@v4
with:
name: Docuflex-Linux-x64
path: desktop/.linux-bundle
- name: Extract offline application resources
run: |
deb=$(find desktop/.linux-bundle -name '*.deb' -print -quit)
test -n "$deb"
mkdir -p desktop/.deb-extracted desktop/.deb-parts
(cd desktop/.deb-parts && ar x "$GITHUB_WORKSPACE/$deb")
data_archive=$(find desktop/.deb-parts -name 'data.tar.*' -print -quit)
test -n "$data_archive"
bsdtar -xf "$data_archive" -C desktop/.deb-extracted
- name: Build current-Arch native package
run: |
chmod +x desktop/scripts/package-arch-linux.sh
desktop/scripts/package-arch-linux.sh \
"$GITHUB_WORKSPACE/desktop/.deb-extracted" \
"$GITHUB_WORKSPACE/desktop/arch-package"
- name: Install and launch-test native package
run: |
package=$(find desktop/arch-package -name '*.pkg.tar.zst' -print -quit)
test -n "$package"
pacman -U --noconfirm "$package"
rm -f /tmp/docuflex-page-loaded
DOCUFLEX_PAGE_LOAD_MARKER=/tmp/docuflex-page-loaded \
timeout 75s xvfb-run -a dbus-run-session -- docuflex-desktop \
> /tmp/docuflex-arch.log 2>&1 &
app_pid=$!
cleanup() {
kill "$app_pid" 2>/dev/null || true
wait "$app_pid" 2>/dev/null || true
}
trap cleanup EXIT
for attempt in $(seq 1 60); do
if ! kill -0 "$app_pid" 2>/dev/null; then
cat /tmp/docuflex-arch.log
echo 'Native Arch package exited before startup completed.' >&2
exit 1
fi
if curl --fail --silent http://127.0.0.1:43128/health >/dev/null \
&& curl --fail --silent http://127.0.0.1:43127/editor >/dev/null \
&& grep --quiet '^editor-loaded$' /tmp/docuflex-page-loaded 2>/dev/null; then
echo 'Native Arch services and WebKit page-load test passed.'
if ! node desktop/scripts/smoke-live-document-tools.mjs; then
find "$HOME/.local/share/com.docuflex.editor/logs" -type f -name '*.log' \
-exec sh -c 'echo "[$1]"; cat "$1"' _ {} \; 2>/dev/null || true
exit 1
fi
exit 0
fi
sleep 1
done
cat /tmp/docuflex-arch.log
echo 'Native Arch package did not become ready within 60 seconds.' >&2
exit 1
- name: Upload Arch and CachyOS package
uses: actions/upload-artifact@v4
with:
name: Docuflex-Arch-CachyOS-x64
if-no-files-found: error
path: |
desktop/arch-package/*.pkg.tar.zst
desktop/arch-package/docuflex-arch-ldd.txt
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ tmp/
.pdf2htmlex/
.ocr/
.document-python/
desktop/.native-runtime/

# Environment files
.env
Expand Down
28 changes: 17 additions & 11 deletions desktop/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,31 @@
# Docuflex for macOS
# Docuflex desktop builds

This directory packages the existing Docuflex `/editor` route as a native Tauri app. The web application source remains unchanged.
The desktop package runs the existing `/editor` application and its Java backend entirely on loopback. Each native package contains Node.js, a reduced Java 17 runtime, Python conversion libraries, OCR tools and language data, `pdf2htmlEX`, and an office converter. Runtime staging fails instead of silently using tools installed on the end user's computer.

The packaged app applies its macOS-only titlebar and sidebar arrangement from `runtime/chrome.js`; those overrides are injected by Tauri and are not included in the website.
## Native packages

## Build
- macOS Apple Silicon: `npm run build` in `desktop/` creates `Docuflex.app`.
- Windows x64: the `Desktop native builds` workflow creates a current-user NSIS installer and embeds the offline WebView2 installer.
- Linux x64: the same workflow creates an AppImage and a Debian package on Ubuntu 22.04.

The current native bundle targets Apple Silicon macOS 26 or newer.
Windows and Linux are deliberately compiled on native GitHub runners. Tauri recommends native CI for installers, and this also ensures that every bundled helper executable matches the target operating system.

Before packaging, `npm run smoke` verifies the bundled Node and Java runtimes, Python imports, all three OCR language files, real OCR output, a real PDF-to-HTML conversion that preserves text, and LibreOffice availability. A failed native dependency prevents the installer artifact from being uploaded.

For a local Apple Silicon build:

```sh
cd desktop
npm install
npm run build
```

The staging step builds the existing SvelteKit application and PDFBox server, downloads and verifies the pinned Node.js LTS runtime, creates a trimmed Java 17 runtime with `jlink`, bundles the native Apple Silicon `pdf2htmlEX` and OCR runtimes, and generates the macOS icon from `public/macos-icon-iOS-Default-1024x1024@1x.png`.

The resulting app is written below `desktop/src-tauri/target/release/bundle/macos/Docuflex.app`.
The app is written to `desktop/src-tauri/target/release/bundle/macos/Docuflex.app`. It targets macOS 26 or newer and uses the icon exported at `public/macos-icon-iOS-Default-1024x1024@1x.png`.

The app runs its frontend and PDFBox services only on `127.0.0.1`. No hosted Docuflex backend is used. Edit Text uses the bundled native `pdf2htmlEX`, Poppler, FontForge, data files, and relocated dylibraries, so conversion works offline without Homebrew, MacPorts, Docker, or a Linux compatibility layer.
The relocatable macOS converter and OCR inputs remain under `vendor/pdf2htmlEX-macos-arm64` and `vendor/ocr-macos-arm64`. Their pinned source versions and checksums are recorded in `runtime/pdf2htmlEX-SOURCE.txt` and the vendor `SOURCE.txt` files; the existing bundling scripts can recreate them from native builds.

The converter bundle is under `vendor/pdf2htmlEX-macos-arm64`. Its pinned source versions and checksums are recorded in `runtime/pdf2htmlEX-SOURCE.txt`; `scripts/bundle-pdf2htmlex.mjs` recreates the relocatable runtime after the native sources have been built.
## Window chrome

Offline OCR uses bundled Apple Silicon builds of Tesseract, `pdftoppm`, and `pdfunite`, plus pinned English, German, and orientation language data. `scripts/bundle-ocr.mjs` recreates that relocatable runtime under `vendor/ocr-macos-arm64`.
- macOS retains the integrated traffic lights and desktop-specific sidebar/header alignment.
- Windows uses the web header with a draggable native window frame and Windows controls on the right; the utility area reserves the corresponding space.
- Linux uses the normal desktop window decoration and the web header unchanged.
4 changes: 3 additions & 1 deletion desktop/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
"type": "module",
"scripts": {
"stage": "node scripts/prepare.mjs",
"build": "npm run stage && tauri build --bundles app",
"smoke": "node scripts/smoke-runtimes.mjs",
"bundle": "node scripts/build.mjs",
"build": "npm run stage && npm run smoke && npm run bundle",
"dev": "npm run stage && tauri dev"
},
"devDependencies": {
Expand Down
Loading
Loading