Skip to content

Desktop and companion clients

Responsibilities and development modes

Electron packages the React frontend and Python backend as one desktop application. Its main process owns the backend child, windows, application protocol, update state and privileged actions. The renderer uses a bounded preload API, never unrestricted Node.js or filesystem access.

Native browser development and Electron development are different entry points:

Mode Frontend Backend owner
Native browser Vite at http://localhost:5173 Root pnpm dev starts Vite and uvicorn
Electron development An independently started Vite at http://localhost:5173 pnpm desktop:dev starts its own uvicorn child on 5002
Packaged Electron Bundled frontend at app://gnosi/index.html Bundled python/cervell_backend, or cervell_backend.exe on Windows

Do not run the native backend alongside Electron development: the desktop supervisor will not adopt another process on port 5002. Electron development does not start Vite and does not request uvicorn reload. Launch it through uv run --frozen --no-sync pnpm desktop:dev after synchronizing the Python environment, so its python3 or Windows python resolves inside that environment. The trusted development origin is HTTP localhost:5173; set VITE_DEV_HTTPS=false for that Vite session. A Word add-in HTTPS session is a separate configuration, not an interchangeable desktop origin.

The desktop README contains setup and recovery instructions. React menu bindings and the update notice belong to frontend/src/app/desktop/; release-note presentation belongs to the control-center feature. Changing internal ownership must not change IPC names, update actions or download destinations.

Startup, windows and IPC

Before opening Chromium or starting services, profile-startup.js obtains the single-instance lock and prepares the existing profile. A conflict or ambiguous recovery state stops startup; it is not permission to erase files.

Each backend launch supplies a fresh GNOSI_DESKTOP_INSTANCE. The supervisor requires a live owned child and a complete, bounded successful health response with the matching x-gnosi-desktop-instance header. That header correlates the process; it does not authenticate a user or change the public health JSON. Timeouts, redirects, malformed responses, early exit and unrelated HTTP 200 responses fail startup and clean up the owned child. A missing packaged executable never falls back to system Python.

New Window, Settings, Dock activation and delayed window display cannot bypass backend readiness or shutdown. Closing the last macOS window does not quit the application; quitting stops its backend. On other platforms, closing all windows quits the app. Startup failure messages are available in English, Catalan, Spanish and French before React loads; technical details stay in logs.

Main windows use contextIsolation: true, sandbox: true and nodeIntegration: false. Only the current top-level frame of a registered window at the trusted development or packaged origin can invoke privileged IPC. Navigation and redirects cannot retain that bridge on another origin. HTTP(S) links requested in a new window are opened externally.

Form filling accepts only a credential-free HTTPS start URL and pins its exact origin before loading. Navigation and redirect guards are installed before the load starts; cleartext and cross-origin destinations are blocked. The final webContents URL is checked again immediately before each synthetic profile injection, so redirected content receives no profile bytes.

The packaged protocol serves frontend assets and proxies /api/ to the local backend. It validates the application authority, prevents filesystem traversal and uses the session cookie jar instead of forwarding raw renderer cookie headers. Preserve this behavior when changing routing or streaming adapters.

The packaged backend-proxy.js bounds individual GET /pages/:pageId/preview requests to 15 seconds, including response headers and the complete JSON body. A stalled cloud-backed preview returns HTTP 504 and releases its HTTP connection, so gallery previews cannot indefinitely queue view saves behind occupied connections. This deadline does not terminate a blocked filesystem read in the backend or guarantee that cloud files are available.

Renderer cancellation propagates to upstream requests. Other API responses, including agent streams and writes, retain their normal lifetime and streaming behavior. The packaging contract requires the proxy helper; regression tests cover six concurrent stalled previews, stalled bodies, cancellation and streams.

All eight extracted handlers have checked request/response contracts. Form filling lives in the already packaged ipc-handlers.js; the main process provides its native window factory and logger. Sender validation still precedes payload access and opening a separate sandboxed window without a preload bridge. URL validation, event ordering, profile serialization and the injected script remain unchanged and are covered by synthetic differential tests. The script inside the string is not type-checked. This does not prove real website behavior, complete main-process typing, installer acceptance or approval of arbitrary form destinations. Preload subscriptions return idempotent disposers; compatibility removal methods remain available to older renderers.

Local data and profile recovery

