Added
- Reconnect-loop observability: every scheduled reconnect attempt is counted in the new
openwa_session_reconnect_attempts_totalPrometheus counter, and every fifth consecutive attempt
of an episode emits asession.reconnect_loopwebhook event ({ sessionId, attempts, nextDelayMs }),
a structured warning log, and anopenwa_session_reconnect_loop_alerts_totalcounter tick — a
session stuck in a reconnect loop is now visible to operators instead of retrying silently forever.
The episode streak re-arms after a stable connection, so recovered sessions do not keep alerting. - The whatsapp-web.js engine now sweeps orphaned Chromium processes before each (re)launch: browsers
are started with an--openwa-session=<id>marker arg, and any leftover browser process carrying
this session's marker from a previous process lifetime (e.g. after the gateway itself was killed)
is terminated before the new launch, alongside the existing stale Singleton-file cleanup. - Messages composed on a linked phone are now persisted to local history (previously only API
sends and inbound messages were stored). Deduplication against the REST send path is atomic on the
existing unique message index, and delivery/read state advances via acks on these rows as well. - The whatsapp-web.js own-send echo now downloads media through the same capped inbound path as
inbound messages (declared-size pre-gate, timeout, concurrency limiter), so phone-composed images
persist and render with their real payload. - The dashboard gains a shared accessible modal dialog — Escape and overlay dismissal, a focus
trap with initial focus, background scroll lock, androle="dialog"semantics. The Sessions page
modals are the first to use it, gaining those behaviors plus a pinned header/footer with a
scrolling body on long content. - The dashboard Message Tester now covers every outbound message type: in addition to
text/image/video/audio/document it can send location, contact-card, sticker, and native poll
messages, forward an existing message to another chat, and submit a bulk text batch (recipients
one per line, optional inter-message delay) with live batch progress polling and a cancel control
in the response panel.
Changed
- Dashboard theming is simplified to a single light/dark toggle button; the accent-palette picker
was removed for maintainability. The globalh2is a real heading
again instead of a forced small uppercase eyebrow (section/card titles were smaller than body
text); the eyebrow look survives as an opt-in.eyebrowclass.
The stored theme is applied before first paint, so standalone
routes no longer flash the OS default, and the message-analytics chart now defaults to 24h. - The dev compose defaults
AUTO_START_SESSIONS=true, so previously authenticated sessions come
back by themselves after a container restart (the application-level default stays off). - Dashboard action buttons are consolidated into shared global
.btn-primary/.btn-secondary/
.btn-dangerclasses (28 page-scoped copies removed), so padding, radius, hover, and disabled
states are consistent across pages; the Plugins hover now uses the--primary-hovertoken and
danger buttons use the single--errorred. - The Infrastructure page's inline-styled elements (including the restart/migration progress modal) are
moved to scoped CSS classes, so all surfaces stay on the design-token system. - Decorative hover/selection effects are flattened for a more professional look: the install/config
tab active state no longer lifts or glows, the restart progress bar is a flat primary fill instead
of a gradient, and the emoji-picker button no longer scales on hover.
Removed
- Verified dead dashboard code: unused CSS across multiple pages, dead client methods and utilities,
unused image assets, and 39 unused i18n keys across all locales. - Verified-unused dashboard i18n keys (19 per locale across all 11 locales): dead
common.*
vocabulary and page-specific keys with zero references in the app.
Fixed
-
Boot no longer warns about (and the plugin list no longer shows) ghost entries for the legacy
bundled extensions removed in v0.7 (auto-reply,translation): when their code directory has no
manifest, the stale registry entry is pruned at startup. The guard is scoped to those known ids so
a temporarily unreadable plugin directory never loses its persisted config. -
Long-lived sessions no longer die permanently after hours of uptime. A dead whatsapp-web.js
Chromium (browser process exit, renderer crash, or closed page) is now detected through the
puppeteer lifecycle handles and driven through the standard disconnect → reconnect pipeline, and
a session watchdog probes READY engines every 60 seconds, treating two consecutive liveness-probe
failures as a disconnect. The reconnect budget is now unlimited by default (exponential backoff
capped at 1 hour, counter reset after 5 stable minutes) instead of a terminal failure after 5
attempts; explicitmaxReconnectAttempts(0= disabled, clamped to 1–20) is unchanged. On the
Baileys engine,connectionReplaced(440) is now terminal instead of fighting the other instance,
duplicate close events no longer burn retry attempts, and a failed reconnect attempt no longer
fails the session. -
Harden session stability further: the Baileys engine now treats
forbidden(403, banned/blocked
account) as terminal instead of retrying forever; stale ChromiumSingletonLock/SingletonSocket/
SingletonCookiefiles are removed before each whatsapp-web.js (re)launch so a previously
force-killed browser can never block startup; and page transport errors (Protocol error,
Target closed, detached frame, …) observed during send/query operations are now treated as an
immediate death signal, cutting dead-session detection from minutes to the first failed call. -
Sent images no longer vanish from the chat thread: the realtime own-send echo carries no media
payload by design, and the live cache merge replaced metadata wholesale, wiping the optimistic
bubble's base64. Metadata now merges per field (a real payload always beats a payload-less echo
marker), and the post-send reconciliation folds the optimistic copy into the echo row. -
Chat thread scrolling now behaves on every path: opens at the latest message, restores the exact
per-chat position when returning (position is saved continuously, not read after the content
swap), and stays pinned while media decodes instead of clamping the restore to the pre-decode
height — releasing cleanly on user scroll. -
The messages-by-type chart no longer shows a misleading Unknown slice: rows with no body and no
metadata (content-less system/event rows) are excluded from the aggregation. -
Full-text search self-heals its schema at boot when migrations are skipped (
DATABASE_SYNCHRONIZE=true),
and SQLite FTS5 queries are sanitized per token, so phone numbers, chat identifiers, quotes, and
parentheses no longer fail as malformed queries. -
Audit log rows now carry the resolved API key and client IP for every call site: the values are
stamped into the per-request async context by the auth guard and auto-filled on write (explicit
context still wins). -
Dashboard CSS no longer references undefined custom properties or fallbacks from a foreign
design system: every danger/danger-color usage now resolves to the single--errortoken, wrong
--primary/--text-secondary/--border/--warningfallbacks are dropped, and the plugin
instances "off" badge shows its background again (it referenced an undefined--bg-secondary). -
Dashboard readability and behavior: the send button stays readable when disabled, API Keys badges
render on desktop (rules were stranded in a mobile-only media query), the Templates page gets real
primary/secondary button styles, fourteen dark-mode selectors are corrected so dark mode applies,
Sessions modals regain the 90vh cap with a scrolling body, QR provisioning uses the realtime push
with fetching gated toqr_ready(no more expected-but-noisy 400 console errors), and enabling a
plugin with unset required config opens its config dialog with a warning instead of failing with a
raw sandbox error. -
Plugins whose config schema declares field defaults no longer fail to enable with those values
missing: defaults are now seeded into the stored config at load time (fresh installs and every
boot), without ever overwriting explicit values. Required fields without a declared default still
need real operator input.