With `OTEL_EXPORTER_OTLP_ENDPOINT` or
`OTEL_EXPORTER_OTLP_TRACES_ENDPOINT` set (and neither
`OTEL_SDK_DISABLED=true` nor `OTEL_TRACES_EXPORTER=none`), Bulwark
loads the OpenTelemetry SDK at startup and exports Next's own spans
plus a client span for every call to Stalwart, WOPI, translation, the
plugin proxy and OAuth over OTLP/HTTP. Calls to Stalwart carry a
`traceparent` header, so Stalwart's spans join the trace. All other
settings come from the standard `OTEL_*` variables.
Every span is scrubbed before export: URLs keep their origin only,
request paths become their route group, query strings, user agents and
client addresses are dropped, and addresses or credentials in other
attributes are removed. Without an endpoint the SDK is never loaded.
The endpoint is listed under the hosts that stay reachable with no
third-party traffic, and System > Monitoring shows whether tracing runs.
- `/api/metrics` in Prometheus text format, off until `METRICS_TOKEN`
or `METRICS_TOKEN_FILE` is set (404 before, 401 without the bearer
token): requests per route group with status class and latency,
sign-in outcomes, calls to Stalwart, WOPI, translation, the plugin
proxy and OAuth, browser errors, errors today, process memory,
event-loop lag, uptime and build info. Labels come from fixed lists.
Each replica serves its own.
- `/api/health/live` answers while the process runs;
`/api/health/ready` checks the config, the shared store, writable
data dirs and Stalwart's session resource. `/api/health` and
`/api/ready` are unchanged.
- the Docker image gets a HEALTHCHECK on the live endpoint
- adds prom-client, and the OpenTelemetry packages the tracing step
loads on demand
A Playwright job on the perf guard's production build runs axe-core's
WCAG 2.2 A/AA rules on the mail list, reading pane, composer, calendar,
contacts and Settings > Accessibility, and fails when a screen has more
serious or critical violations than scripts/a11y-guard/budget.json
allows (all zero today). Two more checks cover what axe can't see: the
keyboard focus outline stays visible in Windows contrast themes, and
single-key shortcuts don't act on the mail behind an open dialog.
axe-core is vendored next to the spec (MPL-2.0, see NOTICE), so the
check adds no dependency. `A11Y_GUARD_UPDATE=1 npm run test:a11y-guard`
rewrites the budget.
Every plugin iframe loaded /plugin-sandbox, a force-dynamic Next page.
Each frame then fetched, parsed and hydrated Next's client router on top
of React and the runtime, and the server rendered the page once per
frame, one after another when a dozen plugins started together.
Sandboxed frames have opaque origins, so the browser's code cache never
helps, and they run on the host page's main thread: with 12 plugins
the frames took 2.5-3 s to come up after the app had loaded.
The sandbox routes now return a small HTML page with the UI kit's
stylesheet and the runtime as an ES module, both built ahead of time:
- `scripts/build-plugin-sandbox.mjs` bundles the runtime with esbuild
and compiles `plugin-kit.css` with the app's Tailwind setup into
`public/plugin-sandbox-assets/`, with a manifest. Named Tabler imports
are pointed at the icons' own files, so the full icon set stays in a
chunk that only loads for an icon name the kit doesn't ship.
- `npm run build`, `npm run dev`, the Dockerfile and the perf-guard
workflow run it before `next build`.
- `lib/plugin-sandbox/sandbox-document.ts` writes the page; proxy.ts
still sets the sandbox CSP and the script carries its nonce.
- The assets are served from public/ with CORS and immutable caching.
A frame on an idle page is ready in 255 ms instead of 362 ms, eleven at
once in 1.0 s instead of 2.1 s.
Typing @ at the start of a word in the rich-text body offers the To and
Cc recipients (contact groups expanded) and inserts the chosen one's
first name as plain text. The name comes from the display name; with a
firstname.lastname@ address, the address tells which word of the name is
the first name, and without a name the address supplies it. A bare
initial keeps the mailbox name (@m.mustermann), and recipients who share
a first name get their full name. Bcc recipients are never offered -
naming one in the text would disclose them to everyone else.
The list only opens while a recipient matches, so Enter and Escape
otherwise keep their usual meaning. While it is open, Escape closes the
list rather than the composer, which handles Escape in the capture phase,
and the list closes when the editor loses focus.
Built on @tiptap/suggestion, at the version of the other TipTap packages.
Remove packages nothing imports any more (`@tiptap/extension-image`,
`asn1js`, `pkijs`, `pvtsutils`, `sonner`, `webcrypto-liner`) and the
`elliptic` override, which only applied through `webcrypto-liner`.
`sharp`, `undici`, `@tiptap/extension-paragraph` and
`@tiptap/extension-heading` were imported directly but only resolved as
transitive dependencies of next, jsdom and starter-kit. List them
explicitly and point the `undici` override at `$undici` so both stay in
step.
Keep the latest Docker tags on the stable release when a GitHub release is marked as a pre-release, so auto-updating deployments on :latest do not pick up beta builds.