The packaged backend selects the first nonempty value in this order: GNOSI_DATA_DIR, GNOSI_LOCAL_DATA, LOCAL_DATA_DIR, then Electron's existing userData directory. It sets the canonical variable and preserves an existing compatibility alias. The desktop fallback is not necessarily the native Python platform default, and does not relocate an old installation. Use absolute overrides and preserve both the Electron profile and any separate backend data directory before an update.

The scoped package name @gnosi/desktop is mapped back to the legacy runtime name gnosi; explicit profile/session locations remain in use. The bundle ID remains com.gnosi.cervell-digital.

Profile protection preserves obsolete databases directories as opaque bytes under .<profile-name>.gnosi-electron-recovery/databases.saved, beside each profile. Atomic no-replace moves and journals prevent an existing destination from being overwritten. Separate user-data and session-data profiles are checked. Unsupported filesystem primitives, missing native modules, overlapping data paths or ambiguous journals stop startup. This preserves bytes, not the removed WebSQL feature. Do not restore that tree under the old name while running newer Electron, or delete journals to force startup.

For known cookie schemas 19–22, the migration stages only the cookie database, validates integrity, schema, row count and a byte-aware projected digest, then activates schema 23 before Chromium opens it. The exact original is retained at .Cookies.gnosi-cookie-recovery/original.sqlite, beside Cookies. Unknown, corrupt, conflicting or custom encrypted stores fail closed. There is no whole-profile copy, guessed decryption key or plaintext fallback.

Explicit cookie rollback requires stopped clients and a completed forward migration. It preserves newer cookies as rollback.current.sqlite, restores a verified original through its own journal and prevents automatic remigration. Keep all recovery files until acceptance; never stamp schema versions or delete cookie databases. The README describes interrupted recovery and isolated old → target → target probes. Fixture success does not prove real profile, OS-secret-store or application-database migration on another machine.

Updates and user actions

Production checks after startup and every six hours while idle. Development disables checks; autoDownload and autoInstallOnAppQuit remain false. The compact update button shows the downloaded percentage. Clicking it authorizes download, verified replacement and restart. The main process owns the sequence, rejects repeated actions, and stops the backend before installation.

macOS uses the packaged Sparkle bridge and ad-hoc signatures, without an Apple Developer subscription. The pinned public key verifies the signed feed and ZIP before extraction. Unsigned-feed fallback is disabled. update-policy.js retains the architecture-specific DMG fallback for older ad-hoc installations without the bridge. Installing the first Sparkle-enabled version is a manual migration; Apple may still require approval to open an unnotarized app. Successful codesign verification alone is not complete application-upgrade acceptance.

Windows uses NSIS replacement with automatic relaunch. Linux supports AppImage replacement and DEB installation through the package manager, which can request administrator authorization. Unsigned Windows installers may encounter system warnings or policy blocks. Operating-system protections remain in force.

The real isolated macOS smoke replaces and relaunches a temporary app; a modified archive is rejected before installation. Windows and Linux tests verify updater behavior at mocked process boundaries. Real platform installers, existing profiles and 2.x data migration still require release acceptance.

The Control Center version link opens the localized public history on https://ismigar.github.io/changelog.html, anchored to the selected version. Background checks never open it. The website includes published releases only.

Toolchain and packaging boundaries

The workspace pins Node 22.22.2 and pnpm 11.19.0. Desktop dependencies currently pin Electron 43.4.1, electron-builder 26.15.3 and ASAR 4.3.0. Electron's embedded Node runtime is separate from the workspace build runtime. The explicit install:runtime command installs its binary; do not enable every dependency install script to repair an absent runtime.

Build the frontend before desktop packaging. build-python.sh requires Python 3.11 exactly, accepts GNOSI_PYTHON_CMD when explicitly configured, and creates a unique temporary environment using the frozen root uv.lock and desktop dependency group. It generates a PyInstaller spec, validates the analysis and bundle, copies the verified output into desktop/dist-python/ and runs the isolated packaged-backend smoke. It does not consume a separate requirements file or the developer's existing environment.

The resource policy reads source without importing the app. It preserves Alembic resources, agent instructions, dynamic translation skills, example plugins and citation styles. It rejects missing, changed, unreviewed or unsafe resources rather than recursively bundling vaults, databases, configuration, secrets or generated tools. The afterPack hook checks the actual ASAR and Python resources before signing. Its complete cold scan remains fail-closed and has a ten-minute process deadline so newly copied Windows bundles are not killed during first-access inspection. Assets belong under desktop/assets/; generated bundles belong under desktop/dist/ and desktop/dist-python/.

