Commit Graph

186 Commits

Author SHA1 Message Date
Elian Doran
897aeb6cbc
chore(flatpak): trim the filterSources comment to the binding constraints
Greptile flagged the fourteen-line block: the measurements and the rejected
--no-devel narrative belong in the commit body of the change that introduced
the filter, not at the call site.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-09-01 14:09:23 +03:00
Elian Doran
6091bffd38
feat(ci/release): drop playwright 2026-08-31 20:58:42 +03:00
Elian Doran
b21e9a717d
feat(flatpak): build generated-sources.json 2026-08-31 20:36:10 +03:00
Elian Doran
ae34d8ad0d
build(nix): pin Electron 44, which drops armv7l and bundled ANGLE
The Update Nix flake workflow has been failing since the bump to Electron
44 landed on main, for two independent reasons.

parseShasums treated every entry of ELECTRON_PLATFORM_TAGS as mandatory,
and Electron 44 ships no linux-armv7l assets at all — 43.4.1 lists seven
armv7l entries in SHASUMS256.txt, 44.0.0 lists none:

  Error: SHASUMS256.txt for Electron 44.0.0 is missing:
  electron-v44.0.0-linux-armv7l.zip

Drop armv7l-linux from the table, and x86_64-darwin with it: the nixpkgs
rev in flake.lock (6d12004) lists only x86_64-linux, armv7l-linux,
aarch64-linux and aarch64-darwin in generic.nix's `tags`, so that hash
could never have been read. Keep the hard throw on a missing asset —
shrinking the platform set is a decision for a human, not something to
paper over by silently pinning fewer systems.

Refreshing the hashes then got the job as far as the verification step,
which failed on its own:

  patchelf: missing filename

The nixpkgs Linux builder rewrites the rpath of Electron's ANGLE
libraries with an unguarded `patchelf ... lib*GL*`. Electron 43.4.1
shipped libEGL.so and libGLESv2.so; 44.0.0 links ANGLE into the main
binary and ships neither, leaving only libffmpeg.so, libvk_swiftshader.so
and libvulkan.so.1, so the glob expands to nothing. There is nothing
upstream to pull — nixpkgs master packages Electron 41-43 only and still
has the unguarded glob — so override that one command to tolerate an
empty match, guarded by a throwIf that fails loudly if the upstream text
changes rather than silently no-opping. Linux only, because the Darwin
branch of the builder defines no postFixup.

`nix build --no-link '.#electron' '.#electron.headers'` now succeeds and
the result reports v44.0.0.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 00:05:17 +03:00
Elian Doran
081de8d0ad
fix(scripts): wipe node_modules without glob
Some checks are pending
Checks / main (push) Waiting to run
CodeQL Advanced / Analyze (${{ matrix.language }}) (none, actions) (push) Waiting to run
CodeQL Advanced / Analyze (${{ matrix.language }}) (none, javascript-typescript) (push) Waiting to run
Dev / Detect affected suites (push) Waiting to run
Dev / Test development (push) Waiting to run
Dev / Test server (push) Blocked by required conditions
Dev / Test standalone (push) Blocked by required conditions
Dev / Test CKEditor 5 (shard ${{ matrix.shard }}) (1) (push) Blocked by required conditions
Dev / Test CKEditor 5 (shard ${{ matrix.shard }}) (2) (push) Blocked by required conditions
Dev / CKEditor 5 coverage gate (push) Blocked by required conditions
Dev / Build Docker image (push) Waiting to run
Dev / Check Docker build (Dockerfile) (push) Blocked by required conditions
Dev / Check Docker build (Dockerfile.alpine) (push) Blocked by required conditions
playwright / E2E tests on ${{ matrix.name }} (arm64, linux-arm64, ubuntu-24.04-arm) (push) Waiting to run
playwright / E2E tests on ${{ matrix.name }} (x64, linux-x64, ubuntu-22.04) (push) Waiting to run
playwright / PDF viewer E2E tests (push) Waiting to run
playwright / Standalone E2E tests on ${{ matrix.name }} (linux-arm64, ubuntu-24.04-arm) (push) Waiting to run
playwright / Standalone E2E tests on ${{ matrix.name }} (linux-x64, ubuntu-22.04) (push) Waiting to run
`glob` is not a dependency of the repo, so the import resolved to a
phantom hoisted glob@7.2.3 pulled in by @electron/asar, archiver-utils
and rimraf. That version takes a single string pattern and a callback,
so the array of patterns threw `TypeError: invalid pattern` out of
minimatch, and an awaited call would have yielded undefined anyway.

