## What Both sidebars ended in a different bottom section. The editor showed an account row (avatar, name, settings); the processor showed a "Link Stirling account" CTA plus a `Settings` nav item and no identity at all. They are now **one shared `<NavFooter>`** rendering the same rows in both apps, in this order: 1. the link-account CTA (self-hosted, when unlinked) 2. free credits remaining 3. **Open \<the other app\>** 4. the account row — avatar, name, settings It's a **single surface** with hairline dividers between rows, not stacked cards. Rows are assembled as a list, so a row this build doesn't show (no wallet, no processor access, nothing to link) takes its divider with it rather than leaving a stray line. This also fixes the profile-picture/initials desync between the sidebar and the account settings page. ## Screenshots Captured with the stubbed Playwright harness at 1600x900, scoped to the sidebar and auto-cropped to the region that actually changed. Base is `origin/main`; every state is driven by dummy backend stubs so all the nav-bar permutations are covered. <img width="2104" height="3044" alt="montage_cloud-dark" src="https://github.com/user-attachments/assets/667c9a71-3ab7-4582-9259-08cda521e238" /> <img width="2104" height="3044" alt="montage_cloud-light" src="https://github.com/user-attachments/assets/126bc994-efa3-436b-bf50-31d76f574eaa" /> <img width="2104" height="1492" alt="montage_editor-dark" src="https://github.com/user-attachments/assets/c725726d-ea90-4fa9-bf0a-6014f3729869" /> <img width="2104" height="1490" alt="montage_editor-light" src="https://github.com/user-attachments/assets/d0d34e0a-ac60-4f8c-af15-afb66fbd679e" /> <img width="2104" height="1066" alt="montage_processor-dark" src="https://github.com/user-attachments/assets/51ccfe63-c327-41f4-ab91-74555b6f7148" /> <img width="2104" height="1068" alt="montage_processor-light" src="https://github.com/user-attachments/assets/c90d7aba-90fa-4998-ac8f-26ce666dde71" /> The free-credits meter is a cloud-build surface, so the self-hosted capture can't reach it. Those states come from the new Storybook stories with dummy wallet data (`Shared/NavFooter`), which is also where the credit tone bands and the collapsed rail are easiest to review. ## How it's wired `NavFooter` is purely presentational. Each app resolves its own data through three `@app/*` seams, so core carries no build-specific gating and any box whose data is absent is dropped rather than rendered empty. | Seam | core | cloud / proprietary / saas | |---|---|---| | `useFreeCreditsSummary` | `null` — self-hosted editor installs aren't metered | cloud reads `freeRemaining` / `freeAllowance` off the same `useWallet()` the Plan page's free meter uses, so the sidebar and Plan can't disagree | | `useOtherAppSwitch` | `null` — core ships no processor | gated on `portalAccess` (`/api/v1/auth/me` in SaaS, the Spring session flag self-hosted) | | Link-account CTA | n/a | unchanged conditions — passed in as `accountExtras`, still only when `linkState === "unlinked"`, still a no-op in SaaS | - The processor reads the meter through its own `@portal/hooks/useFreeCreditsSummary` rather than the editor's `@app` one. Self-hosted resolves `@app/*` as proprietary → core, where the cloud wallet hook isn't in the cascade, and the implementation can't live in `proprietary/` because core/desktop builds ship no portal and must never resolve `@portal`. Keeping it in `portal/` gets the figure to the linked self-hosted processor without weakening that rule; it reads the same `GET /api/v1/payg/wallet` the Usage page's trial meter already renders, gated on link state and behind the portal's query cache. `portal-saas/` just re-exports the cloud hook, so both footers share one fetch. The processor-access gate previously lived in two near-identical `AppSwitcher` copies. It moves into `useOtherAppSwitch`, `AppSwitcher` now reads it too, and the duplicate `saas/components/shared/AppSwitcher.tsx` is deleted — the logo switcher and the footer row can no longer disagree about access. ## Profile picture sync One `useAccountIdentity` hook now backs the editor footer, the processor footer and the account settings page. Previously settings derived its initial from `email[0]` while the sidebar used `displayName[0]`, and the two drew different blue discs. Alongside that, the shared `Avatar`: - falls back to initials when a picture URL fails to load, instead of leaving an empty disc - renders one letter for single-word names (`admin` → "A", not "AD") - gains an `xl` size so the settings hero disc is the same component ## Notes - Labelled **"Free credits"** rather than "free monthly credits": `freeAllowance` is documented as a one-time lifetime grant, not a monthly reset, so "monthly" would misdescribe the data. Happy to change if the backend semantics differ from the type comments. ## Testing - `task frontend:check` and `task frontend:typecheck:all` pass (all 9 build variants). - 9 new `Shared/NavFooter` stories pass the Chromium + axe story scan; `frontend:storybook:a11y:changed` reports no regressions. - Stubbed E2E suite passes, including the `config-button` tour/settings specs that target the account row. Two failures (`console-clean › landing`, `viewer-text-selection › Ctrl+C`) also fail on `origin/main` locally — they need a backend on :8080 and clipboard permissions.
Stirling PDF - The Open-Source PDF Platform
Stirling PDF is a powerful, open-source PDF editing platform. Run it as a personal desktop app, in the browser, or deploy it on your own servers with a private API. Edit, sign, redact, convert, and automate PDFs without sending documents to external services.
Key Capabilities
- Everywhere you work - Desktop client, browser UI, and self-hosted server with a private API.
- 50+ PDF tools - Edit, merge, split, sign, redact, convert, OCR, compress, and more.
- Automation & workflows - No-code pipelines direct in UI with APIs to process millions of PDFs.
- Enterprise‑grade - SSO, auditing, and flexible on‑prem deployments.
- Developer platform - REST APIs available for nearly all tools to integrate into your existing systems.
- Global UI - Interface available in 40+ languages.
For a full feature list, see the docs: https://docs.stirlingpdf.com
Quick Start
docker run -p 8080:8080 docker.stirlingpdf.com/stirlingtools/stirling-pdf
Then open: http://localhost:8080
For full installation options (including desktop and Kubernetes), see our Documentation Guide.
Resources
Support
- Community: Discord
- Bug Reports: GitHub Issues
Contributing
We welcome contributions! Please see CONTRIBUTING.md for guidelines.
This project uses Task as a unified command runner for all build, dev, and test commands. Run task dev to get started running the editor, run task to see the most common commands, or see the Developer Guide for full details.
For adding translations, see the Translation Guide.
License
Stirling PDF is open-core. See LICENSE for details.