Genogram schemas load backend/domains/genograms/option_labels.json during startup. The explicit resource plan includes this reviewed JSON beside its module. A relocated-plan regression creates people and relationship schemas in all four locales without access to the source checkout; the real frozen executable must still pass its startup smoke on every target.

The root project declares uv required-environments for macOS arm64 and x64, Linux arm64 and Windows x64. Regenerate uv.lock with uv so its resolution markers may select different wheel-compatible dependency versions per target; never edit the lock manually. Every selected binary dependency must publish a wheel for its target before packaging starts.

PyInstaller reports implicit namespace packages with source -. The resource policy accepts that sentinel only for third-party roots. An unknown namespace under the owned backend, pipeline, config, frontend or extensions roots still fails closed, while a dependency namespace such as jaraco is not misclassified as repository source.

The packaged plugin verifier imports its immutable public trust root from backend/security/plugin_trust_root.py. Marketplace release tooling reuses that constant, but its private-key environment loader remains outside the desktop resource plan. PyInstaller analysis must fail if the marketplace signing module enters the runtime bundle.

Configured target Runner architecture Installer and update artifacts
macOS arm64 Self-hosted macOS ARM64 Gnosi-<version>-arm64.dmg, ZIP, latest-mac.yml, appcast-arm64.xml
macOS x64 Self-hosted macOS X64 Gnosi-<version>-x64.dmg, ZIP, latest-mac.yml, appcast-x64.xml
Linux arm64 Self-hosted Linux ARM64 AppImage, DEB, latest-linux-arm64.yml
Windows x64 Self-hosted Windows X64 Gnosi-<version>-Setup.exe, latest.yml

Match the frozen backend to the target architecture. macOS targets must not silently package both architectures using one host-native backend. Linux passes --arm64; Windows uses x64 NSIS. These jobs do not pin a macOS OS version and do not cover Linux x64 or Windows arm64. Both macOS jobs are serialized; Windows waits for macOS while Linux can run alongside it. Concurrency is per Git ref, not a global lock proving host capacity.

Windows receives a job-scoped PowerShell execution-policy bypass and provisions Git before checkout when necessary; do not weaken machine-wide policy. The backend build uses quoted interpreter arguments, platform-specific temporary venv layouts and best-effort cleanup. The Python manifest currently constrains macOS x86_64 cryptography to the 48.x line; the current uv invocation does not enforce binary-only installation. Verify wheel/ABI provenance on the actual runner instead of assuming that constraint or substituting its Python/OpenSSL.

All desktop build scripts, including root package:desktop and build:desktop aliases, disable builder publication with --publish never. They prepare local artifacts; they do not certify or publish a release.

Version preparation and candidate-only distribution

The bundled history is frontend/src/features/control-center/releases/releases.json. Root, frontend and desktop manifests, Python metadata, locks, localized notes and the changelog must agree before release. sync-release-version.cjs prepares all four inputs before writing only their version fields. Unreadable inputs, unsupported version assignments or ambiguous duplicates fail before any write. Nested JSON versions, comments and line endings are preserved; an identical version does not rewrite files. The TOML locator supports a single-line quoted [project].version, not full TOML validation. Lock refresh still validates the Python project. Separate writes are not a crash-safe transaction: an I/O failure or interruption can leave partial changes. Review the diff against the preparation branch's recorded baseline before retrying. Catalog/changelog validation and review of refreshed locks remain required.

desktop/release.sh has explicit prepare, package and promote modes. Preparation requires a clean tree, updates only reviewed version and pending release metadata, renders the changelog transactionally and leaves both locks unchanged. Packaging requires a committed clean tree, consumes the pnpm and uv locks in frozen offline mode and must not mutate versions, locks, catalog or changelog. Prewarm dependency caches outside this immutable step. Promotion is a separate post-publication metadata commit: it requires the matching local tag, all four verified artifact groups and the exact published release URL before it marks the entry stable and adds its link. The tagged package therefore never claims an unpublished stable release. New packaging fixes require a new reviewed tag, not different source published under an old tag.

desktop/release-version.js is the shared release-version boundary for the updater and artifact collector. It uses the SemVer implementation locked with electron-updater, accepts canonical build metadata, and rejects surrounding whitespace, v prefixes and invalid or non-canonical versions. Packaging and update policy must not introduce a second parser.