Three fixed patterns of depth two do not need a glob library, and its
version would keep shifting with the lockfile. Enumerate the workspace
directories with readdir instead, matching scripts/build-utils.ts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-26 22:51:23 +03:00
Elian Doran
9f2d87a0df
fix(desktop): macOS app icon renders as noise at small sizes (closes #11187)
`icnsutil compose` picks a chunk's OSType from the image size, so the 16px and
32px slots were stored as `icp4`/`icp5`. macOS IconServices decodes those as raw
pixel data rather than PNG, so every consumer that lands on a small
representation drew colored noise -- Raycast's result list, Finder at 16pt, and
Apple's own `iconutil -c iconset`. Spotlight draws the icon large enough to miss
it.

icnsutil also takes the OSType from the file name, so the intermediates are now
named by OSType instead of by size. That drops `icp4`/`icp5` and fills in the
slots the size-driven loop never produced: `ic08` (256) and the retina
`ic11`/`ic12`/`ic13`. Default and nightly get separate scratch directories --
they used to share `mac/` and be told apart by a `*-dev.png` glob, which cannot
survive OSType names.

The slot table avoids `declare -A`: macOS ships bash 3.2, and this is the
section a Mac user is most likely to re-run.

Also replaces `rm -r mac/*` with `rm -rf mac`. `app-icon/mac` is gitignored, so
on a fresh clone that line aborted the whole script under `set -e`.

Verified by building two throwaway `.app` bundles differing only in their icns
and drawing both through `NSWorkspace.icon(forFile:)`: before, 32pt is noise and
16pt is a cyan wash; after, both render the logo. `iconutil -c iconset` decodes
every slot of the regenerated files.

No 16pt slot is shipped. Its modern type, `ic04`, requires raw ARGB -- a PNG
payload under `ic04` is mis-decoded exactly like `icp4` -- and icnsutil needs
Pillow to produce ARGB. macOS synthesizes 16pt from `ic11` instead.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-25 12:26:15 +03:00
Elian Doran
5d1c5ff973
feat(desktop): switch the Electron main bundle to ESM with code splitting
The main process now builds as main.mjs with dynamic-import boundaries
split into chunks/ that only load on first use; dist/package.json
points Electron at the new entry. The preload stays CJS (Electron's
sandboxed renderer cannot load ESM preloads) and image_worker.cjs
stays CJS (spawned by its path).

The ESM banner now resolves __dirname in a chunk to the bundle root
rather than chunks/: bundled code uses __dirname to find siblings of
the entry (preload.cjs, image_worker.cjs, getResourceDir), and which
chunk a module lands in must not change what the path means.

Verified by launching the built app: the renderer loads over
trilium-app:// and reaches the backend through the preload bridge.
Main-process RSS on identical boots: 348 MB (CJS) -> 287 MB (ESM).
The "Error executing query" lines at boot predate this change
(identical under CJS).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 22:27:37 +03:00
Elian Doran
1fd0f284d1
feat(build): ESM output option with code splitting for backend bundles
buildBackend() gains format: "esm", emitting .mjs with dynamic-import
boundaries split into chunks/. The ESM banner defines require,
__filename and __dirname from import.meta.url (the mirror image of the
CJS import.meta.url shim); esbuild's interop helper picks up the
banner's require for external packages such as better-sqlite3.

Static imports of unpdf, officeparser and the Claude agent SDK become
dynamic imports inside the async methods that use them, so those
subsystems only load on first use. Under the current CJS output the
seams are inert (esbuild inlines the modules); under ESM they split.

Measured on the server bundle (Node 22.21, production build, real
146 MB database), CJS single bundle vs ESM split: settled RSS
221 -> 135 MB, JS heap post-GC 77 -> 54 MB, retained script source
28.2 -> 8.8 MB, V8 parser native allocation peak 77 -> 22 MB. Startup
loads ~9 MB of the 15 MB of emitted code; splitting also restores
seams the single bundle was flattening (core's exceljs/officeparser
dynamic imports, highlight.js per-language loading, migrations).

The server build still emits CJS: flipping it requires updating the
Dockerfiles, start-prod and the docker healthcheck, which all
reference main.cjs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-20 22:14:08 +03:00
Elian Doran
768bc3946e
fix(scripts): read every page of the Weblate language stats
`getLanguageStats()` returned the first response page, so anything past it was
invisible to both callers. The coverage gate would pass by never seeing the
languages it was meant to catch, and the README language bar would quietly drop
them.

Weblate serves 50 translations per page and each component is at 40, so the
current lists are complete and nothing was missed. That is ten languages of
headroom, and a language appears whenever someone starts translating one.

Follow `next` until it runs out, and report the count actually read. A non-OK
response now names the URL rather than surfacing as a JSON parse error on the
error body.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 13:26:02 +03:00
Elian Doran
5897d741bc
feat(website): offer every language Weblate reports as translated
The website picked its languages from a hand-maintained `LOCALES` array that
nothing checked, so it had fallen behind the `website` Weblate component. Thirteen
languages above the 50% bar the client uses were bundled but unreachable, German,
Indonesian, Korean and Irish among them at 100%, while Romanian at 54.9% was
offered. Add them, reusing the endonyms already curated in `packages/commons`.

`mapLocale()` ended in `locale.split('-')[0]`, which cannot express a locale that
carries a region: `en-GB`, `pt-BR` and `nb-NO` would have collapsed to `en`, a 5.1%
`pt` and a nonexistent `nb`. Resolve against `LOCALES` instead, preferring an exact
match, then the region-less entry for the language, then any region sharing it. A
bare `nb` or `pt` now reaches `nb-NO` or `pt-BR`, and a language the website does
not offer returns `en` rather than a locale with no entry in the picker. The
region-less step matters: `LOCALES` is sorted by display name, so without it
`en-US` resolves to `en-GB`.

`check-translation-coverage.ts` now gates the `website` component alongside
`client`, so the next language to cross the threshold fails CI instead of sitting
unreachable. It reports every missing locale rather than the first, and names the
file to add it to.

`LOCALES` moves to `apps/website/src/locales.ts` because the gate runs under tsx,
which cannot evaluate the `import.meta.glob` at the top of `i18n.ts`.

`en` is renamed to "English (United States)", matching `packages/commons`, since
"English" alongside "English (United Kingdom)" names nothing in particular.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 11:01:19 +03:00
Elian Doran
72cdf3b597
fix(scripts): expire the Weblate language-stats cache
The freshness check compared the cache file mtime against a point one day
in the future, which is always true for a file already on disk. Once
`.language-stats-<project>.json` was written it was reused indefinitely, so
`check-translation-coverage.ts` and `manage-readme.ts` kept reading whatever
snapshot happened to be fetched first.

Extract the decision as `isCacheFresh()` and cover it. A future-stamped file
now counts as stale as well, so a skewed clock cannot reproduce the same
never-expiring cache.

CI is unaffected either way: the cache files are gitignored, so a fresh
checkout always queries Weblate.

The scripts project globbed `*.spec.ts`, matching only its own root, so no
spec under `scripts/translation/` would have run. Widen it to `**/*.spec.ts`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-20 10:10:22 +03:00
Elian Doran
ae798be4ea
chore(scripts): typecheck the maintenance scripts
The scripts project is referenced from the root solution, so `pnpm typecheck`
was building it on every run — over an empty program. `include` resolves against
the directory holding the tsconfig, but its globs were written as if they were
relative to the repository root, so `scripts/*.ts` looked for
scripts/scripts/*.ts and matched nothing. Nothing under scripts/ has been
typechecked for as long as the file has looked like this.

Widened to the whole tree, .mts included, which also covers translation/ and
repo-migration/. That found one bug: on anything other than NixOS,
`isNixOS() && getNixLdLibraryPath()` is `false` rather than `undefined`, and
Node stringifies it instead of dropping it — so `pnpm desktop:start` launched
Electron with LD_LIBRARY_PATH=false.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 20:53:11 +03:00
Elian Doran
e3a8bbd7e9
chore(scripts): drop the dead OpenAPI generation script
swagger-jsdoc, the only thing the script ran on, was removed from the server in
eab353ca2e as an unnecessary dependency, and nothing has installed it since. Its
output, apps/server/src/assets/openapi.json, is not in the repository either,
and its `apis` globs still cover only apps/server/src/routes although most of
those routes now live in packages/trilium-core.

The @swagger blocks in the route files are left alone; they are ordinary
comments now.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 20:53:02 +03:00
Elian Doran
892125bba6
ci(release): pin release note images to the tag being published
The notes under docs/Release Notes are exported from Trilium, so their images
are bare filenames sitting next to the Markdown. That renders in the repository,
but the publish step hands the same file to the release body, where GitHub
resolves a relative source against the releases URL — so every image would have
arrived broken. v0.105.0 is the first note to carry any.

The body is now composed by scripts/release-notes-body.mts, which rewrites those
sources to raw.githubusercontent.com URLs pinned to refs/tags/<tag>, and fails
the job when a referenced image is not in the repository. Pinning to the tag
rather than a branch keeps a published body correct if the images later move.

The script has no dependencies, so the publish job runs it off its sparse
checkout without installing the monorepo.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-19 20:47:01 +03:00
Elian Doran
774a73ca89
perf(dev): give each dev server its own Vite dep cache
The client config points `cacheDir` at a single repo-level directory, so
the server, desktop and edit-docs dev servers all shared one optimized-dep
cache. Vite discards that cache whenever its `configHash` changes, and the
hash differs between the Node-hosted dev server and the Electron-hosted
one — so alternating between `pnpm server:start` and `pnpm desktop:start`
re-scanned and re-bundled the whole dependency graph on every launch.

Measured on the desktop dev app, consecutive launches with nothing else
changed: 9055ms to full render with the re-optimization, 5796ms without.
The failed commits also leave orphaned `deps_temp_*` directories behind
(91 MB when measured), because on Windows the optimizer cannot delete a
`deps` directory another dev server still holds open.

Keying the cache directory on the app port removes the sharing. The perf
analyzer is taught the new layout, and the measure-startup-requests skill
picks up the same path change plus two corrections: the eager-load
offenders it listed are both fixed now (dialogs go through `LazyDialog`,
Inter ships as woff2), and a capture run without `TRILIUM_PASSWORD`
silently stops at the login page instead of failing.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 18:05:32 +03:00
Elian Doran
02edbeb1c1
build: name the native compiler @typescript/native, and record why the compat package is unusable
Rename the alias from `tsc-native` to the name Microsoft's TypeScript 7
announcement and Nx's guide both use, so the layout is recognisable to anyone
who has read either. No behavioural change -- same typescript@7.0.2, same
single consumer in scripts/filter-tsc-output.mts.

The rest of the documented layout is deliberately not adopted. Microsoft
recommends aliasing `typescript` to the @typescript/typescript6 compatibility
shim so the native compiler can own the `tsc` bin name; that assumes the only
consumer of the name is tooling. Here it is also a runtime dependency of
packages/codemirror, which runs the language service in the browser, and the
shim breaks it twice over:

- It ships five files and no lib.*.d.ts, so the 96 `typescript/lib/lib.*.d.ts?raw`
  imports in ts_lib_files.ts fail to resolve and the client build dies.
- Giving codemirror its own real typescript to fix that splits resolution --
  @typescript/vfs and @valtown/codemirror-ts are hoisted to the root and follow
  the shim, codemirror's own source follows its nested copy -- so the 3.3 MB
  compiler lands twice in the lazy script-note chunk. Client dist goes 69 M to
  72 M.

Both were measured, not reasoned about; the CLAUDE.md note carries the numbers
so the next reader of the 7.0 announcement doesn't repeat the experiment.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 18:10:36 +03:00
Elian Doran
249d676f7a
build: run pnpm typecheck through the native TypeScript compiler
`tsc --build` over the full project graph took ~25s cold and ~12s after any
change to a widely-imported package such as commons. The native (Go) compiler
does the same build in ~3.5s with identical diagnostics.

It cannot simply replace the `typescript` dependency: TypeScript 7 no longer
exports the JS compiler API, so TypeDoc, typescript-eslint and the Vite plugins
would all break. Hence the `tsc-native` alias -- 6.x stays as the library that
tooling loads, 7 is the binary that typechecks.

While here, stop reporting a failed compiler run as a clean build. Anything tsc
printed that did not look like a `file(line,col): error TSxxxx` diagnostic --
a crash, an unknown option, a missing binary -- was filtered away and the script
exited 0. It now propagates the exit code.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 17:40:16 +03:00
Elian Doran
12475a811b
ci(nix): sync the flake's Electron pin automatically
nixpkgs lags behind the Electron releases we track, so flake.nix builds the
exact pinned version from Electron's official binary release. Renovate bumps
the devDependency but knows nothing about that pin, so every Electron update
left the flake refusing to evaluate until someone refreshed six hashes by
hand.

scripts/update-flake-electron.ts does it instead: the per-platform checksums
come from the release's own SHASUMS256.txt and the headers hash from
nix-prefetch-url --unpack. It is a no-op when the pin already matches.

The workflow runs it on pushes to main that touch apps/desktop/package.json
(and on demand), verifies the written hashes by actually building .#electron
and .#electron.headers, and only then opens a PR.

Two supporting flake changes:

  - packages.electron is exposed, so the pinned binary can be built — and its
    hashes verified — without a full desktop build.
  - electron_<major> is looked up with `or null`. A major bump lands in
    package.json long before nixpkgs has the attribute, and without the
    fallback the flake died with an attribute error rather than reaching the
    throw that explains the mismatch.

scripts/ is not a workspace package, so its tests get their own vitest
project, a `pnpm scripts:test` entry point, and a step in dev.yml.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-08 15:55:22 +03:00
Elian Doran
8881014c12
refactor(ckeditor5): fold keyboard-marker into the aggregate package
`packages/ckeditor5-keyboard-marker` had no consumers outside
`@triliumnext/ckeditor5`, was never published, and carried a full
ckeditor5-package-generator scaffold (sample pages, its own ESLint /
Stylelint / vitest configs, typings) that no root script or CI job ever
invoked. Its GPL-3.0 license combines with this repository's
AGPL-3.0-only, so the split bought no license isolation either, and
upstream (mlewand/ckeditor5-keyboard-marker) targets a long-obsolete
CKEditor 5 version and is not tracked — the source was ported and has
since diverged.

Move the plugin to src/plugins/keyboard_marker/, renamed to match the
todo_list_multistate convention, and adapt it to the destination
package: repo code style, the augmentation folded into a `declare
module` block, and the spec moved onto the shared createTestEditor()
kit. The unused `icons` barrel export is dropped.

Attribution is preserved: the upstream LICENSE moves next to the code,
alongside a README recording the provenance and the divergence.

The plugin's tests land inside the aggregate's 100% coverage gate
without a carve-out; the suite stays at 100% (848 tests).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-01 14:37:49 +03:00
Elian Doran
bee0552938
refactor(ckeditor): drop ckeditor5-premium-features entirely
Every premium plugin Trilium loaded now has an in-tree GPL replacement —
`SlashCommand` -> `TriliumSlashCommands` (#10711), `Template` ->
`TriliumSnippets` (#10727), `FormatPainter` -> `TriliumFormatPainter` — which
left `loadPremiumPlugins()` returning an empty array while the commercial
licence, its key plumbing and the dependency were all still carried around.

- `loadPremiumPlugins()` and its export are gone. `config.ts` always passes
  `licenseKey: "GPL"`, so `getLicenseKey()`, `VITE_CKEDITOR_KEY` (both `.env`
  files, `vite-env.d.ts`) and the `forceGplLicense` build option go with it,
  along with the read-only-editor rebuild in `buildEditor()` — that fallback
  existed only to recover from a licence CKEditor had rejected.
- `getCkLocale()` no longer loads the premium translation bundle. The GPL
  plugins reuse premium's English source strings, so a handful of labels
  ("Insert template", "Copy formatting", the slash palette) now fall back to
  English in non-English UI locales; everything from CKEditor's own core
  translations is unaffected.
- Also removed: the dependency + lockfile entry, the `optimizeDeps` pre-bundle
  entry, the renovate group member, and the premium category in
  `analyze-perf.ts`.

Tests: `plugins.spec.ts` pins the three GPL replacements instead of asserting
the premium list; the licensing block in `config.spec.ts` collapses to one
test; `i18n.spec.ts` becomes table-driven (19 near-identical cases -> 2) and
expects a single translation bundle. Typecheck clean, 834 package tests and 169
client tests pass, and the client builds with the package pruned.

The "Premium features" user-guide note is now obsolete but is left alone — it
needs the docs editor, not a hand edit.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 20:45:47 +03:00
Elian Doran
08651a928f
Merge remote-tracking branch 'origin/main' into renovate/pdfjs-dist-6.x 2026-07-25 23:04:17 +03:00
Elian Doran
f252c7be94
build: fail the build on a pre-v13 better-sqlite3 instead of shipping it
trimBetterSqlite3 returned silently when the copied module had no
prebuilds/ directory, on the assumption that this only happened for
source builds. It also happens when a stale pre-v13 tree is left in
<app>/node_modules -- which copyNodeModules prefers over the hoisted
root -- and the silence let that module ship.

The result was a runtime failure far from the cause: a v12 module needs
the `bindings` package, which is no longer copied into dist, so Node
resolved it from a node_modules above the repository and loaded a
build/Release addon compiled for a different Electron ABI
("NODE_MODULE_VERSION 146 ... requires 148"), with `bindings` in the
stack trace long after it stopped being a dependency.

pnpm's `hoisted` linker never creates <app>/node_modules entries, so
anything found there is residue -- in practice from the electron-rebuild
script that used to copy the module in for its Electron build. Switching
between a branch that predates the v13 bump and one that follows it is
enough to leave one behind.

Throw instead, naming the version found and the fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 19:27:47 +03:00
Elian Doran
fd7ee39963
build: ship only the better-sqlite3 files an artifact actually uses
Since v13 the package bundles a prebuilt binary for all eight platforms
it supports (~17 MB) on top of the SQLite amalgamation needed to compile
from scratch (~10 MB). A packaged artifact loads exactly one binary and
never compiles, so all of that but ~2 MB was dead weight:

  v0.104.1 (v12)   11.93 MB   deps/ + a compiled build/Release binary
  v13, untrimmed   27.0 MB    deps/ + eight prebuilds
  server, trimmed   4.5 MB    lib/ + linux-x64 + linuxmusl-x64
  desktop, trimmed  2.2 MB    lib/ + linux-x64

That is ~6 MB off each compressed Linux artifact, and leaves us below
what v0.104.1 shipped rather than 15 MB above it. lib/ plus one prebuild
is the complete runtime set -- verified by loading a copy pruned to just
those files and running a query against it.

Two things the trim has to get right, both covered:

Linux keeps the musl build alongside the glibc one. The server's dist is
produced once on a glibc runner and then consumed by both the Debian and
the Alpine images, and better-sqlite3 resolves linuxmusl-* at runtime on
the latter, so dropping it would break the amd64 image at startup. The
Electron artifacts opt out, since Electron ships no musl builds.

The platform and architecture are the ones being built *for*, not the
host's: the macOS runners are arm64 and also package darwin-x64, so
trimming by host arch would strip the binary that build needs.
TARGET_ARCH / MATRIX_ARCH already carry the target in CI.

An unsupported platform/arch throws rather than silently producing an
artifact with no native addon.

Also drops the better-sqlite3 cleanup from flake.nix's server install
phase: it removed deps/sqlite3 and the build/ scaffolding that held
build-time store paths, all of which the build now trims itself.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 18:07:02 +03:00
Elian Doran
fe5acbbb4a
build(ci): rebuild better-sqlite3 against an older glibc for Linux releases
better-sqlite3 v13's bundled N-API prebuilds are linked against a much
newer toolchain than the assets prebuild-install used to fetch, which
would have raised the glibc floor of every native Linux artifact and
dropped distributions we still support:

  v0.104.1 (v12 assets)  GLIBC_2.29 / GLIBCXX_3.4.20
  v13 prebuild, x64      GLIBC_2.34 / GLIBCXX_3.4.29
  v13 prebuild, arm64    GLIBC_2.38 / GLIBCXX_3.4.31

The arm64 figure drops Debian 12 "bookworm" -- including Raspberry Pi OS
-- along with Ubuntu 22.04 and RHEL 9; x64 drops Ubuntu 20.04, Debian 11
and RHEL 8. Nothing in better-sqlite3 needs a newer glibc: the addon
picked up GLIBC_2.34 because it was linked where libpthread had merged
into libc, and GLIBC_2.38 because GCC 13+ redirects strtol to the
__isoc23_* symbols. The floor is an artifact of the build host, so build
on an older one and substitute the result for the bundled prebuild.

Compiling in node:22-bullseye restores GLIBC_2.29 / GLIBCXX_3.4.21 --
glibc parity with v0.104.1, and the one-step GLIBCXX move constrains
nothing, since glibc 2.29 already implies a 2019-or-later distro. No
platform is lost.

Notes on the implementation:

- force_build is required. binding.gyp resolves both targets to
  `type: none` whenever a prebuild for the host exists, so an ordinary
  node-gyp run emits a stamp file and no addon.
- The compiled addon replaces prebuilds/<target>.node rather than
  relying on the build/Release fallback, leaving the loader's resolution
  order untouched.
- nodeLinker is "hoisted", so every dependant gets its own copy and the
  build scripts resolve <app>/node_modules before the root; all copies
  are patched. Each is replaced by rename, since these files are
  hardlinks into the pnpm store and writing in place would mutate it.
- python3 is apt-installed when absent: buildpack-deps supplies g++ and
  make but not python3, which node-gyp needs. Sources fall back to
  archive.debian.org so this keeps working once bullseye goes EOL on
  2026-08-31 -- an old *build* container is deliberate here, the same
  approach manylinux and Node's own release builds take.

Docker images are unaffected: they carry their own glibc.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 17:59:19 +03:00
Elian Doran
039b85ddd4
build: adapt the native build pipeline to better-sqlite3 v13
Some checks failed
Checks / main (push) Has been cancelled
v13 moves the addon onto N-API and ships prebuilt binaries for every
supported platform/arch inside the package itself. Three parts of the
build assumed the old prebuild-install/node-gyp layout.

Dropped `bindings` and `file-uri-to-path` from copyNodeModules: v13 no
longer depends on either, so the copy aborted the build outright with
"Unable to find any of the paths: .../node_modules/bindings".

Removed the Electron native rebuild. v13's binding.gyp resolves both of
its targets to `type: none` whenever a prebuild for the host exists, so
electron-rebuild.mts produced a stamp file and no addon on every install,
leaving BETTERSQLITE3_NATIVE_PATH pointing at a build/Release binary that
is never created -- `pnpm desktop:start` died on MODULE_NOT_FOUND. The
prebuilds are ABI-stable and load unchanged under Electron 42 (Node-API
10), so the rebuild step, its @electron/rebuild and prebuild-install
devDependencies, and the ELECTRON_NODEDIR plumbing in flake.nix all go.

Rebuilt the Docker images around the same short-circuit: `pnpm rebuild`
in the builder stages was also a no-op, so the images would have shipped
the upstream prebuild, which needs glibc >= 2.34 (x64) / >= 2.38 (arm64)
against bullseye's 2.31. The glibc images move to trixie and lose their
builder stage; the Alpine pair lose theirs too, since the linuxmusl-*
prebuilds only need libstdc++, which node:*-alpine already provides.
Alpine stays pinned to 3.23 for the musl 1.2.5 reason in #10627.

Dockerfile.legacy must keep compiling from source -- 32-bit ARM has no
prebuild at all, and linux/arm/v8 normalizes to arm64, whose prebuild
outruns that image's bullseye glibc. npm_config_force_build=1 restores
the real compile and link rules.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 17:09:31 +03:00
Elian Doran
cbb1d53f25
fix(desktop): make start-prod launch Electron correctly on NixOS
The `start-prod`/`start-prod-no-dir` scripts invoked the npm prebuilt
Electron binary directly (`electron dist`), which fails to load on NixOS
because it's dynamically linked against FHS system libraries that don't
exist there (libcups.so.2, libgtk-3, libnss3, ...). The dev launcher
already handles this via electron-start.mts, but prod never did.

Add scripts/electron-run-prod.mts, a build-free launcher that reuses the
same NixOS-aware getElectronPath() (preferring the Nix/nix-develop
Electron over the prebuilt binary) and sets LD_LIBRARY_PATH plus
--no-sandbox on NixOS. On all other platforms it's a transparent
`electron <args>` wrapper, so behaviour is unchanged.

Also extract the shared getNixLdLibraryPath() helper so the dev and prod
launchers stay DRY.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-18 13:29:30 +03:00
Elian Doran
c13f6e3469
fix(desktop): don't inject import.meta.url banner into sandboxed preload
The import.meta.url shim added for the bundled Claude Agent SDK prepends a
`require("node:url")` banner to every buildBackend output. The desktop build
compiled main.ts and preload.ts together, so the banner landed atop
preload.cjs, which runs in Electron's sandboxed renderer where
`require("node:url")` throws. That aborted the preload before it could expose
`electronApi`, leaving window.electronApi undefined: the window rendered a
transparent background with no Mica material behind it and DevTools could not
be opened.

Gate the shim (define + banner) behind a buildBackend option and build the
preload without it; the preload never references import.meta.url.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 14:36:01 +03:00
Elian Doran
6dd444e167
fix(desktop): make bundled Claude Agent SDK runnable in production
The desktop build bundles everything into a CJS main.cjs. CJS has no
import.meta, so esbuild rewrites import.meta.url to {} (undefined).
@anthropic-ai/claude-agent-sdk calls createRequire(import.meta.url) at
module top level, so evaluating it threw ERR_INVALID_ARG_VALUE, which
surfaced as "[trilium-app] dispatch failed for GET trilium-app://app/".

Shim import.meta.url to the bundle's own file URL via esbuild define +
banner, so createRequire()/.resolve() anchor at dist/ and still resolve
sibling node_modules packages. Also ship the SDK's per-platform native
claude binary into dist/node_modules, since the JS is bundled but the
binary it spawns at query time cannot be.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 13:32:39 +03:00
Elian Doran
55323a60f2
fix(pdfjs): resolve pdfjs-dist through Node so the build finds it
The build copied pdfjs-dist assets from the package's own node_modules,
but under the hoisted nodeLinker pnpm installs pdfjs-dist into the root
node_modules, so the hardcoded relative path missed it entirely and the
build failed with ENOENT on build/pdf.mjs.

Resolve pdfjs-dist via require.resolve("pdfjs-dist/package.json") so we
pick up exactly the version declared in this package.json wherever pnpm
places it. Expose BuildHelper.rootDir so the script can pass a
root-relative source path to copy().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-04 22:59:44 +03:00
AndreeaCorlaci
0c4b4fab0a Merge remote-tracking branch 'origin/main' into standalone-ios
Some checks are pending
Checks / main (push) Waiting to run
2026-06-08 08:35:56 +03:00
Elian Doran
555a90c789
refactor(ci): clean up relative CI 2026-05-29 11:01:53 +03:00
Elian Doran
74b7067b52
fix(edit-docs): not working due to missing preload 2026-05-25 16:46:37 +03:00
Elian Doran
d7ff9c90ad
chore(setup): remove electron remote 2026-05-24 20:25:59 +03:00
Elian Doran
3ec30e5bba
refactor(electron): use TypeScript for preload 2026-05-24 18:27:52 +03:00
Elian Doran
94265f72b2
fix(nix): not handling sqlite natives 2026-05-19 18:22:30 +03:00
Elian Doran
22650e0681
chore(deps): remove fs-extra 2026-05-16 16:45:17 +03:00
Elian Doran
a63ed029b1
refactor(standalone): rename project from client-standalone 2026-05-16 08:57:55 +03:00
Elian Doran
ea667299af
chore(scripts): remove now redundant standalone script 2026-05-14 21:19:51 +03:00
AndreeaCorlaci
c0dd5ba5f4 feat(mobile/ios): add trilium icon 2026-04-22 14:43:10 +03:00
Elian Doran
0e678a6870
chore(mobile): add a slighter warmer background 2026-04-20 20:23:17 +03:00
Elian Doran
07608a041b
feat(mobile): provide monochrome icon too 2026-04-20 20:21:13 +03:00
Elian Doran
4914ff7601
chore(mobile): icon clipped on Samsung with squircle 2026-04-20 20:20:06 +03:00
Elian Doran
97438ed46f
chore(mobile): icon clipped on Samsung with squircle 2026-04-20 20:16:53 +03:00
Elian Doran
f4790e59eb
chore(mobile): icon clipped on Pixel 10 2026-04-20 20:12:03 +03:00
Elian Doran
e956d951a8
chore(mobile): basic icon set 2026-04-20 20:07:47 +03:00
Elian Doran
28c6826b35
Merge remote-tracking branch 'origin/main' into standalone 2026-04-19 20:26:10 +03:00
Adorian Doran
c4d832eebc contributor list: update script name, change a comment 2026-04-19 18:33:35 +03:00
Elian Doran
2c744122ca
chore: update contributors list 2026-04-19 15:44:52 +03:00
Elian Doran
adc648d277
chore(scripts): tweak criteria for contributors 2026-04-19 15:34:14 +03:00
Elian Doran
a403aca054
chore(scripts): group translators separately 2026-04-19 13:16:08 +03:00