Build Release Candidate is an optional, manual-only workflow. Pushing a version tag never starts hosted Actions. When explicitly dispatched, it verifies that the requested tag exists and peels to the exact checked-out github.sha. Malformed input, missing tags, non-commit targets or mismatches stop before dependency installation. The identity helper uses local Git and does not move refs or fetch by itself. Remote tag protection remains a separate requirement. The zero-hosted-budget publication path builds and verifies all four platform groups locally and publishes only those exact artifacts; do not dispatch the optional hosted workflow without explicit budget approval. Because the repository is public, every shared CI job permits self-hosted execution only when a pull request's head belongs to the canonical repository. Fork pull requests cannot reach owner machines; a maintainer must review them and reproduce the change on a trusted internal branch before protected checks can run. The same preflight also requires the tag version to match the root, frontend, desktop and Python manifests before CI or any architecture build starts.

The workflow then calls the existing CI at the same commit without inheriting secrets. Architecture builds require its success. CI includes documentation, frontend, backend, native smoke and Docker image builds. PR documentation is checked against the exact PR base; candidates check current catalogs and all strict locale portals at their own SHA, not a fictional PR impact review.

Collection downloads only the four named architecture artifacts, excluding previous candidates on reruns. It installs frozen collector dependencies with lifecycle scripts disabled, checks version, references and SHA-512 hashes, rejects missing/colliding files and merges both macOS update manifests. Index generation, release-note rendering and candidate upload follow validation. Marketplace generation is mandatory and fail-closed: the signing key must match the bundled gnosi-official public key. A separate pre-upload verifier checks both detached index signatures, every package signature and SHA-256 digest, the exact announced ZIP sets and nonempty release notes.

The final Actions artifact is candidate-<tag>-<sha>-<attempt>, retained for five days. It contains installers, update metadata, indexes and release notes. It is not confidential storage and must never contain user data or secrets. The workflow has read-only repository permissions and does not create GitHub drafts, publish releases or modify existing public assets or updater channels.

Public distribution stays disabled pending full native, Docker, installer and 2.x upgrade acceptance and a separately reviewed publication path. A successful candidate is not permission to publish 3.0.0.

Web and office clients

The web clipper sends POST /api/public/clip with a Personal Access Token and reads prompting/destination configuration from GET /api/public/clip/config. The backend chooses the vault destination; the extension does not obtain arbitrary filesystem access. Its token and backend URL are stored in extension local storage. Browser packaging and store acceptance are separate from desktop installer acceptance.

The Word task pane lives in frontend/public/word-addin/ and uses Office.js. Its API calls use the pane's origin and an explicitly configured bearer token; a healthy public endpoint does not prove authorized citation access. The manifest's HTTPS origin and trusted certificate must match its deployment. Tools in extensions/office/word-cite/ modify document/package references or the user's Word template for optional pane persistence. Those are explicit document/configuration mutations, not a normal Gnosi startup action.

The LibreOffice client is a Python/UNO protocol handler using standard-library urllib. It reads api_token from its own configuration or GNOSI_API_TOKEN; do not assume it shares the browser session. Both clients use the vault citation-formatting endpoints and the backend's Pandoc/CSL pipeline. Context-sensitive formatting needs document keys in order, including repeated citations. Writer refresh traverses nested tables; headers and footers contribute bibliography keys but are not rewritten by ordered refresh. Office host behavior must be tested in the actual supported host, not inferred from traversal fixtures.

Acceptance and troubleshooting

The packaged-backend smoke requires bounded HTTP 200 health with status: ok, mode: FastAPI and its fresh gnosi_mode probe identity. It uses disposable data and vault paths, disables operational automation, and reaps its child on success or failure. GNOSI_VALIDATION_ROOT validates all selectors and blocks local/shared environment files and credential-store access. OpenAPI generation uses the same isolation. Never set this flag for normal development or installed applications.

Source contracts, fake hosts and a source FastAPI run do not prove a frozen installer or actual upgrade. Before public distribution, verify each real target's installation, first launch, IPC, cookie/profile preservation, database integrity, update path and recovery, plus authenticated browser flows and Docker startup/persistence. Local macOS success cannot certify another target.

Symptom Inspect next Do not do
Electron development stays blank Vite HTTP origin, frozen Python PATH, owned backend startup log Start a second backend on 5002
Profile protection stops startup Exact error, both original/recovery paths, stopped clients Delete journals, cookies or old data
Packaged backend is missing PyInstaller output and final resource policy Fall back to system Python
macOS offers a DMG Missing Sparkle bridge or legacy installation; architecture Treat signing verification as automatic-update acceptance
Office can reach health but citations fail Bearer token, API origin and actual protected response Disable authentication to hide a client failure

Run the repository's desktop contracts, strict IPC check, documentation gate and relevant isolated smoke commands. Inspect browser/desktop output and logs, not just exit codes. Keep target-platform evidence separate from synthetic tests.

Installation repair after 3.0.0

Packaged Electron selects an available loopback port. The Python entrypoint uses BACKEND_PORT only with a valid GNOSI_DESKTOP_INSTANCE, binds to 127.0.0.1, and disables reload. Native services keep their configured ports. The startup probe supplies a conflicting configuration to verify that the parent wins; readiness still requires the owned process. A second launch recreates the main window when the previous one was closed.

The font-independent favicon paths own desktop and PWA artwork. The generator writes to desktop/assets, the actual packaging input. The DMG uses a plain background and explicit 540 × 320 layout. Its desktop disk is a mounted volume, not a shortcut; ejecting it removes the icon. Free ad-hoc signing remains the default and does not imply Apple notarization. See the installation steps in desktop/README.md.

Release 3.0.1 prepares the integrated desktop installation repair for fresh native builds on all four targets. Version preparation keeps the catalog unpublished, preserves both frozen locks, and does not replace any 3.0.0 artifact. Free ad-hoc macOS signing remains the distribution policy.

The backend application version and generated OpenAPI contract must match the prepared manifests; the release gate checks their equality before packaging.

User help

The sidebar Help menu opens the help center, getting started, the current section and engineering documentation in an external browser. The native Help menu exposes the center, getting started and user documentation. The optional locale in set-application-menu preserves earlier callers; the main process validates its type and normalizes regional or unsupported languages. Both clients use desktop/help-links.json for fixed destinations and supported topics. Record IDs, Vault names and private content are never added to help URLs. Unknown sections open the help root. Catalan, Spanish and French use localized paths; English is the fallback. The popup participates in the modal keyboard stack so Escape closes help before the mobile sidebar.

User guides are maintained under docs/learn/ and built separately from engineering. Publish and verify the help portal before distributing a build with new help links. The website provides /learn/ entry redirects to /Gnosi/learn/.

Help opened from the sidebar carries the Gnosi appearance preference. The help portal honors light, dark and system settings and keeps that preference between articles and languages. Links from the public website open a separate browser context.

Release 3.0.2 includes the multilingual help center, contextual help menus and appearance-aware article links. Its desktop, frontend and backend version metadata remain synchronized; the release catalog stays pending until verified installers are published.

First launch without a Vault

The packaged main process reads vault_configured from the owned backend's validated health response. When it is false, vault-startup.js stops that child and opens a native folder chooser before any renderer or updater starts. The user can select an existing Vault or create a folder. Canceling exits cleanly; reopening offers setup again. A successful restart with the chosen DIGITAL_BRAIN_VAULT_PATH saves only the selection in GNOSI_DATA_DIR/desktop-vault.json. Explicit Vault environment overrides win; a missing saved folder is never recreated automatically.

The focused tests cover selection, cancellation, process cleanup, restart, localized labels and persistence. Release acceptance must additionally install the candidate DMG with an empty profile and no injected Vault configuration, exercise the visible chooser and a second launch, then test an upgrade from 3.0.1 with synthetic data. A harness that configures the Vault before launching does not establish first-launch acceptance.

A successful native folder choice also saves an opaque selectionId. The sandboxed preload applies that marker before frontend requests: it clears only the prior active Vault ID, slug, name, catalog and active-Vault cookie. Unrelated preferences and cookies remain intact. The marker is applied once per profile, so normal library switches survive new windows and later launches. Old selection files without a marker remain valid. Canceling recovery preserves the previous selection. Registry identities are never rewritten merely because another folder was chosen. Repeat installed-DMG acceptance for relocation as well as same-path upgrade; this change also requires a newly built frozen backend.

The 3.0.4 candidate includes the autosave preservation fix merged in PR 96. It supersedes the unpublished 3.0.3 candidate without moving its tag or reusing its acceptance claims. All four version fields are synchronized, dependency locks remain unchanged, and the catalog stays unpublished until newly built installers pass acceptance.

The backend runtime constant GNOSI_VERSION also reports 3.2.0; the health API contract test compares it with all four release manifests.

Regenerate the committed OpenAPI document and its SHA-256 after changing the runtime version. For this candidate, the reviewed schema difference is limited to the application version; routes and payloads remain unchanged.

Docker CI retries smoke-container cleanup and runs it again in an independent always-run step before removing CI images, so a failed exit trap cannot silently leave containers blocking the next build.

The reviewed desktop resource list includes personal_memory_0002, so installed builds preserve existing memories while adding scoped memory and private learning projects.

Bounded CI acceleration

Python package snapshots remain disabled: compressing the full runtime previously delayed required checks. CI now reuses the native uv package cache on each runner, scoped by repository, operating system, architecture and Python version. uv owns concurrent cache access; provisioning never deletes this cache. Every job still gets a fresh virtual environment with copied files and frozen dependency resolution. Missing runner cache configuration falls back to job-private downloads. pnpm already reuses its local store; remote cache uploads remain disabled. Docker removes CI image tags after smoke cleanup but retains build layers while at least 12 GiB remains free; low capacity triggers the existing scoped cache pruning. The first run on each runner downloads dependencies, and later runs download only missing artifacts. Runner timings still need measurement. Snapshot restoration and archive creation remain disabled.

If Docker still lacks 12 GiB after pruning its layers and old snapshots, uv releases downloaded prebuilt wheels from the owned package cache; source-built wheels remain cached. This recovery uses uv’s cache command, rejects symlinked or unrelated directories, and stops once capacity is sufficient. A small runner may therefore redownload packages after disk pressure; package reuse cannot replace adequate disk capacity.

ESLint uses content-based caching and mypy retains incremental analysis between jobs, with platform, dependency and configuration changes invalidating the cache. Frontend tests use two workers. The local comparison of 27 representative tests passed with one and two workers; full-suite elapsed time and memory still need to be assessed on the runner. Heavy job ordering remains unchanged.

Only trusted pull requests exclusively changing Markdown in the four engineering portals may omit runtime steps. All five required job names remain visible and the documentation job still validates every locale. Empty or unknown diffs, source moves, code, configuration and dependency changes require full runtime validation. Pushes and release validation always retain all runtime checks.

Because frontend lint uses TypeScript type information, any frontend source or local rule change invalidates its entire cache, including results for unchanged importers. Backend-only changes can reuse it. Unknown source state forces fresh lint.

PDF annotation composition runs all 57 contracts in each of two isolated import orders. Each group has a bounded 300-second budget covering cold backend imports and the full set of checks. Verbose progress, slowest-test timings and a stack dump after a 60-second individual test stall distinguish slow startup from a blocked test. A group timeout or any child assertion remains fatal and its partial output is included in the failure.

Local pre-PR validation and CI invoke the same pnpm lint:frontend entry point. The shared launcher always checks the entire frontend with zero warnings allowed; it adds content-cache options only when GNOSI_ESLINT_CACHE is set. Cache paths are passed as literal arguments without a shell, and lint failures remain fatal. The pre-PR parity test still rejects any CI validation missing from the local full plan.

The 3.1.0 candidate packages the shared principal assistant, task-based model selection, scoped memory, reusable learning and contextual source reading. Existing data is preserved through the reviewed migrations. All manifests and GNOSI_VERSION match; the regenerated OpenAPI contract changes only its version relative to the prepared source. Both dependency locks remain unchanged. The catalog remains unpublished until source-matched installers pass fresh-install, persistence and upgrade acceptance on all four targets.

Release update notice

The desktop update notice periodically checks for newly published releases. When an update has been downloaded, the macOS installer action can be retried from the notice if the first installation attempt fails. Background checks and the renderer action are intentionally guarded so they do not run concurrently.

The 3.2.0 candidate adds assistant teams and specialist roles, provider-specific model comparison and multilingual workspace templates. Root, frontend, desktop, Python and backend runtime versions match. OpenAPI is regenerated with the new version; both dependency locks remain unchanged. The catalog stays unpublished until the source-matched installers pass first-install, persistence and upgrade acceptance on all four supported targets.

Packaged first-start configuration

Before a vault is selected, a frozen backend persists configuration and provider-reference migrations under GNOSI_DATA_DIR/config/params.yaml. Signed application resources remain read-only; later requests still save to the active vault’s .gnosi/params.yaml. First-launch and restart acceptance must verify the installed signature after the app exits, since startup readiness alone does not detect bundle writes.

The frontend Docker image includes the shared PDF asset verifier imported by Vite and checks that it loads during image construction. This prevents a successfully built image from failing at server startup because the verifier is missing.